Skip to content

M7: justify paragraphs whose font shows the space with another code - #1832

Merged
echelon merged 1 commit into
storytold:mainfrom
komo1iddin:feat/justify-cid
Oct 15, 2026
Merged

echelon merged 1 commit into
storytold:mainfrom
komo1iddin:feat/justify-cid

Conversation

@komo1iddin

Copy link
Copy Markdown
Contributor

What this changes

A justified paragraph stretches its spaces with word spacing (Tw). But Tw applies only to the single-byte code 32 (ISO 32000-1 §9.3.3). Two kinds of font show a space with some other code:

  • A composite source font (Type 0, two-byte codes). Justify was turned off for these, so the paragraph came out left-aligned, with no message.
  • The generated Type 3 fallback (Japanese or Hebrew text the source font lacks). Its codes are numbered from 1, so code 32 is whatever the 32nd different character happens to be. Justify set Tw anyway. That widened that one character and left the spaces as they were, so the rows didn't reach the right edge.

Now rewrite_block checks whether word spacing reaches the font's space: does the space encode as a single-byte code 32?

  • Yes: nothing changes, and Tw stretches the spaces as before. That covers simple fonts and the standard-14 substitute.
  • No: a stretched row is shown up to and including each space. Each space is followed by a TJ adjustment of the same amount (-1000 · extra / size), and Tw stays 0. The space glyph is still drawn, so the text reads back unchanged.

Underlines keep the stretched width, as before.

How you verified it

  • cargo fmt --all --check: clean
  • cargo clippy -p pdfcraft-edit --all-targets -- -D warnings: clean
  • Tests:
    • cargo test -p pdfcraft-edit: 134 passed
    • cargo test -p pdfcraft-automation --test automation: 153 passed
    • cargo test -p pdfcraft-engine: everything passed (lib: 233)
    • I ran the same three with CRAFT_FONTS_DIR set, the way CI's Linux job does. I also ran cargo test -p pdfcraft-fonts (56 passed) and cargo test -p pdfcraft-ui-egui --test control --test fonts (47 and 10 passed).
    • cargo test -p pdfcraft-ui-egui --test editing: 116 passed, 1 failed. The failing test, password_prompt_opens_and_security_tab_reports_the_details, also fails on main at 7437bde; test(ui): extend native opening and continuation regression coverage #1764 addresses that area.

Tests.

  • justified_text_in_a_composite_font_fills_each_row_with_adjustments_after_its_spaces. It uses a synthetic Identity-H font, so no font asset is needed. It justifies a three-row paragraph and checks:
    • every row but the last ends at the paragraph's right edge;
    • the source font is the only font used;
    • the text reads back the same;
    • TJ adjustments are present and Tw stays 0.
  • justified_text_in_the_type3_fallback_stretches_its_spaces. It justifies Japanese words separated by spaces, with more than 32 different characters, so one of them gets code 32. It checks the same row ends, and that no Tw is set. Like the existing Type 3 tests, it skips itself when the build has no Japanese face. It runs in CI's job with craft-fonts.

Both fail without this change: the rows stop short of the right edge.

Never-crash

  • No new unwrap, expect or panic! outside tests, and no unsafe.
  • A row is split with split_inclusive(' '), and each piece goes through the same encoders and errors as a whole row.

Provenance

  • Based on ISO 32000-1 §9.3.3 (word spacing) and §9.4.3 (TJ adjustments). No other implementation was read.
  • No assets added. The composite font in the test is built in code.

Parity and reach

  • It is reachable headlessly through text_edit with align: "justify".
  • docs/editing-existing-text.md describes how justified rows are stretched.

Anything a reviewer should look at first

  • Rows with several formats. A paragraph like this is rewritten by rewrite_styled_block, which still treats justify as left alignment. I left that for a separate PR, because open PRs (M7: keep the source operators of rows a text edit doesn't reach #1754 and its follow-up) are changing that row layout. The docs say so.
  • Chinese and Japanese without spaces. A row with no spaces can't be stretched this way. Justifying it would need inter-character spacing, which this change doesn't attempt.
  • More operators. A stretched row in such a font is shown as one Tj per word plus one TJ per space. Folding a row into a single TJ array would be tidier; I'd rather do that together with the glyph-per-BT cleanup in the shaped path, as a separate change.

🤖 Generated with Claude Code

Word spacing reaches only single-byte code 32 (ISO 32000-1 §9.3.3).
Justify was therefore turned off for a composite source font, so the
paragraph came out left-aligned. And the generated Type 3 fallback, whose
codes are numbered from 1, had its 32nd character widened instead of its
spaces.

Where word spacing doesn't reach the space, each space of a stretched row
is now followed by a TJ adjustment of the same amount, and word spacing
stays 0. Fonts whose space is code 32 keep using Tw as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@echelon
echelon merged commit 6aa9de4 into storytold:main Oct 15, 2026
3 of 5 checks passed
@echelon

echelon commented Oct 15, 2026

Copy link
Copy Markdown
Member

Thank you @komo1iddin for justifying paragraphs in composite and Type 3 fonts by adjusting after each space! It's merged into main now.

If you haven't already, please come and join us on Discord: https://discord.gg/artcraft

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.

2 participants