# WeasyPrint Research & Best Practices

**Version:** 68.1 (Latest as of May 2026)  
**Date:** May 13, 2026  
**Author:** Vincent (for Grepples)

---

## TABLE OF CONTENTS

1. [Overview](#1-overview)
2. [@page Rules β€” The Foundation](#2-page-rules--the-foundation)
3. [Page Margins and Margin Boxes](#3-page-margins-and-margin-boxes)
4. [Page Breaks and Fragmentation](#4-page-breaks-and-fragmentation)
5. [Named Pages and Page Selectors](#5-named-pages-and-page-selectors)
6. [Counters and Generated Content](#6-counters-and-generated-content)
7. [Named Strings and Running Headers](#7-named-strings-and-running-headers)
8. [Cross-References and Leaders](#8-cross-references-and-leaders)
9. [PDF Bookmarks](#9-pdf-bookmarks)
10. [CSS Layout Support](#10-css-layout-support)
11. [Fonts and @font-face](#11-fonts-and-font-face)
12. [SVG Support](#12-svg-support)
13. [Images](#13-images)
14. [Colors and Color Spaces](#14-colors-and-color-spaces)
15. [Tables](#15-tables)
16. [CSS Variables](#16-css-variables)
17. [Footnotes](#17-footnotes)
18. [Common Pitfalls and Workarounds](#18-common-pitfalls-and-workarounds)
19. [Known Limitations](#19-known-limitations)
20. [Performance Tips](#20-performance-tips)
21. [Sample Patterns from Official Docs](#21-sample-patterns-from-official-docs)
22. [Python API Essentials](#22-python-api-essentials)
23. [Debugging Tips](#23-debugging-tips)

---

## 1. OVERVIEW

### What is WeasyPrint?

WeasyPrint is a **visual rendering engine for HTML and CSS** that exports to PDF. It is NOT a full browser β€” no JavaScript, no DOM manipulation, no quirks mode. It's purpose-built for pagination.

**Key characteristics:**
- Python-based (3.10+), BSD licensed
- Renders HTML β†’ CSS β†’ PDF directly
- Follows web standards (CSS 2.1, CSS Paged Media, CSS GCPM)
- Version 68.1 as of early 2026
- No JavaScript execution

### Dependencies

```
Python β‰₯ 3.10.0
Pango β‰₯ 1.44.0 (text layout)
pydyf β‰₯ 0.11.0 (PDF generation)
CFFI β‰₯ 0.6
tinyhtml5 β‰₯ 2.0.0 (HTML parsing)
tinycss2 β‰₯ 1.5.0 (CSS parsing)
cssselect2 β‰₯ 0.8.0
Pyphen β‰₯ 0.9.1 (hyphenation)
Pillow β‰₯ 9.1.0 (images)
fontTools β‰₯ 4.59.2 (font subsetting)
```

### Installation

```bash
# Linux (Debian/Ubuntu)
apt install python3-pip libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
pip install weasyprint

# Command line
weasyprint input.html output.pdf

# Python
from weasyprint import HTML
HTML(string='<h1>Hello</h1>').write_pdf('output.pdf')
```

### CLI Options (Selected)

| Option | Purpose |
|--------|---------|
| `-s <file>` | User stylesheet (can repeat) |
| `-p` | Follow HTML presentational hints |
| `--optimize-images` | Optimize embedded images |
| `-j <0-95>` | JPEG quality |
| `-D <dpi>` | Max image resolution |
| `--pdf-variant` | PDF/A, PDF/UA variants |
| `--pdf-tags` | Accessibility tagging |
| `--custom-metadata` | Include HTML meta tags |
| `--srgb` | Include sRGB color profile |
| `-m print` | Media type (default: print) |
| `-v` / `-d` / `-q` | Verbose/debug/quiet |

---

## 2. @page Rules β€” The Foundation

The `@page` rule is **THE** most important CSS feature for PDF generation. It controls page size, margins, and page-level styling.

### Basic Page Setup

```css
@page {
  size: Letter;         /* or A4, A5, 8.5in 11in, 210mm 297mm */
  margin: 2cm;          /* top right bottom left */
  /* or individually: */
  /* margin-top: 2cm; margin-right: 2cm; */
  /* margin-bottom: 3cm; margin-left: 2cm; */
}
```

### Supported Page Sizes

```
Letter, Legal, A0-A10, B0-B10
Custom: 8.5in 11in | 210mm 297mm | 850px 1100px
```

### Page Orientation

```css
@page { size: portrait; }
@page { size: landscape; }
/* or swap dimensions */
@page { size: 11in 8.5in; } /* landscape letter */
```

### Page Background

```css
@page {
  background: #f0f0f0;
  background: url(header-image.png) no-repeat top center;
  background-size: cover;
}
```

### The :first Pseudo-Page

```css
@page :first {
  margin: 0;
  background: url(cover.jpg) no-repeat center;
  background-size: cover;
}
```

**Use case:** Cover pages, title pages with no margins or special styling.

### The :blank Pseudo-Page

```css
@page :blank {
  @top-left { content: none; }  /* No page number on blank pages */
  @bottom-center { content: none; }
}
```

**CRITICAL for worksheets:** When you force a page break, the new page might be "blank" (no regular content started it). Blank pages don't get margin box content by default in some readers, but explicitly setting them ensures consistency.

### The :left and :right Pseudo-Pages

```css
@page :left {
  @bottom-left { content: counter(page); }
  @bottom-right { content: string(chapter); }
}

@page :right {
  @bottom-left { content: string(chapter); }
  @bottom-right { content: counter(page); }
}
```

**Use case:** Book-style layout with mirrored page numbers.

### Named Pages

```css
@page chapter-start {
  margin: 0;
  background: #673ab7;
}

@page no-headers {
  @top-center { content: none; }
}
```

Assign pages to elements:

```css
h1.chapter-title {
  page: chapter-start;
  break-before: right;
}

.section-without-headers {
  page: no-headers;
}
```

### The :nth() Page Selector

```css
@page :nth(3) { background: red; }        /* Third page */
@page :nth(2n+1) { background: green; }   /* Odd pages */
@page :nth(even) { background: #f5f5f5; } /* Even pages */
@page :nth(1 of chapter) { background: blue; } /* First page of each chapter */
```

### Body Margin Override

**IMPORTANT:** Setting `margin: 0` on `body` is common when using `@page` margins, because the page margin is the outer boundary:

```css
@page { margin: 2cm; }
body { margin: 0; }  /* Let @page handle outer margins */
```

---

## 3. Page Margins and Margin Boxes

Margin boxes let you place headers, footers, page numbers, and chapter titles in the page margins.

### Available Margin Box Areas

```
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ @top-left   β”‚ @top-center β”‚ @top-right       β”‚
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  β”‚                                                   β”‚
  β”‚ @left-top     β”‚  PAGE CONTENT  β”‚ @right-top      β”‚
  β”‚                                                   β”‚
  β”‚ @left-middle  β”‚               β”‚ @right-middle     β”‚
  β”‚                                                   β”‚
  β”‚ @left-bottom  β”‚               β”‚ @right-bottom     β”‚
  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
  β”‚ @bottom-left  β”‚ @bottom-centerβ”‚ @bottom-right    β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

### Basic Page Number

```css
@page {
  @bottom-right {
    content: counter(page);
    font-size: 9pt;
    color: #666;
  }
}
```

### Page Counter of Total

```css
@page {
  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
  }
}
```

**NOTE:** `counter(pages)` gives the total page count.

### Complex Header Example (from official samples)

```css
@page {
  @top-left {
    background: #fbc847;
    content: counter(page);
    height: 1cm;
    text-align: center;
    width: 1cm;
  }
  @top-center {
    background: #fbc847;
    content: '';
    display: block;
    height: 0.05cm;
    width: 100%;
  }
  @top-right {
    content: string(heading);
    font-size: 9pt;
    height: 1cm;
    vertical-align: middle;
    width: 100%;
  }
}
```

### Margin Box Styling

Margin boxes accept: `content`, `color`, `font-*`, `text-align`, `vertical-align`, `display`, `background`, `border`, `padding`, `height`, `width`, `opacity`, `z-index`, `position: absolute`.

### Removing Margin Content

```css
@page :blank {
  @top-left { content: none; }
  @top-center { content: none; }
  @top-right { content: none; }
}
```

---

## 4. Page Breaks and Fragmentation

### The Three Break Properties

```css
/* break-before: forces break BEFORE element */
h2 { break-before: always; }
h2 { break-before: avoid; }
h2 { break-before: left; }    /* Forces element to start on left page */
h2 { break-before: right; }   /* Forces element to start on right page */
h2 { break-before: page; }    /* Alias for 'always' */

/* break-after: forces break AFTER element */
.chapter { break-after: page; }
.chapter { break-after: always; }

/* break-inside: controls breaking WITHIN element */
.card { break-inside: avoid; }
table { break-inside: avoid; }
p { break-inside: avoid; }
```

### Legacy Aliases (still work)

```
page-break-before β†’ break-before
page-break-after  β†’ break-after
page-break-inside β†’ break-inside
```

### Orphans and Widows

```css
p {
  orphans: 3;   /* Minimum lines at bottom of page */
  widows: 3;    /* Minimum lines at top of new page */
}
```

### box-decoration-break

```css
.card {
  /* 'clone' = repeat background on each fragment */
  /* 'slice' = extend background across fragments */
  box-decoration-break: clone;
}
```

### margin-break

```css
.box {
  margin-break: auto;  /* Default: margins collapse across page breaks */
  margin-break: always; /* Margins don't collapse across breaks */
}
```

### Critical Break Strategy for Worksheets

From our experience, the most reliable pattern:

```css
/* Force hard page breaks with HTML divs */
/* <div style="page-break-after: always;"></div> */

/* Keep activities together */
.activity-container {
  break-inside: avoid;
}

/* Section titles stay with their content */
.section-title {
  break-after: avoid;
}

/* Notes stay with following content */
.section-note {
  break-after: avoid;
}
```

**WORKAROUND DISCOVERED:** `break-inside: avoid` on containers ALONE doesn't work when titles sit OUTSIDE containers. Add `break-after: avoid` to titles/notes too.

---

## 5. Named Pages and Page Selectors

### Assigning Named Pages to Elements

```css
/* Define the page style */
@page cover {
  size: A4;
  margin: 0;
  background: url(cover-bg.jpg) center/cover;
}

/* Assign to content */
.cover-page {
  page: cover;
  break-before: page;
}
```

### Multiple Named Pages

```css
@page worksheet {
  margin: 1.5cm;
  @bottom-center { content: "Worksheet - Page " counter(page); }
}

@page answer-key {
  margin: 1.5cm;
  @bottom-center { content: "Answer Key - Page " counter(page); }
  background: #f9f9f9;
}

.worksheet { page: worksheet; }
.answers { page: answer-key; break-before: right; }
```

---

## 6. Counters and Generated Content

### Page Counter (Automatic)

```css
@page {
  @bottom-right { content: counter(page); }
}
```

### Custom Counters

```css
/* Initialize/reset */
html {
  counter-reset: lesson-counter;
}

/* Increment */
.lesson {
  counter-increment: lesson-counter;
}

/* Display */
.lesson::before {
  content: "Lesson " counter(lesson-counter) ": ";
}
```

### Nested Counters

```css
html { counter-reset: chapter; }
.chapter { counter-increment: chapter; counter-reset: section; }
.section { counter-increment: section; }

.section::before {
  content: counter(chapter) "." counter(section) " ";
}
```

Output: `1.1`, `1.2`, `2.1`, `2.2`...

### Counter Styles

```css
counter(page, decimal);      /* 1, 2, 3 */
counter(page, decimal-leading-zero); /* 01, 02, 03 */
counter(page, lower-roman);  /* i, ii, iii */
counter(page, upper-roman);  /* I, II, III */
counter(page, lower-alpha);  /* a, b, c */
counter(page, upper-alpha);  /* A, B, C */
```

### content() for Dynamic Text

```css
h2 {
  string-set: heading content();  /* Capture h2 text */
}

@top-right {
  content: string(heading);  /* Display it in header */
}
```

---

## 7. Named Strings and Running Headers

Named strings capture text from the page content and display it in margin boxes.

### Basic Usage

```css
/* Capture the latest h2 on each page */
h2 {
  string-set: chapter content();
}

/* Display in header */
@page {
  @top-right {
    content: string(chapter);
  }
}
```

### Combining with Counters

```css
h2 {
  string-set: heading counter(h2-counter) ". " content();
}
```

### Multiple Named Strings

```css
h1 { string-set: title content(); }
h2 { string-set: subtitle content(); }

@page {
  @top-left { content: string(title); }
  @top-right { content: string(subtitle); }
}
```

**NOTE:** Named strings capture the LAST matching element on the page.

---

## 8. Cross-References and Leaders

### target-counter() β€” Reference Page Numbers

```css
a::after {
  content: " (p. " target-counter(attr(href), page) ")";
}
```

### target-text() β€” Reference Anchor Text

```css
a::after {
  content: ": " target-text(attr(href));
}
```

### Table of Contents Example

```css
#contents a::before {
  content: target-counter(attr(href), page) '. ';
}

#contents a::after {
  content: leader(dotted) target-counter(attr(href), page);
  float: right;
}
```

### Leader Types

```css
leader(dotted)    /* Β·Β·Β·Β·Β·Β·Β·Β· */
leader(dashed)    /* ---------- */
leader(solid)     /* ----------- */
```

---

## 9. PDF Bookmarks

Bookmarks (PDF outlines) are auto-generated for `<h1>` through `<h6>` elements.

### Controlling Bookmarks

```css
/* Default behavior (from UA stylesheet): */
h1 { bookmark-level: 1; }
h2 { bookmark-level: 2; }
h3 { bookmark-level: 3; }
h4 { bookmark-level: 4; }
h5 { bookmark-level: 5; }
h6 { bookmark-level: 6; }

/* Disable bookmark for a specific element */
h1.no-bookmark { bookmark-level: none; }

/* Custom bookmark text */
h2 { bookmark-label: content(); }

/* Collapsed by default */
h1 { bookmark-state: closed; }
```

---

## 10. CSS Layout Support

### Flexbox (Supported, Simple Cases)

```css
.flex-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.flex-item {
  flex: 1;
}

/* All supported: */
/* flex, flex-direction, flex-wrap, flex-flow */
/* justify-content, align-items, align-content */
/* order, align-self */
```

### Grid (Supported, With Limitations)

```css
.grid-container {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px;
}

/* Supported: */
/* grid-template-*, grid-auto-*, grid-area */
/* fr units, minmax(), repeat() */
/* gap, align/justify properties */
/* z-index, order, dense */
/* fragmentation between rows (v67+) */
```

**Unsupported:** grid-template-areas with named areas (check version).

### Floats

```css
.float-left {
  float: left;
  width: 40%;
  margin-right: 10px;
}

/* NOTE: Floats + page breaks can be tricky */
```

### Columns

```css
.columns {
  columns: 2;
  column-gap: 1cm;
}
```

### Box Model

```css
/* All standard properties supported */
box-sizing: border-box;  /* Use for reliable sizing */
display: block | inline | inline-block | flex | grid | none;
position: static | relative | absolute | fixed;
```

**NOTE:** `position: fixed` works differently in print β€” it positions within the page box.

---

## 11. Fonts and @font-face

### @font-face

```css
@font-face {
  font-family: 'MyFont';
  font-weight: 400;
  font-style: normal;
  src: url('fonts/MyFont-Regular.ttf');
}

@font-face {
  font-family: 'MyFont';
  font-weight: 700;
  font-style: normal;
  src: url('fonts/MyFont-Bold.ttf');
}

@font-face {
  font-family: 'MyFont';
  font-weight: 400;
  font-style: italic;
  src: url('fonts/MyFont-Italic.ttf');
}
```

### Font Sizing Units

```
pt    β€” Points (recommended for print: 10pt, 12pt, etc.)
px    β€” Pixels (96px = 1in in WeasyPrint)
in    β€” Inches
cm/mm β€” Centimeters/millimeters
em/rem β€” Relative to parent/root font-size
vh/vw β€” Viewport units (page dimensions)
```

**RECOMMENDATION:** Use `pt` for font sizes in PDF generation. It's print-standard and predictable.

### Font Features

```css
.letters { font-variant: small-caps; }
.fractions { font-variant-numeric: diagonal-fractions; }
.ordinals { font-variant-numeric: ordinal; }
.slashed-zero { font-variant-numeric: slashed-zero; }
.superscript { font-variant-position: super; }
.subscript { font-variant-position: sub; }
.tabular { font-variant-numeric: tabular-nums; }
.oldstyle { font-variant-numeric: oldstyle-nums; }
```

### Font Configuration

```css
/* Font fallback chain */
body {
  font-family: 'CustomFont', 'Georgia', serif;
}
```

Fonts are resolved by Pango/Fontconfig. Install fonts system-wide or use `@font-face` with local files.

---

## 12. SVG Support

### Inline SVG

```html
<svg viewBox="0 0 100 100" width="50" height="50">
  <circle cx="50" cy="50" r="40" fill="#673ab7"/>
  <text x="50" y="55" text-anchor="middle" fill="white" font-size="20">A</text>
</svg>
```

### SVG as Image

```html
<img src="diagram.svg" alt="Diagram" />
```

SVG images render as **vectors** (not rasterized), maintaining quality at any zoom.

### SVG Features Supported

- Shapes: `rect`, `circle`, `ellipse`, `line`, `path`, `polygon`, `polyline`
- `text` elements with `font-family`, `font-size`, `fill`
- `transform`, `opacity`
- `clip-path`
- `url()` gradients (linear, radial)
- `pattern`
- `filter` (basic)
- `viewBox`

### SVG font-face (v68+)

```css
/* SVG text elements can use @font-face fonts */
```

---

## 13. Images

### Supported Formats

PNG, JPEG, GIF, BMP, TIFF, WebP, SVG

### Image Optimization

```bash
# CLI
weasyprint --optimize-images input.html output.pdf

# Python
HTML(string=html).write_pdf('output.pdf', optimize_images=True)
```

### Image Sizing

```css
img {
  max-width: 100%;       /* Responsive to container */
  max-width: 240px;      /* Fixed max */
  height: auto;          /* Maintain aspect ratio */
  object-fit: cover;     /* Fill container, crop */
  object-fit: contain;   /* Fit inside, letterbox */
}
```

### Data URIs

```html
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." />
```

---

## 14. Colors and Color Spaces

### Supported Color Formats

```css
color: #ff0000;           /* Hex */
color: #f00;              /* Short hex */
color: #ff000080;         /* Hex with alpha */
color: rgb(255, 0, 0);    /* RGB */
color: rgba(255, 0, 0, 0.5); /* RGBA */
color: hsl(0, 100%, 50%); /* HSL */
color: hwb(0 0% 0%);      /* HWB */
color: lab(50 80 -10);    /* LAB */
color: lch(50 80 330);    /* LCH */
color: oklch(50 80 330);  /* OKLCH */
color: red;               /* Named colors */

/* Color profiles */
@color-profile my-profile {
  src: url(profile.icc);
}
color: color(my-profile 1 0 0 0);

/* CMYK (v67+) */
color: device-cmyk(0%, 100%, 100%, 0%);
```

### light-dark() (v67+)

```css
color: light-dark(#333, #ccc); /* Light mode / dark mode */
```

### NOT Supported

- `color-mix()`
- `contrast-color()`

---

## 15. Tables

### Basic Table

```css
table {
  width: 100%;
  border-collapse: collapse;  /* or separate */
  border-spacing: 0;
}

td, th {
  border: 1px solid #ddd;
  padding: 8px;
  text-align: left;
}

th {
  background: #2a9d8f;
  color: white;
}

/* Zebra striping */
tr:nth-child(even) { background: #e8f5f3; }
```

### Table Caption

```css
caption {
  caption-side: top;
  font-weight: bold;
}
```

### Table Breaks

```css
/* Allow table rows to break across pages */
tr { break-inside: avoid; }  /* Keep rows together */
thead { display: table-header-group; }  /* Repeat headers */
tfoot { display: table-footer-group; }
```

### Unsupported

- `visibility: collapse`
- min/max width on table cells

---

## 16. CSS Variables

### Definition and Usage

```css
:root {
  --primary: #2a9d8f;
  --primary-light: #e8f5f3;
  --secondary: #e76f51;
  --text: #2b2d42;
  --font-size-base: 12pt;
  --spacing: 10px;
}

.header {
  background: var(--primary);
  color: white;
  font-size: var(--font-size-base);
  padding: var(--spacing);
}

.card {
  background: var(--primary-light);
  border-left: 3px solid var(--primary);
}
```

### calc() (v67+)

```css
.container {
  width: calc(100% - 40px);
  padding: calc(var(--spacing) * 2);
  margin: calc(1cm + 10px);
}
```

### CSS Nesting (Modern WeasyPrint)

The official samples now use nested CSS:

```css
.card {
  background: #e8f5f3;

  h3 {
    color: #2a9d8f;
    margin-top: 0;
  }

  p {
    line-height: 1.6;
  }
}
```

**NOTE:** CSS nesting support depends on your version. Check compatibility.

---

## 17. Footnotes

### Basic Footnotes

```html
<p>This is text with a footnote.<sup class="footnote">1</sup></p>

<div class="footnote">This is the footnote content.</div>
```

```css
.footnote {
  float: footnote;
}

/* Custom marker */
.footnote::footnote-marker {
  content: counter(footnote) ".";
  font-weight: bold;
}

/* Display options */
.footnote {
  footnote-display: block;     /* Block below content */
  /* footnote-display: inside; β€” inline in margin */
  /* 'compact' not supported */
}

/* Footnote policy */
.footnote {
  footnote-policy: single;     /* Try to keep on one page */
  /* footnote-policy: multiple; β€” Allow across pages */
}
```

---

## 18. Common Pitfalls and Workarounds

### Problem 1: Page Breaks Not Working as Expected

**Symptom:** Content you want together gets split across pages.

**Solution:**
```css
/* Use break-inside: avoid on the container */
.activity { break-inside: avoid; }

/* AND break-after: avoid on preceding elements */
.activity-title { break-after: avoid; }
.activity-note { break-after: avoid; }
```

### Problem 2: Titles Bleeding Onto Wrong Pages

**Symptom:** A section title appears at the bottom of page 1, but its content is on page 2.

**Solution:**
```css
.section-title {
  break-after: avoid;  /* Key fix */
}
```

### Problem 3: Margin Boxes Not Showing

**Symptom:** Page numbers or headers don't appear.

**Solutions to check:**
1. Content must be set: `content: counter(page);`
2. Don't use `content: none;` unless intentional
3. Check that `@page` margins are large enough to accommodate margin boxes
4. Margin boxes don't accept all CSS properties

### Problem 4: Images Not Loading

**Symptom:** Images show as broken or missing in PDF.

**Solutions:**
1. Use absolute paths or correct relative paths
2. Use data URIs for embedded images
3. Set `--base-url` for relative path resolution
4. Check file permissions

### Problem 5: Font Not Rendering Correctly

**Symptom:** Text uses wrong font or glyphs missing.

**Solutions:**
1. Use `@font-face` with local font files
2. Verify font file path is correct relative to HTML
3. Check `fc-list` for system-installed fonts
4. Embed multiple font weights/styles

### Problem 6: SVG Rendering Issues

**Symptom:** SVGs appear distorted or misaligned.

**Solutions:**
1. Always include `viewBox` attribute
2. Set explicit `width` and `height`
3. Avoid complex SVG filters
4. Use simple shapes and paths

### Problem 7: Flex Items Wrapping Unexpectedly

**Symptom:** Flex items don't wrap as expected near page breaks.

**Solution:**
```css
.flex-container {
  flex-wrap: wrap;
  /* Set explicit widths on items */
}
.flex-item {
  width: calc(33.333% - 10px);  /* Three per row */
  /* Or use flex: 0 0 calc(33.333% - 10px) */
}
```

### Problem 8: Table Headers Not Repeating

**Symptom:** Table spans pages but header doesn't repeat.

**Solution:**
```css
thead { display: table-header-group; }
/* Ensure border-collapse: collapse is set */
```

### Problem 9: PDF File Size Too Large

**Solutions:**
```bash
# Optimize images
weasyprint --optimize-images input.html output.pdf

# Limit image DPI
weasyprint -D 150 input.html output.pdf

# Reduce JPEG quality
weasyprint -j 80 input.html output.pdf
```

### Problem 10: Blank Pages Appearing

**Symptom:** Extra blank pages between content.

**Cause:** `break-before: right` on a page that's already right, or content ending exactly at page boundary.

**Solution:**
```css
/* Use break-after instead of break-before where possible */
/* Avoid forcing odd/even page breaks unless necessary */
```

---

## 19. Known Limitations

### What WeasyPrint Does NOT Support

| Feature | Status |
|---------|--------|
| JavaScript | ❌ Not supported |
| Cookies / Auth | ❌ Not supported (custom URL fetcher needed) |
| RTL / Bidirectional text | ❌ Not supported |
| `visibility: collapse` on tables | ❌ Not supported |
| System colors/fonts | ❌ Not supported |
| `:hover`, `:active`, `:focus` | ❌ Accepted but never match |
| `:target`, `:visited` | ❌ Accepted but never match |
| `:dir()` selector | ❌ Not supported |
| Input pseudo-classes (`:valid`, `:invalid`) | ❌ Not supported |
| Column selectors (`||`, `:nth-col()`) | ❌ Not supported |
| `text-shadow` | ❌ Not supported |
| `text-underline-position` | ❌ Not supported |
| `text-emphasis-*` | ❌ Not supported |
| `quotes` in content | ❌ Not supported |
| `footnote-display: compact` | ❌ Not supported |
| `element()` start parameter | ❌ Not supported |
| `color-mix()` | ❌ Not supported |
| `contrast-color()` | ❌ Not supported |
| min/max-width on table cells | ❌ Not supported |
| min/max-height on page-margin boxes | ❌ Not supported |

### Partial Support

| Feature | Notes |
|---------|-------|
| Flexbox | Works for simple cases, not deeply tested |
| Grid | Works for simple cases, fragmentation between rows supported (v67+) |
| `box-decoration-break: slice` | Backgrounds always repeated, not extended |
| `@page` counter known limitations | See issue #93 |

---

## 20. Performance Tips

### Image Optimization

```python
# Reduce file size
HTML(string=html).write_pdf(
    'output.pdf',
    optimize_images=True,
    jpeg_quality=80,
    dpi=150  # Lower DPI for faster rendering
)
```

### Caching

```bash
# Disk cache for large documents
weasyprint -c /tmp/weasyprint-cache input.html output.pdf
```

### Font Subsetting

```bash
# Install hb-subset for faster subsetting (default)
# Falls back to fontTools if not available
```

### Memory Optimization

```bash
# For very large documents
weasyprint --cache-folder /tmp/wp-cache input.html output.pdf
```

### Tips

1. **Use SVG over raster images** where possible β€” vectors are smaller and scale infinitely
2. **Subset fonts** (default behavior) β€” only embed used glyphs
3. **Optimize images** before embedding β€” use external tools to compress
4. **Use `--optimize-images`** flag for lossless optimization
5. **Avoid very large inline SVGs** β€” keep them under a few KB each
6. **Limit page count** β€” generate in batches if needed
7. **Use `@font-face` selectively** β€” each font file adds to PDF size

---

## 21. Sample Patterns from Official Docs

### Pattern 1: Complete Report with Headers

```css
@font-face { font-family: 'Fira Sans'; src: url(FiraSans-Regular.ttf); }

:root {
  --black: #393939;
  --orange: #fbc847;
}

@page {
  @top-left {
    background: var(--orange);
    content: counter(page);
    height: 1cm;
    text-align: center;
    width: 1cm;
  }
  @top-center {
    background: var(--orange);
    content: '';
    display: block;
    height: 0.05cm;
    width: 100%;
  }
  @top-right {
    content: string(heading);
    font-size: 9pt;
    height: 1cm;
    vertical-align: middle;
  }
}

@page :blank {
  @top-left { background: none; content: '' }
  @top-center { content: none }
  @top-right { content: none }
}

h2 {
  break-before: always;
  string-set: heading content();
}

html { color: var(--black); font-family: 'Fira Sans'; font-size: 11pt; }
body { margin: 0; }
```

### Pattern 2: Book with Running Headers

```css
@page {
  margin: 2cm 2cm 3cm 2cm;
  size: 148mm 210mm;
}

@page :left {
  @bottom-left { content: counter(page); }
  @bottom-right { content: string(heading); }
}

@page :right {
  @bottom-left { content: string(heading); }
  @bottom-right { content: counter(page); }
}

@page :blank {
  @bottom-right { content: none; }
  @bottom-left { content: none; }
}

h2 {
  string-set: heading content();
}
```

### Pattern 3: Table of Contents with Page Numbers

```css
#contents ul {
  list-style: none;
  padding-left: 0;
}

#contents a {
  color: inherit;
  text-decoration: none;

  &::before {
    content: target-text(attr(href));
  }

  &::after {
    content: leader(dotted) ' ' target-counter(attr(href), page);
    float: right;
  }
}
```

### Pattern 4: Cover Page

```css
@page :first {
  background: url(cover.jpg) no-repeat center;
  background-size: cover;
  margin: 0;
}

#cover {
  display: flex;
  flex-wrap: wrap;
  align-content: space-between;
  height: 297mm;  /* A4 height */
}
```

### Pattern 5: Chapter Break Pages

```css
@page chapter {
  background: #673ab7;
  margin: 0;
  @top-left { content: none; }
  @top-center { content: none; }
  @top-right { content: none; }
}

#chapter {
  page: chapter;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 297mm;
}
```

---

## 22. Python API Essentials

### Basic Usage

```python
from weasyprint import HTML, CSS

# From string
HTML(string='<h1>Hello</h1>').write_pdf('output.pdf')

# From file
HTML(filename='input.html').write_pdf('output.pdf')

# From URL
HTML(url='https://example.com').write_pdf('output.pdf')

# With user stylesheet
HTML(string=html).write_pdf(
    'output.pdf',
    stylesheets=[CSS(filename='styles.css')]
)
```

### Advanced Options

```python
HTML(string=html).write_pdf(
    'output.pdf',
    stylesheets=[CSS(filename='style.css')],
    presentational_hints=True,       # Respect HTML attributes
    optimize_images=True,             # Optimize embedded images
    jpeg_quality=80,                  # JPEG quality 0-95
    dpi=150,                          # Max image DPI
    pdf_variant='pdf/a-3u',          # PDF/A archiving
    pdf_identifier='my-doc-v1',      # PDF version ID
    attachment=Attachment('data.xlsx'),
    pdf_forms=True,                  # Include form fields
)
```

### Render Without Writing

```python
doc = HTML(string=html).render()
print(f"Total pages: {len(doc.pages)}")
print(f"Page 1 size: {doc.pages[0].width} x {doc.pages[0].height}")
doc.write_pdf('output.pdf')
```

### Custom URL Fetcher

```python
from weasyprint import HTML, URLFetcher, URLFetcherResponse

class MyFetcher(URLFetcher):
    def url_fetch(self, url, type=None, nonce=None):
        # Custom fetching logic
        return URLFetcherResponse(...)

HTML(string=html, url_fetcher=MyFetcher()).write_pdf('output.pdf')
```

### PDF Variants

```python
# PDF/A (Archiving)
HTML(string=html).write_pdf('output.pdf', pdf_variant='pdf/a-3u')

# PDF/UA (Accessibility)
HTML(string=html).write_pdf('output.pdf', pdf_variant='pdf/ua-1')

# PDF/X (Print)
HTML(string=html).write_pdf('output.pdf', pdf_variant='pdf/x-4')
```

---

## 23. Debugging Tips

### Verbose Mode

```bash
weasyprint -v input.html output.pdf    # Show warnings
weasyprint -d input.html output.pdf    # Show debug info
```

### Uncompressed PDF (for debugging)

```bash
weasyprint --uncompressed-pdf input.html output.pdf
```

### Check Font Resolution

```bash
fc-list                                    # List all fonts
fc-match "Font Name"                       # Check font matching
```

### Common Console Errors

| Error | Solution |
|-------|----------|
| `TTF/OTF parsing error` | Check font file integrity |
| `Font not found` | Use `@font-face` or install font |
| `Image not found` | Verify path, use `--base-url` |
| `Failed to load stylesheet` | Check CSS path/URL |
| `W3C CSS warnings` | Usually non-critical, check logs |
| `Missing glyph` | Font doesn't support that character |

### Testing Strategy

1. **Test with `-v` first** to catch warnings early
2. **Start simple** β€” basic HTML, then add CSS progressively
3. **Test each page type separately** β€” cover, content, appendix
4. **Use small HTML samples** to isolate issues
5. **Check `--uncompressed-pdf`** for visual debugging

---

## QUICK REFERENCE CARD

```
PAGE SETUP:
  @page { size: Letter; margin: 2cm; }
  @page :first { margin: 0; background: url(cover.jpg); }
  @page :blank { @bottom-center { content: none; } }

MARGIN BOXES:
  @page { @bottom-right { content: counter(page); } }
  @page { @top-right { content: string(chapter); } }

PAGE BREAKS:
  .keep-together { break-inside: avoid; }
  .page-before { break-before: page; }
  .avoid-split { break-after: avoid; }

NAMED PAGES:
  @page special { background: red; }
  .use-special { page: special; }

COUNTERS:
  html { counter-reset: chapter; }
  h1 { counter-increment: chapter; }
  h1::before { content: "Ch. " counter(chapter) " β€” "; }

CROSS-REFS:
  a::after { content: " (p. " target-counter(attr(href), page) ")"; }

BOOKMARKS:
  h1 { bookmark-level: 1; }
  .no-bookmark { bookmark-level: none; }

FLEXBOX:
  .row { display: flex; flex-wrap: wrap; gap: 10px; }
  .col { flex: 1; }

GRID:
  .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

VARIABLES:
  :root { --color: #2a9d8f; }
  .box { background: var(--color); }
```

---

## SOURCES

- [WeasyPrint Official Docs](https://doc.courtbouillon.org/weasyprint/stable/)
- [WeasyPrint GitHub](https://github.com/Kozea/WeasyPrint)
- [WeasyPrint Samples](https://github.com/CourtBouillon/weasyprint-samples)
- [WeasyPrint Changelog](https://github.com/Kozea/WeasyPrint/releases)
- [CSS Paged Media Module Level 3](https://drafts.csswg.org/css-page-3/)
- [CSS Generated Content Module Level 3](https://www.w3.org/TR/css-content-3/)
- [CSS Fragmentation Module Level 3](https://www.w3.org/TR/css-break-3/)
- [MDN @page Reference](https://developer.mozilla.org/en-US/docs/Web/CSS/@page)

---

*Research compiled by Vincent on May 13, 2026. WeasyPrint version 68.1.*