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.
| Variant | Exit code | Examples |
|---|---|---|
Usage | 1 | An unknown option, a malformed value, or an option that will not be implemented |
Input | 2 | A missing file, a font that cannot be read, or a failed write |
Render | 3 | An engine limit, such as the nesting depth, was exceeded |
Timeout | 4 | The 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.
| Sink | Destination |
|---|---|
MemorySink | Memory. finish returns the bytes |
FileSink | A file. Writes to a temporary file and renames it onto the output path only on success |
StdoutSink | Standard output |
BufferedSink | Hands 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
| Features | Default | What it does |
|---|---|---|
cli | On | The sghtmltopdf command and Converter (clap) |
server | On | sghtmltopdf server (tiny_http) |
svg | On | SVG images embedded as vectors (svg2pdf) |
svg-text | Off | <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.