Commit Graph
2702 Commits
Author SHA1 Message Date
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
736acffe5f Desktop: pick the graphics backend before the window exists; DirectX 12 on Windows (#297)
* Desktop: pick the graphics backend before the window exists; DirectX 12 on Windows

On an AMD hybrid-graphics laptop (Radeon RX 6800M beside an integrated
Radeon, with two versions of amdvlk64.dll loaded into one process) the
app died a few seconds after launch with an access violation inside
AMD's Vulkan driver at the first swapchain present. Nothing reached the
log: a fault inside a driver is a native crash the engine's panic guard
never sees. wgpu's default backend set (PRIMARY | GL) enumerates Vulkan
first, so every start on that machine went down the same path, while
WGPU_BACKEND=dx12 and WGPU_BACKEND=gl ran fine and the headless CPU
renderer was never affected.

The desktop app now chooses the backend before creating the window
(apps/designcraft/src/gpu.rs): DirectX 12 first on Windows, then Vulkan,
then OpenGL; Vulkan then OpenGL on Linux; Metal on macOS. OpenGL stays
in the set as the in-process fallback when the chosen backend has no
adapter. WGPU_BACKEND still overrides everything and turns the fallback
off.

Start-up fallback: gpu.json in the settings directory records the
backend being tried until the third frame begins (the first has been
presented by then) or the app exits normally. A record left behind means
that backend never showed a frame, so the next start skips it, tries the
next candidate and says so in the status bar. The list starts over when
every candidate has failed or the app version changes;
DESIGNCRAFT_NO_PREFS records nothing. The adapter, backend and driver
version are logged at start-up for bug reports.

Verified on the affected machine: a default start runs on DirectX 12; a
simulated crashed DX12 start moved to Vulkan, which faulted for real,
and the start after that came up on OpenGL with the status line. Nine
unit tests cover the chooser and the record; cargo xtask ci passes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Merge origin/main; on Windows OpenGL isn't added to the DX12 or Vulkan instance (#167)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 04:11:47 -04:00
aiongg 9d986e4ea7 Warichu: hostile line spacing and break minimums can't break layout
A document's warichu line spacing was used unclamped, so 1e308 (IDML or type.char) put an infinite coordinate into the layout and the PDF; it is clamped to ±16 em and a non-finite value counts as 0. The break minimums' sum could overflow on 32-bit targets (wasm) and now saturates.
2026-10-10 08:09:37 +00: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
aionggandBrandon Thomas 0b69d6856c PDF: a placed image whose file is missing is reported as missing (#299)
A linked image that wasn't found on open has no data; the export warning said it could not be decoded. It now says it is missing and names the link.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 04:01:49 -04:00
37803c8176 PDF: reuse image conversions and index page objects (#176)
Co-authored-by: skylar taylor-barrick <hi@skylar.cc>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 04:00:40 -04:00
aiongg 2b8dc2bdeb IDML: text variables keep their text
A TextVariableInstance becomes the document's variable character instead
of being dropped. The file's TextVariable definitions replace the default
set: custom text, dates, file name, last page number, chapter number and
running headers are computed; other types (live captions) keep their
settings and show the result recorded in the file. Creation and
modification dates come from the package's XMP metadata.

Export writes the definitions, each instance with its current result,
and the XMP dates.
2026-10-10 07:55:33 +00:00
1bccd0c9e4 Keep menus and dialogs usable at larger UI scales (#281)
* ci: simplify testkit dependency match for Clippy

* UI: keep menus and dialogs reachable at larger scales

* UI: share viewport-aware scrolling across menus

Incorporate @aiongg's shared menu-button, anchor-aware sizing, and
reopen-reset work from #279 (717ddb6), retaining #281's clipped-submenu
and scrollbar interaction fixes.

Use the wrapper for workspace, zoom, and panel menus as well as the
menu bar. Preserve checkbox dismissal and cover anchor placement,
scroll reset, and very short viewports with regression tests.

---------

Co-authored-by: skylartaylor <26588250+skylartaylor@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:53:23 -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
74a81f9fb0 Harden temporary files and release inputs (#153)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:46:19 -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
aiongg a82e0dd93c Merge #299: PDF: a placed image whose file is missing is reported as missing 2026-10-10 07:35:42 +00:00
a50d279cba test(datamerge): pin facing-page record placement (#198 no longer reproduces) (#258)
* test(datamerge): pin facing-page record placement (#198)

#198 reported that with `facingPages: true` the merged records after the
first landed at identical coordinates on one spread, leaving a later page
empty. That no longer reproduces: `data.merge` now builds a new document
rather than duplicating pages inside the template, and the three records
land on absolute pages 0, 1 and 2 with no overlap.

Nothing in the engine changed here. These two tests pin the behaviour the
issue asked for, both at the one-record-per-page plan and at the short
block that tiles, so a future refactor of `tile_records` can't quietly
bring the stacking back.

Refs #198

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>

* Merge origin/main; the facing-pages merge tests find each frame's page with page_of_item

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Codebuff <noreply@codebuff.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 03:35:34 -04:00
aiongg 98fad01931 Merge upstream main 9e4f694 (#146, #284, #218) into next
All three PRs were already on next. The landed heads carry maintainer
merges of main with edits; each is carried over.

Conflict: ROADMAP.md: next's side (it already lists Italian and the
  Kashidas IDML note; upstream's Ukrainian sentence is #236's older one).
Conflict: crates/compose/src/lib.rs: tab loop takes main's last_tab line
  with its "A default tab stop is a left tab." comment; squeeze_last,
  the overfull single-word branch and the single-word gap filter keep
  next's side (#293 / mojikumi).
Conflict: crates/compose/src/tests.rs: next's side; both of main's tests
  are already on next (the list test in its justified-too form).
Conflict: crates/ui-egui/src/dialogs.rs: next's ok_label match (with
  findFont); closeDocument keeps "  Save  " in it.
Conflict: crates/ui-egui/src/i18n.rs: main's Italian catalog test (it.rs
  entries must be keys of the shared or Arabic table; missing keys fall
  back to English).
Conflict: crates/ui-egui/src/i18n/it.rs: main's entries for every key both
  sides have (the maintainer's translations); next's machine-translated
  entries kept only for nine keys main lacks that next's open PRs use
  (Align Left Edge, Characters:, Drop Caps, Drop Chars, Drop Lines,
  Lines:, Scale for Descenders, Select an object or text to color its
  fill or stroke., dropcap). "Select an object to color its fill or
  stroke." removed: next (#81/#84) renamed the string, and main's test
  rejects unknown keys.

Auto-merge duplicates removed: #146's close_document in ui-egui menus.rs
and its five close-document tests in ui-egui lib.rs (next has both).

Maintainer edits carried over:
- #146 (c52e1461): activate's doc comment sits directly above activate,
  after close_document. The closeDocument dialog width (380) was already
  on next in its clamped width match.
- #284 (9740ff1e): the last left tab comes from the shared tab_advance
  stop, with the default-stop comment (next already used tab_advance).
- #218 (145782ea): Italian alongside Ukrainian in LANGUAGES, the
  app.language param doc, the language menu and the menu test (already on
  next); the maintainer's 34 Italian translations; English fallback for
  untranslated strings in the catalog test; people.toml entry for
  emanuelebonaventura.
2026-10-10 07:35:27 +00:00
9e4f69428f I18n italian (#218)
* i18n: add Italian (it) UI translation

Adds an Italian interface catalog (crates/ui-egui/src/i18n/it.rs) covering every
key of the shared table and of the Arabic table (1,430 strings), written from the
English source using ordinary Italian desktop-publishing vocabulary.

Registers "Italiano" in Edit > Interface Language, wires it into tr(), and extends
the i18n and menu tests (lookup, no duplicates, full key coverage, translated menu
titles, language switch frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* contributors: add emanuelebonaventura (display name insanityxoff)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:24:12 -04:00
aiongg bd6c12f3d4 PDF: a placed image whose file is missing is reported as missing
A linked image that wasn't found on open has no data; the export warning said it could not be decoded. It now says it is missing and names the link.
2026-10-10 07:20:42 +00:00
aiongg 8ae759a848 Merge #298: Lists: numbering formats and expressions from IDML; an empty paragraph gets no bullet or number
Conflict: crates/compose/src/lib.rs: next's list_label match with its list_label glyph flag; arms take #298's empty-paragraph rule, number_label/bullet_label and saturating counter
Conflict: crates/compose/src/tests.rs: next's file with #298's appended tests
2026-10-10 07:15:49 +00:00
aiongg 20478fd47c Merge upstream main 99af9f5 (#274, #282) into next
Conflict: compose breaker.rs, lib.rs, tests.rs; idml import.rs, tests.rs; pdf tests.rs; ui-egui dialogs.rs: next's side (both PRs are on next with their resolutions; the maintainer's merges of main into their heads only joined tests, as next does). The merged tree equals next's.
2026-10-10 07:14:17 +00: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
a01b00da8f UI: closing a document with unsaved changes asks first (#141) (#146)
File ▸ Close, its shortcut (Ctrl/Cmd+W) and the tab's × closed a document
with unsaved changes at once. Closing also discards the document's
recovery data, so the edits were gone for good.

The three user paths now go through menus::close_document. A document
with unsaved changes opens a dialog with Save, Don't Save and Cancel:

- Save runs the same Save the menu does, then closes. If the document
  is still unsaved afterwards (the file picker was cancelled), it stays
  open.
- Don't Save closes it.
- Cancel (or Escape) does nothing.

A document with nothing to save closes as before. The engine command
file.close is unchanged and never asks, so scripts, MCP and the control
channel's engine.execute keep working unattended. ui.menu.invoke without
params acts like the menu item, as it does for other dialogs.

Quitting the app is not covered here. Recovery data survives a quit, so
unsaved documents come back on the next start.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 03:12:48 -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
aiongg 107f658f0e 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.
2026-10-10 07:01:05 +00:00
aiongg 2a2d20e0c2 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.
2026-10-10 06:57:28 +00: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
aiongg f2a6b0e4c2 Merge upstream main 1d996da (#263, #296) into next
Conflict: ROADMAP.md: next's IDML1 line kept
Conflict: crates/compose/src/lib.rs: next's rule_color() (#83) for Rule Above
Conflict: crates/engine/src/cmd/text.rs: next's type.para params doc (drop cap fields) and apply_char_attrs
Conflict: crates/ui-egui/src/i18n.rs: one "Tint:" row at #296's position (same as next's 35d011f)
Conflict: crates/ui-egui/src/panels/properties.rs: next's menus::menu_button for the ☰ menu (#281)
Auto-merge duplicated #263's dialog tests and its paragraphRules confirm arm in dialogs.rs; kept one copy of each.
2026-10-10 06:47:53 +00:00
aiongg 57801d86a5 Merge #295: Text: Split Columns sets a paragraph in sub-columns within its column
Conflict: crates/compose/src/lib.rs: #295's split_cfg block and sub-column-aware next_column closure, then next's `go` closure unchanged (StartParagraph::NextColumn and column breaks reach the next sub-column; other start options go through Cursor::start, whose next_frame/next_page start a new split part)
Conflict: crates/compose/src/tests.rs: both sides' tests (closing brace restored after next's last test)
Conflict: crates/idml/src/tests.rs: both sides' tests (closing brace restored after next's last test)
2026-10-10 06:41:22 +00:00
Brandon ThomasandClaude Opus 5.5 1d996da82a i18n: one Tint: row per catalog (two PRs each added it) (#296)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 02:38:46 -04:00
aiongg 7e0afa24e8 Merge upstream main 464a442 (7 PRs landed) into next
Landed: #119 #294 #144 #199 #248 #222 #223. #119 #144 #199 #248 #223 were
already on next; #222 (Spanish hyphenation) and #294 (Ukrainian catalog
test) are new here and come in whole. Each squash equals its landed PR
head (fetched to refs/remotes/pr-landed/N).

Conflict: contributors/people.toml: next's side (keeps emanuelebonaventura between moppeeer and thaynej)
Conflict: crates/doc/src/styles.rs: next's side (next's cell/table style tests)
Conflict: crates/pdf/src/export.rs: next's side in both hunks (no Exporter `interpolate` field on next; `let i = mode.interpolate` kept once)
Conflict: crates/ui-egui/src/dialogs.rs: confirm: next's `paragraphRules` arm; basic_character_formats: upstream's side (maintainer's font menu, see below)
Conflict: crates/ui-egui/src/i18n.rs: next's translated "Tint:" row
Conflict: crates/ui-egui/src/lib.rs: next's side (keeps ClipboardFn)
Conflict: crates/ui-egui/src/widgets.rs: next's side (next's numeric field grid test)

Maintainer edits in the landed PR heads:
- #119 (bfe02c9): inbox and PDF page dialog imports go through open_file; import_file uses opens_as_document. Already on next.
- #144 (fa5b942): paragraph_style_options reads a cloned doc; already on next. Basic Character Formats' Font Family is a ComboBox over font_menu_body (blank row clears a sparse family, "—" when unset). Taken from upstream in place of next's font_combo + "×" button.
- #199 (7afa9b7): one Properties test module with both tests. Already on next.
- #248 (856a432): people.toml keeps moppeeer and thaynej. Already on next.
- #222 (ac9b68d): ROADMAP Language line combines #199's "Properties ▸ Character" with #222's Spanish hyphenation note. Comes in with #222.
- #223 (c3d8a07): `mode.interpolate &= self.interpolate` and the decode check in lossless. The decode check is on next; the interpolate line is not, because next has no Exporter `interpolate` field and RasterMode::for_standard already turns interpolation off for every standard, PDF/A included.
2026-10-10 06:36:48 +00:00
zydvc26s67-web 92950c0938 Merge remote-tracking branch 'origin/main' into zydvc26s67-web-paragraph-styles-save-delete
# Conflicts:
#	crates/ui-egui/src/dialogs.rs
2026-10-09 23:32:14 -07:00
zydvc26s67-web 85e3fc879c Merge remote-tracking branch 'origin/main' into zydvc26s67-web-paragraph-styles-save-delete
# Conflicts:
#	crates/doc/src/styles.rs
#	crates/engine/src/cmd/style.rs
#	crates/ui-egui/src/dialogs.rs
#	crates/ui-egui/src/panels/styles.rs
2026-10-09 23:28:38 -07: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
464a442802 PDF/X-4: RGB images carry sRGB, no image interpolation, pdf:Trapped in XMP (#223)
- An RGB image with no ICC profile of its own was written as DeviceRGB
  under the CMYK output intent, and the export only warned. In PDF/X-4
  it now carries the sRGB profile (assets/icc/sRGB-v2-magic.icc, ICC
  v2.1, CC0). JPEGs stay passed through. Untagged PNG, GIF and WebP,
  and the decoded TIFF, BMP and PSD fallback, go in as RGB with the
  profile. A re-encoded JPEG keeps the source's RGB profile or gets
  sRGB. Grey images and images with their own profile are unchanged.
- Every image constructor asked for interpolation. PDF/X-4 and PDF/A
  exports now write images without it, which also removes the
  ImageInterpolation error that stopped PDF/A exports with a placed
  image. Plain exports keep it.
- make_pdfx4 writes pdf:Trapped False into the XMP, matching
  /Trapped/False in the document information.
- check_pdfx4 reports an image that asks for interpolation.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 02:22:52 -04:00
aiongg f39897db0b Text: Split Columns sets a paragraph in sub-columns within its column
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).
2026-10-10 06:22:38 +00:00