Commit Graph
362 Commits
Author SHA1 Message Date
aiongg f1a1f318ba IDML: tracked deleted text takes no room in the layout (#581) 2026-10-12 12:00:42 +00:00
aiongg 7ddd99ee4c Text wrap: words stay whole beside wrap objects; a segment too narrow for its word stays empty 2026-10-12 11:58:08 +00:00
Akash Bhat c310cf72ed fix(compose): enable Norwegian hyphenation patterns
Enable hypher/norwegian in designcraft-compose so no/nb/nn map to the bundled hyph-no.tex patterns. Add unit + composition regression tests, NOTICE attribution, and update parity docs.

Fixes #535.
2026-10-12 12:09:40 +05:30
Sean HammondandClaude Opus 5.5 0635dea065 Text wrap: small single-character frames (tombstones) are set beside text wrap
An article's closing tombstone - a one-character text frame, here a 7.6 pt
Noto Emoji symbol in a 10.5 pt box - was overset (red +, nothing drawn)
whenever its spread had a text-wrap object anywhere, while InDesign sets it.

Two causes in the wrap code:
- Every text frame took every wrap object on its spread as an exclusion,
  whether or not the object reached the frame. Now only objects that
  overlap the frame wrap its text.
- free_slot skips gaps narrower than 1.5 em of the paragraph's size
  (slivers beside a wrapped object), but applied that to the whole column
  too when nothing cut it: with a 14 pt paragraph style, no line could go
  in a frame narrower than 21 pt. The minimum now only applies to gaps a
  wrap object cut; a band no object reaches gets the whole column, however
  narrow.

On the document that showed it (an InDesign 2026 IDML with 12 tombstones and
two unrelated wrap objects on the spread), the 12 tombstone stories are set
and no other story's layout changes (54 -> 42 overset stories, all of the
difference the tombstones).

Tests: small_single_character_frame_sets_beside_unrelated_text_wrap and
narrow_frame_sets_lines_no_wrap_object_reaches fail before (overset) and
pass after.

Probably also #476 (Wingdings bullets overset in 20 x 21 pt frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-12 06:16:04 +00:00
aiongg e5c5f043ee Integration: format 2026-10-12 05:56:41 +00:00
aiongg 5f6ee139da Integration: anchored objects wrap through next's wrap_obstacles (#557 × next's wrap sides and contours)
#557 built an anchored object's exclusion as its offset bounding box; next's Exclusion also has a side and a contour, built by wrap_obstacles for page items. wrap::obstacles keeps the object and its parent → spread transform, and wrap::exclusions runs wrap_obstacles with it, so anchored objects get sides (spine sides by page), contours (Same As Clipping from the clip), custom boundaries, Jump Object and the wrap of a graphic inside, each tagged with the anchor byte (after). Without this the merge doesn't compile. Test: anchored_object_wraps_with_its_contour_and_side.
2026-10-12 05:56:35 +00:00
aiongg 9a95cee172 Merge anchored-wrap: Text wrap: anchored objects wrap text (#557)
Conflict: ROADMAP.md: both progress lines
Conflict: docs/target-app-parity.md: next's header, anchored objects wrapping text added to its change list
Conflict: docs/typography-parity.md: next's header (dated 2026-10-12) with #557's change added; Text wrap row: next's text and caveat, plus "on page items and anchored objects"
Conflict: crates/compose/src/lib.rs: next's Exclusion (side, contour) with #557's after field (Exclusion::wraps_line; next's constructors and the tests' helper set None); next's contour_path and wrap_obstacles in frame_specs; next's line loop (carry, grid, wrap_slot) with the line's start byte passed to wrap_slot; wrap_slot, free_slots and estimate_slots take line_start and skip exclusions that don't wrap the line (Jump Object test and in-band edges included); #557's re-break trigger stays in the line loop
Conflict: crates/compose/src/cache.rs: next's ComposeOptions (section_marker) with #557's neighbour compositions
Conflict: crates/compose/src/tests.rs: both test blocks
Conflict: crates/render/src/damage.rs: #557's test before next's patched_diff helper
2026-10-12 05:54:58 +00:00
aiongg 6e9517b053 Text wrap: anchored objects wrap text (#557)
An anchored object with text wrap pushes aside the text of other stories
whose frames it overlaps on its spread, and the lines of its own story
after the line holding its anchor (custom and above-line objects). Its own
story is composed again with the objects where the previous pass placed
them until they settle, at most four more times. The composition cache
keys a story wrapped by other stories' anchored objects on their
signatures, and canvas damage repaints the stories they wrap.
2026-10-12 05:51:56 +00:00
aiongg 62520485e8 Integration: format 2026-10-12 05:48:15 +00:00
aiongg 3e7cca3a0d Integration: text wrap Same As Clipping follows the graphic's clipping path (#450 × #384)
#384's contour wrap followed the frame's path for Same As Clipping, which was all a graphic could clip to; #450 gives graphics clipping paths. contour_path takes the clip region (Graphic::clip_region) when the graphic has one. Test: a triangle clip widening downwards, Same As Clipping, left side: lines beside the narrow top run further in than those at the base.
2026-10-12 05:40:04 +00:00
aiongg 43a4e70658 Merge section-marker: Sections: the section marker shows the section's marker text (#556)
Conflict: ROADMAP.md: both progress lines (next's, then #556's)
Conflict: crates/compose/src/shape.rs: #556's empty-marker zero-width glyph; next's digit mapping, styles.at(i)/styles and sub.upright() in both arms
2026-10-12 05:34:13 +00:00
aiongg 406f1b3d26 Sections: the section marker shows the section's marker text (#556) 2026-10-12 05:09:40 +00:00
aiongg 0fae2e4b5a Merge #83 into feat/text-editing-and-frames
Conflict: ROADMAP.md: took #83's (main's restructured roadmap); the Threading inventory line goes into docs/target-app-parity.md
Conflict: crates/compose/src/lib.rs: kept this branch's undrawn trailing spaces after warichu; comment covers both the spaces and the drop cap
Conflict: crates/idml/src/tests.rs: took #83's (drop cap style as a Dropcap nested style, table style fills through the cascade); this branch has no changes of its own there
2026-10-12 03:31:39 +00:00
seanth a21e8db14f fix(compose): key parent-page compositions by absolute page index
Parent-page text now uses the absolute page index as its cache key, so repeated page numbers and renumbering do not reuse another page's composition.

Validation: targeted canvas and PDF regressions, plus all six cargo xtask ci gates on main after #514 (1,179 native tests passed, 3 ignored; 15 crates passed WASM compilation checks).

Task: #523
2026-10-11 21:27:52 -06:00
aiongg 0032d57525 Merge upstream main into feat/layout-parity
Conflict: ROADMAP.md: took main's restructured roadmap; this branch's inventory changes (Keep Lines Together default, IDML defaults, drop caps, IDML Kashidas) go into docs/target-app-parity.md, where the inventory lives now
Conflict: crates/format/src/lib.rs: load runs the format upgrade, then merges duplicate assets; kept both sets of tests
2026-10-12 03:24:27 +00:00
moppeeerandskylartaylor 95c8c9cc5b fix(hyphenation): honor capitalized-word settings for uppercase words
Honor the capitalized-word hyphenation setting for uppercase words and retain the current roadmap entries.

Validation: all six cargo xtask ci gates passed on the current main merge candidate; 1,170 native tests passed and all 15 WASM packages checked.

Task: #538

Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
2026-10-11 19:32:49 -06:00
aiongg d50f1b36d9 Merge #538: Words in capitals hyphenate like capitalized words
Conflict: ROADMAP.md: both progress-log lines
2026-10-12 00:17:58 +00:00
aiongg d11ccad30a Merge #523: Parent-page items are composed for their page by index, not page name
Conflict: crates/pdf/src/export.rs: next's cycle guard (item -> item_guarded) and frame_objects (notes and cells) with the PR's page: Option<usize>; the PR's anchored-object loop dropped (frame_objects covers it)
Conflict: crates/render/src/lib.rs: stories keyed by (StoryId, Option<usize>) plus next's drawing_items; next's draw_item -> draw_item_inner guard with page: Option<usize>
2026-10-12 00:14:33 +00:00
japbcoelhoandClaude Opus 5.5 2b46ca664c Hyphenation: Portuguese text is hyphenated with hyph-utf8's Portuguese patterns (#512)
Text set in Portuguese ("Portuguese: Brazilian", "Portuguese", pt_BR) got
no hyphenation, so justified Portuguese columns could only stretch their
word spaces (up to 335 % of the normal space in a 56 mm column of 10 pt
Times).

Turn on hypher's "portuguese" feature, beside Spanish: hyph-pt.tex 1.4
(2024-07-13) from hyph-utf8, BSD-3-Clause, credited in NOTICE.
Lang::for_language already maps the Portuguese names and locale codes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:39:53 -06:00
moppeeerandClaude Opus 5.5 d6ddf749fe fix(compose): Words in capitals hyphenate like capitalized words
A word typed in capitals was never hyphenated, so a heading such as
FERRUM COMMODI UNTI moved the whole word to the next line where InDesign
breaks it (COM-MODI). Capital words now follow Hyphenate Capitalized
Words like words with a capital first letter: on by default, and turning
it off keeps both, acronyms included, whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:24:48 +02:00
Sean HammondandClaude Opus 5.5 5898c084b6 Parent-page composition: the page's name is part of the cache signature
Keying parent-page compositions by page index (previous commit) dropped the
page name from the cache key, and the signature only counts sections, so a
page-number marker on a parent page kept its old text after a section's
start number, style or prefix changed (renaming the page without changing
its index). The page's name is now hashed into the signature whenever a page
is given, so renumbering recomposes it as it did before, while two pages that
share a name stay separate.

Test: parent_page_number_follows_section_renumbering (marker on a parent
page, composed for page 3; the first section restarted at 100) fails before
(still "3", 1 glyph) and passes after ("102").

Reported by aiongg on #523.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-11 21:17:42 +00:00
aiongg 43019733a8 Merge #512: Hyphenation: Portuguese text is hyphenated with hyph-utf8's Portuguese patterns 2026-10-11 18:01:34 +00:00
Sean HammondandClaude Opus 5.5 f23fa33e16 Parent-page items are composed for their page by index, not page name
A running header (or any text variable) in a parent-page frame was empty or
wrong on pages whose page number repeats an earlier one: with two sections
that both number from 1 (front matter 1-10, body 1-...), body pages 1-10
showed nothing. The renderer and PDF export passed only the page's name to
the composition cache, which looked the page up as the first page with
that name - in the front matter, before any heading.

CompositionCache::get now takes the absolute page index (None for a story's
own frames) and derives the name for page-number markers from it; the cache
is keyed by index, so two pages with the same name no longer share a
composed header. The renderer and PDF export pass first_page_of_spread + i.

Test: running_header_on_parent_page_with_repeated_page_numbers (pages named
1, 2, 1, 2; the only heading on the third page). Before the fix the third
page's header was empty (0 glyphs); with it, "Epsilon" (7).

Found with an InDesign IDML (a dictionary with two sections and Match
Character Style running headers on its parent page), together with #301.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-11 16:56:41 +00:00
japbcoelhoandClaude Opus 5.5 db55cb7e49 Hyphenation: Portuguese text is hyphenated with hyph-utf8's Portuguese patterns
Text set in Portuguese ("Portuguese: Brazilian", "Portuguese", pt_BR) got
no hyphenation, so justified Portuguese columns could only stretch their
word spaces (up to 335 % of the normal space in a 56 mm column of 10 pt
Times).

Turn on hypher's "portuguese" feature, beside Spanish: hyph-pt.tex 1.4
(2024-07-13) from hyph-utf8, BSD-3-Clause, credited in NOTICE.
Lang::for_language already maps the Portuguese names and locale codes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:52:46 -03:00
aiongg b6cc59d349 Merge wrap-boundary: IDML: import and wrap around custom text-wrap boundaries
Conflict: crates/compose/src/lib.rs: the custom boundary goes into next's shared wrap_exclusion(); free_slots keeps next's JumpObject handling outside it and takes the branch's combined BoundingBox/Contour arm
Conflict: crates/engine/src/cmd/object.rs: params doc has next's content flag and the branch's custom-boundary note
Conflict: crates/engine/src/tests.rs: both tests
Conflict: crates/idml/src/export.rs: the boundary goes into next's text_wrap_pref()
Conflict: crates/idml/src/import.rs: next's object-style-aware text_wrap() plus the branch's boundary parsing for the item's own wrap; pdf_page_in and wrap_path_of both kept
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-11 13:08:12 +00:00
aiongg 8f5b3313a9 Merge feat/layout-parity update: Layout: a baseline shift doesn't move the Em Box Height first baseline 2026-10-11 13:03:09 +00:00
aiongg 47d17ca6f5 IDML: import and wrap around custom text-wrap boundaries 2026-10-11 12:53:50 +00:00
aiongg 0de31ef27b Layout: a baseline shift doesn't move the Em Box Height first baseline
The line's em box top is measured at the unshifted baseline; the shift
moves only the drawn glyphs. CJK character alignment still counts. The
Ascent, Cap Height and x Height offsets keep counting a raised baseline.
2026-10-11 12:33:55 +00:00
aiongg d5a6eca36d Merge #427 update: Text: Optical kerning spaces pairs from their outlines
Conflict: Cargo.lock: next's (the PR's dependency is already on next)
Conflict: ROADMAP.md: next's (the PR's earlier head is already merged)
Conflict: docs/gaps.md: next's (the PR's earlier head is already merged)
Conflict: docs/typography-parity.md: next's, test count 170 -> 172 as in d0120bf6
Conflict: crates/compose/src/optical.rs: next's plus d0120bf6's kernable() dash and fullwidth exclusion and its unit test
Conflict: crates/compose/src/tests.rs: next's plus d0120bf6's optical_kerning_leaves_dash_pairs_alone
2026-10-11 12:19:35 +00:00
Abdelrehman Fouad d0120bf6a6 Text: Optical kerning leaves dashes and fullwidth forms alone
Two dashes end to end have no ink gap to measure, so -- and the double
em dash opened up by the loosen cap; Chinese sets —— as one continuous
rule. Fullwidth Latin (U+FF21–FF5A) was kernable through its Latin
script and moved CJK text off its em grid. Both are now excluded from
kernable(), with a unit test and a compose test that failed before.
2026-10-11 11:45:29 +02:00
MarekSpodBiedry 30cbd67e3d Honor IDML SameParaStyleSpacing between same-style paragraphs (#449) 2026-10-11 11:43:38 +02:00
Abdelrehman Fouad d02a220b4e Text: Optical kerning spaces pairs from their outlines
Optical kerning was treated exactly like Metrics: the font's kern pairs
and nothing else, so text set with Optical in InDesign came out wider,
rewrapped and went overset in tightly fitted frames.

Now the font's pairs are turned off for Optical text and each pair is
spaced from its outlines (crates/compose/src/optical.rs): every glyph's
ink is sampled into 28 bands across the em, the pair's mean and smallest
ink gap over the bands both glyphs ink are compared with the font's
stem-to-stem gap (the pair nn, or HH then oo), and the pair moves 60% of
the way towards it, keeping 30% of that gap and moving at most -0.12 or
+0.06 em. Latin, Greek and Cyrillic letters, digits and Western
punctuation take part; spaces, marks, missing glyphs, right-to-left
runs, upright and tate-chu-yoko glyphs and pairs across a fallback font
keep their font spacing. Profiles and references are cached, bounded.

Tests: AV, To and r. close up and nn stays; HH closes up under Optical
but not Metrics (fails on main); every change on a pangram stays within
the bounds and spaces keep their width; profile, gap, adjustment and
character-class unit tests.

Docs: typography parity Kerning row and remaining effort, gap 4,
ROADMAP milestone 6 and progress log.

Fixes #181.
2026-10-11 11:39:20 +02:00
aiongg 924a78a2dd Merge feat/layout-parity: Layout: only CJK fonts take the em box top from a one-em typo box
Conflict: crates/compose/src/tests.rs: next's baseline-fit and keep tests, then the branch's OS/2 offset doc and code-page constant
Conflict: crates/fonts/src/tests_vertical.rs: testing imports from both (with_base, with_code_pages)
2026-10-11 05:19:16 +00:00
aiongg c94d7ce8c8 Merge mojikumi-base: CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules
Conflict: crates/compose/src/tests.rs: next's tests, plus the branch's one-em line-end test after its neighbour
2026-10-11 05:18:58 +00:00
aiongg 45032b527f CJK: mojikumi tables based on Nothing and the one-em line-end set apply their rules 2026-10-11 04:38:04 +00:00
aiongg 58f0e8c9b7 Layout: only CJK fonts take the em box top from a one-em typo box
A font's em box is its BASE em box; else, for a CJK font (its font
menu group isn't Western: meta or name languages, CJK code pages, else
kana, Hangul or ideograph coverage), the OS/2 typo ascender when the
typo ascender and descender span exactly one em; else 0.5 em + cap
height / 2; else 0.88 em. Latin fonts with a one-em typo box, such as
DejaVu Serif, centre on the cap height.
2026-10-11 04:28:46 +00:00
aiongg f12b3443a0 Merge #427: Text: Optical kerning spaces pairs from their outlines
Conflicts:
- Cargo.lock: designcraft-compose depends on both unicode-script (#427)
  and unicode-segmentation (next).
- ROADMAP.md: the optical kerning progress-log line goes above next's two
  2026-10-11 lines. Status line keeps next's Last reviewed date and names
  both changes.
- docs/gaps.md, docs/typography-parity.md: same status-line merge.
2026-10-11 02:30:53 +00:00
aiongg cffff885bb Integration: GREP lookbehind overlay test passes the drop-cap end (#425)
next's overlays() takes the drop cap's end as a fifth argument; the test from #316 calls it with 0, like the other overlay tests.
2026-10-11 02:20:00 +00:00
aiongg 037771057e Merge #316 update: GREP styles with a pattern that can't compile are rejected, and imports warn
Conflicts:
- engine cmd/file.rs, cmd/interchange.rs: kept next's file.open and
  file.openIdml docs (content detection, INDD conversion, package parts)
  and added "GREP styles whose pattern can't compile" to their warnings.
  open_idml keeps next's conversion and import-report warnings and appends
  grep_style_warnings after the font warnings.
- engine cmd/style.rs: style.paragraph.create/edit docs combine next's
  startAt and kinsoku notes with the grepStyles rule. create_para and
  edit_para keep next's set_json_over, continue_numbering and
  explicit_default_kinsoku, then call check_grep_styles. The branch's
  grep_style_error, check_grep_styles and grep_style_warnings follow
  explicit_default_kinsoku. Kept next's delete_tests and
  swatch_options_tests; grep_style_tests goes at the end of the file.
- ui-egui dialogs.rs: kept next's style_section_edit tests and added the
  branch's bad-GREP-pattern test.

confirm_paragraph_style_options, grep_error and the dialog status line
merged without conflicts; next had not changed the OK arm they replace.
2026-10-11 02:19:10 +00:00
aiongg ae50689e27 GREP: styles with a pattern that can't compile are rejected, and imports warn
style.paragraph.create, style.paragraph.edit and type.para return the
compile error (bad GREP: ...) for a GREP style pattern that can't compile
and change nothing. Paragraph Style Options shows the error under the
pattern as it is typed, and on OK keeps the dialog open on the GREP Style
section with the command's error.

Opening an IDML or native document keeps such a style and reports a
warning naming the style (or paragraph) and the pattern; composition
keeps skipping it. Adds a lookbehind GREP style test.
2026-10-11 01:51:51 +00:00
Abdelrehman Fouad 62faa8eee2 Text: Optical kerning spaces pairs from their outlines
Optical kerning was treated exactly like Metrics: the font's kern pairs
and nothing else, so text set with Optical in InDesign came out wider,
rewrapped and went overset in tightly fitted frames.

Now the font's pairs are turned off for Optical text and each pair is
spaced from its outlines (crates/compose/src/optical.rs): every glyph's
ink is sampled into 28 bands across the em, the pair's mean and smallest
ink gap over the bands both glyphs ink are compared with the font's
stem-to-stem gap (the pair nn, or HH then oo), and the pair moves 60% of
the way towards it, keeping 30% of that gap and moving at most -0.12 or
+0.06 em. Latin, Greek and Cyrillic letters, digits and Western
punctuation take part; spaces, marks, missing glyphs, right-to-left
runs, upright and tate-chu-yoko glyphs and pairs across a fallback font
keep their font spacing. Profiles and references are cached, bounded.

Tests: AV, To and r. close up and nn stays; HH closes up under Optical
but not Metrics (fails on main); every change on a pangram stays within
the bounds and spaces keep their width; profile, gap, adjustment and
character-class unit tests.

Docs: typography parity Kerning row and remaining effort, gap 4,
ROADMAP milestone 6 and progress log.

Fixes #181.
2026-10-11 03:25:01 +02:00
aiongg 2d67dc8737 Merge #83 update: Em Box Height follows the font's typo box or cap height
# Conflicts:
#	crates/fonts/src/vertical.rs
2026-10-11 01:15:03 +00:00
aiongg 0b72584465 Layout: Em Box Height follows the font's typo box or cap height
A font's em box is its BASE em box; else it tops at the OS/2 typo
ascender when the typo ascender and descender span exactly one em; else
it is centred on half the cap height (OS/2 sCapHeight, else the H
glyph's top), 0.5 em + cap / 2 above the baseline; else 0.88 em. The Em
Box Height first baseline, vertical text and declared_em_box all use
this one em box; declared_em_box is None only for the 0.88 em fallback.
2026-10-11 01:12:08 +00:00
aiongg 16a17604df Merge #360 update: span columns in a one-column frame add no span space
# Conflicts:
#	crates/compose/src/lib.rs
2026-10-11 01:11:47 +00:00
aiongg 91a01a6774 Compose: span columns in a one-column frame add no span space 2026-10-11 01:09:28 +00:00
aiongg 9d717ea417 Merge cjk-run-baseline: CJK: smaller runs align to the line's em box centre
Conflicts:
- crates/fonts/src/fontdb.rs, vertical.rs: next's em cache
  ((top, bottom), declared) with FontFace::declared_em_box and the ICF
  cache/icf_box kept; the branch's base_em_box accessor dropped (same
  value). em_box's fallback chain takes the branch's let em form.
- crates/compose/src/lib.rs: next's cjk_icf_box and the branch's
  alignment_em_box, which reads declared_em_box.
- crates/compose/src/tests.rs: both tests. next applies character
  alignment only under the Japanese composers (5dc8383a), so the branch's
  smaller_cjk_run_aligns_to_the_line_em_box_centre runs under japanese();
  next's cjk_em_center_alignment_moves_small_characters measures centres
  on the alignment em box (declared, else 0.88/-0.12 em).
2026-10-10 23:49:04 +00:00
aiongg 778a270f48 Merge smallcap-tounicode: PDF: small caps and substituted figures keep their text
Conflicts:
- crates/pdf/src/text.rs run_text: the branch's two generated_text
  branches first, then #392's kashida branch (no text), then the cmap
  fallback for other inserted glyphs.
- crates/compose/src/lib.rs: next's kashida PlacedGlyph (with dx) gets
  generated_text: None.
- crates/compose/src/shape.rs hyphen_after: next's rendered_char/moji
  resets and generated_text = None.
- crates/compose/src/ruby.rs: next's ruby module (#363); the branch's
  edit to the old ruby set() has nothing to apply to.
- crates/pdf/src/tests.rs: tests from both.

Also, so the merge compiles: next's ruby (ruby.rs annotate) and kenten
(kenten.rs shape_mark) PlacedGlyph literals take generated_text: None;
they map back through the font like other inserted glyphs. Kanji page
numbers, composite-font note numbers and fallback-face label segments
go through shape_segment's replacement and list labels through
prepend_label, so they carry their generated text.
2026-10-10 23:47:37 +00:00
aiongg 279309ad12 PDF: small caps and substituted figures keep their text
Generated glyphs (page numbers, section markers, list labels) carry the
text they were shaped from, so a figure the font substitutes under small
caps or other OpenType features extracts as the digit instead of U+FFFD.
2026-10-10 23:19:21 +00:00
aiongg 351ccebe86 CJK: smaller runs align to the line's em box centre
Character alignment (em box top, centre, bottom) measured each glyph's em
box from its font's ascender/descender proportions, which puts a Latin
font's em box centre too low, so a smaller CJK run in a Latin line sat
below the line's em box centre. Alignment now uses the font's BASE em box,
else 0.88 em above and 0.12 em below the baseline.
2026-10-10 23:17:09 +00:00
aiongg b6b5d4c705 Merge table-cell-strokes: edits start from the drawn edge, bounded dash patterns, Cell Options fixes, closed corners
Conflict: ui-egui dialogs.rs tests (the branch's new test kept; its copies of the dialog test helpers already on next dropped).
2026-10-10 21:45:22 +00:00