You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 additive — ctx.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_objects → write_font.
The sRGB/sGray ICC references are one-per-Context and are likewise written once.
So, if a singleContext 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.pubfntrees_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).
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:
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 oneFontper font, whoseglyph_set/glyph_remapperaccumulate the glyphs actually used.fill_fonts(group, &mut ctx, fontdb)is additive —ctx.fonts.entry(g.font).or_insert_with(..)then records each used glyph. Calling it repeatedly just unions glyphs into the same per-font entry.Context::write_global_objects→write_font.Contextand are likewise written once.So, if a single
Contexthasfill_fontsrun for every tree before any font is written,write_global_objectsnaturally 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
MediaBoxis that tree'ssize()scaled by thePageOptions::dpiratio, exactly asto_pdfdoes — pages may differ in size.trees_to_pdf(&[&tree], ..)is equivalent toto_pdf(&tree, ..);to_pdfcan be re-implemented as a one-line wrapper.Constraint: all trees must share one font database
Fonts are keyed by
fontdb::ID, andfontdb::IDis a per-Databaseslotmap key that is not stable across independently constructedfontdb::Databaseinstances (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 — oneusvg::Optionsparses them all). Mixing databases would otherwise merge the wrong glyphs into a subset. The implementation should detect this (all trees share oneArc<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).