Commit Graph
59 Commits
Author SHA1 Message Date
Dylan M. TaylorandClaude Sonnet 5.5 c313c53497 refactor: split complex functions in ruby, warichu, pdf text, render text, epub, pdfx (Sonar S3776) (#1728)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 04:51:58 -04:00
Hamza 0fc845750a An underline runs across a tab, as the rest of the line's text (#1466)
A tab draws nothing, so it was left out of the underline and strikethrough
bars: they stopped at the last letter before each tab and began again after it.
Forms rule their fill-in lines with underlined tabs — `Name ⇥⇥⇥⇥, city ⇥⇥⇥⇥` —
so an imported form lost every writing line, and an IDML `Underline="true"` on
a run of tabs drew nothing at all.

A tab is a character with an advance, and InDesign rules its whole width.
`PlacedGlyph::spans_rules` says which glyphs a rule runs across: the drawn
ones, and a tab or a right-indent tab that advances. The spaces a line break
trimmed off the end, the break characters and the markers' carriers are not
part of the line's text and are still skipped, so an underline stops at the
text and not at the frame edge.

Both places that draw the bars from the glyphs use it: the renderer
(`build_line`) and the PDF export (`text_rules`). The HTML and PPTX exports
mark up runs of text, which already included their tabs.

Measured on the repro in the issue, a 420×120 pt page at 2×:

  before   one underline, 63 px: the word "Name"
  now      three, 63 + 219 + 231 px: the word and both tab stretches

and the exported PDF gains the two fills for them. A tab with a leader is one
continuous bar across the leader dots to the right-aligned text after it.

Tests: the renderer's bars and the PDF export's bars for "Name<tab>City" are
one bar covering the tab's width, not two with a gap where it is. Both fail
without the change (two bars, 36→67.7 and 72→93.2, the tab's 67.7–72 unruled).
2026-10-21 20:04:12 +05:00
aiongg 18ae8e4761 Merge upstream main a184db6f8 with our 15 PRs (batch-0) into next
Conflicts resolved per file (both sides kept unless noted):
crates/engine/src/atomic.rs | 1 | main's: `write_file` removed (main routes command writes through `crate::io::write_file`); its one caller in file.rs now uses `crate::io::write_file`
crates/engine/src/cmd/datamerge/fill.rs | 1 | next's `Graphic` literal (has `clip` and `wrap`, which next's `Graphic` carries)
crates/engine/src/cmd/datamerge/mod.rs | 1 | main's `read_path_capped` (crate::io metadata/read_capped, roots checked); next's capped read is the same behaviour
crates/engine/src/cmd/datamerge/parse.rs | 1 | main's (`crate::io::metadata`, `crate::io::Metadata`)
crates/engine/src/cmd/edit.rs | 1 | next's `spread_param(p, "edit.paste")` (the second argument is the command id on both sides)
crates/engine/src/cmd/export.rs | 3 | main's: EPUB params doc, `toc_title` once (main's position; next's duplicate dropped)
crates/engine/src/cmd/file.rs | 7 | place doc: next's (lists page/rect/autoflow params #803 checks); next's recent-file functions + main's `foreign_document`; next's open (load_bytes, warnings, non-DesignCraft refusal) with main's checked `crate::io::read_file` for the bytes; main's `check_write` be
crates/engine/src/cmd/find.rs | 7 | main's Find scope (FindContext, scope/selection/story, continuation, UTF-8 repair) on next's `designcraft_compose::grep::Grep` (dialect GREP): find uses `find_all` capped at MAX_MATCHES, change uses `replacements`/`find_all` (literal change text not expanded), fin
crates/engine/src/cmd/fonts.rs | 1 | next's (`has_style`, resolved/replacement face); `has_style` already compares styles ignoring spacing and case, which is main's `same_style` fix; main's test kept
crates/engine/src/cmd/interchange.rs | 1 | next's `import_idml_report_with_previews` (previews for unreadable links)
crates/engine/src/cmd/layout.rs | 8 | pages.insert: main's overflow-safe `after` + next's checked `parent_ref`; main's duplicateSpread doc; layout.section: next's `SectionEdit` (already keeps unnamed settings, #717) with main's `d.repaginate()` moved into `SectionEdit::apply` (remove and set paths),
crates/engine/src/cmd/links.rs | 5 | next's links.list (no doc clone); main's `text_status` via crate::io; `use std::io::Read` dropped (unused after main's read_capped); test literals main's. `link_missing` (next) uses `crate::io::exists` for the file-guard scan
crates/engine/src/cmd/object.rs | 4 | object.fill/stroke tint-alone: main's (23e1680e7; out-of-range tint is an error, which main's test asserts); object.fit doc: next's (lists `axis`)
crates/engine/src/cmd/paths.rs | 1 | next's (shatai transform on outlines)
crates/engine/src/cmd/place_text.rs | 1 | next's `add_flow_frame` (used by autoflow)
crates/engine/src/cmd/preflight.rs | 2 | next's overset/unplacedStory issue (its `overset_location` already applies the parent transform main's #738 fix adds); next's two test modules kept with main's
crates/engine/src/cmd/scripts.rs | 1 | combined: next's `in_undo_step = false` (#406) + main's full rollback of a failed script (#709)
crates/engine/src/cmd/style.rs | 14 | param docs next's (gradient swatch.create, swatch stops, model colours); create_para next's; rename_style main's structure (rename entry, then `redirect_style_refs`), redirect has next's full reference set (cells, defaults, notes, TOC) + main's never-based-on-it
crates/engine/src/cmd/text.rs | 10 | main's selectSentence/Paragraph/Story + select_around; next's selectionAttrs (mixed) + main's openTypeFeatures/type.feature; main's comments; tracked typing: next's `reserve_command_text` + main's `insert_format_at`; empty-paragraph mark formatting main's (with `
crates/engine/src/cmd/thread.rs | 1 | next's `spread_param(p, "text.thread")`
crates/engine/src/dtd.rs | 1 | next's linear, depth-bounded `expand_parameter_entities` with main's budget (input + 1 MiB, `MAX_EXPANDED_BYTES`) and message ("expand the DTD beyond"); 16 MiB constant removed; both sides' tests pass on message
crates/engine/src/guard.rs | 2 | main's whole-transaction snapshot (docs, active, journal; #742) + next's `in_undo_step` restore; tests both
crates/engine/src/lib.rs | 5 | LoadedGraphic doc next's; next's `new_document_preview`; execute: main's `discard_stale_find_scope()` before next's nested-undo run; modules both (tests_autosize, tests_file_guard, tests_params, tests_roots)
crates/engine/src/recovery.rs | 1 | next's test (superset of main's assertion)
crates/engine/src/tests.rs | 4 | moved tests deduplicated (identical on both sides); next's preflight and direct-selection/kerning tests + main's style-delete, locked-frame and baseline-fit tests
crates/engine/src/tests_idml.rs | 3 | main's order (drop cap and margins tests identical on both sides, once each)
crates/ui-egui/src/canvas.rs | 3 | next's screen_options/DisplayKey block and tests kept; two tests that auto-merge had added twice (parent_guides_on_a_page_are_not_picked_there, baseline_grid_lines_are_the_ones_on_the_page) kept once; main's `typing`/`screen_at`/`click` helpers and its tests clicks
crates/ui-egui/src/control.rs | 3 | main's `count` check and pending budget (control queue + synthetic queue); exact-budget test runs both sides' cases (click 255 on 9,492 pending; drag 9492×255)
crates/ui-egui/src/cursors.rs | 1 | main's (same Pen cursor set)
crates/ui-egui/src/dialogs.rs | 14 | dialog body (newDocument … alert match) is next's text, with main's Find/Change scope reset (`set_field`, `reset_find_scope`, `find_change_params`) and main's Create Guides / Margins and Columns arms ported in; `Dialog::new` ruby section (next) + main's `set_fi
crates/ui-egui/src/i18n.rs | 7 | ka and pl modules, languages, statics and lookups both; `category_first` covers uk, ru, pl, ka and `count_label` keeps next's Czech plurals; shared TABLE union; next's duplicate rows for Auto, Number:, Gutter:, Top:, Bottom:, Inside:, Outside:, Left:, Right: dropped
crates/ui-egui/src/i18n/ar.rs | 2 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/it.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/ja.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/pt_br.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/i18n/uk.rs | 1 | union of rows, no duplicate keys
crates/ui-egui/src/icons.rs | 2 | both tests (main's typography icon, next's distribute icons)
crates/ui-egui/src/lib.rs | 11 | UiState: next's tabs_panel + main's last_color/last_gradient; `cursors` field once; next's SVG paste and paste-on-spread-in-view; next's extensionless document open; opens_as_document next's comment; tests both (next's control bar / key-name tests, main's rich black
crates/ui-egui/src/menus.rs | 15 | app.language doc next's text with pl and ka; UI_COMMANDS both (next's Tabs, Toggle Presentation, Apply None text; main's panel commands, Apply Color, Apply Gradient); Edit › Interface Language stays out (next moved it to Preferences); Type menu main's order with
crates/ui-egui/src/panels/glyphs.rs | 1 | next's (already has main's scroll-bar fix); the scroll-bar test module auto-merge had added twice kept once
crates/ui-egui/src/panels/layers.rs | 3 | next's drag-and-drop `object_rows`, with main's features ported into it: selection reveal (layer opens, row scrolls into view once), selected rows highlighted (also a group whose member is selected), double-click rename in place (`object.rename` queued like
crates/ui-egui/src/panels/mod.rs | 6 | tabs + typography modules and color_panel_tests; SelInfo has wrap_side/wrap_contour and wrap_offsets (offsets from the graphic's own wrap when it is selected); next's swatch_picker returning the chip response; next's content_selection_tests
crates/ui-egui/src/panels/pages.rs | 1 | next's (parents dragged by id)
crates/ui-egui/src/panels/properties.rs | 4 | next's swatch popup (Apply To, Tint); main's four wrap offsets then next's Wrap To / Contour Type; test fixtures main's (`..Default::default()`, no unused builder)
crates/ui-egui/src/panels/swatches.rs | 3 | main's Delete Swatch button + next's New Swatch comment; next's draggable rows; click: main's PICKED + fill/stroke in front, with next's no-apply on double-click
crates/ui-egui/src/toolbar.rs | 2 | main's Apply Color / Gradient / None wells and functions; next's apply_none doc line
crates/compose/src/lib.rs | 5 | next's line loop kept (mark_leading + last_line_mark, wrap segments/carry, grid gyoudori); main's mark-chars base_leading block (auto-merged) stays beside it. `fits`: next's #106 rule (baseline fit for horizontal frames, full descent for paths, vertical text, footnote
crates/compose/src/tests.rs | 5 | next's tests (tombstone set, sliver/one-sided/contour/clipping wrap tests, borders, lists, kenten, grid, anchored wrap, balance, shaped frames) plus main's an_empty_last_paragraph_isnt_overset; main's wrap_object_beside_one_narrow_column_leaves_the_other_alone re-ad
crates/doc/src/assets.rs | 1 | main's doc comment (same meaning)
crates/doc/src/build.rs | 3 | parent_page_for: next's parent_page_for_side (RTL-aware); single_sided_page_shows_first_parent_page test kept once, at main's position
crates/doc/src/item.rs | 1 | next's rotated_group_bounds_follow_the_children_outlines test kept
crates/doc/src/lib.rs | 1 | next's kerning_increment field kept; main's "Bulleted & Numbered" wording
crates/doc/src/page.rs | 1 | next's outer_page_indices + parent_page_for_side (integration of parent-margins with rtl-master-sides); main's parent_page_index dropped, its only caller (build.rs) uses parent_page_for_side
crates/doc/src/slice.rs | 5 | extract: next's `marked()` (get-based, fix_notes/fix_endnotes/fix_marks; main's version indexed slices and could panic); insert_story: main's take() limit on endnote/editorial payload copies with next's id comment, redundant `let ne`/`let nn` dropped; main's excess_note
crates/geom/src/corners.rs | 1 | next's box_outline_rounds_only_closed_corners test kept
crates/render/src/damage.rs | 1 | next's wrapping-anchored-object damage test kept
crates/render/src/fx.rs | 2 | feather: next's choke/corners sigma+join inside main's structure (body and clip at device resolution, with_filters(.., None)); inner glow centre: next's blend `mix` with main's clip argument (bp, xf)
crates/render/src/lib.rs | 3 | next's layer comment; next's front baseline grid draw after items; gradient match on g.kind (main's tuple scrutinee did not match the single-pattern arms)
crates/render/src/text.rs | 5 | paint_glyphs: main's paint_stroke closure structure with next's stroke overprint (overprinting helper, stroke `(TextPaint, bool)`); callers pass next's overprint_fill/overprint_stroke
crates/render/tests/placed_pdf.rs | 1 | main's `near` helper version
crates/tools/src/lib.rs | 1 | main's (PenConvert already declared below, avoids a duplicate variant)
crates/tools/src/pen.rs | 1 | main's module doc
crates/tools/src/select.rs | 1 | next's Drag::Corner variant (live corners) kept
crates/tools/src/tests.rs | 4 | next's tests (content drag, live corners, table border drag, eyedropper matchFrom, fitting handles, pen cursors once); eyedropper_ignores_inactive keeps next's samples_image check (main's exact-JSON compare lacks matchFrom) with main's `..Default::default()` Asset lit
crates/tools/src/text.rs | 2 | next's table border drag (BorderDrag, border_at) kept, then main's comment on locked text frames
crates/tools/src/xform.rs | 2 | main's explicit `Vec<&Item>` for shown children; next's eyedropper matchFrom (integration 796e9d713)
Cargo.toml | 2 | main's craft-pdf workspace dep added (craft-print stays once, in next's craft-libs block); [patch.crates-io] keeps next's winit (rust-windowing #4694 backport) and adds main's krilla patch; main's skylartaylor winit left out (see UNSURE 1)
Cargo.lock | 1 | union: next's craft-atomic and craft-cms entries plus main's craft-pdf entry; needs `cargo` normalisation
README.md | 1 | Open bullet combines INDD import via indd-utils (next) and editable PowerPoint export (main); main's paragraph break kept
contributors/people.toml | 1 | both people kept (switch87 from next, Nmarello from main)
apps/designcraft-cli/src/main.rs | 4 | next's `Export` enum dispatch kept; main's .pptx export added as `Export::Pptx` (run prints slides and warnings, script runs file.exportPptx); main's `--pdf-options` null-as-{} parse; usage lines list .pptx
apps/designcraft/src/main.rs | 3 | `startup.leave()` right after `app.ui` (main #380) and next's macOS `sync_movable` kept after the IME discard; next's Apple Events install kept; main's macOS Dock Quit `with_close_requested_on_terminate` block left out (needs the skylartaylor winit; UNSURE 1)
crates/fonts/src/fontdb.rs | 9 | FONT6 aliases (next) combined with #911 WWS names (main): ScannedFace and both CatalogEntry constructors carry `wws` and `aliases`; `canonical_family` falls back to WWS family names; `exact_style_traits` canonicalises then tries typographic, then WWS names (`named_st
crates/format/src/lib.rs | 1 | tests: both sides kept (next's r7a nullable-default tests; main's oversized ZIP entry/asset/entry-count tests)
crates/idml/src/import.rs | 11 | main's bounded `Package` reader under next's `import_idml_report_with_previews` (metadata read through `pkg.part`); next's `tab_list_repaired` kept; ruler origin: next's `settings.ruler_origin`, and main's guide import reads it (main's private `RulerOrigin` enum and
crates/idml/src/tests.rs | 6 | item-level union: all next tests plus main's new ones (ruler guides, package limits, CJK leading model, liquid page rules module, mojikumi); no test dropped
crates/mcp/src/backend.rs | 1 | test comment merged from both
crates/pdf/src/tests.rs | 1 | tests: both sides kept
crates/pdf/src/text.rs | 4 | next's (skew `tan`, cluster `start`, ruby overprint warning); main had no change here beyond base
crates/textimport/src/docx.rs | 5 | next's (ListCounter integration for list levels, page-break test position); main's side was the older form of the same code
crates/epub/src/lib.rs | 30 | non-test code is next's (StoryWriter holds img_src/images/no_alt/heading_ids; covers main's #787/#791/#782 fixes, plus #314 group transforms, anchored images in images_without_alt, endnote story skipped in the TOC); tests: next's set plus main's extra css_weight_style a
docs/control-protocol.md | 2 | next's rows (ui.paste, count 1–255, documentGrid) and wording
docs/file-format-parity.md | 1 | rows combined (next's ICC notes, main's TIFF indexed colour)
docs/gaps.md | 2 | status change notes unioned; Flex Layout removed from missing (main implemented it), next's Parent Options sentence kept
docs/localization-parity.md | 4 | Polish and Georgian rows; 5 languages beyond the twelve; file list includes cs and ka; both history rows
docs/roadmap.md | 1 | M1 next's (#186 fixed on next), M2 main's IME wording
docs/target-app-parity.md | 6 | next's status line; File formats row next's, UI/UX row main's (IME no longer failing); UI line next's plus main's Swatches detail; one Page tool line (main's fuller one); Load styles and main's fuller Pathfinder line both kept, main's duplicate short Pathfinder line r
docs/ui-parity.md | 1 | IME row main's, Font menu row next's

i18n/fr.rs: five rows dropped that main's shared table now has (Row, Open Path, Flex Layout, Current Page Number, Rulers).
2026-10-16 03:09:22 +00:00
Brandon Thomas 84dbba029f Merge IDML/text cluster
# Conflicts:
#	crates/ui-egui/src/i18n.rs
#	crates/ui-egui/src/i18n/ar.rs
#	crates/ui-egui/src/i18n/it.rs
#	crates/ui-egui/src/i18n/ja.rs
#	crates/ui-egui/src/i18n/pt_br.rs
#	crates/ui-egui/src/i18n/uk.rs
2026-10-14 23:54:49 -04:00
Brandon Thomas d35887f494 Merge PR #825 (gradient text paint goes through #657's stroke-behind-fill painting) 2026-10-14 23:31:39 -04:00
Brandon Thomas af45138718 Merge PR #588
# Conflicts:
#	crates/render/src/lib.rs
2026-10-14 22:54:18 -04:00
Brandon Thomas 3cdda36680 Merge PR #657 2026-10-14 22:05:11 -04:00
moppeeer 1faaebef25 Merge remote-tracking branch 'origin/main' into codex/gradient-text 2026-10-14 09:12:03 +02: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
aiongg 7f0d7f1f32 Merge t13-moppeeer: #253 #832 #753 #754 (a3b97165) #588
# Conflicts:
#	ROADMAP.md
#	crates/idml/src/tests.rs
2026-10-13 19:57:37 +00:00
aiongg 6039be0526 Merge #588: CMYK blend space: blend modes blend in CMYK on screen
# Conflicts:
#	ROADMAP.md
#	crates/render/src/fx.rs
#	crates/render/src/lib.rs
2026-10-13 19:37:18 +00:00
aiongg 1847b8cef5 Merge #825: Gradient swatches on text, laid across the text frame
# Conflicts:
#	ROADMAP.md
#	crates/pdf/src/export.rs
#	crates/pdf/src/text.rs
#	crates/render/src/text.rs
#	docs/target-app-parity.md
2026-10-13 19:30:47 +00:00
moppeeerandClaude Opus 5.5 ac54c8faf7 feat(text): gradient swatches on text, laid across the frame as in InDesign
LYH Studio episode 6 (8:28) gives the "1969" headline the "New Gradient
Swatch" (magenta → cyan) from the Swatches panel. InDesign runs a gradient
on text across its text frame's box. DesignCraft resolved a character
fill to one solid colour, so a gradient swatch drew no text at all.

- render: a character fill or stroke that names a gradient swatch is
  painted with that gradient over the frame's box in text space (type on
  a path: the path's box).
- pdf: text runs take a gradient paint: the shading is laid out in the
  frame's text space and mapped into each run's origin, so skew and
  horizontal scale don't bend it; outlined runs and type on a path too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:30:55 +02:00
moppeeerandClaude Opus 5.5 048d8843bf perf(render): CMYK blending in two ink passes, for every blended object
Separable blend formulas work channel by channel, so the spread is drawn
twice through the ordinary renderer: C, M and Y complements into RGB, K
into a second raster, with the existing transparency groups, masks,
effects and parent-page placement. The inks are converted to sRGB once and
put over the paper. Two passes regardless of how many objects blend;
parent items, nested blends and effect blends (Multiply satin, shadows)
blend in CMYK too, and the pass covers the whole view, so blending
continues past the trim. An unchanged view is reused from a one-frame
cache (64 MiB cap). Spreads with Hue, Saturation, Color or Luminosity keep
the RGB-then-proof approximation. Effect blend layers sit outside their
clips and offscreen filters, so they see the object's backdrop (RGB too).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:11 +02: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
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 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
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
moppeeerandClaude Opus 5.5 1dc33dd280 fix(render): One underline bar per stretch of text, no seams
The canvas drew an underline (and a strikethrough) as one rectangle per
glyph; where two anti-aliased edges met at a fractional pixel they left
a lighter seam between the letters. Contiguous bars of the same colour
and position are merged into one, as the PDF export already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:29:09 +02:00
aiongg 6c8ba2d75d Merge #539: One underline bar per stretch of text, no seams between letters
Conflict: ROADMAP.md: both progress-log lines
2026-10-12 00:18:21 +00:00
moppeeerandClaude Opus 5.5 1e34026a79 fix(render): One underline bar per stretch of text, no seams
The canvas drew an underline (and a strikethrough) as one rectangle per
glyph; where two anti-aliased edges met at a fractional pixel they left
a lighter seam between the letters. Contiguous bars of the same colour
and position are merged into one, as the PDF export already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 00:29:17 +02:00
aiongg b6b5d4c705 Merge table-cell-strokes: edits start from the drawn edge, bounded dash patterns, Cell Options fixes, closed corners
Conflict: ui-egui dialogs.rs tests (the branch's new test kept; its copies of the dialog test helpers already on next dropped).
2026-10-10 21:45:22 +00:00
aiongg ac4b83fa39 Tables: cell stroke edits and queries start from the drawn edge; bounded dash patterns; closed table corners
- table.setCell starts each chosen edge from the stroke rendering draws there (the winning
  copy of a shared edge, the row/column pattern, or the table border) and writes the result
  to both copies; table.getCellStroke reports drawn edges, counting each shared edge once.
  compose::table::cell_side_strokes shares the resolution with the renderer.
- Commands accept dash patterns of 1-64 lengths, each 0 or 0.1-10000 pt. Screen and PDF
  draw a dash or dot pattern that would repeat more than 20000 times along a path as solid.
- Stripes, wavy, hash and gap colour extend by half the weight at table corners.
- Cell Options: an edge change reseeds every stroke field and tolerates a non-object seed.
- Edge proxy: a triple-click decides from the choice before the first click.
2026-10-10 21:44:09 +00:00
aiongg 23da490a42 Merge table-cell-strokes: cell edge strokes in the Stroke panel and Cell Options, with gap colour and tint (#370)
Conflicts: ui-egui dialogs.rs (next's numberingSection arm and the branch's cellOptions arm; the branch's
copies of the dialog test helpers already on next dropped, its cell options test kept); idml tests.rs and
the i18n tables appended at the end on both sides (both kept; the branch's Gap Tint: and Tint: rows that
next already had dropped).
2026-10-10 20:30:54 +00:00
aiongg 20d4178c43 Tables: cell edge gap colour and tint, all stroke types on cell edges, inner horizontal/vertical edge choice
CellStroke and cell/table style edge attributes carry gap colour and gap tint
(IDML ...EdgeStrokeGapColor/GapTint, ...BorderStrokeGapColor/GapTint). The table
renderer and PDF export draw every stroke type on cell edges, with the gap colour
under dashes, dots and stripes.

table.setCell validates stroke values, takes gapColor/gapTint and an edges list
(top/left/bottom/right are the target range's outer sides; innerHorizontal and
innerVertical the lines between its rows and columns). table.getCellStroke reports
the shown values of the chosen edges, null where they differ.
2026-10-10 20:04:57 +00:00
aiongg 0f6f96ae6d Merge feat/cjk-typography-ui: ruby, kenten, shatai, auto tate-chu-yoko, rotate roman, roman word break, grid references and the CJK style dialog panes
Conflict: ROADMAP.md: next's FONT1-3 lines with the branch's CJK1 line
Conflict: docs/cjk.md: the branch's rows, with next's aki below and Japanese composer text kept in the leading model row
Conflict: crates/fonts/src/fontdb.rs: both fields (family/style aliases and the ICF cache)
Conflict: crates/compose/src/ruby.rs: the branch's ruby module; its PlacedGlyph (and kenten.rs's) carry next's rendered_char
Conflict: crates/compose/src/lib.rs: modules both; rtl and para_block both; the branch's grid placement (gyoudori, grid references) replaces next's snap, its paragraph-gyoudori span uses next's line_metrics with mark leading and #330's aki below advance; a wrap push moves the line by whole grid lines and its grid end with it; next's trimmed spaces and drop cap, then the branch's ruby and kenten annotation
Conflict: crates/compose/src/shape.rs: next's pieces loop and StyleRuns with the branch's auto tate-chu-yoko split (after marks join their base) and fit, and Upright mode in every shape_segment call; next's label fallback passes the mode on
Conflict: crates/compose/src/tests.rs: both sets of tests appended at the end
Conflict: crates/idml/src/export.rs: next's metadata and text variable writers, then the branch's CJK settings writers
Conflict: crates/idml/src/tests.rs: both tests
Conflict: crates/ui-egui/src/dialogs.rs: the branch's Ruby dialog default section (next's duplicate characterStyleOptions default dropped); next's section arms with the branch's style_cjk::section_ui
2026-10-10 16:42:01 +00:00
aiongg 0ebe1212f0 Merge feat/paragraph-settings-full: paragraph borders, shading, span space and list levels in the model, layout, IDML and dialogs
Conflict: crates/compose/src/lib.rs: imports from both (ListAlign, ListCounter; ParaBox beside next's hit_note_with); the branch's list labels (ListCounter, named_labels, label_chars, label alignment, Tab Position stop) with next's list_label marking from prepend_label's count; the branch's align_label before next's warichu placement, trimmed spaces and drop cap; next's rule_color for rules above and below in the branch's Deco literals; the branch's split block end (ends_split, span space after) in place of next's two identical copies; next's drop cap helpers, then the branch's label_chars and align_label, then prepend_label
Conflict: crates/compose/src/tests.rs: both sides' tests
Conflict: crates/idml/src/export.rs: next's para_attrs parent name argument with the branch's with_list_tab (its parent attributes renamed parent_props) for styles and paragraph ranges
Conflict: crates/idml/src/import.rs: next's INDESIGN_CORNER_SIZE and tint doc; the branch's corners() placed before tint's doc comment
Conflict: crates/idml/src/tests.rs: both sides' tests
Conflict: crates/ui-egui/src/dialogs.rs: next's deleteParagraphStyle and the branch's paragraphLocal confirm arms
Conflict: crates/ui-egui/src/panels/glyphs.rs: the branch's grid() split, with next's scroll bar room in its column count
2026-10-10 16:26:10 +00:00
aiongg 8f93548572 Paragraph borders, shading, span space and list levels: model, layout and IDML
Borders gain a stroke type (built-in or a document stroke style), cap,
join, gap colour and tint, a shape and size per corner, column or text
width, top and bottom edges, closed boxes in each part of a split
paragraph, and one box for consecutive paragraphs with the same
settings. Shading gains the same corners, edges and width, Clip to
Frame, and Do not Print or Export. Boxes are drawn once the lines are in
place, as filled paths where they aren't plain rectangles.

Span and split paragraphs keep at least their space before and after
from the column text. Lists have levels that number on their own and
restart after a higher level, ^1 to ^9 in the number text, a character
style for the label, a bullet font, left, centre or right alignment at
the line start, and a tab position for the text after the label.

All attributes load from older files with their defaults and
round-trip through IDML, apart from the tab position.
2026-10-10 16:03:11 +00:00
aiongg 4c4dedaa93 Ruby: clears kenten on the same side, stays with the kana it overhangs, overprints in Overprint Preview 2026-10-10 16:02:52 +00:00
aiongg d9bc53d7f8 Ruby: placement, spacing, font, size, overhang, scaling and colour settings with JLReq layout and IDML 2026-10-10 15:56:16 +00:00
aiongg 31a58a8501 CJK: kenten settings, shatai, auto tate-chu-yoko, rotate roman, roman word break and grid references 2026-10-10 15:50:32 +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
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
skylar taylor-barrick 452134c13b FID2: preserve decoration color resets, geometry and paint order (#230) 2026-10-10 01:50:52 -04:00
aiongg 66bf8585f9 Integration: #177's glyph-cache test reads #230's underline list
#230 splits LineGlyphs::decos into underlines and strikes (underlines paint before the
glyphs, strikethroughs after). #177's in-flight owner test still read `decos`, so the
render tests did not compile once both were merged. The test's composition has an
underline, so it checks `underlines`.
2026-10-09 22:54:32 +00:00
aiongg 9f0b34e876 Merge #177: Memory: release retired layouts and reserve text growth exactly
Brings #175 (abf9a40, PSD fixed-size chunks).
Conflict: crates/engine/src/cmd/text.rs: tests only; kept #118's story_query_contract_tests and #177's reserve_tests (insert()'s code merged cleanly: #177's reservations around #84's typing format)
2026-10-09 22:53:16 +00:00
aiongg 01799b4927 Merge #124: IDML: preserve anchored text frames and footnote artwork
Conflict: crates/compose/src/shape.rs: inline object on the baseline: #124's rule (its box alone sets the extents, not max with the font's) applied to every metric #83 tracks (ascent, typo_ascent, cap, xh); the above-line arm keeps #83's four fields
Conflict: crates/compose/src/tests.rs: tests only; kept both
Conflict: crates/engine/src/tests.rs: tests only; kept both
Conflict: crates/idml/src/tests.rs: tests only; kept both
2026-10-09 22:46:08 +00:00
aiongg d268d998a9 Merge #230: Preserve text decoration color resets, geometry and paint order
Conflict: crates/idml/src/import.rs: kept #230's Underline/StrikeThru colour line (Text Color resets) and #83's tint() helper
Conflict: ROADMAP.md: kept the chain's Text engine line and added #230's FID2 line
2026-10-09 22:43:44 +00:00
Skylar Taylor-Barrick a45375c335 FID2: preserve decoration color resets, geometry and paint order 2026-10-09 17:39:08 +00:00
skylar taylor-barrick 4e4eaad1c8 PERF-MEM: release retired layouts and trim edit-history capacity 2026-10-08 15:35:20 -06:00
Shao Wenbin 7c10b3ad41 IDML1: preserve anchored text frames and footnote artwork
Resolve forward story references before importing embedded frames and groups.
Compose embedded stories, retain anonymous-story objects for screen and PDF
rendering, and invalidate layout caches when embedded frame geometry changes.

Measure inline objects by geometry, fill open contours, reject cyclic frame
references, and distinguish unplaced stories from located overset text.

Add synthetic round-trip, composition, preflight, pixel-rendering, and PDF
regressions. Update the roadmap with the supported import and rendering paths.
2026-10-08 18:08:44 +08:00
aiongg 47d3f5b4bf Text: drop caps 2026-10-07 09:04:59 -04:00
Brandon ThomasandClaude Opus 5.5 82d6def80b Never crash: render and pdf
Rasters stay below vello_cpu's u16 limits (a 65535-pixel-wide render
panicked inside it), page renders larger than 2^29 pixels decline instead of
allocating, PNG encoding never panics, and the PDF heading tag can't fail.
Both crates deny the panicking lints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:43:33 -04:00
Brandon ThomasandClaude Opus 5.5 fe40d4b80b T: tate-chu-yoko in vertical frames
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:46:52 -04:00
Brandon ThomasandClaude Opus 5.5 55af964ea8 X: inline XML tagging and tag markers
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:48:58 -04:00
Brandon ThomasandClaude Opus 5.5 77fc006b02 T: vertical type — Vertical Type tool, upright CJK, CJK line breaking
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:18:14 -04:00
Brandon ThomasandClaude Opus 5.5 08495e45d0 T: graphic cells — images in table cells
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 04:30:47 -04:00
Brandon ThomasandClaude Opus 5.5 a4e76ed620 T: Track Changes — marked insertions and deletions, accept/reject all
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:57:22 -04:00
Brandon ThomasandClaude Opus 5.5 3b462285c5 T: editorial notes — anchored, flagged on screen, never printed; Notes panel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:18:21 -04:00
Brandon ThomasandClaude Opus 5.5 af0d241190 T: conditional text — conditions, hide/show in layout and exports, panel, IDML
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 23:02:15 -04:00