Commit Graph
84 Commits
Author SHA1 Message Date
aiongg bcad0182e5 Merge pr/698: Effects apply to frames with pasted-in content (Inner Glow, Basic Feather, shadows…) (#698) 2026-10-13 12:27:17 +00:00
moppeeerandClaude Opus 5.5 96e9f371ac fix(render): effects apply to frames with pasted-in content
A frame holding items pasted into it (Edit › Paste Into) was drawn by
its own branch in draw_item that skipped every effect: Basic Feather,
Inner Glow, shadows and the rest were stored but never drawn. LYH Studio
episode 14 (11:51) gives the outlined "GET MORE." title, with a photo
pasted into it, an Inner Glow and a Basic Feather.

Such a frame now goes the way of any other object: draw_body draws its
fill, its items clipped to the frame and its stroke, so draw_item_fx
wraps all of it. Its silhouette counts as solid for shadow spread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:02:57 +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 80803e3ae3 feat(parents): a parent can be based on another parent (#652)
Pages draw the base parent's items, then the parent's, then their own,
on the canvas, in PDF and image export, and on the parent spread itself.
Chains are followed base first with a depth bound. Overrides, parent hit
testing and the flattener's transparency check include base items.

layout.parents.options {parent, basedOn} sets or clears the base and
refuses cycles; document.inspect reports basedOn per parent. Opening a
.designcraft or IDML file clears a cyclic or dangling base with a warning.
IDML already carried the base as the parent pages' AppliedMaster; a test
covers the round trip.
2026-10-13 00:21:48 +00:00
aiongg 3f6f8453ca Merge #577: fix(render): A group's gradient feather fades the group, on screen and in PDF
Conflict: crates/doc/src/item.rs: both tests (next's rotated group bounds, the PR's feather bounds)
Conflict: ROADMAP.md: both progress lines
2026-10-12 18:29:39 +00:00
moppeeerandClaude Opus 5.5 1411c29fbf fix(render): A group's gradient feather masks everything the group draws
The DestIn mask now covers the whole render target (and the PDF's
exported area), with the gradient kept in item space through the paint
transform, since past the gradient's ends the end opacity carries on: a
child's long drop shadow or thick stroke is faded, not cut off at a box.
feather_bounds counts only shown children and active states, walking
nested groups without recursion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:01:25 +02:00
moppeeerandClaude Opus 5.5 1c4b6885c7 fix(render): A group's gradient feather fades the group, on screen and in PDF
A gradient feather on a group (Gradient Feather tool over grouped text, as
in LYH Studio's poster tutorial) did nothing on screen: groups never went
through the effects path. In PDF the group's mask was the group's own path
bounds, and a group has no path, so the mask covered a box of zero size and
hid the group.

- Item::feather_bounds: the box the feather spans, the objects' bounds for
  a group.
- Render: a feathered group draws its objects in one layer, then fades it
  with the same mask as other objects (fx::gradient_feather_mask).
- PDF: the group's mask spans its objects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 18:01:25 +02:00
aiongg dcabd61597 Merge #570: Blend modes don't blend with the paper
Conflict: crates/render/src/lib.rs: push_layer after next's back document-grid drawing and pop_layer before the front grid, so items never blend with the grid
Conflict: ROADMAP.md: kept both progress-log lines
2026-10-12 12:24:20 +00:00
moppeeerandClaude Opus 5.5 6cc6f92dad fix(render): Blend modes don't blend with the paper
The renderer painted the paper, then blended page items with it: black
text in Color Dodge over blank paper dodged the white to white and
vanished, and other modes showed the same way. In InDesign (and in PDF,
where the page group starts transparent) the paper is not a backdrop:
over blank paper an object in any blend mode shows as it is, and only
the objects below it are blended with. On spreads that use a blend mode
the items are now drawn in their own group, laid over the paper once;
other spreads render as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:33:52 +02:00
aiongg 6c1d5b75db Integration: Graphic and Asset literals in tests name the fields both sides added (#450 × next)
#450 adds Graphic.clip; next added Graphic.wrap and Asset.pdf_box/pdf_crop. #450's tests in doc and render lack next's fields and next's tools test lacks clip; the tests didn't compile.
2026-10-12 05:38:04 +00:00
aiongg af389f9a0b Merge clipping-paths: Images: clipping paths from IDML clip the image on screen and in PDF
Conflict: ROADMAP.md: both progress lines
Conflict: docs/gaps.md: next's header, clipping paths added to its change list
Conflict: docs/target-app-parity.md: next's header, clipping paths added to its change list
Conflict: crates/doc/src/item.rs: both Graphic methods (next's shown_bounds, the branch's clip_region)
Conflict: crates/idml/src/names.rs: both imports (ClippingKind, ContourType)
Conflict: crates/idml/src/export.rs: both pushes, TextWrapPreference first, then the clipping settings
Conflict: crates/idml/src/import.rs: both lets (next's wrap, the branch's clip)
Conflict: crates/engine/src/cmd/file.rs: next's size and transform, plus clip: None
Conflict: crates/engine/src/cmd/datamerge/fill.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/engine/src/cmd/mathexpr.rs: next's Graphic with wrap, plus clip: None
Conflict: crates/pdf/src/tests.rs: both tests
2026-10-12 05:37:19 +00:00
aiongg e111bb74f7 Images: declare a graphic's clipping path beside its transform 2026-10-12 05:32:11 +00:00
aiongg 8125f2490b Images: clipping paths from IDML clip the image on screen and in PDF
A placed graphic keeps its clipping path (Detect Edges, Alpha Channel,
Photoshop Path or User-Modified Path) in graphic space, with the
options IDML carries. IDML import reads ClippingPathSettings and its
saved ClippingPathGeometry (anchors capped, non-finite subpaths
dropped); export writes them back. The canvas and PDF export clip the
image to the path inside the frame clip; an inverted path shows the
image outside it. Without saved geometry nothing changes.

object.clippingPathOptions reports a graphic's clipping path, inverts
it or removes it (Object > Clipping Path > None).

Fixes #450.
2026-10-12 05:31:03 +00:00
seanth a21e8db14f fix(compose): key parent-page compositions by absolute page index
Parent-page text now uses the absolute page index as its cache key, so repeated page numbers and renumbering do not reuse another page's composition.

Validation: targeted canvas and PDF regressions, plus all six cargo xtask ci gates on main after #514 (1,179 native tests passed, 3 ignored; 15 crates passed WASM compilation checks).

Task: #523
2026-10-11 21:27:52 -06:00
aiongg d11ccad30a Merge #523: Parent-page items are composed for their page by index, not page name
Conflict: crates/pdf/src/export.rs: next's cycle guard (item -> item_guarded) and frame_objects (notes and cells) with the PR's page: Option<usize>; the PR's anchored-object loop dropped (frame_objects covers it)
Conflict: crates/render/src/lib.rs: stories keyed by (StoryId, Option<usize>) plus next's drawing_items; next's draw_item -> draw_item_inner guard with page: Option<usize>
2026-10-12 00:14:33 +00:00
japbcoelhoandClaude Opus 5.5 3dc0a1563a Place: PDF pages and SVGs keep their exact size (#511)
file.place sized a placed PDF page (and SVG) from its size rounded to
whole points, while the export draws the page into that size: a page of
91.774 x 613.276 pt became 92 x 613 and was drawn 0.25 % wider and
0.3 % shorter, so pieces laid edge to edge left gaps of up to 1.9 pt.
A 100.4 x 50.4 pt page placed 100.4 pt wide came out 50.2 pt tall:
`Rect { x0: 0.0, y0: 0.0, x1: 100.4, y1: 50.2 }`.

The graphic's natural size is now the exact page (crop box) or SVG size;
the rounded pixel size stays for display and preflight. place.load (the
place cursor) gets the same exact size.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:39:40 -06:00
aiongg 1a9499b465 Merge document-grid: View draws the document grid (#530)
ROADMAP.md: both progress-log lines
docs/target-app-parity.md: next's lines plus the document grid
2026-10-11 20:47:12 +00:00
aiongg 61504dae48 View: draw the document grid (#530)
Show/Hide Document Grid (View > Grids & Guides, Cmd+') draws the grid
on the positions Snap to Document Grid uses: subdivision and major
lines from each page's top-left corner, across the spread's band of the
pasteboard, behind or in front of page items as the grid settings say.
Dense lines are left out at low zoom. The Properties toggle runs the
command.
2026-10-11 20:46:38 +00:00
aiongg 5bc9594679 Merge #511: Place: PDF pages and SVGs keep their exact size
Conflict: crates/engine/src/cmd/file.rs: file_place keeps next's PdfCrop block (pdf_crop, Asset, crop_pdf, (nw, nh) fallback) with the PR's exact natural size for (nw, nh); the PR's crop_box match dropped; pdf_crop_tests keep both tests
2026-10-11 18:01:34 +00:00
Sean HammondandClaude Opus 5.5 f23fa33e16 Parent-page items are composed for their page by index, not page name
A running header (or any text variable) in a parent-page frame was empty or
wrong on pages whose page number repeats an earlier one: with two sections
that both number from 1 (front matter 1-10, body 1-...), body pages 1-10
showed nothing. The renderer and PDF export passed only the page's name to
the composition cache, which looked the page up as the first page with
that name - in the front matter, before any heading.

CompositionCache::get now takes the absolute page index (None for a story's
own frames) and derives the name for page-number markers from it; the cache
is keyed by index, so two pages with the same name no longer share a
composed header. The renderer and PDF export pass first_page_of_spread + i.

Test: running_header_on_parent_page_with_repeated_page_numbers (pages named
1, 2, 1, 2; the only heading on the third page). Before the fix the third
page's header was empty (0 glyphs); with it, "Epsilon" (7).

Found with an InDesign IDML (a dictionary with two sections and Match
Character Style running headers on its parent page), together with #301.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-11 16:56:41 +00:00
japbcoelhoandClaude Opus 5.5 72bdc0a919 Place: PDF pages and SVGs keep their exact size
file.place sized a placed PDF page (and SVG) from its size rounded to
whole points, while the export draws the page into that size: a page of
91.774 x 613.276 pt became 92 x 613 and was drawn 0.25 % wider and
0.3 % shorter, so pieces laid edge to edge left gaps of up to 1.9 pt.
A 100.4 x 50.4 pt page placed 100.4 pt wide came out 50.2 pt tall:
`Rect { x0: 0.0, y0: 0.0, x1: 100.4, y1: 50.2 }`.

The graphic's natural size is now the exact page (crop box) or SVG size;
the rounded pixel size stays for display and preflight. place.load (the
place cursor) gets the same exact size.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:52:46 -03:00
aiongg b77ed8c300 Merge fix/idml-interchange: PDF: Bounding Box crops are measured from the page's drawing, not a render 2026-10-11 05:18:36 +00:00
aiongg 0437ab27f5 PDF: Bounding Box crops are measured from the page's drawing, not a render
Finding a placed PDF's Bounding Box crop rendered its page at 2048
pixels and took the bounds of the painted pixels. PDF export measures
every such crop that import left unmeasured, so exporting a 17-page
document that places one complex illustration took about 4 s longer
than before crops were supported. The box is now the union of the
page's painted paths (strokes included), glyphs and images, each cut
to its clip, collected by interpreting the page without rasterizing
it: the same box, exact instead of to the nearest pixel, in
milliseconds.

Test: content_crop_box_is_measured_without_rendering_the_page.
2026-10-11 04:56:32 +00:00
aiongg 1e1b0bcce0 Merge #362 update: placed PDFs cropped to their bounding box render only when drawn or exported 2026-10-11 03:01:04 +00:00
aiongg 0f3f4e0634 Import: placed PDFs cropped to their bounding box render only when drawn or exported
IDML import found every Bounding Box crop by rendering the PDF page at
2048 pixels, once per placement, so opening a 17-page document that
places one Illustrator file 16 times took about 54 s of CPU. Import now
measures the box only for a graphic without GraphicBounds; drawing finds
it through designcraft_render::shown_box (cached per file, page and crop)
and PDF export through with_pdf_boxes.
2026-10-11 02:46:07 +00: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 d32134d337 Merge wrap-placed-graphic: text wrap set on a placed graphic wraps text
Conflicts: compose lib.rs imports (both; Composer unused), idml tests.rs (both test modules), ui-egui
panels/mod.rs (next's ruler origin with the branch's graphic wrap for Direct Selection).
2026-10-10 21:26:25 +00:00
aiongg 7f2b5cfa68 Layout: text wrap set on a placed graphic wraps text
A placed graphic carries its own text wrap (Graphic.wrap). IDML import
reads TextWrapPreference on the Image/PDF element and export writes it
back there. Layout treats it as an obstacle around the part of the
graphic its frame shows, plus the offsets, and also collects wraps of
items inside groups. object.textWrap acts on the graphic when the
selection is the frame's content, and the Text Wrap panel shows its wrap.
2026-10-10 21:23:00 +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 c3c24a0be8 Merge fix/idml-interchange: $ID/ font names, placed PDF page and crop, packaged IDML links (#338, #339, #340)
Conflict: ROADMAP.md: next's IDML interchange line with the branch's sentence on placed PDFs, $ID/ font names and packaged links appended; next's INDD line kept
Conflict: crates/engine/src/tests_idml.rs: next's drop cap test and the branch's two placed PDF tests
Conflict: crates/fonts/src/fontdb.rs: next's plausible/measured_top/typo_ascent and the branch's plain_family; next's canonical_family lookups, with plain_family in place of without_format_suffix in style_traits, face (two) and has_family; the branch's has_exact_family dropped (has_family uses canonical_family)
Conflict: crates/idml/src/import.rs: next's char_attrs (tab-split family and composite font names), falling back to names::font_family_in
2026-10-10 16:20:17 +00:00
aiongg 289c4a6aa4 IDML: a placed PDF keeps its crop choice
The placed PDF's asset stores its Crop to choice (crop, art, trim, bleed,
media, or the content bounds with visible or all layers) and where that
box sits on the page. The graphic spans the box: the screen, PDF export,
rasterised exports and clipping-path tracing draw only that part of the
page, so a larger frame doesn't reveal the bleed.

IDML export writes the choice as PDFCrop, with GraphicBounds spanning the
box. Import reads PDFCrop back; the engine finds the box on the PDF page
after import (and after relink or update) and maps it onto GraphicBounds,
or gives a graphic without bounds the box's size. Documents saved without
a choice load with the crop box. The Place PDF dialog offers the content
bounds too, and links.list reports the choice.
2026-10-10 15:52:40 +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
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
Brandon ThomasandClaude Opus 5.5 6e8e252cc4 Merge origin/main into feat/layout-parity
Resolve conflicts with the CJK/Arabic composition work: line metrics keep
the first-baseline tops and add the CJK alignment shift; the CJK line
reference adjustment applies before baseline-grid snapping; layout_line
takes the bidi info and the drop cap is spliced in after it; vertical text
is now the story's direction (FrameSpec::vertical); IDML insets use main's
validated inset_spacing over the object style chain; format's JSON upgrade
(vertical frames) and model upgrade (format v2 defaults) both run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 22:34:18 -04:00
Brandon Thomas aee756d604 Merge remote-tracking branch 'origin/main' into feat/cjk-and-fonts
# Conflicts:
#	crates/ui-egui/Cargo.toml
2026-10-07 22:04:42 -04:00
skylar taylor-barrick 4910e42bcc Render: keep outside-aligned strokes out of closed interiors 2026-10-07 16:10:24 -06:00
aiongg 6df33fa344 CJK 1.7: story direction belongs to the story
Vertical is a story property: every frame of a thread is vertical or
horizontal together, a frame threaded onto a story takes its direction,
and frames split off by threading or breaking a thread keep theirs.
Type > Story Direction is type.storyDirection {vertical} (checked in the
menu) for the stories of the text selection or selected frames;
object.textFrameOptions {vertical} and frame.create {vertical} set the
frames' stories. Documents that stored Vertical on the frame open with
each story taking its first frame's direction. IDML StoryOrientation is
read into and written from the story, beside its column direction
(StoryDirection), which stays a separate setting.
2026-10-07 10:44:46 -04:00
aiongg 572799b883 IDML: frames inherit their object style's corners and text frame options
A frame takes the corner options and Text Frame Options it doesn't write
from its applied object style, the styles that one is based on, then
[None]: IDML frames write only what differs from their style. Each
corner's shape and radius resolve separately, item first; at each level
a per-corner attribute wins over the legacy CornerOption / CornerRadius
pair, so a frame that writes one corner keeps the style's other corners,
and a shape without a radius takes the chain's (12 pt at the root). Fill
and stroke lookups end at [None] too.

Checked, unchanged: corner radii are drawn clamped to half the shorter
side as circular arcs, and inside-aligned strokes are the stroke of the
path inset by half the weight (screen and PDF); tests pin both.
2026-10-07 09:05:00 -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 8490965e71 CI: fix new Rust 1.99 clippy lints (as_chunks, ?, redundant ref)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 11:37:09 -04:00
Brandon ThomasandClaude Opus 5.5 558e798dfc E: CMYK transparency blend space on screen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 02:09:26 -04:00
Brandon ThomasandClaude Opus 5.5 b13135472a V: Rotate Spread per spread — canvas layout through per-spread transforms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 00:25:16 -04:00
Brandon ThomasandClaude Opus 5.5 7b636fb9f5 C: separations from source colours
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 23:54:54 -04:00
Brandon ThomasandClaude Opus 5.5 dc2114be26 I: PDF form fields — text, check box, combo, list, signature
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 10:48:58 -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 9ecd3fb9fd E: Inner Glow, Bevel and Emboss, Satin; soft effects kept in PDF export
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:50:43 -04:00
Brandon ThomasandClaude Opus 5.5 37f0cd99e1 P: Object Layer Options for placed PDFs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:38:23 -04:00