Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Streaming mode

With --streaming, the HTML is read in chunks, each page is written to the PDF as soon as its layout is final, and the memory that page used is released.

sghtmltopdf big.html -o big.pdf --streaming

It pays off for HTML with tens of thousands of elements (see Memory and processing time). In exchange, anything that cannot be decided without seeing the whole document becomes unavailable.

Not available (these are errors)

Passing any of these exits with code 3.

Not availableWhy
A background colour or border on <html> or <body> itselfThat decoration would have to be reproduced across every page
<style> or <link rel="stylesheet"> after <body>It cannot be applied retroactively to pages that are already written

Warned about, but processing continues

These change the result, so a warning is printed rather than passing over them silently.

LimitationBehaviour
Finding a system font from a font-family nameThe default font is used instead. Naming the font with --font, --gothic-font, --serif-font, --mono-font, or @font-face resolves it.
Searching the system for a font that can draw a given characterNot done, because the whole document cannot be read ahead. Only when no font at all is given, one CJK font is loaded up front (see Fonts). Characters that cannot be drawn are reported one by one.
:last-of-type, :only-of-type, :nth-last-child(), :nth-last-of-type(), :has(~ ...)The result changes: they match too much, or miss altogether

An element directly under <body> has its style settled the moment its next sibling appears. Only selectors that need to know whether more elements of the same kind follow are therefore undecidable. Rewrite rules such as “remove the rule on the last row only” by adding a class and writing .last { … } instead.

Every other sibling selector — +, ~, :first-child, :nth-child(), :last-child and so on — gives the same result as batch mode. See Selectors for the details.

Still available

The following work exactly as they do in the normal mode.

  • --cover (a cover page), --toc (a table of contents), and --page-offset
  • Headers and footers, [page] and [topage] included, HTML ones too
  • counter(page) and counter(pages) in @page margin boxes
  • Page setup and PDF metadata
  • --grayscale, --dpi, and --zoom
  • The options that change what is drawn, such as --no-images
  • Page breaks, including break-before, break-after, break-inside, orphans, and widows

Decoration that needs the total page count is written last, as PDF Form XObjects the pages refer to ahead of time. Body pages are still written and released as they settle, so neither seeking nor keeping the whole body is needed. The only extra information kept is a reference and a page number per page, and the decoration is drawn one page at a time as well. No object already written is rewritten, so neither an incremental update nor a second cross-reference table is needed. The PDF is complete once the final cross-reference table and trailer are written.

For the table of contents, only each heading’s text, link target, and body page number are kept, and the body’s layout is released. The table of contents pages are written last, but the finished PDF orders its pages as cover, table of contents, then body. The cover counts neither in the page numbers nor in the total. The table of contents and the body are numbered continuously from 1 + --page-offset, and the total does not include the offset. With a table of contents, the body’s headers, footers, and margin boxes are also drawn once the number of table of contents pages is settled.

Memory and processing time

These are measured figures for converting the same HTML in both modes. Each cell shows peak memory and processing time.

ElementsHTML sizeNormal mode--streaming
1,00046KB11MB / 0.02s8MB / 0.02s
5,000233KB26MB / 0.08s10MB / 0.10s
20,000946KB81MB / 0.35s15MB / 0.38s
60,0002.8MB228MB / 1.05s28MB / 1.07s

Peak memory in the normal mode grows roughly in proportion to the document, while with --streaming it barely grows at all; at 60,000 elements it is about one eighth. The slight growth that remains comes from the PDF cross-reference table, which holds the position of every object, and from the tally of glyphs in use, both of which are kept until the end.

Processing time is much the same in both modes. --streaming comes out slightly slower, but the difference stays within 0.03 seconds at every size, and shrinks in relative terms as the document grows (2% at 60,000 elements). You are not trading time away for the large drop in memory.

The measurements were taken as follows.

  • A release build of sghtmltopdf 0.1.0, on an Intel Core Ultra 7 258V with 16GB of memory, under WSL2 (Linux 5.15)
  • HTML consisting of nothing but the given number of <p> elements, each 60px tall, with the font named through --font
  • Peak memory is the maximum resident set size (RSS) of the process, taking the better of two runs

When to use it

  • Producing a statement of several thousand pages from a single HTML file
  • Running where memory is tightly capped, such as under a container limit or on a serverless platform

For a document of a few dozen pages, such as an invoice, the normal mode is fine. There is no reason to take on the limitations.

In server mode the same mode is selected by adding streaming to the query. Combined with ?stream=1, which returns the body chunked, input, rendering, and output all become incremental.

curl --data-binary @big.html 'http://127.0.0.1:8080/pdf?stream=1&streaming' -o out.pdf

How streaming works

flowchart TD
    A["HTML chunk"] --> B["Streaming parser"]
    B --> C["Style cascade"]
    C --> D["Layout + pagination"]
    D --> E["PDF writer"]
    E --> F["PDF output"]
    D -. "on every settled page" .-> E
    E -. "free the memory and read on" .-> B

As soon as a page boundary is final, that page is written to the PDF and the memory it used is released before reading on, which is how large documents are processed without memory use growing.