Commit Graph
782 Commits
Author SHA1 Message Date
aiongg bb4fd03f7e Merge #319: DTD: memoize content-model matching, let entity names be any length 2026-10-10 13:19:59 +00:00
aiongg 3692eba3c4 Integration: fonts match a face's own style name before other faces' aliases (#126)
Static families such as Alexandria name every weight's legacy family
"Alexandria <Weight>" with subfamily "Regular". Matching names and aliases in
one pass let the first such face (Black) answer for Regular, so text set in
Regular came out in Black, wider, and pushed lines into overset.

(cherry picked from commit 681a3ec499834b866dd18244e5b25b425c682b10)
2026-10-10 13:19:53 +00:00
aiongg 9a0e26a6a1 Merge #331: Compose: page numbers take the Digits setting of their text
Conflict: crates/compose/src/shape.rs: next's shape_segment call with `styles`; added the PR's comment and digit mapping of the page name before it
2026-10-10 13:19:50 +00:00
aiongg 1abb74da0f Merge #330: Compose: the CJK leading model and character alignment apply only under the Japanese composers 2026-10-10 13:18:52 +00:00
Ikki NishiharaandClaude Sonnet 5.5 a46da6eff0 DTD: memoize content-model matching, let entity names be any length
Nested repetitions such as ((a)*)* asked the group below for the same end
positions again and again, which is exponential in their depth: remember
the positions of a group per start position while one element's children
are matched. The search for the closing semicolon of a parameter-entity
reference had a 256-byte window, so a longer name was left unexpanded
without an error: stop the search at whitespace or the next percent sign
instead, which keeps a round linear and puts no limit on the name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 21:10:16 +09:00
aiongg 5dc8383a50 Compose: apply the CJK leading model and character alignment only under the Japanese composers
Every IDML root style carries LeadingModelAkiBelow and AlignEmCenter, but only
the Japanese composers (HL Composer J, HL Single J) use them; the Adobe and
World-Ready composers set lines baseline to baseline with every character on
the Roman baseline. Applying them everywhere moved lines down after a size
change and lowered smaller-em glyphs, pushing the last lines of tight frames
(and whole keep-together paragraphs) into overset.

Composer gains Japanese and JapaneseSingleLine, read from and written to IDML;
the composer choice in the UI keeps the kind.
2026-10-10 11:56:45 +00:00
aiongg e6428d2111 Compose: page numbers take the Digits setting of their text
Automatic page numbers (and section markers) were set from the page name as
typed, so a page number in Hindi Digits text drew Western digits, which an
Arabic-only font shows as missing-glyph boxes. They now map their digits like
typed text does.
2026-10-10 11:55:28 +00:00
aiongg 7d39f0061c Integration: #305's reopen tests read legacy JSON as format 1 (#83)
#83 adds native format 2 and an upgrade that pins Keep Lines Together and Align Left Edge where a format-1 style chain leaves them unset. #305's tests save the same document as native ZIP and as legacy JSON (format 1) and compared both as whole documents. The base style now sets both attributes, and only the native ZIP is compared as a whole document; the nullable resolution is checked for both.
2026-10-10 11:13:49 +00:00
aiongg 89ea36eb6c Merge #305: Preserve explicit nullable text defaults when reopening documents
Conflict: ROADMAP.md: both QA1 lines (#315, #305)
2026-10-10 11:09:24 +00:00
aiongg 9a047d7f3e Merge #83 (771bbaf): Fonts: cap height and x-height from glyph outlines when a font doesn't declare them 2026-10-10 10:57:51 +00:00
aionggandSean Hammond 771bbafb82 Fonts: cap height and x-height from glyph outlines when a font doesn't declare them
When OS/2 has no usable sCapHeight / sxHeight, the face measures them from the
unhinted outlines of flat-topped letters (H I E T for cap height, x z v for
x-height) at the face's variation location, and falls back to a share of the
ascent only when none of those letters has a plausible outline. Measuring
outlines instead of the glyf bounding box also covers CFF and variable fonts.

Based on seanth's measurement in #292.

Co-authored-by: Sean Hammond <sean.hammond@und.edu>
2026-10-10 10:54:35 +00:00
aiongg aaff49833c Merge #317: Text: combining marks that start a run are shaped with their base letter
Conflict: crates/compose/src/shape.rs: #317's marks_join_their_base runs on the resolved runs before #81's piece grouping
2026-10-10 10:46:05 +00:00
aiongg 978a95f58e Text: combining marks that start a run are shaped with their base letter
A run that begins with combining marks, after a run that differs only in
OpenType features (ligatures, kerning, OTF features), now gives those marks
to the run before it. The shaper attaches each mark to its base, and a
missing font's fallback draws the mark in the same face as its letter
instead of the substitute's missing-glyph box. Runs in another font, size
or colour keep their marks.
2026-10-10 10:33:42 +00:00
Ikki NishiharaandClaude Sonnet 5.5 794d601471 DTD: bound parameter-entity expansion, nesting and sequence matching
Parameter entities were expanded by up to eight rounds of text.replace
per entity over the whole text, so a few hundred bytes of DTD grew
exponentially and a large one cost entities x length per round. Expand
them one scan per round with a map, bound the result and the depth, and
refuse cycles. Content-model groups recursed once per parenthesis with
no limit (a stack overflow), and the positions after each item of a
sequence were kept as a list of paths (2^n for n optional items): limit
the nesting and keep the positions a set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 19:23:53 +09:00
aiongg f8be188192 Integration: German row for the fill and stroke message as #81/#84 word it
#311's de.rs translates "Select an object to color its fill or stroke."; #81/#84 on next say "Select an object or text to color its fill or stroke.", so the German interface showed it in English. The row takes the new key.
2026-10-10 10:14:57 +00:00
aiongg 4a49cdcaf9 Merge #311: i18n: translate the whole interface into German
Conflict: ROADMAP.md: next's right-to-left and interface-language lines, with #311's sentence that German and Japanese cover every key.
2026-10-10 10:12:50 +00:00
aiongg 7b8b505083 Merge #315: Retry missing font fallbacks after a font rescan 2026-10-10 10:12:35 +00:00
aiongg d86bbd8d6c Merge #316: GREP: the dialect's classes and metacharacters (\l \u \h, ~ codes, lookaround) in GREP styles and Find/Change
Conflict: crates/compose/src/tests.rs: tests only, both (#310's tight word spacing test, #316's GREP style test)
#316's overlay test calls overlays() with the drop_end argument next added (#83).
2026-10-10 10:12:34 +00:00
aiongg f4c0b9f378 GREP: dialect classes and metacharacters (\l \u \h, ~ codes) in GREP styles and Find/Change
One shared compiler (designcraft_compose::grep) translates the GREP dialect
documents and users write: \l \u \L \U letter classes, \h \H \v \V \R \N,
\r paragraph end and \n forced line break, ~ special-character codes,
Unicode-aware POSIX classes, and literal [ & -- inside character classes.
It compiles with fancy-regex, so lookahead, lookbehind, atomic groups,
possessive quantifiers, backreferences and \K work; a backtrack limit stops
pathological patterns with an error.

GREP styles skip a pattern that fails to compile or gives up on a search,
with one log warning. Find/Change returns the error. The paragraph style
dialog validates with the same compiler. Fixes en dash (~=) mapping to an
em dash in Find/Change.
2026-10-10 09:58:12 +00:00
sanyamsingh001andCodex 10e18bac27 QA1: retry missing font fallbacks after a rescan
Co-Authored-By: Codex <codex@openai.com>
2026-10-10 19:53:22 +10:00
aiongg f08899832b Merge #309: Snap: smart guides beat the baseline grid; a box centres between two others
Conflict: crates/tools/src/snap.rs: first_on_axis: a guide or margin in reach competes with the baseline grid through #84's nearer(); otherwise #309's smart-guide passes run and the baseline grid comes last. #84's nearer_of_margin_and_baseline_wins and #309's tests pass.
2026-10-10 09:29:32 +00:00
aiongg db0b16ac39 Merge #308: fix(geom): correct pica and cicero rounding at unit boundaries 2026-10-10 09:28:29 +00:00
aiongg 3b490188cf Merge #307: fix(layout): reject invalid spread rotation angles 2026-10-10 09:28:29 +00:00
aiongg 61483f8b66 Merge #306: fix(geom): prevent invalid zoom values from breaking ruler ticks 2026-10-10 09:28:28 +00:00
aiongg 2f15a954bf Integration: deleting a style points every use at its replacement
#171's style.character.delete (and the style.paragraph.delete it copies) reassigned only text runs;
next's rename_style already rewrites styles, stories, table cells, nested and GREP styles and
footnotes. Both deletes now go through one delete_style that removes the style, rebases its
children and calls rename_style, in one undo step.

- Children of the deleted style are based on the replacement ([None] and [No Paragraph Style]
  clear Based On); when the replacement descends from the deleted style they take its parent,
  so no based-on chain loops.
- rename_style also covers drop cap styles, cell styles' paragraph style, footnote and endnote
  paragraph styles, the endnote heading style, TOC entries and the default paragraph and
  character styles, which fixes renames too.
- A missing, empty, unknown or built-in name, an unknown replacement and a replacement equal to
  the deleted style are errors that change nothing.
2026-10-10 09:27:53 +00:00
dwetscherandClaude Opus 5.5 14b506c373 i18n: translate the whole interface into German
A German table (crates/ui-egui/src/i18n/de.rs) covers every key of the shared,
Japanese, Arabic, Brazilian Portuguese, Italian and Ukrainian catalogs (1,697
rows; before, 164 strings were German). The shared table's German column stays
the fallback for keys added after it. Tests check placeholders, padding, colons
and ellipses against the English source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:27:50 +02:00
aiongg 2fb7ddafe2 Merge #171: Paragraph and Character Styles: wire save and delete into the UI (#169)
Conflict: crates/engine/src/tests.rs: both sides append tests; kept next's then #171's
2026-10-10 09:19:37 +00:00
aiongg f977482e06 Merge #310: Justification: tight word spacing doesn't isolate a word; fixed-width spaces don't stretch
Conflict: crates/compose/src/breaker.rs: next's multi-line Item::boxed/penalty with edge_elastic and start_elastic plus #310's over; glue in items() keeps next's edge_elastic and sets #310's over; kp_pass first node keeps next's first_box/first_hang/first_elastic and ty/tz plus #310's to
2026-10-10 09:18:18 +00:00
aiongg 11eac68fda Merge upstream main 92649ff (#155) into next 2026-10-10 09:15:04 +00:00
noah1400andClaude Opus 5.5 8b940b4bcb Snap: smart guides beat the baseline grid; a box centres between two others
On the vertical axis the baseline grid was tried before smart guides.
It snaps while hidden, and with lines every 12 pt and a box's top,
centre and bottom each reaching 4 pt either side, one of them is nearly
always in the zone. It took almost every vertical move and drew nothing,
so object alignment and equal spacing hardly ever showed on y. The
baseline pass now runs after the smart guide passes.

Smart spacing also only matched a gap between two other boxes. A box
dragged between two neighbours now snaps to where both of its gaps are
equal, and both gaps are marked.

Fixes #188

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N5fQBTFb4hhKXsCFYitZ1P
2026-10-10 09:06:48 +00:00
aiongg 40c3a78e6e Compose: fixed-width spaces keep their width on justified lines
Stretch past the maximum word spacing was shared by every space on the
line, so em, en and thin spaces widened along with word spaces. Only word
spaces take it now; a line without word spaces gives it to its letter
gaps, matching how the paragraph composer rates such a line.
2026-10-10 09:05:06 +00:00
aiongg be7a367050 Compose: a tight word-spacing range no longer isolates a word on a justified line
Stretch past the maximum word spacing was rated in units of the word
spaces' own stretch. With a narrow range (100-105%) that unit is tiny, so
a loose line with several word spaces rated far worse than a single word
letter-spaced across the measure, and the paragraph composer chose the
single word. Rate the overflow in units of at least a third of the word
spaces' width (the default range), so word spaces take the extra stretch.
2026-10-10 09:01:14 +00:00
Nigel Ihza Firdausy 5ea407daf6 Merge latest upstream main into ruler zoom fix
Include unrelated upstream application changes without rewriting the
contribution commit; ruler zoom changes remain isolated.
2026-10-10 15:55:18 +07:00
Nigel Ihza Firdausy 67fce0f63c fix(geom): carry rounded pica and cicero fractions
Apply the displayed precision before testing for a 12-point carry,
so measurements near a pica/cicero boundary do not display as 0p12
or 0c12. Cover positive, negative, coarse, and subunit rounding.
2026-10-10 15:54:14 +07:00
sanyamsingh001andCodex 4a9ebd858e QA1: preserve explicit nullable text defaults in native files
Co-Authored-By: Codex <codex@openai.com>
2026-10-10 18:53:42 +10:00
Nigel Ihza Firdausy 2e03e17820 fix(layout): reject unsupported spread-view rotation angles
Reject non-quarter-turn or non-integer view angles instead of silently
truncating or defaulting them. Add a command-level regression test for
invalid inputs, valid quarter turns, and clearing the rotation.
2026-10-10 15:52:05 +07:00
Nigel Ihza Firdausy 8db2f76798 fix(geom): keep ruler ticks finite for invalid zoom values
Guard non-positive and non-finite viewport scales before the ruler tick
spacing loops, and cap extreme magnitudes. Cover invalid and extreme
zoom values without changing normal zoom behavior.
2026-10-10 15:50:36 +07:00
92649ffdb0 Security: validate engine inputs and package paths (#155)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:50:26 -04:00
aiongg c5cec39b59 Merge #304: IDML: text in a paragraph-level cross-reference source keeps its formatting 2026-10-10 08:49:00 +00:00
aiongg 355ca21461 Merge upstream main fc2ce55 (#293, #196, #295) into next
Conflict: ROADMAP.md, crates/compose/src/breaker.rs, crates/compose/src/tests.rs: next's side (main lacks #129's mojikumi elasticity, which next combines with #293; the maintainer's #293 merge applies the fit tolerance and overfull-word shrink after a line's last tab, as next does)
Auto-merge duplicated #295's split_columns_import_and_round_trip in idml tests.rs; kept one copy. #196 comes in whole.
2026-10-10 08:49:00 +00:00
aiongg 71d33cd02f IDML: text inside a paragraph-level cross-reference source keeps its formatting 2026-10-10 08:35:15 +00:00
zydvc26s67-web cfc9fb7987 Merge remote-tracking branch 'origin/main' into zydvc26s67-web-paragraph-styles-save-delete
# Conflicts:
#	crates/ui-egui/src/dialogs.rs
2026-10-10 01:29:08 -07: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
c657d8545e PDF export: options dialog with remembered settings (#196)
* PDF export: Export Adobe PDF options dialog with remembered settings

File > Export PDF... now opens an options dialog (General / Compression /
Marks and Bleeds / Advanced) that exposes only the parameters the engine's
file.exportPdf already supports: PDF standard, image compression, flattener
preset, spreads, document bleed, crop/bleed/page-info marks with weight and
offset, tagged PDF, page range, and the title. Choosing a preset fills the
dependent fields; editing any field switches the preset to Custom.

The last-used settings persist in ui.json (UiState.pdfExport) and are reused
across exports and sessions. Programmatic callers that pass explicit params
(path, options) still export directly without the dialog; cancelling the save
picker writes nothing.

Adds English and ar/pt-br strings, unit tests for the settings serde, the
dialog defaults and preset mapping, the open/confirm flow (stub writer), and
the menu dispatch, plus a ROADMAP note.

* PDF export: rename dialog title to "Export PDF"

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:23:32 -04:00
aiongg 2a03c416ab Merge #302: Warichu: hostile line spacing and break minimums can't break layout 2026-10-10 08:21:39 +00:00
aiongg 123ee022b5 Merge #301: IDML: text variables keep their text
Conflict: crates/idml/src/import.rs: #301's TextVariableInstance arm, then next's ignored-elements arm without "TextFrame" (next imports embedded text frames)
2026-10-10 08:21:39 +00:00
aiongg bf18909d9d Integration: test warichu on a drop cap paragraph's first line
#280 (warichu) places its run after the line is broken; #83 (drop caps, on next) splices the drop cap into the first line and gives the caret its own metrics. The test checks that they combine: the drop cap keeps its position and size, the note stacks after it and the line closes up, the caret takes the drop cap, row and parent metrics in turn, clicks land on each row, and a selection over both gets the drop cap's band and one per row. It passes on the merge; no code change was needed.
2026-10-10 08:21:17 +00:00
aiongg aadd54a926 Merge upstream main 736acff into next
Upstream main moved 9e4f694 -> 736acff by 10 squashes: #258, #285, #153, #280, #288, #281,
#176, #299, #298, #297. New to next: #258 (datamerge test), #153 (temporary files and release
inputs), #280 (warichu), #176 (PDF image conversion cache), #297 (graphics backend before the
window). Already on next: #285, #288, #281, #299, #298.

Conflict: ROADMAP.md: next's lines; CJK1 takes main's wording (ruby unfinished, warichu set after the line is broken)
Conflict: crates/compose/src/lib.rs: list labels (#298): next's list_label structure; paragraph start / break moves: next's `go` (with #295's sub-columns) under the maintainer's #285 comment; Cursor: next's start/next_page/next_baseline with tops; line placement: #280's warichu::place right after layout_line, before next's trimmed trailing spaces and the drop cap splice (so neither is stacked and the spaces follow the closed-up end_x); caret(): next's drop-cap branch first, then #280's stacked-row metrics
Conflict: crates/compose/src/tests.rs: next's tests kept; #280's tests auto-merged
Conflict: crates/engine/src/cmd/text.rs: both (next's TypingFormat/typing_format, #280's warichu_cmd)
Conflict: crates/idml/src/tests.rs: next's side; #280's warichu round-trip test auto-merged
Conflict: crates/pdf/src/export.rs: #176's content cache and test counter with next's RasterMode (no Exporter `interpolate` field on next, so the maintainer's `mode.interpolate &= self.interpolate` is not carried); test exporter gets next's drawing_items field
Conflict: crates/ui-egui/src/canvas.rs: #280's compose::highlight_quads (its RTL branch equals next's mixed-direction spans) followed by next's drop-cap highlight
Conflict: crates/ui-egui/src/menus.rs: next's formatting_affects_text test kept

Auto-merge duplicates removed: #288's table-style tests and #298's list test in idml tests.rs,
#288's tests in doc styles.rs and #285's docx test (both files equal next's), #281's dialog
tests in dialogs.rs (equals next's) and its menu_button/menu_max_height/clipped_submenu in
menus.rs. contributors/people.toml takes main's (next had a duplicated
[people.emanuelebonaventura] table from the 9e4f694 merge, which broke the ui-egui build
script's TOML parse).

Maintainer edits carried over: #285 break characters and Next Column through one closure
(next's `go`, comment added); #281 viewport-aware menu_button in the app bar and the Paragraph
menu, per-dialog widths clamped to the window (all already on next); #280 warichu after the
last-line rules (layout_line with ends_para/forced_mid) and rules at each warichu row's
baseline (rule_baseline in render and pdf text_rules); #176 CMYK fallback through
lossless(.., mode) and its updated contract tests; #258 test fixes; #153 and #297 as landed.
2026-10-10 08:20:36 +00: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
aiongg b832cdb065 Revert "Merge #168: Fix Windows startup crash by defaulting to DirectX 12"
This reverts commit 6e8f33c386, reversing
changes made to de5d32c90f.

#168 is closed; upstream main has #297, which picks the backend before the window exists.

Conflict: apps/designcraft/src/main.rs: kept mod logging; dropped #168's graphics module and its call
2026-10-10 08:13:14 +00:00