chore(deps): update rust crate printpdf to 0.12.0#68
Open
renovate[bot] wants to merge 1 commit into
Open
Conversation
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
2 times, most recently
from
June 22, 2026 14:19
33401ac to
1ec2bab
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
from
June 23, 2026 15:25
1ec2bab to
d731851
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
from
July 6, 2026 11:44
d731851 to
b4e4210
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
2 times, most recently
from
July 14, 2026 11:42
f3a550d to
7738b33
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
2 times, most recently
from
July 18, 2026 13:16
447b35b to
d96d285
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
from
July 18, 2026 20:36
d96d285 to
cdd8fc1
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
2 times, most recently
from
July 23, 2026 09:12
84e2a0c to
dc152f7
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
from
July 23, 2026 15:05
dc152f7 to
37aea44
Compare
renovate
Bot
force-pushed
the
renovate/printpdf-0.x
branch
from
July 23, 2026 18:01
37aea44 to
66485c4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR contains the following updates:
0.7.0→0.12.0Release Notes
fschutt/printpdf (printpdf)
v0.12.4Compare Source
v0.12.3Compare Source
v0.12.2Compare Source
v0.12.0The release where OTF fonts display correctly in Acrobat again (#280), the parser grew
up, the wasm demo came back from the dead, and the whole thing is verified end-to-end
in a headless browser on every push.
CID-keyed CFF fonts render correctly in Acrobat and Preview (#280). Fonts like
NotoSansJP carry a CID-keyed CFF whose charset does NOT map glyph ids 1:1 to CIDs
(they diverge from glyph 365 on). printpdf wrote glyph ids as the Identity-H codes -
but a spec-following viewer resolves every code as a CID through that charset
(ISO 32000-1, 9.7.4.2), so Acrobat and Preview drew wrong CJK glyphs and nothing at
all for the fullwidth brackets, while Chrome's PDFium (which falls back to
code == GID) looked fine and copy-paste worked (ToUnicode was keyed by the written
code). Both embed paths were wrong - the subsetter preserves the original CIDs in
the subset charset while the content stream emitted renumbered sequential gids. The
content stream,
/Wwidths and/ToUnicodeare now all keyed by the embeddedcharset's CIDs; parsing resolves codes back CID→GID (through
/CIDToGIDMapstreamsand CFF charsets - foreign Adobe/dvipdfmx CJK PDFs re-render correctly now too, and
legacy code==GID files are detected and kept stable). Guarded three ways: a
deterministic mock-font suite (generated TTF/CFF/CID-CFF faces with distinct defined
widths, so glyph identity is checked as arithmetic), a hard-coded-CID canary, and a
fontTools CI job that re-resolves every code with Acrobat's semantics, independent of
printpdf, allsorts and poppler alike. Related font-dictionary fixes: the
XXXXXX+subset tag only appears when the font was actually subset,
/FontNamematches/BaseFont, CIDFontType2 carries an explicit/CIDToGIDMap /Identity,.ttcsources embed the single selected face instead of the whole collection, and
ToUnicode synthesis covers supplementary planes. Text-showing ops outside
StartTextSection/EndTextSection- the classic "my PDF is blank" mistake (#254) -now warn loudly at save time. Type0 fonts with non-Identity-H encodings warn on
parse instead of silently scrambling.
Requires
rust-fontconfig 4.4.6; ships against the publishedazul-layout 0.0.10(theagg-rust-azulrasterizer under its svg path is pinned<1.0.3, whoseAPI break made fresh
svg-feature builds fail to compile - the #279 failure shape,one dependency over). The azul master improvements - including the wasm
Instant::now()clock fix on the font LRU path (rust-fontconfig's half of that fixis in 4.4.6) - arrive with the azul-layout 0.0.11 bump in a follow-up release.
PDF parsing / round-trip, rewritten where it lied. Text decoding no longer guesses
code width from string byte-parity - it is resolved per font at
Tf(builtin/simplefonts decode per byte through ToUnicode-else-WinAnsi; Type0 through 2-byte gids with
per-glyph Unicode carried from the ToUnicode CMap, synthesized from the font's own cmap
when the PDF has none). Form XObjects, undecodable images and printpdf's own
FlateDecode raw bitmaps (with
/SMaskalpha) survive parsing instead of being dropped;MediaBox inherits through the page tree;
'/"operators, link annotations(
/Rectwas misread), layers (/Propertiesnames now correlate - no more "MarkedContent unknown"), and
/Shadingresources +shops (axial/radial withexponential/stitching functions) all round-trip. Embedded fonts named
F1–F14are nolonger mistaken for the builtin standard-14 on either parse or save (that was instant
mojibake for foreign PDFs). The ToUnicode CMap parser is a real tokenizer now:
single-line CMaps, multi-codepoint bfchar targets, surrogate pairs - a ligature no
longer kills the whole CMap, and regenerated CMaps keep full multi-char targets
("Configure" copy-pastes with its "fi" intact). Ten corpus PDFs round-trip
visually identical with text surviving
pdftotext, twice.Font subsetting fixed at the root. The original→subset glyph renumbering comes from
the subsetter's own input-order mapping instead of being "recovered" through the subset
cmap - shaper-substituted glyphs (ligatures, digit alternates) have no cmap entry, so
list markers and page numbers rendered as ☒ tofu and ligatures fell back to their first
letter. Inline-span backgrounds also no longer paint over their own text (the bridge
now flushes text at azul's own paint position).
SVG conversion is self-contained and Acrobat-clean. The svg2pdf page's full
/Resourcessubtree (ColorSpace dictionaries, ExtGState, Pattern, Shading, Fonts) iscarried into the Form XObject with every indirect reference inlined and streams hoisted
back to indirect objects at write time; the content stream is byte-preserved instead of
lossily re-parsed (
/p0 scnused to come back as0 g- solid black). Gradients,tiling patterns, transparency groups and clip paths render correctly (poppler error
count on the tiger: ~600 → 0), and
<text>without a resolvable font-family is a loudwarning instead of a silent drop.
The wasm demo is rebuilt and continuously tested. New endpoints:
Pdf_RegisterFonts(register default fonts once instead of resending ~10 MB of base64per keystroke) and
Pdf_DecodeImage(images/signatures decode to the document'sRawImageJSON - the sign tab was impossible without it). Pixel data serializes asbase64 (
{"u8b64": …}) instead of JSON number arrays - a 1024×1024 RGBA signature wentfrom ~16 MB of JSON to ~5.6 MB, and the old array format stays readable forever. The
API returns a structured error envelope even when the response fails to serialize
(it used to return an empty string). The demo page itself is new (tabs, live preview,
minimap, warnings drawer, dark mode) with every JS↔wasm wire-format mismatch fixed,
and
tests/e2e/drives the real UI in headless Chrome - boot, render with fonts,image embedding, download, re-upload of our own PDF, signature stamping, save - on
every push (
demo-e2e.yml), with poppler validating the downloaded files. Pagesdeploys (
static.yml) now ship the exact build the e2e suite tested, automatically onrelease tags.
PdfPage::to_svg(the page preview) renders text again. It had three compoundingbugs: the glyph position was applied twice (once as
<text>attributes, once in thetransform) with a wrong Y-flip that threw every glyph ~800 pt off the top of the page;
the current font was never recorded so all text used the Times builtin; and the
@font-faceembedded the PDF subset (which dropsOS/2/name/post), whichbrowsers reject. Text now renders upright, on-page, in the embedded font, and stays
selectable. (A re-parsed document whose only font is a subset still falls back in the
browser preview, since a subset is not a valid webfont; the PDF itself is unaffected.)
The demo also gained a browser HTML reference view, in-browser font/image extraction
from a parsed PDF, and image optimization on save (a one-photo document dropped from
~6 MB to ~150 KB).
Housekeeping:
Op::UseXobjecttransforms grewno_auto_scale(parsed content alreadycarries its placement; the parser previously had to emit a reciprocal scale to cancel
the automatic one), saving a document containing images without any image-format
feature writes raw FlateDecode pixels instead of panicking, and
get_external_font_idsreturns real ids instead of a stub (page previews finally receive their fonts).
v0.11.2Compare Source
0.11.0 and 0.11.1 could be impossible to install, depending entirely on the
consumer's dependency resolution (#279). printpdf asked for lopdf's
timefeature, andlopdf 0.43/0.44's
timecode only compiles againsttime>= 0.3.48, whereFormatItem::StringLiteralfirst exists (J-F-Liu/lopdf#518). A fresh resolution on acurrent toolchain lands on
time0.3.53 and works - which is exactly why CI and therelease builds stayed green - but any workspace whose lockfile predates
time0.3.48(June 2026), or whose MSRV-aware resolver walks below it, got a compile error inside
lopdf. printpdf never used anything the feature gates (PDF dates are formatted and
parsed in printpdf itself), so the feature is simply dropped. If you need lopdf's
Object <-> timeconversions, enable lopdf'stimefeature from your own manifestonce a lopdf ships in which it compiles.
Three more "the declared floor admits versions that cannot build" bugs of the same
class:
timeis now>= 0.3.36(0.3.25–0.3.35 fail type inference on rustc >= 1.80),imageis>= 0.25.2(image::ImageReaderfirst exists there), andrust-version = "1.88"is declared (it was already the effective floor via lopdf 0.44,just invisible to MSRV-aware resolvers). weezl moved to 0.2 so consumer graphs stop
linking two copies of the LZW coder, and a vestigial getrandom 0.3 dependency - whose
only remaining dependent was printpdf itself - is gone from wasm builds.
The reason this bug class kept shipping is that every CI job proved only "today's
freshest resolution on this runner compiles". Two new guards close that gap. In
ci.yaml,
consumer-resolutionbuilds the tree the way a consumer resolves it (freshproject, fresh lockfile, path dependency on the checkout) and then re-pins the
known-bad lines -
time0.3.47,image0.25.2 - so every breakage that ever shippedstays red forever. And the new
post-release.ymlverifies the published artifact:on every master push it
cargo add printpdf@=<Cargo.toml version>s into a pristineproject (no checkout, no lockfile) across Linux/Windows/macOS/wasm32 and the feature
matrix; pushes whose commit message starts with "release" wait up to 20 minutes for
crates.io and then fail if the version never appears. A daily cron re-checks the
newest published version, so a dependency release that breaks resolution within our
semver ranges turns CI red the next morning instead of waiting for an issue report.
publish-safety also now fails if the .crate approaches crates.io's 10 MiB cap, which
the package sits at ~87% of (the examples/assets tree is include_bytes!'d by tests and
examples, so it must ship).
Fonts can now be passed in from the outside on every path that needs them.
Svg::parse_with_fontsfeeds caller-supplied fonts into SVG<text>conversion -previously that path only ever saw system fonts, and on wasm (where there is no system
database to scan) no fonts at all. For the HTML/text-layout path,
build_font_pool_with_memory_fontsregisters pre-patternedrust_fontconfigmemoryfonts with their metadata intact instead of re-deriving patterns from the bytes, and
printpdf::htmlre-exportsrust_fontconfigso the pattern types are constructiblewithout version-matching the crate yourself. Related wasm/dependency cleanups: the
js-sys browser fast paths (canvas decode/encode) are compiled out on wasi targets,
whose binaries otherwise carried
window/canvas imports no wasi runtime can satisfy;time's unused serde features are dropped;rust-fontconfigis bounded< 4.5(4.4.4 was the double-allsorts release); and CI now builds
text_layoutalone,js-syson wasip2,svgon wasm, and runs the svg test suite - configurations thatpreviously had zero coverage.
Hostile PDFs are also harder to crash now. A
/CreationDatecontaining any non-ASCIIbyte panicked
PdfDocument::parse(string slicing at byte offsets asserts charboundaries); parsing works on raw bytes now. A
/ToUnicodebfrange withend < startpanicked under overflow checks, and a single
<00000000> <FFFFFFFF> <0041>lineexpanded to 2^32 map entries - an OOM from a few bytes of input; both now error, which
costs the hostile font its ToUnicode map and nothing else. PDF/A / PDF/X XMP metadata
wrote single-digit months (
D:2018-9-19T...- veraPDF rejects it) because theMonthenum's
Displayignored the{:02}width. Andunix_timestamp/from_unix_timestampare now the same pure calendar arithmetic on every target: theold wasm implementations went through the JS
Date, which interpreted the fields inthe browser's local timezone, ignored the stored offset, and rejected pre-1970 dates
(wasi builds just returned 0).
v0.11.1Compare Source
printpdf linked two copies of allsorts -
0.17directly, and0.16again underneathrust-fontconfig. Two font parsers means two incompatibleParsedFont/CmapTarget/FontData, so a type from one cannot cross into the other, and the binary carries the wholeparser twice.
rust-fontconfig 4.4.5moves to allsorts 0.17, and printpdf now requires it,so exactly one copy is linked.
only_one_allsorts_is_linkedkeeps it that way.The subset-cmap panic reported in 0.11.0's notes was fixed upstream rather than worked
around: it only ever existed in
allsorts-azul≤ 0.16.5(
CmapSubtableFormat4::from_mappingsdidmappings.iter().next().unwrap()under a "safe asmappings is non-empty" comment - it isn't). 0.17 handles an empty mapping set correctly,
emitting just the mandatory
0xFFFFsentinel segment. printpdf still declines to subset afont whose used glyphs are all unreachable from its cmap, but for a different reason: the
glyph renumbering is recovered from the subset's own cmap, so an empty one would leave the
content stream emitting original glyph ids against a renumbered font.
Upstream,
azulno longer patchesallsorts-azulto a vendored tree - the drift betweenthat tree and the published crate, while both claimed version 0.17.0, is what shipped
azul-layout 0.0.9 broken. It is consumed from the registry now, and azul CI fails if a
[patch]is placed on anything its published crates depend on.v0.11.0Compare Source
The headline is that printpdf no longer carries a
[patch.crates-io]section. It buildsagainst exactly the crates it publishes.
cargo publishsilently strips[patch], so acrate released while one exists is compiled against dependencies nobody ever tested - the
local checkout and the published artifact are different programs. That is precisely how
0.10.0 shipped with every embedded font empty (#277), and the trap bit azul twice more on
the way out. It is now a CI failure for a
[patch]section to exist at all.That required releasing the dependencies for real: azul-css 0.0.9, azul-core 0.0.9,
azul-layout 0.0.10 and allsorts-azul 0.17.1. The published
azul-layout 0.0.9SIGSEGVs on any hosted OS - it carried 48 ungated
core::ptr::write_volatile(0x400EC as *mut u32, …)calls (web-lift diagnostic markerswriting to a hardcoded absolute address, unmapped on native targets). 0.0.10 routes them
through a no-op unless the
web_liftfeature is on, so the HTML/layout path works again.Breaking
LineDashPatternis now{ offset: f32, pattern: SmallVec<[f32; 8]> }- anarbitrary-length array of reals - rather than three fixed
Option<i64>dash/gap pairs.The PDF spec always allowed this (#262, PR #263 by @tower120). Build one with
LineDashPattern::new(offset, &[dash, gap, …])orLineDashPattern::solid(); you do notneed a
smallvecdependency of your own.printpdf::ParsedFontis a printpdf type (a newtype overazul_layout::ParsedFont, withDeref), which guarantees the source bytes stay attached. Field access and method callsare unchanged;
into_inner()/as_azul()reach the azul face.text_layout,FontParseWarningis a struct rather than aStringalias, soParsedFont::from_byteshas the same signature with and without the feature (#260).lopdf0.39 → 0.44, andallsorts-azul0.16 → 0.17. Both appear in printpdf's publicAPI, so pinning either yourself means bumping in step.
Fixed
@Zynora-fr).
allsorts::subset()builds thesubset's cmap from the original font's cmap restricted to the kept glyphs, and panics
mappings.iter().next().unwrap(), under a"safe as mappings is non-empty" comment). Shaping emits glyph ids directly, and ligatures
have no character of their own, so a page whose glyphs were all unreachable from the cmap
aborted. printpdf now declines to subset in that case and embeds the full font.
text_layout(#258). The fallbackParsedFont::from_bytesnever parsed the cmap, so every glyph resolved to.notdefandpages came out blank - while the font embedded perfectly, which is why it looked so
baffling. And
PdfDocument::parse'sType0branch was feature-gated, so it could notread an external font back either. The fallback now parses cmap/hmtx/head/hhea/OS-2 with
allsorts, which is a non-optional dependency and was there all along.
/ItalicAngle,/Flagsand/StemVwere hardcoded to0 / 32 / 80for every font - "upright, non-symbolic, medium weight", whatever you embedded. Readers
use these to synthesise or substitute a face. They are derived from the hhea caret slope
and OS/2
usWeightClassnow (#271, residual).getrandommust havewasm_jsenabled per majorversion, and lopdf pulls 0.4 alongside the 0.3 other deps use.
Changed
SIMD issues, test on Windows only", so the suite only ever ran on Windows - which is why
a Linux-only font-resolution panic sat undetected. The no-default-features job runs the
whole suite too, not just
--lib, which is why the blank-page bug above went unnoticed.tests/no_text_layout.rscovers the fallback font path;tests/external_tools.rschecksthe output with poppler (
pdffonts,pdftotext), which shares no code with printpdf.v0.10.1If you are on
0.10.0, upgrade. In 0.10.0 every external font embedded as an empty/FontFile2: readers reported "Cannot extract the embedded font",pdffontsreported"Embedded font file may be invalid", and no glyph rendered. A PDF that should have been
165 KB came out at 2.7 KB. Nothing warned and nothing failed.
0.10.0 cannot be repaired by releasing a fixed
azul-layout. Cargo readsazul-layout = "0.0.9"as^0.0.9:=>=0.0.9, <0.0.10, so for0.0.zversions only0.0.9ever satisfies it - 0.10.0 is permanently pinned to the broken dependency. The fixhad to come from printpdf, and it does: printpdf now retains font bytes itself and no
longer depends on azul's retention policy at all.
Fixed
/FontFile2.azul_layout::ParsedFont::from_bytesdoes not retain the source bytes (a deliberate perf change - layout and rasterization
never read them, and retaining them duplicated a 4.27 MiB
.ttconce per face).printpdf read them straight off the struct and
.unwrap_or_default()'d theNoneintoan empty
Vec.printpdf::ParsedFontis now its own type, which attaches the sourcebytes explicitly, so embedding is correct against any
azul-layout..notdef. allsorts requires glyph 0 to be present and first inthe subset glyph list; printpdf passed only the used glyphs, so the first real glyph
was renumbered into slot 0. Subsetting "Roboto" produced
R→0, b→1, o→2, t→3, and theRwas drawn as.notdef.OTTOfont was written asCIDFontType2+/FontFile2, both of which mean "TrueTypeglyfoutlines". The descendant subtype isnow taken from the sfnt magic of the program actually being embedded, and a full
OTTOsfnt is written to
/FontFile3as/Subtype /OpenType.WinAnsiEncodingstream (#273). "Grüße aus Köln"extracted as "Grüße aus Köln" - the text was not copy-able or searchable. printpdf now
owns the WinAnsi encoding table.
'and"operators ignored the selected font, emitting raw UTF-8 - wrong for aWinAnsi built-in font and for an Identity-H external font, where the bytes must be
glyph ids. The used-glyph collector also ignored them, so a page that drew text only via
'/"registered no glyphs, its font was skipped as unused, and the text disappearedfrom the PDF entirely.
PdfWarnMsg::errorand omits/FontFilerather than writing a zero-length one. A missing font is a legal font that readers
substitute for; an empty font program is a corrupt one they reject.
Changed
printpdf::ParsedFontis now a printpdf type (a newtype overazul_layout::ParsedFont,with
Deref, so the existing API is unchanged). It guarantees the source bytes areretained.
ParsedFont::from_bytes, field access and method calls all work as before;into_inner()/as_azul()reach the underlying azul face, andprintpdf::font::AzulParsedFontre-exports it.text_layoutnow pulls inrust-fontconfig, which is needed to name the type thatcarries the retained bytes.
Added
tests/font_embedding.rs- opens the produced PDF withlopdf(deliberately notprintpdf's own parser, where a symmetric writer/reader bug would cancel itself out) and
asserts on the bytes a real reader sees: the font program is a parseable sfnt, every
content-stream glyph id exists in it, and
/Wand/ToUnicodeare keyed by those sameids and round-trip the exact source text.
tests/external_tools.rs- verifies against poppler, which shares no code with printpdf:pdffontsmust accept the embedded program, andpdftotextmust round-trip the sourcetext exactly (this is literally what copy/paste and search do).
published-deps- rebuilds the published dependency graph by stripping[patch.crates-io], then runs the font tests against it.cargo publishstrips thatsection, so without this the local checkout and the published crate are different
programs. This is the gate that 0.10.0 needed and did not have.
v0.9.0Compare Source
v0.8.0Compare Source
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.