Commit Graph
4827 Commits
Author SHA1 Message Date
aiongg 4fb32bdb1b Merge #567: Swatches: Load Swatches refuses .ase files over 100,000 colors and merges in linear time
Conflict: ROADMAP.md: kept both progress-log lines (the PR's 2026-10-12 line above next's 2026-10-11 lines)
2026-10-12 12:24:00 +00:00
aiongg fbf0fabd1a Merge #565: fix(compose): enable Norwegian hyphenation patterns
Conflict: docs/typography-parity.md: kept next's Justification row (#531 note), took the PR's Hyphenation row (Portuguese and Norwegian)
2026-10-12 12:23:42 +00:00
aiongg 40e7b1fed7 Merge upstream main 33842244 2026-10-12 12:23:26 +00:00
aiongg 4c296c05bf Groups: a rotated group's bounds follow its children's outlines 2026-10-12 12:04:24 +00:00
aiongg f1a1f318ba IDML: tracked deleted text takes no room in the layout (#581) 2026-10-12 12:00:42 +00:00
aiongg 7ddd99ee4c Text wrap: words stay whole beside wrap objects; a segment too narrow for its word stays empty 2026-10-12 11:58:08 +00:00
Marco Arnulfo 4acd5ba43c feat(desktop): portable mode, portable.txt next to the executable (#574)
A file named portable.txt beside the executable keeps settings, logs,
the GPU start-up record and crash recovery in a DesignCraftData folder
next to it instead of the per-user folders, as PhotoCraft and FilmCraft
do. A folder of that name is not a marker. DESIGNCRAFT_CONFIG_DIR still
wins, empty or not. The Windows portable zip now ships portable.txt;
the MSI and the macOS app bundle don't, so nothing changes there.
2026-10-12 13:45:05 +02:00
Brandon ThomasandClaude Opus 5.5 3384224419 Release prep: 0.7.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 05:44:24 -04:00
moppeeerandClaude Opus 5.5 fbb238af88 feat(ui): Effects panel shows the level, Isolate Blending and Knockout Group
The Effects panel had the blend mode, opacity and Drop Shadow only. Like InDesign's it now names the level the settings apply to with its blend mode and opacity (Group: Color Dodge 100%, Object: Normal 100%), and has Isolate Blending and Knockout Group, enabled for a selected group, through object.transparencyGroup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:03:34 +02:00
moppeeerandClaude Opus 5.5 cff0e8b2f8 feat(ui): Step and Repeat dialog with Create as a grid and Preview
Edit > Step and Repeat opened the generic command form (Count 1..1000, Dx, Dy, Rows, Columns, Ids). It now has InDesign's dialog: Repeat (Count, or Rows and Columns when Create as a grid is on), Offset (Vertical, Horizontal, in the document's ruler units or any unit typed) and Preview, which shows the copies on the page while the dialog is open. Cancel, Esc and ui.dialog.cancel take the preview away; OK makes the copies once, as one undo step. A value that isn't a number keeps the dialog open and says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:58:17 +02:00
noah1400 d298555e43 Styles: Load Styles from another document or IDML file (#405)
Styles could not be taken from one document into another; a client's
styles had to be rebuilt in every new file. The Paragraph Styles and
Character Styles panels now have Load Paragraph Styles, Load Character
Styles and Load All Text Styles: pick a .designcraft or .idml file,
tick the styles to load and choose what happens to one the document
defines differently (redefine, keep, or load under a new name). The
styles, swatches and lists a loaded style names come along.
`style.load` does it in one undo step.
2026-10-12 10:41:37 +02: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
moppeeerandClaude Opus 5.5 6d4d013156 fix(ui): Bounded font previews; a family missing from the read is read once
- Previews were kept in the context's data for each sample text, size
  and colour, and never let go: every new text selection loaded textures
  for every visible row. They now live in a cache of at most 256 that
  drops the least recently used.
- A family the background read didn't list (installed since) was read
  from disk on the UI thread every frame and never kept; it is now read
  once and stored with the others.
- Return and Escape in a dialog's font menu are covered by #447's test
  on main; this branch's duplicate is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:03:50 +02:00
moppeeerandClaude Opus 5.5 b2d8d3af7b fix(ui): The font menu's filters don't keep every font loaded
The class, Recently Added and Show Similar Fonts filters read every
family's kind through face(), which loads the family and keeps its bytes
for the session (with no size cap). They use family_kind now, which
reads each file and lets it go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:56:51 +02:00
moppeeerandClaude Opus 5.5 5116b6ade6 fix(ui): The font menu keeps its keys, filters off the UI thread, uses theme colours
- Return, Escape and the arrows are consumed by the menu (Escape closes
  it), so a dialog under the menu never confirms or cancels (#447).
- The class and Recently Added filters and Show Similar Fonts read every
  family's kind on a background thread, once per document font scope;
  the menu says Loading fonts... meanwhile. The web reads its few bundled
  fonts inline.
- The colour font badge uses theme tokens instead of fixed colours.
- Every font field, the hand-drawn ones included, reports as a combo box
  with the current family as its value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:56:51 +02:00
moppeeerandClaude Opus 5.5 40ba9915c9 test: The recently used font is the topmost row, whatever fonts are installed
With few fonts installed the font's own row in the main list is on screen
too, so the label matched twice (FreeBSD CI).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:55:02 +02:00
moppeeerandClaude Opus 5.5 8b0bc54bdb feat(ui): Lay out the font menu like InDesign's
The font menu (Control bar, Character panel, Properties, task bar, style
dialogs, Find Font, Glyphs) now has InDesign's layout:

- a search field with a clear button, and a Filters row: classification
  (All Classes, serif, slab serif, sans serif, script, monospaced,
  decorative, symbol), Show Favorites Only, Show Recently Added (fonts
  added in the last 30 days);
- the sample text (Selected Text, Sample, Aa Bb Cc, a pangram, digits)
  in three sizes, remembered with the UI state;
- Recently Used fonts at the top, then every family;
- a family of several styles shows how many and opens to list them, each
  previewed in its own face; picking a style applies it as it is;
- each family's format after its sample, and Show Similar Fonts (≈) to
  list the families of the same class nearest by PANOSE.

Find More and the activated-fonts filter are Adobe Fonts services and
are left out. The font combo is a field and a plain popup now: egui's
combo box kept its menu at the size it last had, so a family opening or
a filter clearing didn't show. The ≈ button stays while the pointer is
on it (the row under it no longer counts as hovered there). New strings
are translated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:55:02 +02:00
dotCooCoo 45f2a2090b Contributors: add dotCooCoo (Robert Lee) 2026-10-12 00:53:01 -07:00
moppeeerandClaude Opus 5.5 84a919d57a feat(engine): Report the selected text; remember recently used fonts
type.selectionAttrs reports the start of the selected text on one line
(`selectedText`, up to 60 characters), for previews such as the font
menu's Selected Text sample. The preferences keep `recentFonts`, the
fonts last picked in the font menus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:43:22 +02:00
moppeeerandClaude Opus 5.5 cff8a9aa07 feat(fonts): A family's kind without keeping it loaded
ScopedFonts::family_kind reads a family's format, class, PANOSE and file
date from its Regular face. A loaded face is asked; an installed family
that isn't loaded is read from its file, which is let go afterwards,
instead of loading every family the font menu's filters look at for the
rest of the session. Files over MAX_DOCUMENT_FONT_BYTES are skipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:43:21 +02:00
moppeeerandClaude Opus 5.5 eb80809034 feat(fonts): Font format, classification and PANOSE for the font menus
Each face reports what InDesign's font menu shows and filters by, read
once from its tables: its format (TrueType, OpenType/CFF, variable,
colour), its class (serif, slab serif, sans serif, script, monospaced,
decorative, symbol) from PANOSE, else the OS/2 IBM family class, else
the family name, else the shape of its "l" (serifs widen the foot), its
PANOSE numbers (with a distance for similar fonts) and when its file
was added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:43:21 +02:00
moppeeerandClaude Opus 5.5 be4f60ff85 fix: A selected graphic's Transform values are the graphic's
With a placed graphic selected, transform.set acted on the graphic but
the Control bar and Properties still read the frame's values: after
setting the image of a -45° frame to 0°, a spinner step sent -44, and a
200 pt frame holding a 400 pt image showed W 200, so 201 halved the
image. transform.info now reports the graphic's values at the top level
in that case, and sel_info gives the graphic's bounds.

Also: `id` names the target explicitly, like `ids`, so it isn't taken
as a content selection; and a content selection of locked frames
records no empty undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:42:06 +02:00
moppeeerandClaude Opus 5.5 2f21b005f3 fix(engine): Transform values act on a selected placed graphic, not its frame
With a frame's content selected (Direct Selection, or selection.set
{content: true}), transform.set still rotated, scaled and moved the
frame. It now transforms the placed graphic inside the frame and leaves
the frame alone, as InDesign's Transform panel does: rotation, shear and
scale are absolute and measured as transform.info reports the content;
x, y, width and height are the graphic's bounds on the page. A
`content` parameter picks either explicitly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:36:32 +02:00
Akash Bhat c310cf72ed fix(compose): enable Norwegian hyphenation patterns
Enable hypher/norwegian in designcraft-compose so no/nb/nn map to the bundled hyph-no.tex patterns. Add unit + composition regression tests, NOTICE attribution, and update parity docs.

Fixes #535.
2026-10-12 12:09:40 +05:30
dotCooCoo 591cb579b3 Swatches: Load Swatches refuses .ase files over 100,000 colors and merges in linear time
ase::read accepted any number of colors in a swatch exchange file, and
swatch.load looked up each incoming name with a linear search through
the document's swatches. The time to load a file grew with the square of
its number of colors: in a test build, 50,000 colors used 2.5 s of CPU
and 100,000 used 9.6 s. No limit applied to larger files.

ase::read now returns an error for a file with more than 100,000 colors
(ase::MAX_COLORS). load_swatches looks up each name's first swatch in a
HashMap, and each color added from the file is indexed too. A file of
100,000 colors now loads in under 0.1 s. The merge follows the same
rules as before: names already in the document are kept unless replace
is set, a name repeated in the file matches its first copy, and a
locked swatch is left unchanged.
2026-10-11 23:18:46 -07:00
Sean HammondandClaude Opus 5.5 0635dea065 Text wrap: small single-character frames (tombstones) are set beside text wrap
An article's closing tombstone - a one-character text frame, here a 7.6 pt
Noto Emoji symbol in a 10.5 pt box - was overset (red +, nothing drawn)
whenever its spread had a text-wrap object anywhere, while InDesign sets it.

Two causes in the wrap code:
- Every text frame took every wrap object on its spread as an exclusion,
  whether or not the object reached the frame. Now only objects that
  overlap the frame wrap its text.
- free_slot skips gaps narrower than 1.5 em of the paragraph's size
  (slivers beside a wrapped object), but applied that to the whole column
  too when nothing cut it: with a 14 pt paragraph style, no line could go
  in a frame narrower than 21 pt. The minimum now only applies to gaps a
  wrap object cut; a band no object reaches gets the whole column, however
  narrow.

On the document that showed it (an InDesign 2026 IDML with 12 tombstones and
two unrelated wrap objects on the spread), the 12 tombstone stories are set
and no other story's layout changes (54 -> 42 overset stories, all of the
difference the tombstones).

Tests: small_single_character_frame_sets_beside_unrelated_text_wrap and
narrow_frame_sets_lines_no_wrap_object_reaches fail before (overset) and
pass after.

Probably also #476 (Wingdings bullets overset in 20 x 21 pt frames).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CgGsJkVe5GYG9qK6VFdECa
2026-10-12 06:16:04 +00:00
Fabio LussiandClaude Opus 5.5 fb90a508b1 docs: drop the docs workflows until the checker is on artcraft-services main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:09:21 +02:00
MarekSpodBiedry 2c2e3d73c7 Drop the arithmetic-only app bar test; the test window test covers it (#481) 2026-10-12 08:06:16 +02:00
MarekSpodBiedry f8f8eb619f Test the app bar scroll bar at 115% scale in the test window (#481) 2026-10-12 08:05:01 +02:00
MarekSpodBiedry 11b9a0f104 Export explicit SameParaStyleSpacing Ignore so it round-trips (#449) 2026-10-12 08:04:46 +02:00
aiongg e5c5f043ee Integration: format 2026-10-12 05:56:41 +00:00
aiongg 5f6ee139da Integration: anchored objects wrap through next's wrap_obstacles (#557 × next's wrap sides and contours)
#557 built an anchored object's exclusion as its offset bounding box; next's Exclusion also has a side and a contour, built by wrap_obstacles for page items. wrap::obstacles keeps the object and its parent → spread transform, and wrap::exclusions runs wrap_obstacles with it, so anchored objects get sides (spine sides by page), contours (Same As Clipping from the clip), custom boundaries, Jump Object and the wrap of a graphic inside, each tagged with the anchor byte (after). Without this the merge doesn't compile. Test: anchored_object_wraps_with_its_contour_and_side.
2026-10-12 05:56:35 +00:00
aiongg 9a95cee172 Merge anchored-wrap: Text wrap: anchored objects wrap text (#557)
Conflict: ROADMAP.md: both progress lines
Conflict: docs/target-app-parity.md: next's header, anchored objects wrapping text added to its change list
Conflict: docs/typography-parity.md: next's header (dated 2026-10-12) with #557's change added; Text wrap row: next's text and caveat, plus "on page items and anchored objects"
Conflict: crates/compose/src/lib.rs: next's Exclusion (side, contour) with #557's after field (Exclusion::wraps_line; next's constructors and the tests' helper set None); next's contour_path and wrap_obstacles in frame_specs; next's line loop (carry, grid, wrap_slot) with the line's start byte passed to wrap_slot; wrap_slot, free_slots and estimate_slots take line_start and skip exclusions that don't wrap the line (Jump Object test and in-band edges included); #557's re-break trigger stays in the line loop
Conflict: crates/compose/src/cache.rs: next's ComposeOptions (section_marker) with #557's neighbour compositions
Conflict: crates/compose/src/tests.rs: both test blocks
Conflict: crates/render/src/damage.rs: #557's test before next's patched_diff helper
2026-10-12 05:54:58 +00:00
aiongg 6e9517b053 Text wrap: anchored objects wrap text (#557)
An anchored object with text wrap pushes aside the text of other stories
whose frames it overlaps on its spread, and the lines of its own story
after the line holding its anchor (custom and above-line objects). Its own
story is composed again with the objects where the previous pass placed
them until they settle, at most four more times. The composition cache
keys a story wrapped by other stories' anchored objects on their
signatures, and canvas damage repaints the stories they wrap.
2026-10-12 05:51:56 +00:00
aiongg 62520485e8 Integration: format 2026-10-12 05:48:15 +00:00
aiongg b4e2e553e5 Merge #408: Align panel: add objdistribute-* icons, reuse upstream objalign-*
Conflict: crates/ui-egui/src/icons.rs: test module keeps both (next's arrow fill test, #408's distribute icon test)
2026-10-12 05:48:01 +00:00
aiongg 5284bca791 Integration: the Color Picker from the Fill/Stroke box follows Formatting Affects Text (#552 × next's color_target)
#552 opens the picker without the "text" field and reads the selected text's colour itself. On next the proxy's swatches already come from color_target, which follows colors_affect_text (selected text, or text frames with Formatting Affects Text on), so the special case goes and the dialog gets "text": colors_affect_text(app) as the Swatches and Color panels' pickers do. Without it, OK coloured the frame instead of its text. Test: the_color_picker_from_the_fill_box_follows_formatting_affects_text; #552's test shares its double-click helper.
2026-10-12 05:46:45 +00:00
aiongg 048baca521 Merge #552: Double-clicking the Fill or Stroke box opens the Color Picker
Conflict: ROADMAP.md: both progress lines
2026-10-12 05:41:55 +00:00
aiongg 64b834f2e0 Merge anchored-settings: IDML: anchored objects take unset anchor settings from their object style (#555)
Conflict: crates/idml/src/import.rs: next's with_style kept after attr_or_style (#555 has no counterpart there); the anchored arm merged as next's header (TextFrame, no has_text guard) with #555's body
2026-10-12 05:41:43 +00:00
aiongg 3e7cca3a0d Integration: text wrap Same As Clipping follows the graphic's clipping path (#450 × #384)
#384's contour wrap followed the frame's path for Same As Clipping, which was all a graphic could clip to; #450 gives graphics clipping paths. contour_path takes the clip region (Graphic::clip_region) when the graphic has one. Test: a triangle clip widening downwards, Same As Clipping, left side: lines beside the narrow top run further in than those at the base.
2026-10-12 05:40:04 +00: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 b7b0001cce Integration: IDML AppliedFont keeps a family's spaces through font_family_in (#558 × next's $ID/ handling)
#558 keeps an AppliedFont family exactly as written apart from line breaks; next routes the value through names::font_family_in (for InDesign's $ID/ prefix), which trimmed it. font_family_in strips CR/LF and the $ID/ prefix and keeps spaces; a blank or $ID/-only value is still no font. #558's applied_font_keeps_spaces_and_font_style_nothing_inherits fails without this.
2026-10-12 05:36:23 +00:00
aiongg 8f60e6349b Integration: idml tests.rs: restore the closing brace of hostile_custom_wrap_boundary_is_dropped
The keep-both resolution of tests.rs in the #558 merge (0ff65578) put #558's test after next's last test without its closing brace, which git had shared between the two sides.
2026-10-12 05:36:23 +00:00
aiongg 0ff65578b0 Merge idml-font-names: IDML: font family names keep their spaces; FontStyle Nothing inherits (#558)
Conflict: ROADMAP.md: both progress lines
Conflict: crates/fonts/src/fontdb.rs: next's style_traits/traits_of/linked_style, canonical_family lookup, family_kind and postscript_face; #558's alternate_names (as written, then trimmed) now chains plain_family ($ID/ prefix and format suffix) and is the fallback in style_traits, face (canonical_family, then substitute_family) and has_family
Conflict: crates/idml/src/import.rs: #558's CR/LF-only stripping (next's f.trim() dropped), next's composite_names lookup (by the trimmed name) and names::font_family_in; #558's FontStyle Nothing → unset
Conflict: crates/idml/src/tests.rs: both tests
2026-10-12 05:35:36 +00:00
aiongg 43a4e70658 Merge section-marker: Sections: the section marker shows the section's marker text (#556)
Conflict: ROADMAP.md: both progress lines (next's, then #556's)
Conflict: crates/compose/src/shape.rs: #556's empty-marker zero-width glyph; next's digit mapping, styles.at(i)/styles and sub.upright() in both arms
2026-10-12 05:34:13 +00:00
aiongg ab231cde62 IDML: a left-aligned anchored object's X offset reads in the layout's direction (#555) 2026-10-12 05:32:59 +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
aiongg 25c215faf9 IDML: anchored objects take unset anchor settings from their object style (#555) 2026-10-12 05:20:12 +00:00