Commit Graph
4115 Commits
Author SHA1 Message Date
aiongg a386c73d82 Integration: PDF crop box is part of an asset's identity; clippings keep anchored graphics (#391)
Asset::same_file compares pdf_crop (#362) with type, page and bytes: one
PDF page shown with two crop boxes keeps two assets, so add_asset,
adopt_asset, insert_asset (IDML/INDD import) and the load/save merge
never point a placement at another crop. Test in
identical_files_share_one_asset.

The copy clipping (edit.rs clip_doc) keeps the assets used_assets finds
in it, so graphics anchored in copied text, graphic table cells (#379),
notes and media posters keep their files; it collected only graphics on
the copied items before. Test:
copied_text_frames_keep_the_graphics_anchored_in_their_text.
2026-10-10 23:44:15 +00:00
aiongg cfd57cb3ca Merge asset-dedupe: Assets: one asset per image, and saves leave out unused ones
Conflicts:
- crates/engine/src/cmd/{datamerge/fill.rs, file.rs, mathexpr.rs,
  media.rs, table.rs}: the branch's add_asset calls, with next's Asset
  fields (pdf_crop, pdf_box) through ..Default::default(); file.place
  keeps next's PdfCrop-aware asset (crop_pdf) and adds it with add_asset;
  the data merge graphic keeps next's wrap field.
- crates/format/src/lib.rs: load runs next's upgrade(doc, version), then
  merge_duplicate_assets (zip and legacy JSON); tests from both, with the
  round-trip test placing its asset (saves drop unused ones).
- crates/idml/src/import.rs graphic(): the branch's linked_assets reuse and
  insert_asset, with next's PDF page and PdfCrop on the asset and the
  graphic's text wrap (#389 integration) in the shared graphic closure.
  linked_assets is keyed by (path, page, crop), so placements of one PDF
  showing different pages or crop boxes keep separate assets.

Also, so the merge compiles: Asset/Graphic literals in the branch's tests
(crates/doc/src/assets.rs, format, engine links.rs) take next's pdf_crop,
pdf_box and wrap fields.
2026-10-10 23:41:30 +00:00
aiongg 27312c1cf9 Integration: feathers inherit from object styles; merge closes its functions (#390)
The merge of idml-feathers left overlay() in crates/idml/src/import.rs and
imports_and_round_trips_text_wrap_side_and_contour_type in tests.rs
without their closing braces (both sides added code at the same place);
this closes them.

Test: feathers_come_from_the_object_style_unless_its_category_turns_them_off
(gradient and directional feathers come from the style through
with_style; EnableGradientFeather="false" in
ObjectStyleObjectEffectsCategorySettings drops the style's gradient
feather; the item's own Applied="false" wins).
2026-10-10 23:38:21 +00:00
aiongg 15bcbae754 Merge idml-feathers: IDML: gradient and directional feathers import and export
Conflicts:
- crates/idml/src/import.rs, free functions: next's effect_flag/overlay
  (#389) and the branch's finite/gradient_feather_in both kept. The
  feather reads sit inside the TransparencySetting block, which next reads
  through with_style, so object style feathers are inherited and their
  EnableGradientFeather/EnableDirectionalFeather category flags apply.
- crates/idml/src/tests.rs: tests from both.
2026-10-10 23:37:23 +00:00
aiongg 75ce3f88e0 Integration: placed graphics take text wrap from their object style (#389)
A graphic element that names an AppliedObjectStyle reads its
TextWrapPreference through with_style, so mode, side, offsets and
ContourOption come from the style where the graphic sets none. Test:
text_wrap_side_and_contour_come_from_the_style_for_frames_and_placed_graphics.

Also: swatch_ref's doc comment sits on swatch_ref again.
2026-10-10 23:37:01 +00:00
aiongg 06baf887fb Merge idml-style-effects: IDML: frames take corners, effects and text wrap from their object style
Conflicts:
- crates/idml/src/import.rs, style chain helper: next's object_style_chain
  (trims BasedOn, ends with [None], cycle-safe) kept; the branch's
  style_chain dropped. with_style and attr_or_style use object_style_chain.
- import.rs corners: next's corner import over the object style chain kept
  (per-corner then all-corners at each level, InDesign default radius);
  the branch's second corner pass dropped, so corners are read once.
- import.rs text wrap: with_style(e, "TextWrapPreference") feeds next's
  shared text_wrap() reader (side, ContourOption, offsets) instead of the
  branch's inline TextWrap build.
- import.rs TextFramePreference: next's chain-aware text_frame_options
  with column rule colour kept; BaselineFrameGridOption read through
  with_style.
- crates/idml/src/tests.rs: test modules from both.
2026-10-10 23:37:01 +00:00
con3codeandClaude Opus 5.5 70cfd2d4fa UI: installed CJK faces are read once, without waiting for the font scan
- install_fonts no longer waits for the system font catalog on the first
  frame: the built-in faces are installed at once, the scan runs in the
  background, and the installed CJK faces are added when it is done.
- The installed faces are read once and shared: changing the interface
  language reorders them without copying the font files into egui again.
- fonts: line_metrics no longer sits between feature's doc comment and
  feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 08:30:39 +09:00
aiongg 6b3d7c4ba8 Integration: installed CJK UI faces are read once per process (#394)
install_fonts caches system_ui_fonts in a OnceLock; the face bytes are
leaked once and borrowed (Cow::Borrowed) by each FontData, so a language
change reorders the faces without re-reading font files or copying them.
SystemFont.bytes is a Cow<'static, [u8]>.

Also: line_metrics no longer sits between feature's doc comment and
feature (crates/fonts), and the canvas texture test's frame closure
drops an unneeded mut.
2026-10-10 23:30:09 +00:00
aiongg 2b96dffda1 PDF: right-to-left text extracts in reading order
Lines with right-to-left text are wrapped in a marked span whose
ActualText is the line's text in reading order, so viewers that order
text by position (Preview, PDFKit) no longer return it reversed.

A base letter and its marks (Hebrew points) map glyph by glyph when
each glyph stands for one of the cluster's characters, and are drawn in
text order, so ToUnicode-only extraction reads each letter once with
its points after it. Marks stay in their run, placed by glyph offsets
(compensated for skew), instead of starting runs of their own.
Kashidas inserted by justification carry no text.

Fixes #392.
2026-10-10 23:29:46 +00:00
aiongg fbfc4eecf9 Merge #394: CJK interface text from installed fonts, on the Latin baseline
Conflict: crates/ui-egui/src/theme.rs install_fonts: the PR's
system_ui_fonts/add_system_fallbacks replace next's #126 system-font loop
(fallback_for per CJK sample char into system-ui-{id} faces when
CRAFT_FONTS is empty), so installed faces are not loaded twice. No #126
helper in theme.rs is left unused; FontDb::fallback_for stays (compose,
images use it).
2026-10-10 23:29:09 +00:00
aiongg daf1a0ccb3 Assets: one asset per image, and saves leave out unused ones
Placing a file that the document already holds reuses its asset (same
bytes, type and PDF page), in file.place, place.load, media and poster
placement, graphic table cells, math, data merge image fields, IDML
import (a linked file is read once) and paste/duplicate between
documents (which also maps ids that collide in the target).

Saving merges identical assets into the first and leaves out assets
nothing references; the open document keeps them for undo. Opening a
file saved with one copy per placement shares the identical bytes as
they are read and merges the assets. Package and Copy Links write only
used files, and links.list lists only used assets.

Refs #391
2026-10-10 23:28:24 +00:00
aiongg ea69df5bff Integration: canvas edits patch only a full-resolution texture (#393)
patch_texture writes full-resolution dirty rects with set_partial; when
upload() has shrunk the canvas texture to max_texture_side (a viewport
wider than the GPU limit), the edit takes a full render instead of a
partial write past the texture's bounds (a wgpu validation error). Test:
edits_patch_the_canvas_only_at_full_resolution.

The content ghost proxy goes through widgets::fit_texture (capped at 4096)
instead of skipping graphics larger than the limit.
2026-10-10 23:28:09 +00:00
con3codeandClaude Opus 5.5 cc836fe4df UI: an edit repaints a sampled-down canvas texture whole
patch_texture computed the damaged region in view pixels and wrote it
with set_partial into the canvas texture upload() had sampled down to
the GPU limit: a viewport wider than max_texture_side put the region in
the wrong place or outside the texture (a wgpu validation error on the
first edit). Such a texture is now rendered whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 08:25:43 +09:00
aiongg 9ba7e9e8c7 Merge #393: no crash on textures past the GPU limit
No conflicts.
2026-10-10 23:25:09 +00:00
aiongg 62fe6eb23b Merge #396: the welcome screen's Discord button clears the text beside it
No conflicts.
2026-10-10 23:24:27 +00:00
aiongg 153fa60054 Merge #388: Show the Pencil stroke while drawing
Conflict: crates/ui-egui/src/canvas.rs draw_tool_overlays: the PR's
Overlay::Path arm (draw_path_overlay) and next's Overlay::ContentGhost
arm both kept; draw_path_overlay sits before draw_content_ghost.
2026-10-10 23:23:48 +00:00
aiongg 505e748aac IDML: gradient and directional feathers import and export
Import reads GradientFeatherSetting (Applied, Type, Angle, Length,
GradientStart, first and last OpacityGradientStop) and
DirectionalFeatherSetting (Applied and the four widths) on page items.
Export writes both when the effect is on.
2026-10-10 23:21:17 +00:00
aiongg 62df5e764a Merge file-open-os: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- apps/designcraft/Cargo.toml: the macOS target keeps objc2 0.6 / objc2-app-kit 0.3 (IME) and
  adds fmv-macos-events =0.1.0, which uses the same objc2 and objc2-app-kit versions.
- apps/designcraft/src/main.rs: the shared Arc<gpu::Startup>, catch_unwind and restart flow
  stay; the Apple-event handlers are installed before run_native (outside catch_unwind, held
  until the window ends), the app drains the inbox each frame, and documents that arrived are
  noted on the Startup. The open dialog keeps the INDD/INDT filter and ends with All files.
  The open-filter test checks both; the save-dialog test is added.
- apps/designcraft/src/gpu.rs: Startup::opening records the documents the OS handed this run;
  a start again after a graphics failure (both finish and the first-frame watch) passes them
  as file arguments, since Finder doesn't send them twice.
- apps/designcraft/src/apple_events.rs: poll returns the documents it handed the app, and
  queued() returns those still in the inbox when the window ends.
- crates/engine/src/cmd/file.rs: file.open decides by content first (IDML by its mimetype
  entry, then a DesignCraft package or JSON), then an InDesign file by its signature or
  .indd/.indt extension, then a .idml or .designcraft extension for the reader's own error;
  anything else is "not a DesignCraft or IDML file". INDD stays in file.openBytes. Save
  commands add .designcraft as on file-open-os; the params docs describe all of it.
- crates/engine/src/cmd/mod.rs: re-exports from both sides (is_document,
  with_document_extension and layout::chapter_numbering).
2026-10-10 23:21:12 +00:00
aiongg 5d872fbf1e IDML: frames take corners, effects and text wrap from their object style
A page item takes every value it lacks from its applied object style and
the styles that one is based on, attribute by attribute: corner shapes and
radii, TransparencySetting (blending, drop shadow, feather), text wrap,
text frame options, the baseline frame grid and frame fitting. Nested
elements merge level by level. Effects whose category the applied style
turns off come from the item alone.

Export writes the column rule attributes on every frame, so a frame keeps
its own rule settings over those of its object style on import.

Fixes #389.
2026-10-10 23:21:08 +00:00
aiongg 279309ad12 PDF: small caps and substituted figures keep their text
Generated glyphs (page numbers, section markers, list labels) carry the
text they were shaped from, so a figure the font substitutes under small
caps or other OpenType features extracts as the digit instead of U+FFFD.
2026-10-10 23:19:21 +00:00
aiongg 351ccebe86 CJK: smaller runs align to the line's em box centre
Character alignment (em box top, centre, bottom) measured each glyph's em
box from its font's ascender/descender proportions, which puts a Latin
font's em box centre too low, so a smaller CJK run in a Latin line sat
below the line's em box centre. Alignment now uses the font's BASE em box,
else 0.88 em above and 0.12 em below the baseline.
2026-10-10 23:17:09 +00:00
con3codeandClaude Opus 5.5 6252dc3737 UI: the welcome screen's Discord button clears the text beside it
The community card is wider and its text wraps before the button's
space, so a longer translation of the line under the title no longer
runs under the button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 07:49:49 +09:00
aiongg bc357c3885 Integration: German entry for Kanji 2026-10-10 22:47:23 +00:00
con3codeandClaude Opus 5.5 a62188aa9b UI: CJK interface text from installed fonts, on the Latin baseline
Without the craft-fonts build input the interface had no CJK glyphs:
egui can't find system fonts, so Japanese and Chinese menus and labels
drew as boxes, and Korean and Traditional Chinese font names in the
font menus did in every build.

- The first installed UI face of each script (Japanese, Simplified
  Chinese, Traditional Chinese, Korean; macOS, Windows and Linux
  families) is added as a fallback for the scripts craft-fonts doesn't
  cover.
- A fallback face sits on the UI face's baseline: egui centres a
  fallback's line height in the primary's, so a CJK face with a taller
  ascent or a line gap sat above or below the Latin text beside it.
  The tweak is computed from the two faces' line metrics.
- The interface language's script comes first, before the built-in CJK
  faces, so Han characters take that language's forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 07:47:14 +09:00
aiongg 47736dfb5b Merge swatch-layer-rename: Swatch Options and Layer Options dialogs rename them
Conflict resolutions:
- crates/engine/src/cmd/layout.rs: next's start_number, chapter_numbering and
  layer.move validation kept; the branch's set_layer added after them;
  spread_rotation_input_tests and layer_options_tests both kept.
- crates/engine/src/cmd/style.rs: next's delete_tests and the branch's
  swatch_options_tests kept as separate modules.
- crates/ui-egui/src/dialogs.rs: both module declarations (style_cjk,
  options) and both confirm arms (deleteCharacterStyle, swatchOptions /
  colorGroupOptions / layerSettings) kept.
- crates/ui-egui/src/i18n.rs, i18n/{ar,it,ja,pt_br,uk}.rs: both sides' rows
  appended; no key was duplicated.
- crates/ui-egui/src/panels/layers.rs: #378's drag-and-drop row
  (Sense::click_and_drag, deferred cmds, payloads) kept; the branch's
  accessibility name, double-click -> app.layerOptionsDialog (after the
  eye/lock/triangle/selection-square branches, before activate) and the
  Layer Options for "<name>"... context entry added through cmds.
- crates/ui-egui/src/panels/swatches.rs: next's apply_swatch kept, with the
  branch's single-click-only guard so a double-click opens Swatch Options.

Integration:
- Document::rename_swatch_refs also renames table cell stroke gap colours
  (CellStroke and CellStrokeAttrs gap_color), kenten and ruby fill/stroke
  colours (CharAttrs kenten_fill, kenten_stroke, ruby_fill, ruby_stroke)
  and paragraph border gap colours (ParaAttrs border_gap_color).
- The swatch rename test covers a table's cell gap colour, kenten fill,
  ruby stroke and border gap colour.
- The Layer Options double-click test checks that the double-click does
  not reorder layers.
- German rows for the 29 new shared keys in i18n/de.rs.
2026-10-10 22:46:57 +00:00
con3codeandClaude Opus 5.5 bf173db1de UI: no crash on textures past the GPU limit
The Glyphs panel drew its whole grid as one texture: up to 1200
characters of a large CJK font in a narrow panel (7 columns x 60 px)
made it 10320 px tall and wgpu panicked past the 8192 px limit. The
grid now stops at the rows the largest texture holds, and every UI
texture goes through widgets::fit_texture / load_texture, which
sample an oversized image down instead of panicking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013phQ3ekq6uGBEWD6FjFYG2
2026-10-11 07:46:07 +09:00
aiongg 11532d961e Documents: Save adds .designcraft, and the OS opens DesignCraft and IDML files
- file.save / file.saveAs / file.saveACopy add the .designcraft extension to a
  given path that lacks it and report the path written; saving back to the
  document's own file keeps its name.
- file.open tells DesignCraft and IDML packages apart by content, so files
  without an extension open; anything else is "not a DesignCraft or IDML file".
- Dropped or web-picked files without an extension open as documents when their
  content is one.
- Desktop save dialogs filter on the suggested extension and add it to a name
  typed without it, asking before replacing an existing file; the open dialog
  gains "All files".
- macOS: open-documents Apple events (Finder, Dock, Open With) through
  fmv-macos-events, queued until the window is ready; Info.plist declares the
  .designcraft type and an imported .idml type with CFBundleDocumentTypes.
- Windows MSI: .designcraft ProgId with an Open verb, IDML under Open with.
- Linux: shared-mime-info for both types and MimeType in the .desktop file.

Fixes #158, #211, #375
2026-10-10 22:40:06 +00:00
aiongg bf8c3486f1 Swatches and layers: Swatch Options and Layer Options dialogs rename them
- swatch.options: rename, Name with Color Value, process/spot, colour mode
  and values, tint, in one undo step; special swatches keep their names,
  and a taken or empty name changes nothing.
- Document::rename_swatch_refs points every use at a renamed swatch:
  tints, colour groups, inks, paragraph/character/object/cell/table
  styles, item fills, strokes, gaps, effects and column rules, text
  colours, rules, shading, borders, table cells and the footnote rule.
- layer.set takes every Layer field (show guides, suppress wrap when
  hidden), a layer colour name, and rejects empty or duplicate names and
  unknown colours.
- Swatches panel: double-click or Swatch Options… opens the dialog;
  colour groups get Color Group Options…. Layers panel: double-click or
  Layer Options for "name"… opens Layer Options (app.layerOptionsDialog).
2026-10-10 22:37:47 +00:00
aiongg bafd86d65e Merge content-move: move a placed graphic inside its frame
Direct Selection selects and drags a frame's content, the Selection tool
drags it by the content grabber, and double-clicking a frame with content
toggles between frame and content, alongside #84's text threading and
canvas click counting.

Conflicts:
- crates/tools/src/select.rs: module doc keeps both paragraphs (ports,
  content). The frame/content toggle runs on the second press of a double
  click (Down with clicks 2, or a DoubleClick from the control channel),
  Selection tool only, after threading ports and the text-frame
  double-click, so those keep priority; the DoubleClick arm stays empty.
  Overlays keep the Drag::Place marquee and add the content ghost.
- crates/tools/src/tests.rs: both sets of tests and fixtures kept; the
  graphic fixture sets Graphic::wrap; the double-click toggle test drives
  the click-count sequence and the DoubleClick form.
- crates/engine/src/tests.rs: both sets of tests kept.
- crates/engine/src/cmd/object.rs: both test modules kept.
2026-10-10 22:37:06 +00:00
aiongg 971cad3c0f Tools: move a placed graphic inside its frame
Direct Selection selects and drags a frame's content; the Selection tool
drags it by the content grabber (hand cursor, shown on hover too) and
double-clicking a frame toggles between frame and content. Arrow keys
nudge a content selection. transform.move takes content?: bool (default:
the selection's content flag) and moves the placed graphic or pasted-in
items in the frame's inner space; nested items move in their container's
space. A content drag shows the whole graphic faintly with its bounds.
2026-10-10 22:21:06 +00:00
moppeeerandClaude Opus 5.5 f6e96d2106 fix(ui): Show the Pencil stroke while drawing
The Pencil tool's stroke only appeared once the mouse button was
released. The tool reported the stroke so far as a path overlay, but the
canvas skipped path overlays (they were never drawn), so nothing showed
while drawing. The canvas now draws them: curves flattened to about a
quarter of a screen pixel, dashed when asked. That also brings back the
Smooth and Erase tools' dashed drag previews.

The Pencil's preview was black, lost on a dark pasteboard; it is drawn in
the colour of the layer the path goes on, as InDesign does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:18:59 +02:00
aiongg f359098e8e Merge #374: Russian interface
Conflict: ui-egui i18n.rs tr_context (#374's match with next's Japanese context arm).
2026-10-10 22:12:52 +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 17c7bb3fd2 Merge contour-wrap: wrap both sides of an object and around its contour
Combines contour-wrap (wrap side, contour type, multi-segment lines) with
the placed-graphic and nested wraps already on next.

- crates/compose/src/lib.rs: exclusion() and the new graphic_exclusion()
  share wrap_exclusion() (offsets, spine sides resolved per page, contour).
  wrap_obstacles() builds every obstacle through them: the item's own wrap,
  the graphic's wrap around its shown bounds, and children of groups and
  pasted-into frames with their own transform (depth <= 64). Contour type
  Bounding Box follows the shown part of the graphic, other types the frame
  path. wrap_slot() returns all free segments (free_slots) and steps 1 pt
  at a time beside a contour; Jump Object stays in wrap_slot only. The line
  loop keeps grid gyoudori, Jump Object leading steps and grid snapping for
  a line's first segment, and carries the other segments (with the line's
  grid end) to the next lines; estimate_slots() lists every segment through
  wrap_slot(). hit_line() breaks ties between segments by x, used by hit()
  next to the drop-cap lookup.
- crates/compose/src/tests.rs: both sides' tests; the hostile-geometry
  test builds Exclusion with side and contour.
- crates/engine/src/cmd/object.rs: object.textWrap is text_wrap() with
  mode/side/contour (unknown values are errors) and content?, applied to
  the frame's or the graphic's wrap.
- crates/idml/src/import.rs, export.rs: ContourOption/@ContourType is read
  in the shared text_wrap() and written by text_wrap_pref() as a child of
  TextWrapPreference after Properties, so graphic wraps carry it too.
- crates/idml/src/tests.rs: both sides' tests. tests/wrap.rs gains a test
  of a contour, right-side wrap on a placed graphic shown in part.
- crates/ui-egui/src/panels/mod.rs: the Text Wrap panel's side and contour
  combos read the targeted wrap (tw), the graphic's when content is selected.
2026-10-10 21:39:26 +00:00
aiongg fbfd0b2fe9 Merge gpu-startup: recovered documents survive restarts, the watchdog only restarts a hung adapter, software adapters last, the skip list as an argument
Conflicts: designcraft main.rs ui() (#366's macOS marked-text discard, then the startup watch's leave);
engine recovery.rs (the branch's rewrite-then-discard with next's bump_revision).
2026-10-10 21:28:17 +00:00
aiongg fbda023d7f Layout: wrap both sides of an object and around its contour
Lines beside an object that lets text through on both sides are set in
segments, left then right, and the paragraph runs on from one to the
next. One-sided wraps (left, right, spine sides, largest area) block the
other side. A contour wrap follows the object's outline band by band,
with the offset around it; graphic frames follow their contour type
(bounding box of the graphic, frame path, same as clipping; detected
edges, alpha channel and image paths follow the frame path). The Text
Wrap panel sets the wrap side and contour type through object.textWrap,
and IDML import and export keep ContourOption.
2026-10-10 21:28:03 +00:00
aiongg aa743eee88 Merge kanji-page-numbers: Kanji page numbers
Conflicts: engine layout.rs and notes.rs params docs (next's lists plus kanji).
2026-10-10 21:27:19 +00:00
aiongg 158f366af8 Startup: restarts keep recovered documents, a busy UI never counts as a GPU hang, software renderers last
- Recovery: file.recovery.open moves each entry to the reopened document's uid
  (written before the old one is removed), so recovered documents stay on disk
  until saved or closed, also when the app restarts on another adapter or
  stops before its first recovery save.
- First-frame watch: a late first frame is logged and noted in the status bar.
  The adapter counts as hanging only when the UI thread has been stuck outside
  the app's code (logic/ui mark entry and exit) with no progress for 60 s while
  the window isn't known to be minimized, occluded or unfocused. The watch asks
  for a repaint on each look, so an idle UI ends it. VectorCraft restarts on
  the late frame alone, which a held-back compositor or a long compose can set
  off.
- Software renderers (WARP, llvmpipe) are passed over while another backend is
  left; once no hardware adapter of any backend could show the window, the app
  starts again with them allowed, once.
- A restart clears gpu.json's backend entry just before exec or spawn and puts
  it back when starting again fails. Backends a restart left out are no longer
  recorded as failed.
- The watch ends a hung process with TerminateProcess on Windows.
- The skip list travels as the restarted app's --gpu-skip= argument instead of
  an environment variable, so processes the app starts don't inherit it.
2026-10-10 21:26:51 +00:00
aiongg ee529362d6 Merge rtl-master-sides: right-to-left binding puts parent page items on the right side
Conflict: idml tests.rs test modules (both).
2026-10-10 21:26:39 +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 cdd32a5066 Layout: right-to-left binding puts parent page items on the right side
A right-to-left bound spread lists its right page first, parent spreads
included. The parent page shown behind a document page was picked by list
index (first = left), so every page of an imported right-to-left document
showed the other side's parent items. Pick it by position instead: the
leftmost parent page for a left page, the rightmost for a right page.

New parent spreads in a right-to-left document list their pages right
first, and changing the binding reorders existing parent spreads, so IDML
export writes them in binding order. Spread bleed on the canvas and in
spread PDF export takes the outer edges by position as well.
2026-10-10 21:24:02 +00:00
aiongg 720165a709 Merge layers-drag: group members move with their group, no-op drops record nothing, locked layers need Cmd 2026-10-10 21:23:20 +00:00
aiongg c28387eaf9 Sections: Kanji page numbers
Kanji numbering style (一, 二, 三, …, written digit by digit: 10 is 一〇)
for sections, footnotes, endnotes and lists. IDML PageNumberStyle and
FootnoteNumberingStyle read and write Kanji and the leading-zero styles;
the list format "一, 二, 三, 四..." maps to it.
2026-10-10 21:23:01 +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 b84a58391f Layers: group members move with their group; no-op reorders leave history alone; Cmd for locked layers
- object.setLayer moves a group member's top-level group (and its members)
  and rejects an unknown or missing layer; object.reorder sets the layer on
  moved groups' members too.
- object.reorder returns without editing when the order and layers would
  not change, so no undo step is recorded.
- Layers panel: locked and hidden layers take object and selection drops
  only with Cmd/Ctrl held; the selection square drops the selected
  members' top-level groups.
2026-10-10 21:22:57 +00:00
aiongg c403cae4c7 Merge #83 update (feat/layout-parity f386a98d): Em Box Height first baseline offset
Conflicts: fonts fontdb.rs (the branch's em box with its from-BASE flag beside next's ICF cache); idml
tests.rs (the branch's copies of tests already on next, and the mojikumi test #129 replaced, dropped;
the Em Box Height test kept). German entry for Em Box Height.
2026-10-10 21:16:13 +00:00
aiongg f386a98d7c Layout: Em Box Height first baseline offset
The first baseline sits the em box top below the frame's top inset: the
font's BASE em box (idtp, else ideo + 1 em), else 0.88 em, for the
largest size on the first line. IDML FirstBaselineOffset="EmboxHeight"
imports and exports as itself; the footnote options list it and the
frame and footnote option commands take emboxHeight.
2026-10-10 21:04:33 +00:00
skylar taylor-barrick c62866443c UI.9: integrate shared panel docking and preserve workspace behavior 2026-10-10 14:42:40 -06:00
aiongg 754d09ac2d Merge table-cell-strokes: the style test names the [Black] swatch 2026-10-10 20:38:00 +00:00