Invalid Date

You've built your HTML to PDF pipeline, but the output has garbled text, broken columns, tables spilling off the page, or images that simply don't appear. These problems are frustrating because the HTML looks perfect in the browser.

The root cause is almost always the same: a mismatch between how PDF rendering engines work and how your HTML or CSS is written. This guide covers the ten most common HTML to PDF problems — with Before/After code examples — when using FUNBREW PDF or any HTML to PDF tool. If you need the full picture of available conversion methods, start with the HTML to PDF Complete Guide.

Problem 1: Garbled Text and Missing Fonts

Symptoms

Text appears as boxes (□□□□), question marks, or is replaced by an unrelated character. CJK characters are especially susceptible.

Cause

The PDF rendering server doesn't have the required font installed, or CSS doesn't specify a fallback font chain that includes that language.

Fix

Before (generic font stack)

body {
  font-family: sans-serif; /* No CJK font specified */
}

After (explicit font stack with CJK support)

body {
  font-family: 'Noto Sans JP', 'Hiragino Sans', 'Yu Gothic', 'Meiryo', sans-serif;
}

code, pre {
  font-family: 'Noto Sans Mono', 'Source Code Pro', monospace;
}

For self-hosted environments where you control the font files, embed the font as Base64:

@font-face {
  font-family: 'IPAexGothic';
  src: url('data:font/truetype;base64,AAEAAAA...') format('truetype');
}

body {
  font-family: 'IPAexGothic', sans-serif;
}

If the rendering server has network access, Google Fonts works well:

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=swap');

body {
  font-family: 'Noto Sans JP', sans-serif;
}

FUNBREW PDF ships with Noto Sans JP pre-installed. Japanese text renders correctly without any extra configuration. For a comparison of font support across tools, see HTML to PDF API Comparison 2026.

FUNBREW PDF API example

curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<style>body { font-family: \"Noto Sans JP\", sans-serif; }</style><p>日本語テキスト / Japanese text</p>",
    "options": { "format": "A4" }
  }' \
  --output output.pdf

Problem 2: CSS Layout Breaks

Symptoms

Two-column layouts collapse to one column in the PDF. Flex containers behave unexpectedly. The PDF looks like a mobile layout even though the browser renders it correctly.

Cause

PDF engines — especially WebKit-based ones like wkhtmltopdf — don't fully support modern CSS. Viewport width assumptions also differ between browser and PDF rendering contexts.

Fix

Before (modern CSS that may break in older engines)

.container {
  display: flex;
  gap: 24px; /* gap is unsupported in older engines */
}

.column {
  width: 50%;
}

After (table-based layout for maximum compatibility)

.container {
  display: table;
  width: 100%;
  border-spacing: 0;
}

.column {
  display: table-cell;
  width: 50%;
  padding-right: 12px;
  vertical-align: top;
}

.column:last-child {
  padding-right: 0;
  padding-left: 12px;
}

When using a Chromium-based engine, flexbox works fine if you lock in the width:

@media print {
  .container {
    display: flex;
    width: 794px; /* A4 at 96dpi */
    margin: 0;
    padding: 0;
  }

  .column {
    flex: 0 0 50%;
    box-sizing: border-box;
  }
}

Responsive designs often switch to a mobile layout at small viewport widths. Override this in print:

@media print {
  .container {
    max-width: none !important;
    width: 100% !important;
  }

  .mobile-only {
    display: none !important;
  }

  .desktop-only {
    display: block !important;
  }
}

Background colors are also frequently missing. Force them with:

* {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

For a detailed breakdown of CSS support per engine, see wkhtmltopdf vs Chromium.


Problem 3: Page Breaks in the Wrong Place

Symptoms

A table is split mid-row. A heading is left alone at the bottom of a page. You need a section to start on a new page.

Cause

Missing break-inside or break-before declarations, or missing legacy fallback properties for older engines.

Fix

Before (no page break control)

table { width: 100%; }
/* The engine decides where to break — often badly */

After (explicit page break control)

/* Prevent elements from splitting across pages */
table, figure, .card, .invoice-item {
  break-inside: avoid;
  page-break-inside: avoid; /* Fallback for older engines */
}

/* Never break right after a heading */
h1, h2, h3 {
  break-after: avoid;
  page-break-after: avoid;
}

/* Force a new page before specific sections */
.chapter, .page-break {
  break-before: page;
  page-break-before: always;
}

/* Prevent orphan and widow lines */
p {
  orphans: 3; /* Minimum lines at bottom of page */
  widows: 3;  /* Minimum lines at top of page */
}

For multi-page tables, always repeat headers and prevent row splits:

thead {
  display: table-header-group;
}

tfoot {
  display: table-footer-group;
}

tr {
  break-inside: avoid;
  page-break-inside: avoid;
}

For invoices, keep the summary block together on one page:

<div class="invoice-body">
  <table class="line-items">
    <thead>
      <tr><th>Item</th><th>Qty</th><th>Unit Price</th><th>Total</th></tr>
    </thead>
    <tbody>
      <!-- line items -->
    </tbody>
  </table>
</div>

<!-- Keep the summary block together — no page break inside -->
<div class="invoice-summary" style="break-inside: avoid; page-break-inside: avoid;">
  <table>
    <tr><td>Subtotal</td><td>$1,000.00</td></tr>
    <tr><td>Tax (10%)</td><td>$100.00</td></tr>
    <tr><td><strong>Total</strong></td><td><strong>$1,100.00</strong></td></tr>
  </table>
  <p>Wire transfer: Bank of Example, Account #1234567</p>
</div>

For more page break techniques, see CSS Tips for HTML to PDF.


Problem 4: Images Don't Appear in the PDF

Symptoms

Images are blank, show a broken image icon, or display a completely different image. The issue doesn't exist in the browser.

Cause

Relative URLs or local file paths that the PDF rendering server cannot resolve.

Fix

Before (relative paths that the server can't reach)

<!-- Local file path: inaccessible from the rendering server -->
<img src="../images/logo.png" alt="Logo">

<!-- Relative URL: the server doesn't know the base URL -->
<img src="/images/logo.png" alt="Logo">

After (absolute URL)

<img src="https://example.com/images/logo.png" alt="Logo">

When network access isn't available, or you want to avoid extra HTTP requests, embed images as Base64:

import base64

def image_to_base64(path: str) -> str:
    with open(path, 'rb') as f:
        return base64.b64encode(f.read()).decode('utf-8')

logo_b64 = image_to_base64('logo.png')

html = f'<img src="data:image/png;base64,{logo_b64}" alt="Logo" width="200">'
const fs = require('fs');

function imageToBase64(path) {
  return fs.readFileSync(path).toString('base64');
}

const logoB64 = imageToBase64('./logo.png');
const html = `<img src="data:image/png;base64,${logoB64}" alt="Logo" width="200">`;

For SVGs, embed the markup directly — no encoding needed:

<svg width="200" height="60" xmlns="http://www.w3.org/2000/svg">
  <text x="10" y="40" font-size="32" font-family="Arial">FUNBREW</text>
</svg>

FUNBREW PDF API example with image

curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<img src=\"https://your-cdn.example.com/logo.png\" alt=\"Logo\"><p>Content below logo</p>",
    "options": { "format": "A4" }
  }' \
  --output output.pdf

Problem 5: Tables Overflow or Get Cut Off

Symptoms

A wide table extends beyond the right edge of the page. Content on the right side of the table is missing from the PDF.

Cause

PDF has no concept of horizontal scrolling. overflow: auto has no effect. If a table is wider than the page, the content is simply clipped.

Fix

Before (scroll works in browser, but PDF clips content)

.table-wrapper {
  overflow-x: auto; /* No effect in PDF */
}

table {
  min-width: 900px; /* Wider than A4 (~794px at 96dpi) */
}

After (fit the table to the page)

table {
  width: 100%;
  table-layout: fixed; /* Distribute column widths evenly */
  word-break: break-all; /* Wrap long text within cells */
}

@media print {
  table {
    font-size: 8pt; /* Reduce font size for dense tables */
  }

  td, th {
    padding: 4px 6px;
  }
}

For tables with many columns, switch to landscape orientation:

curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<table>...</table>",
    "options": {
      "format": "A4",
      "landscape": true
    }
  }' \
  --output output.pdf

Hide supplementary columns in PDF output:

@media print {
  .col-notes,
  .col-actions {
    display: none;
  }
}

Problem 6: External CSS or Styles Are Not Applied

Symptoms

The PDF ignores font styles, colors, or layout rules that look correct in the browser. External stylesheet links don't appear to load.

Cause

The PDF rendering server cannot reach the relative URL of the external CSS file, or the file is behind authentication.

Fix

Before (external CSS reference)

<!-- Rendering server may not be able to reach this -->
<link rel="stylesheet" href="/assets/style.css">

After (inline CSS)

<style>
  body {
    font-family: 'Noto Sans JP', 'Arial', sans-serif;
    font-size: 11pt;
    line-height: 1.7;
    color: #1a202c;
    margin: 0;
    padding: 0;
  }

  h1 { font-size: 18pt; font-weight: 700; margin-bottom: 12pt; }
  h2 { font-size: 14pt; font-weight: 700; margin-bottom: 8pt; }

  .highlight { background: #fef9c3; padding: 2px 4px; }
</style>

Read and inline CSS server-side before sending to the API:

def build_html_with_styles(html_content: str, css_path: str) -> str:
    with open(css_path, 'r', encoding='utf-8') as f:
        css = f.read()
    return f'<style>{css}</style>{html_content}'

html = build_html_with_styles('<h1>Report</h1><p>Content</p>', 'style.css')
const fs = require('fs');

function buildHtmlWithStyles(htmlContent, cssPath) {
  const css = fs.readFileSync(cssPath, 'utf-8');
  return `<style>${css}</style>${htmlContent}`;
}

const html = buildHtmlWithStyles('<h1>Report</h1>', './style.css');

Problem 7: Dynamic Content Missing (JavaScript-Rendered Elements)

Symptoms

Charts, graphs, or dynamically rendered UI components appear blank or empty in the PDF.

Cause

The PDF engine captures the page before JavaScript finishes rendering the content.

Fix

Before (JavaScript hasn't finished when PDF is captured)

{
  "html": "<canvas id='chart'></canvas><script>renderChart();</script>",
  "options": {}
}

After (signal readiness before capture)

const html = `
<canvas id="chart"></canvas>
<script>
  async function main() {
    await renderChart();
    // Signal that rendering is complete
    document.title = 'pdf-ready';
  }
  main();
</script>
`;

For Chart.js, disable animations — they prevent the engine from capturing the final state:

const chart = new Chart(ctx, {
  type: 'bar',
  data: { /* ... */ },
  options: {
    animation: false,  // Disable animations
    responsive: false, // Fix dimensions
    plugins: {
      legend: { display: true }
    }
  }
});

The most reliable approach is to generate charts as images server-side:

import matplotlib.pyplot as plt
import io, base64

def chart_to_base64(data: list) -> str:
    fig, ax = plt.subplots(figsize=(6, 4))
    ax.bar(range(len(data)), data)
    buf = io.BytesIO()
    fig.savefig(buf, format='png', dpi=150, bbox_inches='tight')
    buf.seek(0)
    return base64.b64encode(buf.read()).decode()

chart_b64 = chart_to_base64([10, 25, 40, 35, 55])
html = f'<img src="data:image/png;base64,{chart_b64}" alt="Revenue Chart">'

Problem 8: Headers and Footers Not Appearing

Symptoms

You've set up @page margins and added header/footer elements, but the PDF renders them incorrectly — overlapping content, cut off, or completely missing.

Cause

Header and footer rendering works differently across PDF engines. Some engines use the @page margin boxes (@top-center, @bottom-center), while others require specific API options. CSS-only approaches often fail because the rendering engine doesn't support the @page margin-at rules.

Fix

Before (CSS-only header that doesn't work in most engines)

@page {
  @top-center { content: "Company Name"; }
  @bottom-right { content: "Page " counter(page); }
}

After (fixed-position elements for headers/footers)

<style>
  @page {
    margin: 25mm 15mm 25mm 15mm; /* Leave space for header/footer */
  }

  .page-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 15mm;
    text-align: center;
    font-size: 9pt;
    color: #718096;
    border-bottom: 0.5pt solid #e2e8f0;
    padding-bottom: 2mm;
  }

  .page-footer {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: 15mm;
    text-align: center;
    font-size: 8pt;
    color: #a0aec0;
  }
</style>

<div class="page-header">Company Name — Confidential</div>
<div class="page-footer">Generated by FUNBREW PDF</div>
<div class="content">
  <!-- Main content here -->
</div>

For page numbers, use the FUNBREW PDF API's built-in header/footer options:

curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "html": "<h1>Report</h1><p>Content...</p>",
    "options": {
      "format": "A4",
      "margin": { "top": "25mm", "bottom": "25mm" },
      "displayHeaderFooter": true,
      "headerTemplate": "<div style=\"font-size:9px; text-align:center; width:100%;\">Confidential Report</div>",
      "footerTemplate": "<div style=\"font-size:9px; text-align:center; width:100%;\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
    }
  }' \
  --output output.pdf

Problem 9: PDF Generation Timeout or Slow Performance

Symptoms

The API request takes over 30 seconds and eventually times out, or the PDF is slow to generate even for simple pages.

Cause

Large images, external resource loading (fonts, stylesheets, images from slow CDNs), complex JavaScript execution, or excessively large HTML payloads.

Fix

Reduce image sizes before embedding

from PIL import Image
import io, base64

def optimize_image(path: str, max_width: int = 800, quality: int = 80) -> str:
    """Resize and compress an image before Base64 embedding."""
    img = Image.open(path)
    if img.width > max_width:
        ratio = max_width / img.width
        img = img.resize((max_width, int(img.height * ratio)), Image.LANCZOS)
    buf = io.BytesIO()
    img.save(buf, format="JPEG", quality=quality, optimize=True)
    return base64.b64encode(buf.getvalue()).decode()

Inline all external resources

import re, requests, base64

def inline_external_resources(html: str) -> str:
    """Replace external image URLs with Base64-encoded inline data."""
    def replace_src(match):
        url = match.group(1)
        if url.startswith("data:"):
            return match.group(0)
        try:
            resp = requests.get(url, timeout=10)
            content_type = resp.headers.get("content-type", "image/png")
            b64 = base64.b64encode(resp.content).decode()
            return f'src="data:{content_type};base64,{b64}"'
        except Exception:
            return match.group(0)

    return re.sub(r'src="(https?://[^"]+)"', replace_src, html)

Set appropriate timeouts and reduce payload size

// Compress HTML by removing unnecessary whitespace
function compressHtml(html) {
  return html.replace(/\s+/g, ' ').replace(/>\s+</g, '><').trim();
}

const response = await axios.post(
  'https://pdf.funbrew.cloud/api/v1/generate',
  { html: compressHtml(html), options: { format: 'A4' } },
  { timeout: 60000 } // Set a generous but bounded timeout
);

Problem 10: PDF File Size Too Large

Symptoms

A single-page PDF is several megabytes. Batch-generated PDFs consume excessive storage. Email attachments exceed size limits.

Cause

High-resolution embedded images (especially uncompressed PNGs), embedded fonts, or redundant CSS.

Fix

Compress images before embedding

Use JPEG instead of PNG for photographs, and SVG for logos and icons:

# Use JPEG for photos (much smaller than PNG)
img.save(buf, format="JPEG", quality=75, optimize=True)

# Use SVG for logos and simple graphics (no Base64 needed)
logo_svg = '<svg width="200" height="60">...</svg>'

Limit image resolution for print

img {
  max-width: 100%;
  height: auto;
  image-rendering: optimizeQuality; /* Let the engine handle DPI scaling */
}

Post-process: compress the generated PDF

import subprocess

def compress_pdf(input_path: str, output_path: str):
    """Use Ghostscript to compress a PDF file."""
    subprocess.run([
        "gs", "-sDEVICE=pdfwrite",
        "-dCompatibilityLevel=1.4",
        "-dPDFSETTINGS=/ebook",  # Good quality, smaller size
        "-dNOPAUSE", "-dBATCH",
        f"-sOutputFile={output_path}",
        input_path,
    ], check=True)

# /screen  = 72 dpi (smallest, low quality)
# /ebook   = 150 dpi (good balance)
# /printer = 300 dpi (high quality)
# /prepress = 300 dpi (highest quality)
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);

async function compressPdf(inputPath, outputPath) {
  await execAsync(
    `gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/ebook -dNOPAUSE -dBATCH -sOutputFile=${outputPath} ${inputPath}`
  );
}

Debugging Workflow

Use this step-by-step approach to isolate the cause of any PDF issue.

Step 1: Check browser print preview

Most problems reproduce in the browser's print preview, which is the fastest way to diagnose them.

  1. Open the HTML in Chrome
  2. Press Ctrl+Shift+P (Mac: Cmd+Shift+P) to open the command palette
  3. Type "Emulate CSS media type" → select "print"
  4. Open Print Preview with Ctrl+P

If the issue appears here, it's a CSS problem. If print preview looks correct, suspect a rendering engine issue.

Step 2: Test in the Playground

Paste your HTML into FUNBREW PDF's Playground for instant PDF output — no API key required.

Step 3: Compare engines

FUNBREW PDF offers two engines: fast (WebKit-based) and quality (Chromium-based).

# Test with the fast engine (wkhtmltopdf-based)
curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<p>Test</p>", "options": {"engine": "fast"}}' \
  --output test-fast.pdf

# Test with the quality engine (Chromium-based)
curl -X POST https://pdf.funbrew.cloud/api/v1/pdf/generate \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html": "<p>Test</p>", "options": {"engine": "quality"}}' \
  --output test-quality.pdf

If the outputs differ, you've found a CSS compatibility issue. See wkhtmltopdf vs Chromium for specifics.

Step 4: Create a minimal reproduction

Strip the HTML down to the smallest snippet that still reproduces the problem.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
  /* Only the relevant styles */
  body { font-family: 'Noto Sans JP', sans-serif; }
</style>
</head>
<body>
  <!-- Minimal element that triggers the issue -->
  <p>Test: Hello World / 日本語</p>
</body>
</html>

Step 5: Check error responses

import requests

response = requests.post(
    "https://pdf.funbrew.cloud/api/v1/pdf/generate",
    headers={"X-API-Key": "your-api-key"},
    json={"html": html_content, "options": {}},
)

if response.status_code != 200:
    print(f"HTTP {response.status_code}")
    print(response.json())  # Read the error message
else:
    with open("output.pdf", "wb") as f:
        f.write(response.content)

For a comprehensive guide to error handling and retry logic, see the PDF API Error Handling Guide.


Quick Reference

Problem Common Cause Fix
Garbled text (tofu boxes) Missing font on rendering server Specify full font stack; use pre-installed fonts
Layout breaks CSS incompatibility, wrong viewport Table layout; @media print with fixed widths
Wrong page breaks Missing break-inside break-inside: avoid + orphans/widows
Images missing Relative URLs or inaccessible paths Use absolute URLs or Base64 inline embedding
Table overflow Width exceeds page table-layout: fixed + word-break: break-all
Styles not applied External CSS unreachable Inline all CSS before sending to the API
Dynamic content missing JS not finished before capture Server-side image generation
Headers/footers missing @page margin-at rules unsupported position: fixed or API header/footer options
Slow generation/timeout Large images, external resources Optimize images, inline resources, compress HTML
File size too large Uncompressed images, embedded fonts JPEG instead of PNG, SVG for icons, Ghostscript post-processing

Summary

Almost every HTML to PDF issue comes down to one of three things: fonts aren't available on the server, CSS isn't compatible with the rendering engine, or resources (images, stylesheets) can't be accessed by the server.

  • Garbled text: Specify an explicit font stack. FUNBREW PDF includes Noto Sans JP out of the box
  • Layout issues: Inline your CSS; use table-based layouts for maximum compatibility
  • Page breaks: Add break-inside: avoid and orphans/widows where needed
  • Missing resources: Use absolute URLs or Base64 embedding for all images and fonts
  • Headers/footers: Use position: fixed or the API's built-in header/footer template options
  • Slow generation: Optimize and inline images before sending; compress HTML whitespace
  • Large file size: Use JPEG for photos, SVG for graphics; post-process with Ghostscript if needed
  • Debugging: Browser print preview first, then Playground, then minimal reproduction

For deep dives into specific areas, see CSS Tips for HTML to PDF and the wkhtmltopdf vs Chromium comparison. Start testing in the Playground — it's the fastest way to confirm whether your HTML produces the PDF you expect.

Related

Powered by FUNBREW PDF