Commit Graph
4860 Commits
Author SHA1 Message Date
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 d9499bc0a3 Handle font wheel input in native viewports 2026-11-04 19:41:47 +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 7a3c09c05d Keyboard Shortcuts: the backtick key's label ignores the next key's text on the web
eframe's web build sends each key's text before its press. When several
keys arrive in one frame, the editor paired the backtick press with the
text typed by the key after it, so a shortcut on that key could show the
next key's character. The editor now pairs each text with its press
through `shortcuts::typing_presses`, which shortcut dispatch already
uses, for both event orders.
2026-11-04 18:56:43 +07:00
dotCooCoo af14fe3d59 Align Text Tweaker ranges to character boundaries 2026-11-04 18:56:40 +07:00
dotCooCoo a7266dd10a Character: Kerning reads (10+5)*2 as nothing instead of 10+5*2
Numeric fields dropped every parenthesis before reading a value, so
(10+5)*2 typed into a field committed 20. Only the Kerning field, which
shows its automatic value as (12), now drops one pair of parentheses
around the whole text. Parentheses anywhere else are refused, and fields
without that display refuse them too.
2026-11-04 18:56:36 +07:00
dotCooCoo 6fa0d6e4e0 Navigator: cached raster effects follow Draw Dashed Lines as Solid Lines
The raster-effect cache key now includes the setting, so a renderer that
draws the same art with it on and then off no longer reuses a drop
shadow or other raster effect made with the other setting.
2026-11-04 18:29:29 +07:00
dotCooCoo ddf3373cf0 Navigator: translate the Light Red view box preset
Add a Light Red row next to Light Blue in all 26 catalogs, so the preset
name in Panel Options shows in the interface language.
2026-11-04 18:29:28 +07:00
dotCooCoo 30f903bf60 Navigator: Panel Options sets the view box color and draws dashed lines as solid
The Navigator panel menu's Panel Options… is enabled and opens a dialog with
the view box color (a preset or a custom color; Light Red, today's color, is
the default) and Draw Dashed Lines as Solid Lines, off by default, which draws
dashed strokes without their dashes in the panel's thumbnail only. The
options are kept with the UI state, and ui.navigatorPanelOptions sets them.

RenderOptions::solid_dashes draws dashed strokes as solid lines, including
type strokes and the silhouettes of stroke effects.
2026-11-04 18:29:28 +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 12508e6c60 Dialogs: a color with an alpha part is refused instead of losing its alpha
`Color::from_hex` reads `#rrggbbaa` and keeps only its first six digits, so
a `color` set to `#ff000080` through `ui.dialog.set` was applied as
`#ff0000` without an error by Tile Edge Color, Live Paint Selection Tool
Options, Live Paint Bucket Options and Gap Options.

- A new `checked` next to `resolve` in `dialogs/tile_edge_color.rs` gives
  the existing "is not a colour" error for an 8-digit hex color, and
  otherwise reads the color as `resolve` does. `resolve` is unchanged, so
  the swatches the dialogs draw with it show such a color by its RGB as
  before.
- Each of the four dialogs' OK checks the color with `checked` first, and
  leaves the dialog open with nothing changed. Tile Edge Color and Gap
  Options already refused `#rgba`, which `from_hex` does not read. Live
  Paint Selection and Live Paint Bucket used to ignore a color they could
  not read, such as `#f008`, and apply their other fields; their OK now
  refuses it.
- With Preview on, Gap Options sends no color for a `#rrggbbaa` value, so
  the groups keep their own color, and the previewed gaps draw in black.

Docs: the params text, docs/mcp.md, docs/control-protocol.md and
docs/target-app-parity.md.
2026-11-04 18:29:13 +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 dbfe3fdc38 Global Edit: the options dropdowns close when Global Edit starts
The Global Edit Options dropdowns in the Control bar and in the
Properties panel's Quick Actions turn their button off while Global
Edit is on, but a popup that was already open stayed open and still
changed the options. Both dropdowns now close their popup while Global
Edit is on, and it stays closed after Global Edit stops until the
button is clicked again.
2026-11-04 18:29:06 +07:00
dotCooCoo f11df30d23 Type: Kerning menu lists Auto and Optical for mixed kerning modes
A selection that mixed kerning modes left the Kerning dropdown without
its Auto and Optical entries, so one mode could not be set on all of
it. The menu now lists both entries with neither checked.
2026-11-04 18:29:03 +07:00
dotCooCoo 4a9ccc6fe3 Preferences: a Render Threads change before the first UI pass restarts the canvas renderer
The first UI pass applies the saved preferences again (#4837) by removing
the record of what was last applied. A Render Threads change made by a
control request between an earlier tick and that pass was then applied
with no previous value to compare, so the canvas renderer kept its old
thread count. The pass now forces the theme refresh and keeps the
record, so the thread change still restarts the renderer.
2026-11-04 18:29:00 +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 1600696aea Canvas: a drag's release reads Backtick as it was at the button-up
A press or release of Backtick, or a window focus loss, that came after
the mouse button went up in the same frame changed whether the drag
committed a pattern-only transform. The release now takes Backtick's
state at the button-up from the frame's ordered input events.
2026-11-04 18:28:53 +07:00
dotCooCoo 5ebb6796fe Tests: skip the Arabic Optical kerning test without craft-fonts
optical_pair_after_rtl_separate_mark_uses_base_outline sets Noto Sans
Arabic, which is not in assets/fonts. Without CRAFT_FONTS_DIR the font
database returns a fallback face, so the test checked that face instead.
The test now skips when Noto Sans Arabic is missing, as the Shippori
Mincho tests in the same file do.
2026-11-04 18:28:50 +07:00
dotCooCoo c24c06ef7f Direct Selection: a marquee over a long path tests its segments without building a path for each
The marquee's segment test built a two-anchor path for every segment and ran
hit::intersects_rect on it. It now skips a segment whose anchors and handles
all lie past one side of the marquee, and tests the rest directly with the same
bounds and edge checks, so it selects the same segments.

A marquee over 10,000 anchors, press to release (anchor_bench, median of 7):
one open path of 200,000 anchors 84.9 ms -> 24.1 ms, 2,000 closed paths of 50
anchors 27.5 ms -> 13.5 ms, one closed path of 50,000 anchors 14.6 ms -> 8.5 ms.
2026-11-04 18:28:47 +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 55dc5bb4a5 .ai import: a brush object without a name counts as a brush left out
A plug-in object that names a brush kind but ends before the brush's
name was dropped without a trace. When it was the file's only brush
object, the document kept the default brushes and showed no note. It is
now counted with the other brushes that can't be read, so the file gets
the Brushes panel note and no default brushes.
2026-11-04 18:28:40 +07:00
dotCooCoo 67cc383d77 Shortcuts: a held Escape that cancels a Tools or ruler move skips the tool
Holding Escape to cancel a floating Tools panel move or a ruler-origin
drag no longer passes its key repeats to the active tool or to the
other Escape actions (pattern edit, isolation, a flyout) once the
cancel frame has passed. The repeats are skipped until Escape is
released or pressed again. Tests hold Escape through both cancels in
each screen mode.
2026-11-04 18:28:37 +07:00
dotCooCoo 4c51f853c1 Render: node renders follow the anti-aliasing option
render_node_with and render_node_thumbnail_with drew into a new context
without setting its aliasing threshold, so AntiAlias::None still gave
smooth edges. Both now set the threshold from the options, as a page
render does.
2026-11-04 18:28:35 +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 8d0f251c37 Examples: anchor_bench's doc comment says what the example prints 2026-11-04 16:55:30 +07:00
dotCooCoo d6190e79e0 Render: a filter layer's offscreen culls art and crops long strokes to its own area
On a multithreaded context, a drop shadow or glow drawn through a filter
layer is drawn into an offscreen that covers the art plus the blur's
spread, but its frame kept the view's visible rect. Art outside the view
but within the spread was culled, so it cast no shadow, and a long stroke
was outlined only near the view, so its far parts no longer fed the blur.
The offscreen's frame now takes its visible rect from its own area, and
the frame of content kept from an earlier split takes the window it is
drawn over.
2026-11-04 16:55:30 +07:00
dotCooCoo 676cc0ee8e Render: a long stroke is outlined only near the view after an edit
The renderer outlined the whole stroke of an edited path on every frame,
so dragging one anchor of a path with many anchors re-outlined every
segment. A stroke line of more than 1,024 elements without dashes or a
width profile is now outlined only for the segments within the stroke's
reach of the view, inflated by the view's own size on each side. The
cached outline keeps that region, and a frame outside it outlines the
stroke again. Closed subpaths that a wide stroke crosses over stay whole,
and a cut closed subpath is walked from a removed segment so no cut lands
where it starts and ends. The pixels are the same.

At 1600 x 1200, the frame after one anchor moves takes 28 to 30 ms
instead of 103 ms on an open path of 200,000 anchors, and 10 ms instead
of 30 ms on a closed path of 50,000 anchors (the new anchor_bench
example: cargo run --release -p vectorcraft-engine --example anchor_bench).
2026-11-04 16:55:30 +07:00
dotCooCoo af604625b3 Panels: showing a hidden floating group returns its window's panels
`window.panel` on a panel whose group was hidden returned the whole
layout and every floating panel in every window. It now returns
`{floating}` with the panels of the restored panel's window, as it
does when it shows a panel that already floats.
2026-11-04 16:55:26 +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 1d459796ad i18n: term checks match whole words and the Info row's Tracking label
The Leading check now matches each word of the Leading field as a whole
word in languages that separate words with spaces, so a longer word such
as "liderlik" no longer stands in for "lider". Chinese, Japanese and
other unspaced scripts keep substring matching.

The Info row check now looks for the whole Tracking name in the label in
front of {tracking}, after taking out a Kerning or Leading name that
contains it. A word of the name found in the Size label, or a Kerning
label that contains the Tracking name, no longer passes.

The Czech Auto Leading row uses the singular "proklad", as the Leading
field does.
2026-11-04 16:55:19 +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 322f1c24db .ai import: brushes come in from the editing data
Squashed from #5046 onto main.
2026-11-04 15:41:24 +07:00
dotCooCoo f4469e16d6 Control bar: the Global Edit Options dropdown shows while Start Global Edit can run
While Select > Start Global Edit can run, the Control bar shows a Global Edit
Options dropdown after Recolor Artwork. It opens the Properties panel's Global
Edit Options (Match: Appearance, Size; On Artboards; Include Objects On
Canvas; Result(s)), so both set the same options, and it is off while Global
Edit is on, as the Properties dropdown is. The bar leaves it out when no room
is left for it.

Public reference:
https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html

The dropdown takes room ahead of the inline X/Y/W/H fields, so the ruler-origin
tests that read X/Y from the Control bar draw it 1700 px wide.
2026-11-04 15:41:15 +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 a8c6d078e5 .ai import: blends keep their Specified Distance and Smooth Color spacing
A blend's record in the editing data holds its Blend Options spacing.
Word 1 is the spacing: 0 is Smooth Color, 1 is Specified Steps and 2 is
Specified Distance. For Specified Distance, word 2 is the distance in
points as a 32-bit float. The reader took only records whose word 1 is
1, so every Smooth Color and Specified Distance blend came in as its
drawn steps.

A Smooth Color or Specified Distance blend now opens live with that
spacing where VectorCraft lays out as many steps between each pair of
keys as the file draws, in the same places and colors. Otherwise it
keeps its drawn steps with the existing note. The distance is read as
the shortest decimal that reads back as the stored float. The steps are
counted before anything is laid out, and a blend takes from the
import's budget only when its count matches. Specified Steps blends
are read as before, with the steps counted from the drawn ones.

Tests: import::ai::tests::a_blend_keeps_its_spacing,
a_blend_of_three_keys_keeps_its_distance_between_each_pair,
a_blend_whose_spacing_doesnt_lay_out_its_steps_keeps_them,
import::ai::blend::tests::records_say_their_spacing,
a_blend_takes_from_the_budget_only_when_it_is_laid_out,
a_distance_past_the_keys_lays_out_no_steps, and the fuzz case
ai_blend_spacing_never_panics.
2026-11-04 15:41:08 +07:00
dotCooCoo 663c3264ca Type: list stylistic sets whose lookups come from FeatureVariations
A variable font can leave an ssNN feature empty in GSUB or GPOS and
supply its lookups through a FeatureVariations substitution. The
OpenType panel skipped those sets because the default feature has no
lookups. A set is now listed when a substitution in a variation record
replaces its feature with one that has lookups, and its name is read
from the default feature or the substituted one.

Records after one without conditions are ignored, and at most 4096
variation records and 4096 substitutions are read per table.

Follows #5447.
2026-11-04 15:41:05 +07:00
dotCooCoo 9c191793e2 Images: a PNG read a row at a time counts all of the reader's buffers
The room estimate for a large PNG read a row at a time counted 32 bytes
a column. The png reader also keeps at least 128 KiB of rows already
read, the 32 KiB deflate window and a growth step in its stream buffer,
and up to four rows for wide images, so a decode could be approved with
less room than reading takes. The estimate now comes from the PNG
header's row length and these buffers, and a PNG is read a row at a
time only while that estimate fits the decode allowance.
2026-11-04 15:41:03 +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
dotCooCoo 4aa8a8b0c8 Layers: Template always locks the layer in Layer Options
Template sets Lock again even when Lock was set in the dialog before it,
so a template layer is always locked. Only a Dim Images set before
Template keeps its value.
2026-11-04 14:56:41 +07:00