Commit Graph
4833 Commits
Author SHA1 Message Date
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
dotCooCoo 2ac1fa4cce Layers: Layer Options keeps a Lock or Dim Images that was set before Template changes
Setting Template in the dialog (the checkbox, or ui.dialog.set of template)
also set Lock and Dim Images, replacing a value set in the dialog earlier.
That value was then sent to every selected row, and ui.dialog.set gave
different results depending on the order of its fields. A Lock or Dim Images
set in the dialog now keeps its value when Template changes.
2026-11-04 14:56:41 +07:00
dotCooCoo e5834f7686 .ai import: the layers are compared with every page of a multi-artboard file
A .ai file whose PDF part has a page for each artboard had its layers
compared with the first page only. Art that the layers drew on a later
artboard and its page didn't, or that a later page drew and the layers
didn't, opened from the layers with no warning, and a text object that a
later page doesn't draw stayed.

When the PDF part has a page for every artboard, each page is now
compared with the layers on its artboard, and the comparison fails when
any page differs by more than it allows. Text objects that a later page
doesn't draw are weighed there and left out, unless their layer doesn't
print. Art that differs on a later page is handled as on the first page:
such files now open from their PDF part with the usual warning, where
they opened from their layers before. A PDF part with pages for some of
the artboards, or with its pages cropped to the art, is still compared
on its first page only. Pages are compared until their comparison
drawings take 8,388,608 pixels (about 67 letter-size pages, or 4 when the
PDF part is only images), and at most 128 pages are compared. The type
share in the import's note is now that of the page whose type differs
most.

The layers are drawn once for the pages of the same size and cut into
each page's part. Each page moves by less than half a pixel to start on
a whole pixel, and the column or row the move takes past its artboard
isn't compared. Each page's drawing and result are kept until the layers
change, for the last two documents weighed. Taking a text object out
draws again only the pages within reach of it (every page when type
wraps).

An artboard, or the page of an EPS's editing data, whose sides are within
half a point of its page's is drawn on an area of the page's size, so
both drawings have the same number of pixels. Before, such a file could
open without its layers when the half point changed the rounded size of
its drawing, even when its layers matched the page.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-04 14:56:38 +07:00
dotCooCoo f15a16f2c1 Render: placed-document bitmaps draw undecoded-image marks when the view does
On screen, a placed document is drawn from a bitmap made in the
background, and that bitmap was rendered with the default options. An
image inside the placed file whose pixels cannot be decoded therefore
drew nothing in the bitmap, even though the canvas marks such images
with a crossed box. The bitmap now follows the frame's
mark_undecoded_images setting, the bitmap cache keys on it, and a
bitmap standing in for one still being made has the same setting.
2026-11-04 14:56:36 +07:00
dotCooCoo 3bd1514acb Type: offer stylistic sets only for an installed font, by their own names
A font that is not installed offered the sets of the face drawn in its place; the menu now offers none. Set names the font supplies are shown as they are, without UI translation.
2026-11-04 14:09:34 +07:00
dotCooCoo 4fa4862613 Type: translate the numbered stylistic set label
StylisticSet.name now holds only a name the font supplies. The OpenType panel shows a set without one as `Set {n}` through tl!, so the label is translated, and each UI catalog has a `Set {n}` row.
2026-11-04 14:08:30 +07:00
dotCooCoo 1858da4106 Type: use selected font version for stylistic sets 2026-11-04 14:08:30 +07:00
dotCooCoo b72c822f1d Add font-aware OpenType stylistic sets menu 2026-11-04 14:08:30 +07:00
dotCooCoo f7bebdeaf0 Live Paint: preserve face colors at shared curved boundaries
Capture sweep-piece source attribution before endpoint pinning can move its
midpoint off the original outlines. Keep the existing lookup after pinning
when the original piece has no source.

Add planar coverage and native compound Make/Expand/undo regressions, and
document the existing front-most source-fill rule.
2026-11-04 14:08:23 +07:00
dotCooCoo ccf4252a73 Type: typing a list's start in a continued paragraph starts no list
In a threaded frame whose first paragraph continues from the frame
before, the text at the frame's start is not the start of a paragraph,
and the frame draws no list marker for it. Typing "* " or "1. " there
removed the characters and set a list that never showed. typed_list_at
now returns None for that paragraph.
2026-11-04 14:08:20 +07:00
dotCooCoo 251cc964a5 Actions: exclude recorded steps from playback
Squashed from #5422 onto main.
2026-11-04 13:42:33 +07:00
dotCooCoo a9a2c4dee8 Select: the font Same commands look through a selected group that holds the isolated group
With a group isolated, a selected layer or group that holds the isolated
group is not selectable itself, so the type inside the isolated group was
never reached and the font Same commands returned "select a text object".
The walk now goes through such a layer or group to the type inside the
isolated group that can be selected.
2026-11-04 13:42:03 +07:00
dotCooCoo 8733f3c65d Tests: the Color Picker's R, G and B fields show the working RGB values
The new test opens the Color Picker in each working RGB profile and reads
the R, G and B fields the dialog draws. It fails when the fields show the
display sRGB values instead.
2026-11-04 13:42:00 +07:00
dotCooCoo d18499613c .ai save: an artboard the document does not have is refused, as an export refuses it
A save as .ai or .ait (file.saveCopy, file.saveAs, document.save and
file.saveAsTemplate) set range to "all" before the file was written.
The one exception was a legacy .ai of version 7 given a range, which
its encoder checks. Every other save accepted an artboard, artboards or
range that names an artboard the document does not have (artboards: [5]
on a document of three artboards) and wrote every artboard.
document.export refuses the same value.

The save now refuses such a value with the export's error, under the
name of the command that was run, and writes no file. A range that is
not text, such as range: 42, was also replaced by "all". The save now
leaves it for the encoder, which refuses it as an export does. An
artboard the document has, or an empty artboards list, is still saved
with every artboard, and a legacy .ai still writes the artboards of its
range.
2026-11-04 13:16:24 +07:00
dotCooCoo 6cbfb83c65 Select: Same commands take their reference from an object that can be selected
Select > Same > Fill Color, Stroke Color, Fill & Stroke, Stroke Weight,
Opacity, Blending Mode, Appearance, Shape, Graphic Style and Appearance
Attribute compared the other objects with the first selected object,
even when select.set had put a hidden or locked object, one on a
template layer or one outside the isolated group first. They now take
the first selected object that the Select commands could select, in
selection order, and keep the compound path and isolation rules for it.
A selection that holds no such object returns "nothing selected".
2026-11-04 12:50:38 +07:00
dotCooCoo 6a8e381dfa Actions: New Action leaves its set as the only selected set
New Action in the current set of a Shift-selected range kept the whole range
selected. After the new action was deleted, Delete Selection asked about every
set of the range. The set recorded into is now the only selected set, as it is
when Record starts a recording.
2026-11-04 12:50:35 +07:00
dotCooCoo c54c4567c7 Color controls: preserve working RGB components in numeric edits
Read RGB and HSB numbers in the active working RGB space in the Color panel
and Color Picker. Keep native values through hex and web-safe controls and
cache live components by native color and color settings.
2026-11-04 12:44:49 +07:00
dotCooCoo f581aa8a56 Select: the font Same commands take their reference from the type on a selected layer
A layer that was selected (for example with select.set) gave no reference to
Select > Same > Font Family, Font Family & Style, Font Family, Style & Size,
Font Size and the text fill and stroke color commands: the layer itself is
not selectable, so the type on it was never reached and the command returned
"select a text object". A selected layer or sublayer now gives the type on it
that can be selected. A hidden, locked or template layer gives none.
2026-11-04 12:44:48 +07:00
dotCooCoo 5c224a0d08 Symbols: Break Link keeps the instance's opacity mask
Break Link to Symbol dropped the instance's opacity mask. The art now
keeps it, or a group around the art holds it when the symbol's art has
a mask of its own. The objects in the instance's mask art count against
Break Link's limit of 1,000,000 objects.
2026-11-04 12:44:46 +07:00
ruchirguitarandClaude Opus 5.5 bf58f605fe Text Tweaker: refuse ranges that aren't whole bytes, make no step when nothing changes (#4924)
A `start` or `end` such as -1 or 1.5 was read as absent and changed text
before the range; it is now refused. Characters that already have the value
are left out, so a run that changes nothing returns count 0 and makes no
undo step. docs/mcp.md cites the public formatting-type page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 12:44:44 +07:00
dotCooCoo 79102f6104 Tests: range stroke weight test joins the character stroke tests
The test moves from an inline module in cmd/textedit.rs to
tests/charstroke.rs and uses that file's helpers. docs/mcp.md says that
text.setRangeStyle gives a range's stroke a 1 pt weight as text.setStyle
does, and that its strokeWidth and strokeOptions lengths are in document
points.
2026-11-04 12:44:42 +07:00
dotCooCoo b5301ca74d Fix range text stroke weight defaults and scaling 2026-11-04 12:44:42 +07:00
dotCooCoo 22c2e2c9bd .ai import: copies drawn by a layer's or group's look keep their type
When an effect on a layer or a group draws copies of its art, the editing
data holds the type of each copy inside the drawn look. The reader skipped
that type, so the copies came in without text objects, the page's type for
them was given to other groups by painting order, and the import noted that
a text object may be in another group than it was.

The reader now keeps such type as a text slot without a story, in the
copy's group. The page's type that lies over a copy's other art (at least
half of it in the art's box) goes to that copy's slots by painting order,
before the rest is given out. Of overlapping copies, the one that holds
more of it takes it, and the smaller one on a tie. When the page's type
outside the copies is too little for the other text objects, a copy with
more of the page's text objects than slots leaves the extra ones to them.
A slot that gets no type
is removed and not counted as a text object that couldn't be read.

Type beside a copy's art, and every copy's type in a file past the import's
text-object limits or opened with its type as outlines, goes as before. A
text object with an appearance keeps its own type as before.
2026-11-04 12:06:08 +07:00
dotCooCoo a0c9726491 .ai import: boxes of differing patches on an image-only page use the pixel size of the comparison raster
The comparison of an image-only page draws at four times the comparison
scale and resizes the result to the comparison size. The two sizes round
differently along the shorter side, so a pixel is not 1/scale points
wide. A box at the far edge of a large page moved by up to 0.625/scale
points and could lie outside the object that holds it, so the import
used the PDF part instead of the layers. The boxes now use the size the
raster was drawn at.
2026-11-04 12:06:06 +07:00
dotCooCoo c8128ee65b Graphs: series marks keep a fill or stroke opacity when the graph is drawn again
A graph keeps the paint and stroke weight given to a series' marks, but not
the opacity of their fill or stroke row. An opacity set with
`transparency.set {item}` or `appearance.setItem {opacity}` on a column,
bar, line or other series path was lost the next time the graph was drawn
(a data, type or option change).

- `SeriesPaint` gains `fillOpacity` and `strokeOpacity`. A setItem opacity
  on the topmost fill or stroke of a series path stores it, in the same
  undo step, and the generator puts it on the topmost fill and stroke of
  every path directly in that series' group. Graph designs keep their own
  opacity. A value read from a file is held to 0..1.
- The Appearance commands that change the opacity of a series path's
  topmost fill or stroke (add, remove, duplicate and move a row, Clear
  Appearance, Reduce to Basic Appearance, `appearance.transfer` and the
  Eyedropper) update the stored opacity. They do not change the stored
  paint, which only `paint.setFill` and `paint.setStroke` set.
- An edit that leaves a series path fully opaque stores nothing for a
  series the graph keeps nothing for.
2026-11-04 11:45:16 +07:00
Dylan M. Taylor 0cdcf3039b engine, plugins: cap radar dimensions, resume slice-name suffixes, weigh cache keys 2026-11-04 11:45:15 +07:00
Davit MatchakhelidzeandClaude Opus 5.5 cd53262faf i18n(ka): keep the 23 rows the maintainer asked for
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-11-04 11:45:09 +07:00
Davit MatchakhelidzeandClaude Sonnet 5.5 288bca85c2 i18n(ka): bring the Georgian catalog up to the other catalogs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YMTAbGNRxnMEzRvYnQGphF
2026-11-04 11:45:09 +07:00