Commit Graph
105 Commits
Author SHA1 Message Date
Brandon Thomas 6c2064ae48 Merge PR #949 2026-10-14 22:05:12 -04:00
Brandon Thomas 3cdda36680 Merge PR #657 2026-10-14 22:05:11 -04:00
Brandon Thomas 0e63dcad11 Merge PR #604 2026-10-14 22:05:10 -04:00
Brandon Thomas 1495575c19 Merge PR #589 2026-10-14 22:05:10 -04:00
Brandon Thomas 54400d8d91 Merge PR #584 2026-10-14 22:05:10 -04:00
Brandon Thomas 71cf3671f1 Merge PR #565 2026-10-14 22:05:10 -04:00
Brandon Thomas f268e17986 Merge PR #559 2026-10-14 22:05:10 -04:00
zydvc26s67-webandCopilot App 4516989e51 Type: let a blank line's hidden paragraph mark take leading immediately
Selecting an empty paragraph (a blank line) puts a collapsed caret on its
hidden paragraph mark, the only character on that line. Previously
type.char either deferred the formatting to the next typed character (in
a non-empty story) or dropped it on the floor for composition, so there
was no way to change a blank line's height — matching InDesign's ability
to select the pilcrow and adjust leading.

- engine/cmd/text.rs: recognize an empty paragraph's own caret position
  (empty_para_mark) and apply character attributes straight to that
  byte's run instead of deferring to typing format.
- compose/lib.rs: an empty paragraph has no glyphs of its own, so resolve
  the hidden mark's run (if overridden) for the paragraph's base leading
  instead of always falling back to the paragraph's plain default.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-14 12:05:35 -07:00
Claude 23105a6df9 Merge upstream main (0.8.0) into small-frame-wrap
Conflict: crates/compose/src/tests.rs, both sides added tests at the same place (upstream's #83 Jump Object tests, our tombstone tests); kept both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-14 15:50:20 +00:00
Brandon Thomas c1ce6f77bf Merge PR #860 2026-10-14 01:54:39 -04:00
Brandon Thomas d34384574c Merge PR #853 2026-10-14 01:54:37 -04:00
0f4f382b0e feat(text): improve editing, snapping, and threading
Add multi-click text selection, pending caret formatting and clipboard support, text-aware colour controls, guide/grid snapping, and frame threading with loaded-port interactions.

Include the layout, drop-cap and IDML updates carried from #83, with safeguards for generated and linked stories, external text references, local IDs, locked frames and atomic colour edits.

Fixes #23.

Co-authored-by: Sean Hammond <sean.hammond@und.edu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:41:46 -06:00
Seon-Jin YoonandClaude Opus 5.5 0aaaa9600d fix(compose): odd/even page starts keep their parity at the top of a frame
Start options were skipped for a paragraph at the top of a frame. That is
right for Next Page, Next Frame and Next Column (there is nothing to break
away from), but a Next Odd Page paragraph that lands at the top of an even
page stayed there, as did Next Even Page at the top of an odd page, and the
same went for odd and even page break characters. A part or chapter opener
whose previous section ended exactly at the bottom of an odd page therefore
opened on a left-hand page.

Cursor::start_at_top now moves such a paragraph to the next page of the
asked parity, for paragraphs, tables and break characters alike. Other
start options at the top of a frame are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:12:08 +09:00
noah1400 1bddba0e75 Compose: cross-reference index no longer rebuilds itself (#851)
The cross-reference index composed its destination stories through
the cache. One with a running header asked for the running-header
index, which composed the story holding the cross-reference, which
asked for the cross-reference index again: the stack overflowed and
the app aborted. Destination stories with a running header are now
composed without it, like those with cross-references already were.
2026-10-13 23:07:21 +02:00
seanth b676d9aebe Merge branch 'storytold:main' into small-frame-wrap 2026-10-13 10:18:17 -04: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
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
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
Isaac Levine d66f2c47ae Engine: route book chapters and atomic saves through the checked writers, and take file access out of fonts, IDML and ICC loading
Rebased onto the atomic save work: book chapter replacement, canonicalize and
the writability probe, and every command write now check the roots before
they reach crate::atomic. IDML import no longer reads linked images itself
(the engine passes its checked reader), load_icc_file is gone (profiles
arrive as bytes), the script loader goes through the host reader, and
FontDb::load_document_fonts asks a caller-supplied check about the folder and
each font file, so a link that leads out of the read root is skipped.
2026-10-12 16:50:04 -04: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
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
Sean HammondandClaude Opus 5.5 1278957455 Text wrap: the minimum gap applies only where a wrap object reaches the column
free_slot held a column to the minimum gap width (1.5 em) whenever a wrap
object overlapped the line's band vertically, even if the object sat beside
another column of the frame: in a frame of narrow columns, an object beside
column 2 dropped column 1's lines. The minimum now applies only when the
object reaches the column itself (aiongg's review on #589).

Test: wrap_object_beside_one_narrow_column_leaves_the_other_alone (two
18 pt columns in a 14 pt paragraph, an object beside column 2) fails before
(overset) and passes after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-12 15:14:08 +00:00
aiongg 152086b3ea Footnotes: notes stack baseline to baseline (#582) 2026-10-12 12:28:06 +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 406f1b3d26 Sections: the section marker shows the section's marker text (#556) 2026-10-12 05:09:40 +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
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
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
fc2ce5556f Text: Split Columns sets a paragraph in sub-columns within its column (#295)
Consecutive paragraphs with the same Split Columns settings form a split
block: their text fills the sub-columns of the column one after the
other from the block's top, inset by the outside gutter and separated by
the inside gutter, then moves on to the next column. When text follows
the block, its last part is balanced across the sub-columns and the text
continues below the deepest one at the full column width; a block that
ends the story fills its sub-columns in turn. Keep options don't apply
between a block's lines.

IDML: SplitColumnInsideGutter and SplitColumnOutsideGutter import and
export (paragraph attributes split_inside_gutter, split_outside_gutter).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:27:59 -04:00
0b54cc3d66 Text: a word longer than the line breaks at the column edge; a line that exactly fills the measure fits (#293)
* Text: a line that exactly fills the measure fits

A line whose natural width overruns its measure by less than 0.01 pt
(rounded frame widths and advances) fits, in the paragraph and the
single-line composer. A one-word line that exactly filled its frame was
hyphenated and lost its last letters.

* Text: a word longer than the line breaks at the column edge

The paragraph composer gets emergency break points inside any word (or
part between hyphenation points) that is wider than the narrowest
measure even fully shrunk. They cost more than a hyphen, so a legal
hyphenation point wins where one fits; justified pieces are rated by the
space they leave, so each line takes as many letters as fit. Tracking is
part of the width.

Justified lines shrink no further than the paragraph's minimum word and
letter spacing and glyph scaling: an overfull line stays overfull
instead of overlapping its glyphs, and an overfull single word shrinks
within the letter and glyph limits.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:18:49 -04:00
aionggandBrandon Thomas 70974afc66 Lists: numbering formats and expressions from IDML; an empty paragraph gets no bullet or number (#298)
* Lists: numbering formats from IDML (A, B, C; i, ii, iii; 01, 02 …)

A paragraph's numbering format, number expression, bullet character and
text after the bullet import from IDML and export back. Numbered labels
come from the expression ("^#.^t", "Tabel ^#^t") with its
metacharacters expanded, in place of a fixed "n." label. Adds 001 and
0001 number styles, keeps letter and roman labels short for huge
numbers, and counts saturate instead of overflowing.

* Lists: an empty paragraph gets no bullet or number

A list paragraph with no characters before its return draws no label,
and numbering carries on past it: in a story's own list, in named lists
(also across stories) and in cross-reference paragraph numbers.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 04:08:56 -04:00
b581031d91 IDML: resolve table styles and correct cell height fitting (#288)
* ci: simplify testkit dependency match for Clippy

* IDML: resolve table styles and shared cell edges

* Compose: size table cells by baseline and physical bounds

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:50:09 -04:00
d0d4812ada Set a text selection as warichu inside the parent line (#280)
* Set warichu as smaller lines inside the parent line.

Type › Warichu stacks the selected run after the line is broken, closes up the rest of the line, and round-trips the IDML warichu properties. The line break itself still sees the full-size run.

* Keep clicks, the caret, and Up/Down on each warichu row.

The glyphs were already stacked inside the parent line, but hit testing, the caret, and vertical movement still used that parent line. Break minimums, row widths, spacing, and tab leaders now follow the small lines, and underlines sit on the small baseline.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:47:14 -04:00
8146737e97 Text: a column, frame or page break ends its paragraph (#285)
A break character is stored as the last character of the paragraph it
ends, followed by the usual '\n' separator. The story model, paragraph
ranges, styles, editing and every exporter keep splitting paragraphs on
'\n' only, so the change stays in the places that create, lay out or
write break characters:

- IDML import writes the break and ends the paragraph, so the range that
  follows keeps its own paragraph style. NextOddPage and NextEvenPage map
  to new odd and even page break characters; IDML export writes a
  paragraph that ends in a break as that one Br with its
  ParagraphBreakType, so export and re-import give the same story.
- Word and RTF import, Type > Insert Break Character (with odd and even
  page breaks added) and typing end the paragraph after a break. Typing
  between a break and its return goes to the next paragraph; deleting
  either deletes both.
- Layout: a break that ends a paragraph leaves no empty line; the next
  paragraph starts in the next column, frame or page, as a paragraph start
  option would. Page breaks and Next Page start options move to the next
  frame on a later page; odd and even ones to the next page with that
  page number parity (no such frame: overset). A break inside a paragraph
  from an older document still moves the rest of that paragraph on.

Storing the break as a paragraph separator of its own would have changed
every place that counts or splits paragraphs on '\n'.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:36:47 -04:00
56479fb9ea Text: a justified line with a tab justifies the text after its last tab (#284)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:14:06 -04:00
99af9f513f Text: a line with a tab wraps at the column edge (#282)
* Footnotes: the first line wraps at the column edge after the number

Line breaking gave tabs no width: a tab only reached its stop when the
line was laid out, so a line holding a tab ran past the measure by the
tab's advance. A footnote whose separator is a tab overflowed its first
line by the gap between the number and the tab stop, and body lines
with tabs did the same.

The greedy breaker now asks for each tab's advance at its position on
the line, computed by the same tab_advance helper line layout uses.

* Lists: the first line after a bullet or number wraps at the column edge

In a hanging indent (a negative first-line indent), the tab after a
bullet or number went to the next default tab stop, past the left
indent: the first line's text started late and pushed the line past the
column edge.

The left indent is now a left tab stop of its own. A tab before it
reaches it unless an explicit stop comes first, so the first line's
text lines up with the lines below.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:03:25 -04:00
60ad82497b Text frames: column rules on screen, in PDF and in IDML; Column Rules in Text Frame Options (#274)
* Text frames: column rules draw between columns on screen and in PDF

A frame with Column Rule on gets a bar centred in each gutter, inside the
insets, broken around paragraphs that span columns; right-to-left and
vertical frames included. object.textFrameOptions takes columnRuleWeight
and columnRuleColor (unknown swatches are an error) and an inset array
whose null sides stay; document.inspect reports the rule.

* Text Frame Options: Column Rules fields; OK changes only edited fields

The dialog shows the selected frame's options, with an inset field per
side, and sends only what the user changed, so unequal insets survive.
The rule weight is read in points.

* Column rules: tint, horizontal offset and top/bottom insets; IDML import and export

Text Frame Options gains Tint %, Top Inset, Bottom Inset and Horizontal
Offset for the rule, and object.textFrameOptions and document.inspect
take and report them. IDML reads the ColumnRule attributes of
TextFramePreference on frames and object styles and writes them when a
rule is set.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:49:00 -04:00
01500ceb04 Paragraph Rules: a dialog for the selected paragraphs and a section in Paragraph Style Options (#263)
* Paragraph rules: a partial ruleAbove/ruleBelow edit keeps the other rule fields; Rule Above honours Text width

* Paragraph Rules: dialog for the selected paragraphs and a Paragraph Style Options section

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:28:09 -04:00
e161878e4c Hyphenation: Spanish text is hyphenated with Javier Bezos's patterns (#222)
Text set in Spanish was not hyphenated at all. It now uses Javier
Bezos's Liang patterns, hyph-es.tex 5.0 from hyph-utf8 under MIT,
through the hypher crate 0.1.8 with only its alloc and spanish
features. The full feature, which embeds 48 languages under several
licences, stays off.

- hyphen::Lang names the hyphenator for a run: English, or a hypher
  language. Lang::for_language maps a language name or locale code.
- word_breaks_in and hyphen_points_in take the language. word_breaks
  and hyphen_points stay as the English entry points.
- Composition hyphenates a non-English run whose language has
  patterns and skips the rest. The word cache key includes the
  language.
- NOTICE carries the Bezos copyright line and the MIT text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:21:25 -04:00
aiongg e6b5a52a85 Text: the line before a page break is a paragraph's last line (#283)
A line ended by a column, frame or page break takes the paragraph's
last-line alignment, so a Left Justify paragraph sets it flush left.
A line ended by a forced line break stays justified.

The break that moves the following text to the next column or frame
is the line's last glyph, so a space after the break no longer keeps
the text in the same column.
2026-10-10 01:51:26 -04:00
aiongg b744953d70 Span columns: text after a spanning paragraph flows below it (#276)
A spanning paragraph closes the band of columns above it. Those columns
are balanced first and the paragraph sits below the deepest of them; the
columns after it start below the paragraph, so text that moves to the
next column no longer restarts at the frame top over the heading. A
spanning paragraph that doesn't fit moves to the next frame. Span counts
below the column count set the paragraph across the first columns only.
2026-10-10 01:51:21 -04:00
skylar taylor-barrick 452134c13b FID2: preserve decoration color resets, geometry and paint order (#230) 2026-10-10 01:50:52 -04:00
Haseom Shin 07b95eb7b3 Compose: don't panic when a hyphenated paragraph starts with a no-break letter (#115) 2026-10-10 01:09:36 -04:00
Brandon Thomas aee756d604 Merge remote-tracking branch 'origin/main' into feat/cjk-and-fonts
# Conflicts:
#	crates/ui-egui/Cargo.toml
2026-10-07 22:04:42 -04:00