Commit Graph
2036 Commits
Author SHA1 Message Date
dotCooCoo b0d1ca4419 Glyphs: a character with a combining mark reports the glyph it is drawn with
text.setGlyphAlternate looked for the character's glyph only in a
cluster as long as the character. A character followed by a combining
mark is shaped in one cluster with the mark, so the lookup found
nothing: under a substituting feature such as salt, `gid: null`
reported the default glyph instead of the one drawn, and a request for
the default glyph changed nothing.

The lookup now also takes a cluster that holds the character's
grapheme, and in a cluster of several glyphs takes the character's
default glyph or one of its alternates. Text that overflows its area
is shaped with the whole grapheme.
2026-11-05 01:56:16 +07:00
dotCooCoo 584b9706cc Gradient: Perceptual PDF help counts stops as written, CMYK documents included
The method help of paint.editGradient and docs/mcp.md said a Perceptual
gradient with spot stops becomes an image in PDF unless every other stop
is paper white, CMYK or gray. PDF export checks each stop's color as it is
written: a CMYK document, or an export that converts colors to a CMYK
destination profile, writes RGB and Lab stops in CMYK, so the gradient
becomes a DeviceN shading. Both texts now say so, and a test covers the
CMYK document and CMYK destination cases.
2026-11-05 01:31:24 +07:00
dotCooCoo be8438c13d Tabs: setting the stops a paragraph already has ends the typing session
text.tabs.set and text.tabs.clear return without an edit when every
targeted paragraph already holds the stops. That path skipped the
settlement Session::edit does first, so a typing session in progress
stayed open. The command now ends it first, as the Variables commands
do: the typing is its own undo step whether or not the stops change.
2026-11-05 00:55:35 +07:00
dotCooCoo f026ebe686 Tests: an RGB spot color converts with Document Color Mode and stays spot
A spot swatch defined in RGB keeps its spot flag and its own plate when
the document changes to CMYK, and its definition converts like any other
swatch: a fill that uses it and a 40% tint stroke take the converted
color. The Lab spot test's comment now says it covers a Lab definition.
2026-11-04 23:58:58 +07:00
dotCooCoo fae49d31fd Glyphs: the default glyph can be chosen back under a substituting feature
Follows #5442.

- text.setGlyphAlternate keeps the default glyph as a choice when a
  feature of the character's style (salt, a stylistic set) draws another
  glyph for it, so the default glyph is drawn. A character that is not
  laid out (overflowing text) is shaped alone in its style to decide.
  gid: null reports the glyph the style draws.
- The Glyphs panel's alternates grid shows the default glyph first when
  the font does not list it, so a chosen alternate can be put back.
- A test for a chosen character that starts a longer grapheme (f with a
  combining acute), which is not split from its mark.
- The alternates tests in the engine hold the shared font lock while
  they add fonts to the global font database.
2026-11-04 23:58:54 +07:00
dotCooCoo a9eae3a333 Appearance: Add New Fill and Add New Stroke comment names the paint they take
The comment in add_item said a new fill or stroke is a copy of the top
one. The code takes only the top item's paint, and for a stroke its
weight. The comment now says so, as the params text does.
2026-11-04 23:44:58 +07:00
ruchirguitarandClaude Opus 5.5 6e1c1c9377 Select by Artboards: test that a symbol instance is measured by its art (#5525)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:57:59 +07:00
ruchirguitarandClaude Opus 5.5 7e016a0bcc Select by Artboards: select objects overlapping, inside or outside several artboards (#5525)
addon.select.byArtboards {artboards, relation} in a new Selection Tools
add-on. Overlapping is All on Active Artboard's test, now one shared
helper (select::on_artboard); Fully inside is within one artboard, edges
included; Fully outside is on none. Objects are those Select All takes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:57:57 +07:00
dotCooCoo aee5671734 File formats: Pixar tile pointers into the header or tile table are refused
A nonzero tile pointer below the end of the tile pointer table made the reader decode header or table bytes as pixels. The table end is now computed with checked arithmetic, and such a pointer is refused as damaged image data.
2026-11-04 22:57:53 +07:00
dotCooCoo 88b5170622 Glyphs panel: alternates for the current text selection
Squashed from #5442 onto main.
2026-11-04 22:29:53 +07:00
dotCooCoo 0990bfb244 .ai save: the editing copy keeps transparency, clipping and opacity masks
The editing copy of the art that a PDF-compatible .ai carries now holds:

- Transparency: `mode opacity isolate knockout shape Xy` (the blending
  mode's number, Knockout Group 0 off, 1 on, 2 neutral), written when it
  changes: in a path's paint after its colors, before a guide, and after
  a group's end followed by `0 0 Xd` and `6 () XW`. A group without
  transparency of its own ends `9 () XW`, and a group's name comes
  before its state block. A layer's transparency follows its art: `Xy`,
  `0 0 Xd`, `7 () XW`, `LB`.
- Clipping groups as `q`, the art, the clipping path or compound path last
  with `h` (or `H`) and `W` before its painting operator, `Q`, as section
  5.6.2 of the version 7 specification shows them
  (http://mercator.elte.hu/~saman/hu/okt/AI7FileFormat.pdf). A clipping
  layer whose clipping object is a path or compound path sets the last
  `Lb` flag and writes its clipping path last.
- Opacity masks as an `AI9_Mask` entry in the object's dictionary: the
  mask's art between `X=` and `X+`, and Clipping, Inverted, Disabled and
  Linked.

Before, clipping groups were written as their art cut to the clipping
path, and transparency and opacity masks were left out. Still left out,
with a warning: the opacity and blending mode of single fills and
strokes, opacity masks on layers (disabled ones too), and opacity masks
nested more than 8 deep. A clipping group or layer whose clipping object
isn't a path or compound path is written as a group of the art cut to
that object, in a layer that doesn't clip, and so are the clipping
groups inside that art. A clipping path's paint that a plain fill and
line can't draw is written as paths inside the clipping, fills under the
art and strokes over it, each side in a group of its own when it has
several, with the clipping path's transparency, lock and hidden state;
the clipping path's opacity mask stays on the clipping path, with a
warning. A clipping group without members is an empty group, and an
empty clipping layer is an empty layer.

Each of these is written in the form VectorCraft's .ai reader reads, and
the tests read the written data back through that reader.

Reading .ai editing data: an `Xy` after a layer's last group or compound
path, followed by the layer's `7 () XW`, is the layer's (a following
`6 () XW` keeps it the group's); groups, compound paths and layers keep
Opacity & Mask Define Knockout Shape.

The `.ai` option `editingData` and docs/file-format-parity.md describe
what the copy now holds.
2026-11-04 22:29:43 +07:00
d8abyte-dkandClaude Opus 5.5 b9594b233c Plug-ins: color parameters, and filters that make guides and lock
A `color` parameter shows a color button that opens the color picker in plug-in
dialogs and the Appearance panel's effect editor; its default and values may be
a color object or a hex string, and the plug-in gets a color object. A filter's
returned objects may set `guide` and `locked`; objects it locked or made guides
are left out of the selection after the run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 22:29:38 +07:00
dotCooCoo 3c80b314e1 Select: All on Same Layers takes the layers of objects that can be selected
Select > Object > All on Same Layers took the layer of every selected
object, including one that select.set had kept while it is hidden or
locked, on a template layer, on a locked artboard, or outside the
isolated group. That object's layer then decided which art was
selected. The command now takes the layers only of the selected
objects that the Select commands could select. When the selection
holds no such object, it clears the selection.
2026-11-04 22:29:22 +07:00
dotCooCoo eb03e8943f Gradient: PDF shading notes name linear and radial gradients and DeviceN
The PDF writer writes a spot gradient as a Separation or DeviceN shading
only when it is linear or radial; freeform gradients and dithered fills
are written as images in process colors.

- `paint.editGradient`'s method help and the Method paragraph in
  docs/mcp.md limit the Perceptual exception to linear and radial
  gradients.
- The Linked gradient stops paragraph in docs/mcp.md no longer says the
  writer has no DeviceN: a gradient mixing spot inks with other spot,
  CMYK or gray inks exports as a DeviceN shading, and only a mix with RGB
  or Lab colors is written in process colors, with a warning.
2026-11-04 21:58:59 +07:00
dotCooCoo be52e4e8af Tabs: setting the stops a paragraph already has records no undo step
text.tabs.set and text.tabs.clear return without an edit when every
targeted paragraph already holds the stops. A Ctrl-drag that the 0 pt
bound keeps from moving, or a drag released where it started, no longer
adds an undo step that changes nothing. When one targeted paragraph
holds other stops, the command still sets them all.
2026-11-04 20:51:10 +07:00
dotCooCoo 535d423d6c Appearance: Add New Fill and Add New Stroke params text match the code
appearance.addFill copies the active fill row only when the call has no
`ids`, no `id` and no `target: "contents"`; the params text now names
`id` too. appearance.addStroke gives the new stroke at least 1 pt, so a
0.5 pt top stroke gives a 1 pt stroke; the params text now says so.

Tests pin both: `id` alone leaves the active row alone and puts the new
fill on top, and Add New Stroke on a 0.5 pt stroke adds a 1 pt one.
2026-11-04 20:51:07 +07:00
dotCooCoo dd41552c30 PDF export: warn about spot stops in freeform gradients without points
A freeform gradient whose field has no points of its own takes its
colors from the stops, so spot-linked stops in it are written in
process colors too. The export now warns in that case, as it does for
the other gradients drawn as images.
2026-11-04 20:36:11 +07:00
Don Omondi 33b57fb581 Batch Trace Folder: move to Object › Plug-ins as an add-on
Batch Trace Folder replaces a script, so it is now a Document Tools
add-on command, addon.document.batchTraceFolder, under Object ›
Plug-ins › Document Tools (as Export All Open Documents is), and no
longer in Object › Image Trace. Behaviour, caps and the refusal of a
folder too big to list in full are unchanged; the tests move with it.

The status messages have @msgplural rows in every catalog, as does the
menu label. When some images trace and some don't, the status is one
counted message ("Traced {n} images; not traced: {failed}") so it
translates as a whole.
2026-11-04 20:12:07 +07:00
Don Omondi 7790c37eb3 Batch Trace Folder: refuse a folder too big to list in full
list_dir cuts its listing before sorting, so a cut listing could skip any image. The batch now
stops with an error instead of tracing part of the folder.
2026-11-04 20:12:06 +07:00
Don Omondi 7c04fd8973 Fix #4921: Image Trace > Batch Trace Folder
imageTrace.batchFolder opens each image in a folder, traces it with a
preset (optionally expanded), saves it into a destination folder under
its own name (vectorcraft, svg, pdf, ai or eps; taken names numbered so
nothing is replaced) and closes it again whether it traced or not; the
active document stays active. Object > Image Trace > Batch Trace Folder
asks for both folders. Not on the web.
2026-11-04 20:12:05 +07:00
Don Omondi ed634a78f4 Guide clipboard: Paste after a guides-only Copy takes the guides, not older system clipboard content
A Copy of ruler guides alone left the system clipboard as it was, so the
next Paste took whatever another app had copied before instead of the
guides. The guides-only Copy now empties the system clipboard (on the
web, empty text through egui with Copy As SVG on), as any Copy replaces
what the clipboard held, and drops an earlier Copy's formats not written
yet. What another app copies afterwards still pastes.
2026-11-04 20:12:01 +07:00
Don Omondi ad64852255 Guide clipboard: guides alone stay off the system clipboard; test two pastes and layers
Copying only ruler guides published an empty SVG and text to the system clipboard, replacing
what other apps put there. The clipboard now offers other apps nothing without art. Tests cover
two pastes adding guides of their own on the current layer (following its visibility) and the
SVG carrying no guides.
2026-11-04 20:12:00 +07:00
Don Omondi 2402f8e1aa Copy and paste guides: Cut with direct-selected anchors takes the guides too
With anchors and ruler guides selected, Cut deletes the anchors and the
selected guides in one undo step (edit.clear's guides: true reaches the
anchor-deleting path).
2026-11-04 20:11:59 +07:00
Don Omondi 484a78f812 Copy and paste guides: Cut takes the selected guides with the objects
Cut with objects and ruler guides selected now removes both, in one
undo step: edit.clear takes guides: true beside ids to delete the
selected guides with the objects it names.
2026-11-04 20:11:59 +07:00
Don Omondi f3faa115c6 Fix #3226: copy and paste ruler guides
Edit > Copy and Cut put the selected ruler guides on the clipboard,
with the selected objects or on their own. Pasting puts them on the
layer new art goes to, selected, in the same undo step: with objects
they move as the objects do; alone they keep their places, while Paste
in Place / in Front / in Back put them on the active artboard as they
lay on theirs, and Paste on All Artboards on every artboard.
2026-11-04 20:11:59 +07:00
dotCooCoo ac0ca081bb Commands: a color param with an alpha part is refused instead of losing its alpha
Squashed from #5603 onto main.
2026-11-04 20:08:42 +07:00
Don Omondi 1348f827d0 Area Type Options: applying to several frames keeps each frame's own options
text.areaOptions given for several area type frames (ids, or a selection of
several) copied the first frame's options onto all of them, so setting one
option (say the vertical alignment) also replaced the others' columns, gutter,
inset and fit. Each frame now takes only the options given over its own, and
fit resolves against each frame's own setting, so fitMinPercent alone changes
only frames that already shrink text to fit. Options that change nothing add
no undo step and keep redo.
2026-11-04 20:07:58 +07:00
dotCooCoo 9275b902a9 PDF/.ai import: a file whose type all comes from its text document opens without its PDF part
A PDF-compatible .ai (or a PDF saved with the editing data) now opens
from its layers without its PDF part being imported, and without being
compared with its page, when:

- its text document makes the type of every text object, hidden ones
  included, at least one text object shows, and none is drawn by the
  appearance of the layer or group it is in;
- its layers hold no unread object, no placed file with a second
  placement and no live envelope with drawn art to fall back on, and
  no image too large to read;
- it declares no large canvas, and its artboards are its pages' trim
  boxes one for one, in points or in the first page's UserUnit.

A file whose layers draw differently from its page then opens from its
layers instead of its PDF part. The bleed comes from the pages' bleed
boxes. The check runs only when no pages are picked, layers and the
editing data are on, the crop box is asked for and type comes in as
text. Files saved without PDF compatibility, files whose PDF part
draws only type or nothing, and files whose PDF part's content can't
be read take the same path when they qualify: their bleed comes from
the pages' bleed boxes, pages in units larger than a point draw them
at their size in points, and the note that the PDF part couldn't be
read is not given, since its content is not read.

Files that don't qualify open as before, and the editing data decoded
and read for the check is reused after the PDF part is imported
(layered_ai_from).

When a file whose PDF part can't be read is scaled up to its size in
points, its text wraps are now laid out again at that size, and the
default graphic styles of a file without graphic styles of its own are
no longer scaled. The same applies on the new path.
2026-11-04 19:41:56 +07:00
dotCooCoo 3f9545c962 Native files: documents from before 0.21.0 keep their Twist turning as saved
#5139 (in 0.21.0) made a positive Twist angle turn clockwise, but
documents saved by earlier releases stored their angles with the other
sign and reopened twisted the other way.

The document version (FORMAT_VERSION) goes from 1 to 2. Opening a
version 1 document negates its Twist angles (in layers, opacity-mask
art, Shaper edits, symbols, graph designs, pattern swatches, graphic
styles and brush definitions), unless the file is of the current file
version and its generator is VectorCraft 0.21.0 or later. The document
opens and saves as version 2. A save for an older file version, which
only apps before 0.21.0 read, writes a version 1 document with the
angles negated back. Graphic style libraries (.vcstyles) go to version
2: a version 1 library's Twist angles are negated when it is read.
Readers do not check either version, so older builds open the new files
as before.
2026-11-04 19:41:51 +07:00
dotCooCoo 27fc855246 Appearance: Add New Fill takes the active row's paint, as before
Add New Fill again copies only the active fill row's paint; Duplicate
Item copies a whole row. The params text of appearance.addFill and
appearance.addStroke now says the new fill or stroke takes the paint
(and for a stroke, the weight) of the row it starts from, and a test
pins the paint-only copy.
2026-11-04 19:41:42 +07:00
dotCooCoo db468928d4 Appearance: Add New Fill copies the whole active fill row
With a fill row active, Add New Fill copied only the row's paint and
reset its opacity, blend mode, visibility, overprint and effects. The
new row is now a copy of the whole active row, as the command's
description says.
2026-11-04 19:41:42 +07:00
dotCooCoo 5626a1614c Gradient: method help names the PDF Separation and DeviceN shadings
The `method` text of `paint.editGradient` and the Method paragraph in
docs/mcp.md said SVG and PDF exports draw a Perceptual gradient as an
image. PDF writes an eligible one natively: tints of one spot ink as a
Separation shading in that ink, and spot inks mixed with other spot,
CMYK or gray inks as a DeviceN shading. Gradients without spot stops,
with RGB or Lab stops, dithered fills and curves with too many stops
still become images there. Both texts now say so.
2026-11-04 19:41:38 +07:00
dotCooCoo 95b3fbfec9 Type: the font warning leaves out characters Missing Glyph Protection kept
text.setStyle and text.setRangeStyle collected the fonts for their
warning after Missing Glyph Protection had run. A character the new font
has no glyph for keeps its earlier font, and when that font was not
available the warning named it, although the command did not set it.

protect_missing_glyphs now returns the byte ranges that kept their font,
and both commands leave those ranges out of the warning's fonts.
2026-11-04 19:19:04 +07:00
dotCooCoo bc2af8df35 Appearance: the active row is matched in each selected object on its own
Reduce to Basic Appearance and Add New Fill matched the Appearance
panel's active row only when every selected object had an equal row.
With three objects whose third had a different lower fill, the second
object lost the match too: Reduce kept its top fill instead of the
active one, and Add New Fill put the copy on top instead of above the
active row.

items_per_target now matches each object against the first one
separately, so an object keeps the match whenever it has an equal row.
2026-11-04 19:19:00 +07:00
dotCooCoo af14fe3d59 Align Text Tweaker ranges to character boundaries 2026-11-04 18:56:40 +07:00
dotCooCoo a403e75802 Validate character style numeric dimensions
Squashed from #5540 onto main.
2026-11-04 18:29:25 +07:00
dotCooCoo d79f5ccb75 Layers: a layer or gap color with an alpha part is refused instead of losing its alpha
layer.setProps, layer.new, layer.newSublayer and livePaint.gapOptions read
an 8-digit hex color such as #ff000080 as #ff0000 and set it without its
alpha. They now refuse it with the error a bad color already gets, and
change nothing. #rgba was already refused. #rrggbb, #rgb, a preset index
and a preset name are read as before.
2026-11-04 18:29:17 +07:00
dotCooCoo 8762242dfe Select: describe which object Same Symbol Instance compares with
The params text of select.same.symbolInstance, docs/mcp.md and
docs/target-app-parity.md now say that the object compared with can be
of any kind (a group member selected on its own, or an object that is
not a symbol instance, which leaves the selection empty), and that new
art on a locked artboard is not passed over while it stays selected.
A test covers the three cases.
2026-11-04 18:29:09 +07:00
dotCooCoo c220c500e9 Type: Done during a Cmd press does not save empty point type
Done (pattern editing), Stop Editing Opacity Mask and Exit Isolation
Mode end the Type tool's edit first, so point type a click placed and
left empty is discarded. While a Cmd/Ctrl press had lent a selection
tool, the Type tool was not the active tool, its edit was not ended,
and the empty point type was saved into the pattern tile or the mask.

The lent tool is now given back first. A drag the lent tool started is
kept as its own step, with its last position applied when Real-time
Drawing and Editing is off.
2026-11-04 18:28:56 +07:00
dotCooCoo e67277ebe7 Hide: Unselected Artwork keeps groups that hold a selected layer or a locked guide
A layer nested in a group whose art was all locked counted for nothing,
so the group was hidden or locked with the layer inside it. The selected
layer now counts as selected itself.

While Lock Guides was on, a guide path that was locked itself, or sat in
a locked object, was not kept, so the group around it was hidden. Guide
paths inside locked objects are now kept too.
2026-11-04 18:28:44 +07:00
dotCooCoo abc2201664 Select: the font Same commands skip type on a template sublayer
Squashed from #5566 onto main.
2026-11-04 18:28:32 +07:00
dotCooCoo f0f19077df Document Color Mode: convert reusable paints and restore them with Undo
Squashed from #4517 onto main.
2026-11-04 16:55:43 +07:00
dotCooCoo e3a55b97fd Type: Text Tweaker leaves byte offsets to range_of
Remove Text Tweaker's own start and end check: range_of already refuses
a bad offset given with an id, and like the other type commands Text
Tweaker now ignores start and end without one. The test's "2" case
gets an id so it still reaches the check, and docs/mcp.md lists
addon.type.textTweaker with the commands that refuse such offsets.
2026-11-04 16:55:22 +07:00
dotCooCoo 595db10900 Type: refuse negative or fractional byte offsets in text commands
text.startList read a negative or fractional `at` as the end of the
text, so it could remove a typed list marker there and start a list.
The `start` and `end` of text.editRange, text.getRange,
text.setRangeStyle and the type shortcut commands given an `id` read
such values as the start or end of the text.

These now return "`at` must be a whole number" (naming the key) and
change nothing. Omitted or null keys keep their defaults, and 2.0 is
read as 2.
2026-11-04 16:55:22 +07:00
dotCooCoo a7acf43cb1 .ai import: objects come in bound to their variables
Squashed from #5481 onto main.
2026-11-04 16:33:25 +07:00
dotCooCoo a176ffae46 PDF/.ai import: the file is read and checked once when it opens
Opening a .ai or a PDF read and checked the whole file up to five
times: for the document it carries, for whether it holds the editor's
private data, for the import, for the editor's copy and for its File
Info. Each pass parses the file and walks every object, about 190 ms on
a 15 MB .ai.

- vectorcraft_pdf::open_pdf reads and checks the file once, on the
  import's deep-stack thread, and import_opened imports pages from that
  reading. document.open takes the carried document, the page count,
  the editor's copy and the File Info from it. The editor's copy is read
  after the page import and only when the file is not restored from a
  carried document; so is the File Info.
- A .ai saved without PDF compatibility opened with pages that name
  every page reads its editor's copy from the same reading.
- document.pdfInfo reads the file once for its pages and the thumbnail.
- The Open and Place dialogs read the file once to decide whether to
  ask for pages, instead of twice.

A file whose PDF part fails the check still reads its editing data
separately, as before.
2026-11-04 15:41:11 +07:00
dotCooCoo 146d30502a Expand: selected symbol instances become their art, as Break Link places it
Object > Expand with Object on now turns each symbol instance in the
selection, including one in a selected group or in a placed document's
art, into its art, as Break Link to Symbol places it. It goes one level
deep: the instances in that art stay instances. The art keeps the
instance's id and place in the stacking order, and Expand stays one
undo step.

- Instances with effects stay instances, and Expand Appearance bakes
  them as before. Instances with an effect on a fill or stroke,
  instances in perspective, and those in blends, repeats and Intertwine
  groups stay instances.
- The art keeps the instance's opacity mask, as Break Link gives it.
- A hidden or locked instance's art stays hidden or locked.
- A placed document in an instance's art is expanded too.
- When the instances' art would make more than 1,000,000 objects, they
  stay instances, the limit Break Link uses.
- The Fill and Stroke options (object.expand.info, and the Expand
  dialog) count the fills, strokes and gradients in the art of those
  instances, with a dynamic instance's own changes applied. The info
  reports under symbolArt which of them only that art holds, and the
  dialog enables those options only while Object is on.
2026-11-04 15:14:02 +07:00
dotCooCoo a79ab62712 Select: Same Symbol Instance takes its reference from an object that can be selected
Squashed from #5511 onto main.
2026-11-04 15:13:32 +07:00
dotCooCoo 92640179e5 Type: Select All in a threaded frame selects the whole story
While the Type tool edits a frame of threaded text, Select All selects
the text of every frame of the story, highlighted in each frame. The
tool edits the first frame that holds text.

- Typing, Delete and Cut replace the whole story as one undo step
  (`text.editRange` takes `clear`, the other frames whose text goes).
- Copy and Cut take the whole story (`text.getRange {id, story: true}`).
- Fills and strokes, `type.step`, `appearance.copyFrom`, the Character,
  Paragraph, OpenType and text style panels, the Tabs panel's tab
  stops, the font menus and the Justification dialog act on every
  frame's text (`text.setRangeStyle {id, story: true}` styles them in
  one edit).
- A caret move, a click or a word or paragraph selection ends it.

Select All in unthreaded type is unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-04 15:13:20 +07:00
dotCooCoo 40318753db Blends: Convert on a replaced spine changes only its handles
Convert to Smooth or Corner on a blend whose spine came from Replace
Spine stored key anchors with the converted spine, so the spine's
points then moved onto the key centers. It now changes only the
spine's handles: the points stay where they are and the keys stay
spread along the spine. Direct Selection's spine conversion and the
other spine edits are unchanged.
2026-11-04 15:13:18 +07:00