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

From Rust

The engine is published on crates.io as sghtmltopdf. It is the same crate that builds the CLI binary. See docs.rs for the full API.

cargo add sghtmltopdf

Converter

Converter takes the same list of options as the CLI. Everything in the option reference works with the same name and the same meaning.

use sghtmltopdf::{with_render_stack, Converter};

let converter = Converter::from_args(["--page-size", "A4", "--margin-top", "20mm"])?;
let html = std::fs::File::open("invoice.html")?;
let pdf = with_render_stack(|| converter.render_to_vec(html))?;
std::fs::write("invoice.pdf", pdf)?;

The options are validated once, in from_args. A Converter can then render any number of documents, and it is Send, so it can be moved to another thread.

An input path, --output and the server subcommand are rejected. The HTML is read from the reader passed to render, and the PDF is written to the sink passed to it.

The character encoding is detected in the same order as a browser does (BOM, <meta charset>, then UTF-8). --encoding overrides it.

Stack size

Rendering recurses as deep as the document is nested. A thread’s default stack can overflow on a deep document, so run it through with_render_stack, which uses a thread with a large enough stack (16MiB). The closure’s return value is passed back as is, and a panic propagates to the caller.

Errors

ConvertError has the same four classes as the CLI’s exit codes.

VariantExit codeExamples
Usage1An unknown option, a malformed value, or an option that will not be implemented
Input2A missing file, a font that cannot be read, or a failed write
Render3An engine limit, such as the nesting depth, was exceeded
Timeout4The time limit was exceeded (HTTP server mode only)

ConvertError is #[non_exhaustive], so a match on it needs a _ arm.

Writing pages out as soon as they are final

render_to_vec collects the whole PDF in memory before returning it. Pass your own Sink to render instead, and it receives the bytes each time a page’s layout becomes final.

The engine calls write with the bytes in order (at least once per completed page), and finish exactly once at the end. What finish returns is what render returns.

use std::io::{self, Write};
use sghtmltopdf::{with_render_stack, Converter, Sink};

struct WriterSink<W: Write>(W);

impl<W: Write> Sink for WriterSink<W> {
    type Output = W;
    type Error = io::Error;

    fn write(&mut self, bytes: &[u8]) -> io::Result<()> {
        self.0.write_all(bytes)
    }

    fn finish(mut self) -> io::Result<W> {
        self.0.flush()?;
        Ok(self.0)
    }
}

let converter = Converter::from_args(["--page-size", "A4"])?;
let html = std::fs::File::open("invoice.html")?;
let socket = std::net::TcpStream::connect("127.0.0.1:9000")?;
with_render_stack(|| converter.render(html, WriterSink(socket)))?;

A sink passed to Converter::render must have io::Error as its Error.

These sinks are provided.

SinkDestination
MemorySinkMemory. finish returns the bytes
FileSinkA file. Writes to a temporary file and renames it onto the output path only on success
StdoutSinkStandard output
BufferedSinkHands the bytes to a callback in parts of a given size. Meant for S3 multipart uploads, where every part but the last must be at least 5MB

Combined with streaming mode (--streaming), the memory of each page is released once it is written, and reading continues. See Streaming mode for the features it cannot use.

Engine

Engine is the lower-level API. Build EngineOptions, feed the HTML in chunks, and finish at the end.

use sghtmltopdf::{Engine, EngineOptions, FontSpec, MemorySink, Mode, PageSize};

let mut options = EngineOptions::default();
options.mode = Mode::Streaming;
options.settings.size = PageSize::A4;
options.fonts = vec![FontSpec { path: "fonts/NotoSansJP-Regular.ttf".into(), index: 0 }];

let mut engine = Engine::new(options, MemorySink::new());
engine.feed(b"<!DOCTYPE html><p>Hello</p>")?;
let pdf: Vec<u8> = engine.finish()?;

EngineOptions is #[non_exhaustive], so it cannot be written as a struct literal outside the crate. Start from EngineOptions::default() and assign the fields you need.

Detecting the character encoding, turning simple options such as --header-center into @page rules, and reading header and footer HTML are done by Converter. Engine treats the bytes it receives as UTF-8.

Local file access has a different default too. EngineOptions defaults to no restriction, and does not refuse paths outside the base directory the way the CLI does. Set local_access when handling untrusted HTML.

Unless you have a reason not to, use Converter.

Features

FeaturesDefaultWhat it does
cliOnThe sghtmltopdf command and Converter (clap)
serverOnsghtmltopdf server (tiny_http)
svgOnSVG images embedded as vectors (svg2pdf)
svg-textOff<text> inside SVG images

For library use, dropping the HTTP server trims the dependencies.

[dependencies]
sghtmltopdf = { version = "0.5", default-features = false, features = ["cli", "svg"] }

Stability

Only the items available at the crate root follow semver. Modules such as sghtmltopdf::layout are hidden from the documentation. They are reachable only for the tests and the Ruby binding, and may change in any release.