Commit Graph
306 Commits
Author SHA1 Message Date
aiongg c538979fc7 Merge cache-keys: fix(compose): text recomposes when the baseline grid, a wrap layer's visibility or section numbering changes (#718) 2026-10-13 14:29:08 +00:00
aiongg d6f4c0133a fix(compose): text recomposes when the baseline grid, a wrap layer's visibility or section numbering changes (#718) 2026-10-13 14:18:20 +00:00
Brandon Thomas a390905beb Merge branch 'pr/647' into batch1
# Conflicts:
#	crates/tools/src/lib.rs
#	crates/tools/src/tests.rs
#	crates/ui-egui/src/canvas.rs
2026-10-13 02:23:33 -04:00
Brandon Thomas 9bd099c0ae Merge branch 'pr/639' into batch1 2026-10-13 02:23:11 -04:00
Brandon ThomasandClaude Opus 5.5 b70b586a16 fix(path-type): NaN-safe bracket clamping (f64::clamp panics on a NaN path length)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:22:34 -04:00
Brandon Thomas a0330b03a0 Merge branch 'pr/585' into batch1 2026-10-13 02:21:39 -04:00
aiongg df9db7890a Merge #669: PARITY-TAB-LEADERS: draw right-indent tab leaders 2026-10-13 01:15:20 +00:00
aiongg 8d30e0f41b Integration: format 2026-10-13 01:07:08 +00:00
aiongg 6b2e319f96 Integration (#636): bracket clamps can't panic on a NaN path length 2026-10-13 01:02:40 +00:00
aiongg ebae0c7805 Merge parent-based-on: Parents: a parent can be based on another parent (#652)
# Conflicts:
#	ROADMAP.md
#	crates/compose/src/cache.rs
#	crates/doc/src/build.rs
#	crates/engine/src/cmd/file.rs
#	crates/engine/src/cmd/layout.rs
#	crates/idml/src/import.rs
#	crates/idml/src/tests.rs
#	crates/pdf/src/tests.rs
#	crates/render/src/lib.rs
#	crates/render/src/text.rs
#	crates/tools/src/snap.rs
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-13 01:00:08 +00:00
sanyamsingh001andCodex e507b98601 PARITY-TAB-LEADERS: draw right-indent tab leaders
Co-Authored-By: Codex <codex@openai.com>
2026-10-13 10:51:50 +10:00
aiongg 182db943c2 fix(parents): bound every based-on walk; base items follow the parent showing them (#652)
Cycle checks walk based_on with a seen-set over all parents, so a cycle
longer than the drawing depth cap is still found. Snapping lists parent
items through shown_parent_pages instead of recursing, and offers base
items on a parent spread. A base parent's text on a parent spread is
composed for that spread, so page-number markers show its prefix.
Cmd+Shift-click hits what is drawn: it honours Show Parent Items, hidden
and locked layers, the facing-page filter and layer order. IDML import
warns when a parent spread's pages name different bases or their own
spread.
2026-10-13 00:51:41 +00:00
aiongg 6797589402 Merge pr/647: Text: edit type on a path along the curve (#647)
# Conflicts:
#	crates/engine/src/cmd/text.rs
2026-10-13 00:10:31 +00:00
aiongg 99e6922981 Merge pr/636: Tools: drag type-on-a-path start and end brackets (#636)
# Conflicts:
#	ROADMAP.md
#	crates/idml/src/tests.rs
#	crates/tools/src/lib.rs
#	crates/tools/src/select.rs
#	crates/tools/src/tests.rs
#	crates/ui-egui/src/canvas.rs
2026-10-13 00:08:12 +00:00
aiongg 14eff698dd Merge pr/639: Fonts: fall back to a font made for Malayalam (#639) 2026-10-12 23:56:26 +00:00
aiongg 9487b80220 Integration: format 2026-10-12 22:24:41 +00:00
aiongg b234500e9b Integration (#646): wrap_slot's no-progress check reads next <= b (clippy neg_cmp_op_on_partial_ord) 2026-10-12 22:24:31 +00:00
aiongg 5b01f90baf Merge shaped-text-frames: Text frames: lines follow an oval or polygon frame's shape (#646)
Conflict: crates/compose/src/lib.rs: modules and exports both (outline, optical, parabox, hit_note_with); placement = next's carry/grid/wrap_slot code, entered for outlines too (and the first-word `need`); free_slots starts from the outline's intervals with cut set (1.5 em filter, segments in reading order, reversed for RTL); the branch's free_slot dropped
Conflict: crates/compose/src/lib.rs: next's bands are top..baseline (descenders hang into wraps); a shaped frame's outline holds the descenders too: wrap_slot and free_slots take the line's descent, used only for the outline's intervals (the branch's oval tests check descender corners)
Conflict: crates/compose/src/lib.rs: estimate_slots takes the band (the paragraph's ascent from line_metrics, max 0.8 em, and descent, max 0.25 em, for a shaped frame; next's 0.8 em and no descent otherwise), returns early only without wraps and outline; est_first uses that ascent; wrap_slot's 1 pt step for outlines (and contours) returns None when it makes no progress or is not finite
Conflict: crates/compose/src/tests.rs: tests both
Conflict: ROADMAP.md: logs both
2026-10-12 22:23:22 +00:00
aiongg 116b3b4ec9 Text frames: lines follow an oval or polygon frame's shape (#646) 2026-10-12 22:07:43 +00:00
aiongg e391002be6 Merge text-stroke-behind: Text: stroke behind fill (#619)
Conflict: crates/render/src/text.rs: the branch's paint_glyphs with next's fill and stroke overprint (overprint_fill / overprint_stroke, an overprinting() Multiply layer around each paint); next's cell_stroke(kind, weight, length); path text strokes don't overprint, as before
Conflict: crates/pdf/src/text.rs: the branch's text_run / paint_outlines / glyph_outlines split with next's skew tan, vertical mark offsets and split_clusters glyph order (#421)
Conflict: crates/engine/src/cmd/paths.rs: next's shatai transform + the branch's stroke_behind_fill key
Conflict: crates/idml/src/tests.rs: tests both
Conflict: docs/typography-parity.md, ROADMAP.md: headers combined, logs both
2026-10-12 22:04:10 +00:00
moppeeerandClaude Opus 5.5 f96895f400 feat(text): Edit type on a path along the curve
The Type tool's clicks, drags and double-clicks on type on a path went to
the character at the same spot of the straightened line, and the caret
and highlight were drawn straight. A click now finds the character where
its nearest point along the path is (compose::PathTextMap, from the line
to the path and back, as path_glyphs places the glyphs); the caret stands
across the path there and the highlight bends with the text. A click on
the letters, above the path, hits the frame.

Depends on #636 (PathWarp::nearest).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:35:58 +02:00
aiongg 270dd46b50 Text: stroke behind fill (#619)
A character attribute strokeBehindFill paints a text stroke under the fill
at double width, so its full weight lies outside the letters and they keep
their shape. Screen and PNG rendering, PDF export (stroke as outlines
first, then the filled text, so the text is not duplicated; clipped to the
outside of the glyphs when there is no fill), Create Outlines (outside-
aligned stroke), the Properties panel and Character Style Options.

IDML StrokeAlignment on CharacterStyleRange, CharacterStyle and
ParagraphStyle imports and exports as the attribute (OutsideAlignment =
on, CenterAlignment = off).
2026-10-12 21:06:42 +00:00
Marco Arnulfo de31bd5647 fix(fonts): fall back to a font made for Malayalam (part of #628)
Malayalam took the first installed file mapping it: on macOS Arial Unicode MS, which has no Malayalam shaping, so conjuncts like ക്ഷ fell apart. Fonts made for Malayalam are tried first, loaded on demand through the CJK chain lookup.
2026-10-12 23:03:22 +02:00
moppeeerandClaude Opus 5.5 cded7d997e feat(tools): Drag type-on-a-path start and end brackets
Selected type on a path shows its start and end brackets across the path,
as in InDesign, and the Selection and Direct Selection tools drag them
along it (Move Bracket, one undo step): the text starts and ends where
they are, and text past the end bracket is overset.

- doc: PathType.end (None: the path's end)
- geom: PathWarp::nearest, the arc length of the nearest point
- engine: type.pathOptions {end}; brackets stay on the path and can't cross
- compose: the line runs from the start to the end bracket
- idml: EndBracket read and written
- tools: Cursor::PathBracket

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:32:56 +02:00
aiongg e2dd71378e Merge #609: Balance Columns evens out the last frame's columns
balance-columns 30ba4721.

Conflict: crates/compose/src/lib.rs: story-end check (balance_end) before next's deleted-paragraph filler, then prange; next's `go` closure with the branch's band tail on Next Column; next's Cursor::clip replaced by the branch's tail-aware clip (the auto-placed duplicate dropped)
Conflict: crates/compose/src/tests.rs, crates/idml/src/tests.rs: tests both
Conflict: ROADMAP.md, docs/target-app-parity.md: both (auto-size and Balance Columns lines)
2026-10-12 20:06:06 +00:00
aiongg 30ba47211e Text frames: Balance Columns evens out the last frame's columns (#609)
In the frame a story ends in, Balance Columns lowers the bottom of the
columns after the last explicit column break as far as the text still
fits, reusing the bounded search that balances columns above spanning
paragraphs. Frames the text runs on from fill normally. Text Frame
Options gets a Balance Columns checkbox.
2026-10-12 19:18:45 +00:00
aiongg 3719470140 Integration: format 2026-10-12 17:26:21 +00:00
aiongg 7cc6f6db72 Merge idml-deleted-text (update): deleted text inside nested paragraphs stays deleted; a deleted break doesn't break (#581)
Conflict: crates/idml/src/tests.rs: both sides' tests (#473's SameParaStyleSpacing tests, then the branch's deleted-text tests)
2026-10-12 17:25:31 +00:00
aiongg aef8c39a3b Integration: wrap tests use the top-to-baseline band; a clipping path contour stops at its frame (#384, #561, #589, #557)
an_image_contour_stops_at_its_frame builds its Graphic with next's clip and wrap fields.
a_wrap_object_cutting_a_narrow_frame_leaves_its_sliver_empty and lines_in_band test a line's
band from its top to its baseline. New test: a Detect Edges clipping path overhanging its frame
wraps text (Same As Clipping) only within the frame.
2026-10-12 17:23:45 +00:00
aiongg cb83b6071a Merge contour-wrap (update): a line meets the wrap from its top to its baseline; an image's contour stops at its frame (#384)
Conflict: crates/compose/src/lib.rs: wrap_exclusion keeps next's custom boundary and outline closure, which now yields (outline, clipping frame) and limits the contour to the frame via Contour::within
Conflict: crates/compose/src/lib.rs: contour_path keeps next's (it, kind) form; Same As Clipping (the graphic's clip path) and Bounding Box (shown bounds) return the frame path as the clip, other kinds the frame with no clip
Conflict: crates/compose/src/lib.rs: main loop and estimate_slots keep next's grid and wrap_slot code; wrap_slot drops its desc argument and tests the band from the line top to its baseline (free_slots and the in-band edge search), for both callers
2026-10-12 17:20:50 +00:00
aiongg dad91f0c74 Merge #473: Honor IDML SameParaStyleSpacing between same-style paragraphs (#449)
Conflict: crates/compose/src/lib.rs: same-style gap replaces the pending space; otherwise next's text_above span space-before rule
Conflict: crates/doc/src/attrs.rs: same_para_style_spacing before the drop-cap fields and their doc
Conflict: crates/compose/src/tests.rs: both sides' tests
Conflict: crates/idml/src/tests.rs: both sides' tests
2026-10-12 17:16:10 +00:00
aiongg 254cc49d1b Text wrap: an image's contour stops at its frame 2026-10-12 17:04:48 +00:00
aiongg 90890e37aa Compose: a deleted break doesn't break
A column, frame or page break character that tracked changes delete, or a
hidden condition hides, no longer sends the next paragraph on. Deleted
paragraphs between a live break and the next paragraph don't stop the break.
Tests cover a deleted paragraph range inside a change, a deleted frame break in
both nestings, and a live break before a deleted paragraph.
2026-10-12 17:02:46 +00:00
aiongg 67ae415506 Text wrap: a line meets the wrap from its top to its baseline 2026-10-12 17:02:08 +00:00
moppeeerandClaude Opus 5.5 6880042c6a fix(compose): Hyphenation Zone applies to the Single-line Composer only
InDesign's Hyphenation Zone ("the white space allowed at the end of a
line of unjustified text before hyphenation begins") applies only with the
Single-line Composer. The Paragraph Composer, the default, weighs every
break of the paragraph instead. DesignCraft applied the default 3p0 zone
to both, so ragged paragraphs under the Paragraph Composer refused
hyphens InDesign makes. In LYH Studio's poster tutorial the placeholder
text breaks "aper-natem" and "mosticsia-tur"; ours ran a line longer.

The breaker now gets the zone only for unjustified Single-line Composer
paragraphs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:29:01 +02:00
aiongg a5fbf5d8d4 Merge table-border-drag: Tables: drag row and column borders with the Type tool
Conflict: crates/compose/src/lib.rs: one re-export line with next's hit_cell_with and the branch's BorderHit/hit_border
Conflict: crates/tools/src/text.rs: next's IME/preedit, guides and unit_selected kept alongside the border drag; border press checked after the Down resets; drag keeps next's `cell_drag || unit_selected` guard after the border branch; Esc cancels a border drag before next's preedit guard; BorderHit via next's `compose` alias
Conflict: crates/tools/src/tests.rs: both sides' appended tests kept
Conflict: docs/target-app-parity.md: next's IDML line, branch's Tables line
Conflict: ROADMAP.md: branch entry on top of next's entries
2026-10-12 14:56:19 +00:00
aiongg 44fde50463 Tables: drag row and column borders with the Type tool
Hovering a table's inner column or row border, or its end or bottom
edge, shows a double-arrow cursor; dragging resizes instead of selecting
text. A column drag changes the column before the border and moves later
columns; Shift keeps the table width (the next column compensates), and
Shift on the end edge scales every column. Rows work the same way, and a
dragged row becomes At Least. Sizes clamp at 3 pt. One undo step per
drag, Esc cancels, locked frames refuse, and the pointer is mapped
through the frame's transform so rotated frames and turned spreads work.

New command table.resizeBorder {story?, table?, frame?, axis, index,
delta | size, keepTableSize?}; the Type tool emits it through
Begin/Preview/Commit. compose::hit_border finds the border under a point
in frame inner space.

Closes #595
2026-10-12 14:52:31 +00:00
aiongg 4f16d88309 Merge #589: (fix)Small frame wrap for overset small text boxes
Conflict: crates/compose/src/lib.rs: next's wrap_obstacles and free_slots kept; frame_specs keeps only exclusions that reach the frame's text area (as wrap.rs does for anchored obstacles), and free_slots applies the 1.5 em minimum segment only when an exclusion reaches the column
Conflict: crates/compose/src/tests.rs: both sides kept; the PR's tombstone tests plus tests for a sliver beside a wrap object in a narrow frame and a narrow first column beside a wrap object in column 2
2026-10-12 14:14:12 +00:00
aiongg 90eab17186 Integration: hyphenation zone applies to both single-line composers
#585 tests Composer::SingleLine; next's Composer also has JapaneseSingleLine, so the zone check uses Composer::single_line().
2026-10-12 14:10:58 +00:00
aiongg 432e1cbbcc Merge #585: Hyphenation Zone applies to the Single-line Composer only
Conflict: ROADMAP.md: both progress-log lines kept
2026-10-12 14:10:25 +00:00
moppeeerandClaude Opus 5.5 0c4a6641cb fix(compose): Hyphenation Zone applies to the Single-line Composer only
InDesign's Hyphenation Zone ("the white space allowed at the end of a
line of unjustified text before hyphenation begins") applies only with the
Single-line Composer. The Paragraph Composer, the default, weighs every
break of the paragraph instead. DesignCraft applied the default 3p0 zone
to both, so ragged paragraphs under the Paragraph Composer refused
hyphens InDesign makes. In LYH Studio's poster tutorial the placeholder
text breaks "aper-natem" and "mosticsia-tur"; ours ran a line longer.

The breaker now gets the zone only for unjustified Single-line Composer
paragraphs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:57:09 +02:00
aiongg 72aacb5858 Merge footnote-spacing: Footnotes: notes stack baseline to baseline (#582) 2026-10-12 12:41:20 +00:00
aiongg bc9011863e Integration: format 2026-10-12 12:36:45 +00:00
aiongg e2eae1c62b Merge idml-deleted-text: IDML: tracked deleted text takes no room in the layout (#581)
Conflict: crates/idml/src/import.rs: the branch's `over.change` line moved into next's `range_format()`, which takes the enclosing format for it (its CrossReferenceSource caller passes it too); next's `TextVariableInstance` arm kept beside the branch's `Change` arm, with next's ignore list
Conflict: crates/idml/src/export.rs: kept next's metadata, text-variable and CJK settings helpers and the branch's `change_type`
Conflict: crates/idml/src/tests.rs: kept next's `$ID/` font test and the branch's tracked-change test
2026-10-12 12:36:40 +00:00
aiongg e8d38d13e3 Merge contour-wrap (update): Text wrap: words stay whole beside wrap objects; a segment too narrow for its word stays empty
Conflict: crates/compose/src/lib.rs: kept next's baseline-grid code (grid_end/carry_grid, gyoudori placement) and wrap_slot loop; ported the branch's `need`/`holds`, the `position(holds)` carry selection (which also takes carry_grid) and the `skip_while(!holds)` segment filter, now inside wrap_slot through a `holds` argument (the slot predictor passes one that accepts every segment); #563's wraps_line(line_start) filtering stays
Conflict: crates/compose/src/tests.rs: kept next's Same As Clipping test and the branch's sliver-wrap tests
2026-10-12 12:35:22 +00:00
aiongg 152086b3ea Footnotes: notes stack baseline to baseline (#582) 2026-10-12 12:28:06 +00:00
aiongg fbf0fabd1a Merge #565: fix(compose): enable Norwegian hyphenation patterns
Conflict: docs/typography-parity.md: kept next's Justification row (#531 note), took the PR's Hyphenation row (Portuguese and Norwegian)
2026-10-12 12:23:42 +00:00
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