Skip to content
This repository was archived by the owner on Jul 10, 2026. It is now read-only.
This repository was archived by the owner on Jul 10, 2026. It is now read-only.

Convenience API to combine N SVG trees into one multi-page PDF, sharing fonts and color profiles across pages #100

Description

@tfenne

Convenience API to combine N SVG trees into one multi-page PDF, sharing fonts and color profiles across pages

I'd like to add a public function that converts several usvg::Trees into a single multi-page PDF — one page per tree — embedding each font subset and ICC color profile once for the whole document rather than once per page. This is mostly a small convenience API over what already exists - the added benefit is just the deduping of fonts/glyphs and color profiles.

Related but different issues

This is a distinct feature from the two existing multi-page issues:

  • Multiple pages #15 (closed, "not planned") is about auto-paginating a single oversized SVG into page breaks.
  • SVG with multiple pages #92 (open) is about an SVG file that itself contains multiple pages, which depends on upstream usvg support.

This proposal is a third, orthogonal case: N independent trees → N pages, driven by the caller.

Motivation

I'm two steps removed, as a user of kuva, a plotting library that uses svg2pdf to generate PDF renderings of it's plots. I'm working on a PR there to support multi-page PDF emission, which can be done today with the existing svg2pdf API. But while working on this I noted that the current API will re-embed a) the ICC color profile, and b) font subsets, for each and every page/scene added to the PDF. So e.g. a PDF of 5-10 pages each with it's own plot will embed similar font subsets 5-10 times, and 5-10 color profiles.

All the pieces exist to emit a single PDF with consolidated font tables and a singular (or minimal set of) color profiles, the missing piece is just a pub API that allows users to do it.

Implementation Direction

I'm working on a PR, but wanted to submit the issue so there's some independent documentaiton on why I'm making the PR...

The font-subsetting pipeline already accumulates glyph usage per font and defers the actual subsetting to the end of conversion:

  • Context.fonts: HashMap<fontdb::ID, Option<Font>> holds one Font per font, whose glyph_set/glyph_remapper accumulate the glyphs actually used.
  • fill_fonts(group, &mut ctx, fontdb) is additivectx.fonts.entry(g.font).or_insert_with(..) then records each used glyph. Calling it repeatedly just unions glyphs into the same per-font entry.
  • Subsetting + writing the font program happens once, lazily, in Context::write_global_objectswrite_font.
  • The sRGB/sGray ICC references are one-per-Context and are likewise written once.

So, if a single Context has fill_fonts run for every tree before any font is written, write_global_objects naturally emits one union subset per font and one ICC profile per color space for the whole document — no new subsetting logic required. The feature is just a new API seam that ties it all together.

Proposed API

/// Convert several SVG trees into a single multi-page PDF, one page per tree.
/// Fonts and color profiles are embedded once and shared across all pages.
pub fn trees_to_pdf(
    trees: &[&usvg::Tree],
    conversion_options: ConversionOptions,
    page_options: PageOptions,
) -> Result<Vec<u8>>;
  • Each page's MediaBox is that tree's size() scaled by the PageOptions::dpi ratio, exactly as to_pdf does — pages may differ in size.
  • trees_to_pdf(&[&tree], ..) is equivalent to to_pdf(&tree, ..); to_pdf can be re-implemented as a one-line wrapper.
  • An empty slice returns an error, since a PDF must contain at least one page.

Constraint: all trees must share one font database

Fonts are keyed by fontdb::ID, and fontdb::ID is a per-Database slotmap key that is not stable across independently constructed fontdb::Database instances (two fresh single-font databases hand out the same ID for their first face). So all trees passed together must be parsed against the same font database (the normal usage — one usvg::Options parses them all). Mixing databases would otherwise merge the wrong glyphs into a subset. The implementation should detect this (all trees share one Arc<fontdb::Database>) and return an error rather than silently producing wrong output.

Scope

Cross-page sharing covers fonts and ICC profiles. Repeated raster images / gradients / patterns across pages are not deduplicated (each page renders independently).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions