Commit Graph
3023 Commits
Author SHA1 Message Date
dotCooCoo 589c56da22 PDF export: a white stop linked to a process swatch keeps a spot gradient on its ink
A gradient of one spot ink's tints and paper white is written as a
Separation shading in that ink, with each white stop at 0% of it. A
white stop linked to a swatch that is not a spot color (a global White
swatch, for example) did not count as paper white. In an RGB document
the spot ink was then dropped: a Classic gradient was written in the
stops' own colors, and a Perceptual one was exported as an image in
process colors.

Such a stop now counts as paper white in the Separation and DeviceN
paths and in the checks that pick them, as an unlinked white stop does.
A stop linked to a white spot color is still a stop of that ink.
2026-11-01 16:28:40 -08:00
dotCooCoo 2adf5a9a61 Symbols: Replace Symbol clears the old symbol's dynamic overrides
An override on a dynamic symbol instance names an object of its symbol's
art by path. Replace Symbol kept every override whose path the new
symbol's art also had, so a change made to one object of the old symbol
was applied to an unrelated object of the new one. An instance given a
different symbol now drops its overrides; an instance replaced with its
own symbol keeps them. Undo brings them back.
2026-11-01 16:24:33 -08:00
dotCooCoo e441d3ba18 Affinity: pressure curves keep dips between samples and stay within a document-wide limit
- Each segment of a pressure curve is also sampled where its width stops
  rising or falling, so a dip between the regular samples of a long
  curve is kept. The segment's values are scaled into -1..1 before that
  is solved, so huge values in a file can't overflow it.
- A curve counts as full width only within 1e-9 of it, so a curve just
  under full width keeps its profile.
- The pressure points of all strokes in one read are limited to about a
  million, as for characters and table cells. Curves at full width count
  too, and once the limit is reached no more curves are read; the
  strokes past it are imported as plain strokes with a warning.
2026-11-01 16:21:57 -08:00
dotCooCoo 0823672c82 Merge main into feat/ai-editing-data 2026-11-01 15:57:03 -08:00
dotCooCoo 6024f86da1 Artwork Cleanup: Empty Text and Off-Artboard Art leave type that clips art
A clipping path hides the art outside it. Empty Text removed empty type
that clips a clipping group, and Off-Artboard Art removed type that clips
a layer from off every artboard, so the art they hid showed. Both now
leave type that is a clipping path, as the other kinds already did, also
when the clipping path itself is selected.

Docs: docs/mcp.md (Artwork Cleanup kinds table).
2026-11-01 15:05:54 -08:00
dotCooCoo b473716e3e Fix #2089: Affinity strokes keep their Width tool profile
An Affinity stroke shaped with the Width tool stores its pressure curve
(LDeP: position along the path against width). The importer warned and
drew the stroke at full width all along.

- The pressure curve is read and sampled into the stroke's width
  profile (2 to 8 points per segment, 256 segments at most, widths up
  to the full weight). A curve at full width all along stays a plain
  stroke; one that can't be read is warned about.
- Brush strokes keep their own warning.
- The pinned Affinity 3 signatures for the five corpus files with such
  strokes are updated.
2026-11-01 14:17:23 -08:00
Brandon Thomas 639c9d6ce9 Merge pull request #5113 from dotCooCoo/feat/recolor-color-sliders
Recolor Artwork: color sliders in a chosen color mode edit the selected row's new color
2026-11-01 15:28:03 -05:00
Brandon Thomas fceaf44fd0 Merge pull request #5138 from dotCooCoo/fix/vars-p6
Variables: a text value on threaded type replaces the whole story
2026-11-01 15:27:58 -05:00
Brandon Thomas 33010204cb Merge pull request #5137 from dotCooCoo/fix/vars-p16
Variables: Lock Variables' description names the refused import
2026-11-01 15:27:53 -05:00
Brandon Thomas 2aa24c1f3a Merge pull request #5136 from dotCooCoo/fix/vars-p5
Variables: a graph variable's data set applies its CSV as graph.setData does
2026-11-01 15:27:48 -05:00
Brandon Thomas 7e5eebc3ed Merge pull request #5135 from dotCooCoo/fix/vars-p15
Variables: Rename reports a missing variable or data set before a taken name
2026-11-01 15:27:45 -05:00
Brandon Thomas 0271e034ce Merge pull request #5123 from dotCooCoo/fix/ai-os22-g6
.ai import: version 7 palettes keep their swatches without names
2026-11-01 15:27:41 -05:00
dotCooCoo 9726e691f5 .ai import: names that aren't UTF-8 can be read in a legacy code page
A legacy .ai stores layer, object, color, gradient, pattern, style and
swatch names as bytes, without saying which code page they are in.
Names that aren't UTF-8 are still read as Windows-1252 by default, and
now each gives a warning naming it and the encoding it was read in (the
first three, then a count).

document.open takes legacyNameEncoding (windows-1252, macintosh,
windows-949 or euc-kr, shift_jis, gbk, big5) and vectorcraft-cli
convert takes --legacy-name-encoding, to read those names in another
code page. Definitions and the fills that name them are read alike, so
they still match. Type, comments, dictionary keys and PDF layer names
are read as before.

encoding_rs becomes a direct dependency of vectorcraft-eps.

Part of #2831
2026-11-01 12:17:42 -08:00
dotCooCoo 39e69e8446 Tests: move the lock params text test next to the lock test 2026-11-01 11:48:41 -08:00
dotCooCoo b104efc53c Graph data: a dropped empty row adds no blank series
Dropping rows with neither a label nor a value read the data padded to
the widest row, so a dropped row wider than the rest (`,,`) left every
kept row one blank cell wider. The graph then had an extra blank series,
and a right-axis assignment to that index was kept. Each kept row now
keeps its own width. This applies to graph.setData and to a graph
variable's data set.
2026-11-01 10:19:45 -08:00
dotCooCoo 637229931f Paste: Paste in Back keeps a clip group's clipping path at the bottom
With the clipping path of a clip group or a clipping layer selected, or a
member of a compound path that is the clipping path, Paste in Back put the
clipboard's objects below it. The bottom pasted object then became the
clipping path. With nothing selected in a clipping layer, Paste in Back did
the same at the bottom of the layer.

Paste in Back now puts the objects right above the clipping path, as Send to
Back keeps art above it.
2026-11-01 10:18:06 -08:00
dotCooCoo be20929877 Variables: a text value on threaded type replaces the whole story
Applying a data set wrote a text value into the bound frame only, and the
thread's reflow then joined it with the other frames' old text. The art no
longer matched the row it had just applied, a captured row applied back lost
text, and a value bound to a later frame deleted the rest of the story.

- A text value bound to any frame of threaded text replaces the whole story,
  which then flows through the frames again. `overflow` names the story's
  last frame.
- Capture Data Set, Update Data Set and the Variables panel's match check
  read such a frame as the whole story, frames joined in thread order.
- When any frame of the story is locked, the text value is skipped and
  counted in `skipped`, and the story is not read.
- Two text variables bound in one story apply in definition order, as for
  two on one object: the one defined last wins.

Docs: docs/mcp.md.
2026-11-01 08:58:35 -08:00
dotCooCoo e49e102032 Variables: Lock Variables' description names the refused import
variable.lock's params text and the doc comment on Variables::locked said every
dataset command still runs while the variables are locked. dataset.import is
refused when its header would define a variable; both texts now say so, as
docs/mcp.md and dataset.import's params already do.
2026-11-01 08:43:56 -08:00
dotCooCoo 07282d95e0 Variables: a graph variable's data set applies its CSV as graph.setData does
Choosing a data set gave a graph variable's CSV to the graph without the
two steps graph.setData takes after new data: rows with neither a label
nor a value were kept and drawn as empty slots, and a right-axis series
the new data no longer had stayed assigned, so a series added by a later
row started on the right axis. The data set now goes through the same
steps.
2026-11-01 08:43:16 -08:00
dotCooCoo f98b086c5f Variables: Rename reports a missing variable or data set before a taken name
variable.rename and dataset.rename checked whether newName was taken before
checking that name exists, so renaming a missing name onto a taken one said
the new name already exists. Both now report the missing name first.
2026-11-01 08:23:40 -08:00
dotCooCoo 3698a76c5d File formats: JPEG XL refuses colors left in a profile that is not an RGB one
A color JPEG XL image whose embedded ICC profile the decoder cannot convert to sRGB kept its samples as stored and lost the profile when that profile was not an RGB one, so the image drew those samples as sRGB. It is now refused as unreadable, as a gray image in such a profile already is.
2026-11-01 08:09:11 -08:00
dotCooCoo 7f843e1e4a .ai import: objects with a variable-width stroke come in as their drawn outline
The editing data keeps each styled object's drawn look, the outline as
the file draws it, next to the editable path. An object whose art style
was one stroke with a width profile came in as a live stroke instead, and
VectorCraft's width profile does not draw these strokes at the widths the
file draws them, so outlines no longer met the fills built against them.

Such an object now comes in as its drawn look, as other styles that
VectorCraft cannot draw the same way already do. Type keeps the stroke
and its profile live, since its drawn look cannot be retyped. The import
warning for drawn looks now names variable-width strokes.
2026-11-01 07:59:14 -08:00
dotCooCoo e6ae67154d Merge main into feat/jxl-import 2026-11-01 07:56:25 -08:00
dotCooCoo baeef1ec05 Blend: an edit merged into an open undo group drops the first pick
An edit merged into the newest undo step (an undo group) left
History::step unchanged, so a first pick made while the group was open
survived the edit. The newest step now takes a new number whenever it is
extended.
2026-11-01 07:12:43 -08:00
dotCooCoo f1fd255120 .ai import: version 7 palettes keep their swatches without names
Version 7 files write each palette swatch as its paint and `Pc`, with no
name. The reader only kept a swatch with a name before `Pc`, so such a
palette gave no swatches and the document got the default set.

A swatch without a name is now named by its spot color, gradient or
pattern, or by its values in the format the swatch import already uses
for unnamed colors ("Gray K=25", "C=25 M=0 Y=0 K=0"). `Pn` and a `Pc`
with no paint before it add nothing. Named swatches keep their names.

An operator the reader does not know, or a `Pg`, now ends the paint
before it, so a later `(name) Pc` no longer takes that paint. The
swatch limit is kept with a running count instead of summing every
group at each `Pc`.
2026-11-01 06:55:53 -08:00
dotCooCoo 4b663906e9 Blend: tests for a pick dropped by Redo and for the history step
A Redo made outside the Blend tool drops its first pick, as an edit or an
Undo does, and two picks with no edit between them blend. History::step
changes with each new step, Undo and Redo, also when an Undo and a new
edit leave as many steps as before, and stays the same for a selection
change and for an edit merged into an undo group.
2026-11-01 06:53:56 -08:00
dotCooCoo 52061b1029 Undo: tests for the default history budget with no memory reading, 0, small and large memory
history_budget_for takes the memory reading itself, so the 1 GiB fallback for no reading
or 0 is tested with the quarter, the 64 MiB floor and a reading of 2 GiB, as a cgroup limit
lower than the installed memory gives. The budget is unchanged.
2026-11-01 06:52:08 -08:00
dotCooCoo 827e137b5c Paste: in front or back of a compound path's member, other art goes beside the compound path
With a member of a compound path selected, Paste in Front and Paste in Back
put the clipboard's objects next to that member, inside the compound path,
whatever they were. A pasted group, compound path or type object then sat
inside a compound path, which holds only paths. The model-based test
`random_command_sequences` stops on this with "compound #N has a non-path
child".

When the clipboard holds anything other than paths, the objects now go
right above or below the compound path. Paths still go next to the member,
inside the compound path.
2026-11-01 06:42:33 -08:00
dotCooCoo a4436388cc Batch: a folder source's file name keeps a backslash on Linux and macOS
Output stems for a folder batch went through safe_file_name, which turns
`\` and `:` into `-`, so `a\b.svg` on Linux was written as `a-b.svg`.
The stem now only loses trailing periods and spaces and gets `-` after a
Windows device name, and keeps every other character.
2026-11-01 06:35:40 -08:00
dotCooCoo b5cf31950b Merge main into feat/kinsoku-shori-type 2026-11-01 06:26:35 -08:00
dotCooCoo fd973bdf48 Tests: Outline Stroke with Round Corners, no effect, hidden effects, blur and undo 2026-11-01 06:23:42 -08:00
dotCooCoo 1f24099dd9 .ai save: test that a file opens from its VectorCraft document when the editing copy differs
A .ai whose editing copy for other apps holds no transparency, while the VectorCraft document it carries holds 50% opacity, opens in VectorCraft with the opacity of the VectorCraft document. Read with native: false, the same file gives the editing copy.
2026-11-01 06:22:13 -08:00
dotCooCoo fc6178d021 Recolor Artwork: spell color the American way in the new comments and test names 2026-11-01 05:26:35 -08:00
dotCooCoo 9b853004d5 Recolor Artwork: a slider edit changes the color mode of its own row only
The color sliders now mark the row they edit with `model: "new"`
instead of a dialog-wide flag, and `recolor.apply` reads a row's own
`model` before the command's. Rows the sliders did not edit keep each
color's model, so with RGB art and a CMYK color group, a slider edit of
one row no longer turns the other rows' art CMYK. Another edit of the
row's new color, Reset and rows reduced again remove the mark, and
`recolor.randomize` moves it with its new color.
2026-11-01 02:06:46 -08:00
dotCooCoo f242b9caa6 Recolor Artwork: a row's tint stays with its spot color
A wheel drag or a harmony rule on the Edit tab now removes a row's tint
when it gives the row another new color, as the color sliders, the hex
field and the Color Picker already do. With Limit to Library, an edit
that snaps back to the row's own color keeps the tint.

recolor.randomize moves each row's tint with its new color when it
shuffles the order, and removes the tint from a color that saturation
and brightness changes.
2026-11-01 01:55:46 -07:00
dotCooCoo 83227d368d Merge main into feat/character-anti-aliasing-method-r
# Conflicts:
#	crates/engine/src/cmd/panelcmds.rs
#	crates/ui-egui/src/panels/character.rs
2026-10-31 22:16:39 -07:00
dotCooCoo 46cb088eae Recolor Artwork: color sliders edit the selected row's new color in a chosen mode
The Assign tab gets a Color Mode menu (HSB, RGB, CMYK, Lab, and Tint when
the row's new color is a spot color of the Limit to Library library) and
its sliders under the rows. They edit the first selected row's new color
and are disabled with no row selected. Once they have edited a color in a
mode, the preview and OK send recolor.apply with model "new", so the art
takes the color in that mode.

recolor.apply rows take an optional tint (percent, default 100): art linked
to the row's global or spot library swatch takes that share of its tint.
recolor.randomize keeps a row's tint.
2026-10-31 21:42:42 -07:00
dotCooCoo d9dc0ea7e7 Merge main into feat/character-anti-aliasing-method-r
# Conflicts:
#	crates/engine/src/cmd/panelcmds.rs
#	crates/ui-egui/src/panels/character.rs
2026-10-31 21:29:22 -07:00
dotCooCoo e1440a3a90 Paragraph: Kinsoku Shori Type chooses Push In First, Push Out First or Push Out Only
The Paragraph panel menu (with the East Asian options) gains Kinsoku Shori
Type, and text.setFormat takes kinsokuType. Where the Kinsoku Set keeps a
character off the start of the next line or the end of this one, Push In
First sets it on the line and closes the line's gaps next to CJK
characters to fit, each by at most a tenth of an em of the character
before it; a line that cannot close up that much, or that holds a tab,
pushes out. With Optical Margin Alignment or Roman Hanging Punctuation
on, the gaps that hanging punctuation keeps open are not counted. Push
Out First pushes in only when pushing out would leave the line more than
an em short. Push Out Only, the default, is the line breaking area type
had before, so existing documents lay out unchanged. The setting does
nothing with Kinsoku Set None, is saved only when it is not Push Out
Only, and paragraph styles carry it. Reset Panel sets it back to Push
Out Only.

Docs: docs/mcp.md, docs/type-parity.md, docs/target-app-parity.md.
2026-10-31 21:14:57 -07:00
dotCooCoo b949038a2b .ai save: the editing copy draws the artboards' background colors
The PDF pages of a .ai draw each artboard's background color (Include
Artboard Fill, on by default), but the editing copy of the art left it
out, so the two differed and the file reopened from its PDF part. The
editing copy now takes the same option.
2026-10-31 21:09:39 -07:00
bflatastic a76d9215da Fix #4923: Find Nearest Swatch matches colours to a library's swatches
Object > Plug-ins > Color Effects > Find Nearest Swatch... picks a
library (the document's swatches, a built-in library, or a library or
colour book the user saved or loaded) and Fill and Stroke, and lists
each colour of the selection with its three closest swatches and their
CIEDE2000 difference. Apply gives each colour its nearest swatch as one
undo step.

- addon.color.nearestSwatch (a query): the closest swatches of a
  library to each colour of the selection's fills and strokes (solid
  colours and gradient stops, inside groups and type), or to a colour
  given, with deltaE; count 1..50.
- addon.color.applyNearestSwatch: recolours those paints (and gradient
  meshes) to their nearest swatch; global and spot swatches link, and a
  library's swatches the colours take are added to the document first
  (as swatch.library.add adds them), so a spot colour stays spot.
- The library picker Image Trace's Document Library uses moves to the
  Swatches panel module and is shared.
- No colour book data ships: only libraries VectorCraft computes or the
  user loads are searched.
2026-11-01 11:09:11 +07:00
Don Omondi 6d6757c929 Export All Open Documents: every page and artboard, linked data included
Each format's copy options are given in full, so no remembered option
applies: PDF and .ai copies write every page (range all), SVG copies
the art of every artboard in one file (useArtboards false), native,
.ai and EPS copies include linked files' data.
2026-11-01 11:09:10 +07:00
Don Omondi c101576df3 Fix #4920: one file per copy
Each copy is saved with images embedded and artboards together, so a
document last saved with linked SVG images writes no image files beside
its copy; any other file a save reports is reserved for the copies still
to come.
2026-11-01 11:09:10 +07:00
dotCooCoo 38bb213917 PDF export: a Perceptual gradient that mixes spot and process inks keeps its inks
A Perceptual linear or radial gradient that mixes spot colors with each
other or with CMYK or gray stops was written as an image in process
colors. It is now written as a DeviceN shading of its inks, with the
stops on its Perceptual curve: at each one, the inks of the two stops on
either side are mixed at the weight where the two colors mixed in
display RGB have the OKLab lightness of the color on the curve.

- Gradient::ink_mixes gives the curve's stops as weights between stops,
  under the same 4,096-stop limit as ink_tints.
- Perceptual gradients that mix a spot color with RGB or Lab stops,
  dithered fills and freeform gradients stay images, and the warning
  now names those cases.
2026-11-01 11:09:09 +07:00
dotCooCoo 7e5e099f8e Export for Screens: a second file with a taken name is numbered instead of skipped
Squashed from #5028 onto main.
2026-11-01 11:09:08 +07:00
dotCooCoo ff036557fb Symbols: Edit Symbol opens the symbol in its own editing mode, and every instance follows each edit
Squashed from #5064 onto main.
2026-11-01 11:09:03 +07:00
dotCooCoo 5716a996ab Type: Touch Type moves a character rotated near 90 degrees across its line
Baseline shift now moves a character in the line's frame, outside
Character Rotation, so a positive shift moves a character up from its
line at any rotation, and the character turns about its shifted pen
position. The Touch Type tool splits a drag into its part along the line
(kerning) and its part up from the line (baseline shift) at every
rotation; before, within about 6 degrees of a quarter turn it dropped
the cross-line part.

Characters that already combine a rotation with a baseline shift are
drawn in a new position. Their stored values do not change.

- text: glyph transform order, PositionedGlyph::pivot (the turning
  point), tab leaders in the same order
- tools: Touch Type splits drags against the line's up axis and measures
  rotate-handle turns about the shifted pen position
- engine: text.touchType description; dy is up from the line
- docs: mcp.md Touch Type paragraph, target-app-parity.md progress line
2026-11-01 11:08:57 +07:00
dotCooCoo 94ef9b87c2 Type: Character panel toggles show off for a mixed selection, and text.setFormat checks on/off values
The Character panel's All Caps, Underline and Strikethrough buttons, and
its menu's All Caps, Underline, Strikethrough, Proportional Metrics,
Tate-chu-yoko and No Break, read the first selected character's style.
With only some characters marked they could show on, and choosing one
turned the attribute off for every character. They now show on only
when every selected character has the attribute, and a click on a mixed
selection turns it on for all, as Small Caps and Standard Vertical Roman
Alignment do. type.underline (Cmd+Shift+U) underlines a mixed selection
the same way. The Character Alignment and Digits submenus check no item
while the selected characters differ.

text.setFormat ignored a value that was not true or false for underline,
strikethrough, allCaps, proportionalMetrics, tateChuYoko,
standardVerticalRoman, noBreak, hyphenate, hyphenateCapitalized and
romanHangingPunctuation. It now refuses it with "`<key>` must be true or
false" and changes nothing, as text.setRangeStyle does.
2026-11-01 11:08:56 +07:00
dotCooCoo 427bce12b0 Character: Font Size can measure the cap height, x-height or ICF box
Squashed from #5026 onto main.
2026-11-01 11:08:55 +07:00
dotCooCoo af133b1ee4 .ai import: envelopes open as live envelopes
An envelope in a .ai's editing data opened as its distorted art, with
its mesh and contents dropped. It now opens as a live envelope of its
contents: a warp with its style, bend, distortions and orientation, a
mesh with its points and handles, or a top object with its outline,
with Envelope Options, Fidelity and Edit Contents.

The envelope replaces its drawn art only when VectorCraft draws it like
that art, with a budget so that live envelopes don't make the file open
from its PDF part. When the layers still differ too much from the page,
the drawn art is put back. An envelope that can't be read, or that
draws differently, keeps its drawn art with a note. Envelopes of type
keep their drawn art and hidden type as before.
2026-11-01 11:08:49 +07:00