Skip to content

fix(pdf): apply the /ExtGState stroke parameters, not just the alphas - #810

Merged
andiwand merged 1 commit into
mainfrom
fix/pdf-ext-g-state-stroke-params
Sep 2, 2026
Merged

fix(pdf): apply the /ExtGState stroke parameters, not just the alphas#810
andiwand merged 1 commit into
mainfrom
fix/pdf-ext-g-state-stroke-params

Conversation

@andiwand

@andiwand andiwand commented Sep 2, 2026

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Three more pdf files for the private corpus, and the bug the first of them turned up.

The bug

gs (ISO 32000-1 8.4.5) folded ca/CA, /BM and /SMask into the graphics
state and dropped the rest of Table 58. /LW, /LC, /LJ, /ML and /D are
just the w/J/j/M/d operands as state-dictionary entries, and a
producer is free to set them only there. Canva does: every stroke on a
Canva-designed pdf drew at the initial line width of 1 instead of the width the
file asked for.

On the new MOSAIK_Kurzfassung one-pager that is a /LW 3.2641706 on the frame
around each image — a 3pt border that came out a barely visible hairline.
Verified against both ghostscript and CoreGraphics, which agree with each other
and now with us:

before after gs CoreGraphics
border ink at the same pixel column 0.53 px 1.8 px 2.3 px 2.1 px

The files

file what it is
pdf/Invoice-E5AEACA0-2816.pdf a Stripe invoice — 1 page, an embedded logo, a /URI link
pdf/MOSAIK_Kurzfassung_1755173097.pdf a Canva-designed project one-pager — 3 pages, heavy vector + raster graphics
pdf/Spanische Tortilla de Patatas - Gaumenfreundin Foodblog.pdf a recipe printed to pdf by Chrome on Android (Skia/PDF)

A second copy of the invoice was in the same download with identical page
content, so only one is added.

Each was checked page by page against a ghostscript raster (side by side, and
ranked by blurred pixel diff) and its text against -sDEVICE=txtwrite; all
three extract their full text and render faithfully.

Reference output

Across the whole corpus the fix moves exactly one existing file:
order-EK52VKL0.pdf's page frame carries /ML 4, so its stroke-miterlimit
drops from the initial 10 to 4. No pixel changes — a rectangle's 90° joins
never reach either limit. odr-public is untouched.

opendocument-app/OpenDocument.test-private@d0bedc8
opendocument-app/OpenDocument.test-private.output@2f5964b

`gs` folded `ca`/`CA`, `/BM` and `/SMask` into the graphics state and dropped
the rest of ISO 32000-1 Table 58. `/LW`, `/LC`, `/LJ`, `/ML` and `/D` are the
`w`/`J`/`j`/`M`/`d` operands as state-dictionary entries, and a producer is
free to set them only there — Canva does, so every stroke on a Canva-designed
pdf drew at the initial width of 1 instead of the width the file asked for.

Found on the three pdf files this also adds to the private corpus: a Canva
one-pager whose image frames lost their 3pt border, a Stripe invoice and a
recipe printed by Chrome on Android.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PcgGX9T64rQzbvuSRy36B2
@andiwand
andiwand merged commit 2daf8f2 into main Sep 2, 2026
36 checks passed
@andiwand
andiwand deleted the fix/pdf-ext-g-state-stroke-params branch September 2, 2026 20:22
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