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
counter(pages), and [topage] in headers and footersThe total page count is not known in a single pass
--tocSame reason; a table of contents needs the page numbers of the body
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-child, :nth-last-child, :last-of-type, :nth-last-of-type, :only-child, :only-of-type, and :emptyNever match, because they cannot be decided until the parent’s child list is complete

Selectors that look backwards do not work because the style has to be settled the moment the element is read. Rewrite rules such as “remove the rule on the last row only” by adding a class and writing .last { … } instead.

Still available

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

  • --cover, the cover page
  • Headers and footers, as far as [page]; [topage] is not available
  • 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

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.