Skip to content

fix(pdf): word spacing, page rotation and glyph names - #702

Merged
andiwand merged 1 commit into
mainfrom
fix/pdf-word-spacing-rotation-glyph-names
Aug 18, 2026
Merged

fix(pdf): word spacing, page rotation and glyph names#702
andiwand merged 1 commit into
mainfrom
fix/pdf-word-spacing-rotation-glyph-names

Conversation

@andiwand

@andiwand andiwand commented Aug 18, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Four rendering fixes, found by reading six new pdf test files into the private corpus.

Word spacing (Tw)

html/pdf_file.cpp emitted CSS word-spacing only on the substitute-font
path. A run painted with an embedded font carries PUA-re-encoded glyphs, whose
space is not a real U+0020, so the property was skipped outright — and every
justified line rendered ~9% short, the shortfall pooling into gaps mid-word:

Lithiumbatterien gelten als gefährlich und müssen laut V⎵⎵⎵⎵orschrift …

Byte 32 now keeps a literal U+0020 in the visual layer and write_font_face
maps U+0020 onto the very glyph the font puts at that code, so the painted
shape is unchanged and the browser can space it. The visual run is escaped with
escape_markup rather than escape_text, whose   substitution is a
different character that word-spacing does not move.

/Rotate

pdf_document_parser.cpp resolved and normalized page.rotate, and nothing
ever read it. It now composes onto begin_page's box transform, and a quarter
turn swaps the reported page dimensions. svg_background_with_page_rotation_issue402.pdf
— the file added for issue 402 — renders landscape for the first time.

Glyph names through the font's charset

abstract::Font gains glyph_for_name, implemented on CffFont against the
charset; Font::glyph_for_code consults it before the glyph-list detour, as
9.6.6.2 prescribes for simple Type1/CFF fonts. Subset producers name glyphs
gidNNNNN, which no glyph list translates, so those files fell all the way
through to the code-as-GID last resort and painted unrelated glyphs
(Gutenbergstr, 216cm87G8utemun▯▯2). It also picks the exact named
variant a producer asks for — one file had been drawing a comma where the pdf
asked for hyphen.case.

Flate streams without an ADLER32 trailer

A producer that ends its streams at the final deflate block and omits the
4-byte checksum made the whole file fail to decode. zlib_inflate drops the
check (ghostscript does the same, loudly) and clears the input queue, which
also stops stray bytes trailing a stream from being appended to what it
decodes.

Test

  • Full suite green; reference output regenerated for both repos and all four
    data pins advanced.
  • New unit tests for the name lookup at both layers: CffFontTest.SelectsGlyphByName
    and PdfFont.simple_font_glyph_for_code_via_font_charset.
  • Each fix was checked page by page against a ghostscript raster of the same
    file.

Known gap left for follow-ups: JBIG2 image masks and filled form-field
appearance streams.

Four rendering fixes found while reading six new pdf test files.

Word spacing: `Tw` reached only the substitute-font path. A run painted with
an embedded font carries PUA glyphs, whose space is not a U+0020 for CSS
`word-spacing` to move, so the property was skipped and every justified line
rendered short — the shortfall pooling into gaps mid-word. Byte 32 now stays a
literal space and the emitted font maps U+0020 onto the same glyph, so the
painted shape is unchanged.

Page rotation: `/Rotate` was parsed and read by nothing. It now composes onto
the page-box transform and swaps the reported dimensions on a quarter turn.

Glyph names: a simple font's `/Encoding` names now select the glyph through the
font program's own charset (9.6.6.2) before the glyph-list detour. Subset
producers name glyphs `gidNNNNN`, which no glyph list translates, so those
files fell through to code-as-GID and painted unrelated glyphs. It also picks
the exact named variant a producer asks for, e.g. `hyphen.case` over `hyphen`.

Flate streams: a producer that omits the ADLER32 trailer no longer fails to
decode, and bytes trailing a stream are no longer appended to what it decodes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014VsAxi7RXU1TNQVz8EM7VJ
@andiwand
andiwand force-pushed the fix/pdf-word-spacing-rotation-glyph-names branch from f03169c to b7426e2 Compare August 18, 2026 18:09
@andiwand
andiwand merged commit bf64fd4 into main Aug 18, 2026
25 checks passed
@andiwand
andiwand deleted the fix/pdf-word-spacing-rotation-glyph-names branch August 18, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant