Skip to content

Justify every line through its glyph offsets; leave forced breaks alone - #187

Merged
danmolitor merged 1 commit into
mainfrom
fix/justify-glyph-offsets
Oct 6, 2026
Merged

danmolitor merged 1 commit into
mainfrom
fix/justify-glyph-offsets

Conversation

@danmolitor

Copy link
Copy Markdown
Owner

Addresses #186. Thanks @jarvis-ai-ia for the reproduction; all three problems reproduced on main.

  1. Registered (embedded) fonts were not justified. Knuth-Plass builds justification into its positions, but when it found no solution within its tolerance (the reporter's paragraph, 10pt in 300pt) it fell back to the greedy breaker, and the stretch existed only as Tw. Tw never applies to Type0 fonts (ISO 32000-1 9.3.3), so the paragraph was drawn ragged. This was broken in every version: 0.26.0's positioned TJ covered only the lines Knuth-Plass justified. After layout justifies a line, any gap left before the column edge is now shared out over its spaces, so a registered font is justified by its TJ. Where Knuth-Plass already filled the line, the gap is nil and nothing moves.
  2. Styled runs overlapped when justified (a regression in 0.26.0). On a line drawn with Tw, each group started where the previous group's glyphs ended, which leaves out the stretch Tw gives that group's trailing space: "leaders" ran into "(finance" by 7.1pt. Groups on such lines now start at their own first glyph, as registered-font groups already did.
  3. A line ending with a forced break was stretched. The line breakers (greedy and Knuth-Plass, plain and runs) now mark such lines with hard_break, and layout leaves them unjustified, as CSS text-align-last specifies.
0.25.0 0.26.0 0.27.0 this PR
registered font justified no no no yes
justified mixed runs overlap no yes yes no
line before \n stretched yes yes yes no

Tests (all three fail on main)

  • test_justified_registered_font_reaches_the_edge_when_knuth_plass_falls_back: the reporter's paragraph in Liberation Sans (metric twin of Arimo), plain and as runs; every line but the last reaches the edge in layout, and is drawn where it is laid out.
  • test_justified_styled_runs_do_not_overlap: the reporter's bold/italic paragraph in Helvetica; replaying the content stream, each group's drawn pen end (advances, Tc, Tw on its spaces) meets the next group's start.
  • test_justified_line_before_a_forced_break_is_not_stretched: Helvetica and Liberation, plain and runs; the line before \n has no word spacing and the wrapped line after it is justified.

What moves

  • Nothing in the existing suites: the byte wall is identical, all 15 template-compat corpus documents are byte-identical to main, and no structural snapshot or template baseline changes. The existing justification tests (paragraphs Knuth-Plass handles) are unchanged.
  • The reporter's five-box reproduction renders every box correctly. Text extraction of registered-font text is unchanged; the overlapping Helvetica runs now extract with their spaces ("leaders (finance").
  • Engine (676), visual, html and package tests pass; veraPDF passes PDF/UA-1 and every PDF/A level. Gallery manifest regenerated (hashes only).

For the next changelog: the 0.26.0 notes said justified registered-font text was fixed; that held only where Knuth-Plass found a solution.

Knuth-Plass builds justification into its positions, but when it found
no solution within its tolerance and fell back to the greedy breaker,
the stretch existed only as Tw. Tw never applies to registered (Type0)
fonts, so those paragraphs were drawn ragged. After layout justifies a
line, any gap left before the column edge is now shared out over its
spaces, so a registered font is justified by its positioned TJ. Where
Knuth-Plass already filled the line the gap is nil and nothing moves.

On a line drawn with Tw, each styled group now starts at its own first
glyph. The running cursor left out the stretch Tw gives a group's
trailing space, so the next group started that much early and
overlapped it ("leaders" ran into "(finance"), a regression in 0.26.0.

A line that ends at a forced break is no longer justified, as CSS
text-align-last specifies: the line breakers mark it with hard_break.

The three new tests fail on main; the byte wall and all 15 template
corpus documents are byte-identical to main.

Addresses #186.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danmolitor
danmolitor merged commit 5a7ff76 into main Oct 6, 2026
13 checks passed
@danmolitor danmolitor mentioned this pull request Oct 6, 2026
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