Commit Graph
11025 Commits
Author SHA1 Message Date
dotCooCoo a386b3e955 Color fields: a pasted hex code replaces the field's digits
A click put the caret among the six digits and the text box holds seven
characters, so only the first character of a pasted code went in. The
field then held no new color, and Enter closed the dialog with the old
color. A press on a field drawn before the hex field also dropped what
was typed, while Tab and Enter applied it.

The hex fields of the Color Picker, Swatch Options, Recolor Artwork, the
Color panel, Document Setup, Duotone, Color Themes and Perspective Grid
now share one set of rules:

- A click or keyboard focus selects the digits.
- A code of six digits, with or without #, replaces the digits wherever
  the caret is.
- A code of three digits also replaces them, unless the paste holds only
  those digits and exactly three digits stay. Those digits then edit the
  text in place.
- A paste of only one, two, four or five digits edits the text in place
  when it does not cover all of the text and the result has at most six
  digits.
- Any other paste leaves the field as it was.
- A press elsewhere commits the text in the frame of the press.
- A commit returns the digits without # and writes a three-digit code out
  as six, so Duotone reads a typed # and Perspective Grid, whose parser
  reads six digits only, takes a three-digit code.

Part of #5013
2026-11-08 11:22:35 +07:00
dotCooCoo 7d1f2062e3 Actions: Insert Stop pauses playback and shows a message
Squashed from #6028 onto main.
2026-11-08 11:22:34 +07:00
dotCooCoo fe0f3d919c .ai save: the editing copy keeps variables, data sets and bindings
Squashed from #6024 onto main.
2026-11-08 11:22:34 +07:00
dotCooCoo 57dbbbb41b Docs: .ai brush art spot color limits
The file-format entry states the 20,000-name limit across brush art and per section, the first-definition rule, and that colors past the limit still count toward the color mode.
2026-11-08 11:22:33 +07:00
dotCooCoo 8a34c89d7c .ai import: remove the duplicated editing-copy paragraph 2026-11-08 11:22:33 +07:00
dotCooCoo 77b72da70d .ai import: Stair Step spacing is fitted from its glyphs' origins
Stair Step's glyphs stay upright, so their matrices never show the path
turning and the spacing fit found no turn at all. The fit now takes the
path through each glyph's origin, which lies on the path, and measures
how far it turns over a step from the circle through three origins in a
row.
2026-11-08 11:22:32 +07:00
dotCooCoo d180ad456e .ai import: brush art brings at most 20,000 color names, each once
Each brush art section keeps only the first definition of each color name, and at most 20,000 names. The art of the brushes that come in brings at most 20,000 different color names in all, every section's color operators before any section's gradient stops, so art that no brush uses or that a later section replaces takes no places. Colors left out still count toward the color mode.

A test also checks that a global process color which only a brush's art defines with its color operator stays a process color when a gradient stop names it.
2026-11-08 11:22:32 +07:00
dotCooCoo cbfca36c85 .ai import: spot colors in a kept brush's art become swatches
The reader kept a brush's art but dropped the colors it read from it, so
a spot color that only a Scatter or Art brush's art used made no swatch,
and the art's process colors did not count toward the document's color
mode.

The reader now keeps each brush art section's colors with its art. When
the document is finished, the colors of the art that a kept brush uses
are added after the rest of the file's, in the order the art was read:
its spot colors make swatches and its colors count toward the color
mode. Art that no kept brush uses (a pattern brush's tiles, a section
that a later one of the same name replaced, a section inside a symbol's
definition) brings none of its colors, and a spot color that the page's
art, a pattern or a symbol also defines keeps the value defined there.

Part of #637.
2026-11-08 11:22:32 +07:00
dotCooCoo 2fbd60a6dd .ai import: 3D Ribbon spacing holds where the path turns back
3D Ribbon's level baseline faces the way the path runs, so where the path
runs left its baseline and foot are turned round. The spacing fit read
that as a half turn and left the pair out, so a short run on either side
of a vertical tangent came in with no spacing. The fit now turns those
glyphs back first. With an alignment, the two glyphs either side of that
point lie on opposite sides of the path, and that pair is left out.
2026-11-08 11:22:31 +07:00
dotCooCoo 2faec2fa11 .ai import: Skew, Stair Step and Gravity measure the path's turn square to it
The spacing fitted from a type-on-a-path layout measured each turn against
the glyph's own foot. Skew's feet stay upright, so on a tilted or leftward
path the turn picked up a false angle and the fitted spacing was wrong (a
straight path at 30 degrees gave -9.7 instead of 0). Effects other than
Rainbow and 3D Ribbon now measure the turn against the baseline turned a
right angle.

The file-format doc states that a frame whose options set key 6 to true
keeps the default effect, alignment and Flip.
2026-11-08 11:22:31 +07:00
dotCooCoo bc5eb7aa47 Docs: state the limits of .ai type on a path import
Align to Path takes its distance from the path from VectorCraft's font metrics, and the fit under Gravity does not reproduce the file's layout.
2026-11-08 11:22:31 +07:00
dotCooCoo bc19dd64db Simplify: Convert to Straight Lines keeps the tip of a path that turns back past its end
`rdp` measured each sample to the infinite line through the ends of the
line that would replace it. A sample beyond either end could be far from
that line segment and still be dropped: a line to (100, 0), a 0.2 pt
U-turn and a line back to (50, 0.2) became one line at a tolerance of
1 pt, leaving the tip 50 pt away. `rdp` now measures to the segment, so a
sample beyond an end is measured to that end, and a zero-length chord
measures to its point as before.

The test `a_path_that_turns_back_past_its_end_converted_to_straight_lines_keeps_its_tip`
covers that path.
2026-11-08 11:22:30 +07:00
dotCooCoo 53f6aafe87 .ai import: type on a path keeps its effect, alignment and flip
Type on a path from a file's text document opened as Rainbow on its
baseline, not flipped, whatever Type on a Path Options the frame kept.
Each frame now keeps its effect (Rainbow, Skew, 3D Ribbon, Stair Step,
Gravity), Align to Path (Ascender, Descender, Center, Baseline) and Flip.
A code or flag that can't be read leaves that option at its default.

Flipped type opens with the frame's path reversed once, after a copied
object takes its own art path, and an open path's end bracket stays so
the type starts where the file's range ends.

Spacing is still fitted from the file's layout. The fit now measures along
the path when Align to Path moves the baselines off it, and it follows the
glyphs' feet under 3D Ribbon, whose baselines stay level.
2026-11-08 11:22:30 +07:00
dotCooCoo 489552c14b Simplify: the slider's Escape test previews level 0, which changes the path
The test's path zigzags by 0.1 pt. At level 100 the tolerance is 1/32 pt,
and a fit now stays within it, so the preview at that level leaves the
path as it was. Level 0's 32 pt tolerance takes the zigzag out.
2026-11-08 11:22:30 +07:00
dotCooCoo a0bd9bbbad Artboards: duplicate, copy, cut, move and rearrange journal whether locked and hidden art goes
Squashed from #5994 onto main.
2026-11-08 11:22:29 +07:00
dotCooCoo 4a7d732197 Simplify: smoothing a densely sampled path takes time in proportion to its samples
Object > Path > Smooth weighed every sample within three spreads of each
point, so the work grew with the square of the anchor count on traced
paths: 360 steps per sample for 1,000 anchors and 2,949 for 16,000.
Consecutive samples within 1/16 of a spread of each other are now
weighed together as their count at their mean place, found through a
tree of sums over the run's samples. A traced outline takes 203 to 286
steps per sample from 1,000 to 64,000 anchors, and every smoothed point
stays within a thousandth of a spread of weighing the samples one by
one (300 random runs).
2026-11-08 11:22:29 +07:00
dotCooCoo 199c4c3467 Simplify: fitted curves stay within the tolerance of the path, and serpentines simplify faster
The curve fitter split a piece over the tolerance at its worst point and
accepted any piece at depth 41 whatever its error. A worst point near the
end of a piece peels off a few points, so a long path refitted nearly all
of its points at every level. A serpentine of 200,000 anchors kept pieces
with a fitted error of up to about 1,598 pt at a tolerance of 1 pt.

Simplify also took at most 256 samples per segment, so the lines between
the samples on a long arc could be farther from it than the tolerance.

- `fit_rec` moves a split that is near an end of its piece to two fifths
  of the piece from that end, so the depth is logarithmic, and it keeps no
  piece over the tolerance. A piece of 32 points or more is first fitted
  to about 16 of them, and it is split when that fit is over the tolerance.
- A curve is also rejected when it is over the tolerance from the lines
  between the points it fits, with samples close enough that it stays
  within 1% of the tolerance of the chord between them. A pair of points
  that would need more than 1,024 samples counts as too far.
- `join_pieces` joins neighboring pieces when one curve fits them, up to
  33 pieces per curve.
- `sample_steps` takes enough samples of a curved segment to keep it within
  0.5% of the tolerance of the lines between them. A segment that would
  need more than 65,536 samples stays as it is, and so does a segment
  after a run's first 16,777,216 samples or 2,097,152 result segments. With
  Convert to Straight Lines such a segment becomes evenly spaced lines, up
  to 65,536. A fit holds at most 1,048,576 samples, so a longer run is
  fitted in parts.
- Simplify fits at 98% of the tolerance, since turning a nearly straight
  curve into a line moves it by up to 1.5%.
- Above slider level 0, Object > Path > Smooth fits a whole closed path
  with no corner along one direction across its start, when no segment
  stays as it is and the path is fitted as one part. A direct-selected
  section does not get this. The start can still have a kink: a first or
  last fitted curve that is nearly straight becomes a line, which leaves
  along its chord, and a refit with more anchors than the path is
  discarded, so the path stays as it is.

A closed subpath that refits to one curve is still dropped, as on main, so
it can end up farther than the tolerance from the result. docs/mcp.md
states this.

The anchors of every simplified path can differ from before, and so can
the results of the Pencil, Blob Brush and Smooth, which use the same fit.
2026-11-08 11:22:29 +07:00
dotCooCoo b8233f7c19 .ai save: a reopened file opens on the artboard active when it was saved
Squashed from #5975 onto main.
2026-11-08 11:22:28 +07:00
dotCooCoo 74863fa778 Engine: execute refuses keys a command doesn't name, in every build
Session::execute ran check_params only in the engine's own tests, so a
direct execute in a release build ran a misspelled key with the
command's default. It now checks every call, the commands a command runs
included, as MCP and the control port already did. A command.batch or
an action.batch is checked before its first step runs, and each step is
checked again as it runs, so an effect.setParams step is checked
against the effects the steps before it added.

Saved steps play through Session::replay, which takes keys a command no
longer reads, so an action recorded by an older version still plays and
Session::replay still plays an older journal. The Actions panel's Play
(Accelerated, Step by Step and Pause For, its function keys and
ui.action.play) and the Batch dialog use it.

Docs: the cmd/params.rs module doc, the Parameter keys section of
docs/mcp.md and a line in docs/target-app-parity.md describe both
paths.
2026-11-08 11:22:28 +07:00
dotCooCoo fab3425f54 Merge main into feat/js-builtins-text 2026-11-07 20:18:06 -08:00
dotCooCoo 52e94d4f8a JavaScript: compiling a pattern charges the room its lists reserve
- The compiler charges each working list's added capacity before it
  grows (capacity doubles), the code that joins alternatives, and the
  flattened code with its stack, instead of an estimate from
  instruction and fragment counts.
- The scan for capturing groups and the syntax check before compiling
  each take one unit of fuel per unit of the pattern.
- Bytes a dropped HeldBytes released leave the heap's total at the
  next limit check, not at the next collection.
- The progress line lists the deletable RegExp.prototype flag
  properties among the test262 failures.
2026-11-07 20:12:42 -08:00
dotCooCoo 3dd08468a1 Merge main into feat/js-builtins-core 2026-11-07 20:11:06 -08:00
dotCooCoo d503688e16 3D (Classic): effect.lightingPreset refuses Extrude & Bevel (Classic)
Extrude & Bevel (Classic) has no lighting presets; its lights are set in
its own options. effect.lightingPreset now refuses a target whose effect
at the index is Extrude & Bevel (Classic) with an error that names it,
before any object changes. Before, every preset failed on it with an
unknown-parameter error for lightColor. The params text and docs/mcp.md
state the rule.
2026-11-07 20:10:52 -08:00
dotCooCoo f16cf72f07 JavaScript: strings the built-ins build take fuel and stay counted in collections
The buffer that join, JSON.stringify, the URI functions, escape and
unescape write into takes one unit of fuel for every 16 code units
written, so writing a long run of separators or copying a long string
over and over ends with the fuel limit. Its bytes are added back to the
heap total after each collection until the string is finished, and a
native call that ends early gives them back, so a collection while a
string is being built no longer lets the heap estimate drop below what
is in use.
2026-11-07 20:09:09 -08:00
dotCooCoo 4969ea6cab i18n: French rows keep the no-break space before punctuation 2026-11-07 20:08:02 -08:00
dotCooCoo 02e9d4b454 Merge main into feat/plugin-menu-commands 2026-11-07 20:05:58 -08:00
BFlat a7733acc7e Merge pull request #5984 from BOXELS/fix/5980-nssavepanel-null
#5980: macOS save sheet uses alloc/init instead of +savePanel
2026-11-08 12:00:58 +08:00
dotCooCoo edfd25e26c JavaScript: join reads length before it converts the separator
Array.prototype.join now reads and converts the object's length before
it converts the separator, and still converts the separator when the
length is 0, as ECMA-262 5.1 section 15.4.4.5 steps 2 to 6 give them.
2026-11-07 19:58:05 -08:00
dotCooCoo 447a3cc65f JSON: stringify unwraps Number and String objects to their primitive values
Number objects made by ToObject now carry their primitive value, as
String and Boolean objects already did (ECMA-262 5.1 sections 8.6.2
and 9.9). JSON.stringify reads it for a value, a replacer list entry
and the space argument (section 15.12.3, Str step 4, step 4.b.ii and
step 5) when the method that ToNumber or ToString calls first is not
found before the object's own prototype, so JSON.stringify(Object(3))
gives 3 and JSON.stringify(Object('abc')) gives "abc". A valueOf or
toString on the object or on its prototype still runs.
2026-11-07 19:46:01 -08:00
dotCooCoo 3d1ce948bc Docs: the JavaScript text built-ins line matches the radix output and matcher memory 2026-11-07 19:45:57 -08:00
dotCooCoo f916d981d3 Plug-ins: a live effect's commands may change the document
A live effect that declares commands may list `doc.write` and `state`, and
gets them when its manifest leaves `permissions` out, as a filter does. Only
its commands use them: drawing the effect changes no object and keeps no
data. Before, every output of such a command that changed an object was
refused, since a live effect could never hold `doc.write`.
2026-11-07 19:42:38 -08:00
dotCooCoo b6fdecc2e0 Graphs: Appearance, Default Fill and Stroke and graphic style help names the stored opacity and blend mode
The help text of appearance.addFill, addStroke, clear, reduceToBasic,
removeItem, duplicateItem, moveItem, copyFrom, showAllHidden and transfer,
paint.default, and graphicStyle.apply and redefine said the graph stores
only the paint and weight of the topmost visible fill and stroke. These
commands also store that row's opacity and blend mode, as
appearance.setItem's help and docs/mcp.md already say.
2026-11-07 19:42:24 -08:00
dotCooCoo c4aa5cec8a Merge feat/js-interpreter into feat/js-builtins-text 2026-11-07 19:34:34 -08:00
dotCooCoo cea958b782 JavaScript: text built-ins hold their working memory against the heap limit
Native code now holds the Rust memory it works in against the heap limit
until it is done with it: a matcher's capture state and stack, a pattern
being compiled, the matches and pieces of a replacement, and the text
Date.parse reads. A collection counts these bytes as live while they are
held.

- replace reads its replacement pattern once into parts that refer to
  ranges of the pattern, so literal text is not copied for each match.
  Each piece takes fuel for its units, and the result's length is
  checked against the heap as each piece is added.
- The regular expression compiler charges heap through its callback as
  it builds, including the ranges of a character class, and each class
  atom and range takes fuel. A compiled form counts the capacity of its
  lists.
- Clearing the groups inside a quantifier and clearing the matcher's
  state before each attempt take fuel.
- Flattening a rope in a String, RegExp or Date method reserves the
  flat copy on the heap and takes fuel for it.
- A RegExp object's bytes are reserved together with its compiled form,
  so a collection between the two does not drop the compiled form from
  the heap count.
2026-11-07 19:29:53 -08:00
dotCooCoo 65691ceb7b JavaScript: unshift with no arguments reads and writes each element
Section 15.4.4.13 steps 6 and 7 move every element by the argument count, so with no arguments each element is read and put back in place: getters and setters run, and a read-only element throws a TypeError.
2026-11-07 19:27:44 -08:00
dotCooCoo 9d528621f7 Docs: built-in test262 counts after the array length order change 2026-11-07 19:24:49 -08:00
dotCooCoo ab9612c4e6 Keyboard Shortcuts: lists made for removed plug-in commands are freed
The Keyboard Shortcuts entries and the default keymap were made again
for each set of installed plug-in commands and kept for the rest of the
process. They are now shared values kept only for the current set of
plug-in commands, so an older one is freed once nothing holds it.

The test that compares the keymap's catalog with the entry list reads
both for one set of plug-in commands, so a test that installs a plug-in
alongside it no longer makes it fail.
2026-11-07 19:24:19 -08:00
dotCooCoo 2d13f219f6 JavaScript: getOwnPropertyNames lists an array's length after its other names
Section 15.2.3.4 leaves the order of the names open. An array's length now comes after its indices and other properties, so Object.getOwnPropertyNames([0, 1, 2]) gives 0,1,2,length, as later editions and test262 expect. length is not enumerable, so Object.keys and for-in are unchanged.
2026-11-07 19:23:56 -08:00
dotCooCoo 3967847772 .ai save: the large-canvas warning says geometry effects are applied to the art
On a large canvas no art style is written, so the editing data holds an
object's geometry effects only as the artwork they made: the effects can
no longer be edited when the file is opened again. The warning now says
that the geometry effects are applied to the artwork and can no longer be
edited, and that the object's other effects are left out.
2026-11-07 19:18:10 -08:00
dotCooCoo 813fe78a8c Merge feat/js-interpreter into feat/js-builtins-core
Takes #6178's rooting of every native call's arguments, the fuel per argument apply reads and the Error objects as NativeError prototypes. The bound-function and apply arm that rooted arguments only for those calls is removed, since every native call now roots them; the parsing line in docs/target-app-parity.md takes #6178's wording.
2026-11-07 19:12:18 -08:00
dotCooCoo c7baa679d9 JavaScript: apply takes fuel for each argument it reads
Function.prototype.apply reads up to 65,536 elements from an array-like
object. Each element read now takes a unit of fuel, so a long list ends
the run with the fuel limit as other loops in the interpreter do.
2026-11-07 19:02:53 -08:00
dotCooCoo a9e0d95226 JavaScript: assigning an array's length converts the value twice, as defining it does
Section 15.4.5.1 steps 3.c and 3.d convert the new length once with
ToUint32 and once with ToNumber. [[Put]] reaches that algorithm through
[[DefineOwnProperty]], so a.length = v now calls v's valueOf twice, the
same as Object.defineProperty(a, 'length', {value: v}).

Tests count the valueOf calls on both paths, check that JSON.stringify
converts Number and String objects with ToNumber and ToString, and check
that toLocaleString calls each primitive element's method on the object
ToObject makes.
2026-11-07 19:00:44 -08:00
dotCooCoo 14ea556e92 Plug-ins: a malformed ids is refused, and a command out of budget changes nothing
plugin.run and plug-in commands read `ids` strictly: a value that is not
a list, or an entry that is not a non-negative integer, is an error
naming `ids` ("ids must be an array of non-negative integer object
ids"). Left out or null, `ids` is still the selection.

A new test shows that a plug-in command that runs out of its
instruction budget leaves the document and the undo history unchanged,
and that the error names the plug-in and the command.

docs/plugins.md and docs/mcp.md name MCP's `command_list` and
`list_commands` as the listings that show plug-in commands, and the
params text, docs and progress line describe the `ids` rule.
2026-11-07 18:47:22 -08:00
dotCooCoo 60e617f861 JavaScript: parseInt and parseFloat count their copies toward the heap limit
parseInt collected every digit into a vector outside the heap count and
then copied the digits again for a base-10 parse, and parseFloat copied
its whole decimal prefix the same way, so one call on a long string could
allocate more than the heap limit. parseInt now counts the digits in
place, reads bases other than 10 straight from the string, and both
functions reserve heap for the base-10 copy before they make it. A
concatenation that the global functions flatten is charged to the heap,
as sort keys are.
2026-11-07 18:28:44 -08:00
dotCooCoo 02fffcd00c JavaScript: String, Number, RegExp and Date built-ins
A realm now has String, Number, RegExp and Date as ECMAScript 5.1
sections 15.5, 15.7, 15.9 and 15.10 define them, with Annex B's substr,
getYear, setYear and toGMTString.

- String: every ES5 prototype method; split, replace, match and search
  take strings and RegExps. Each new string's length is checked against
  the heap limit before it is built, and scans take fuel.
- Number: toString(radix), toFixed, toExponential and toPrecision
  format from the number's exact decimal value.
- RegExp: a backtracking matcher keeps its choice points and capture
  state on the heap, within the heap room left, and takes fuel for every
  step, so a catastrophic pattern stops at the fuel limit. Patterns
  nested past Options::max_depth give a SyntaxError. Flags g, i and m,
  lastIndex, exec, test and toString.
- Date: chrono converts days to calendar dates, shifting by whole
  400-year cycles past its years. Realm::set_clock and
  Realm::set_time_zone set the clock and local time (UTC by default).
  Without a hint, a Date converts to a primitive as a string.

cargo xtask test262 also runs the suite's built-ins tests for the four
and prints their counts.
2026-11-07 18:24:27 -08:00
Claude 8b6a88e698 Fix #4312: tests for the edges and centre of a move landing on ruler guides
Covers what review asked for: each edge and the centre on its own guide from
either side, two guides at once, which line wins a tie and that a nearer one
wins, the snap distance in screen pixels at several zooms, and a ruler guide
that is hidden (guides off, or its layer hidden) pulling nothing.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113uEh4wFdhVAzX8oKSWCHC
2026-11-08 01:57:13 +00:00
dotCooCoo 28bc571e34 JavaScript: bind converts the target's length to an integer
A host function has no own length, so a script can define one with a
fraction, and the bound function's length kept it. Function.prototype.bind
now converts the target's length to an integer before it subtracts the
bound arguments, as later editions specify.
2026-11-07 17:55:51 -08:00
dotCooCoo 864b9b9950 Plug-ins: a command's parameters keep their declaration order
The commands list is read one entry at a time from the manifest itself,
so a command's dialog shows its parameters in the order the manifest
declares them, and a parameter declared twice in a command is refused
with an error naming the command.
2026-11-07 17:55:26 -08:00
dotCooCoo e03cf7c5f7 JavaScript: defining a new property checks the heap limit first
Object.defineProperty, Object.defineProperties and Object.create added
each new property without checking the heap limit, so one call could
take the heap estimate past the limit before the run stopped. The
property's size is now reserved before it is added, with the object and
the descriptor's values kept reachable while a collection runs.
2026-11-07 17:45:16 -08:00
dotCooCoo ea27cc0a2e Plug-ins: a command list read during an install is not cached under the new revision
registry::install now changes the revision while it holds the plug-in set's write lock, so a reader that sees the new revision and lists the plug-ins always finds the new one. command_specs also skips caching a list when the revision changed while it was listing. Before, a menu or shortcut lookup that ran during an install could keep the old command list until the next install or removal.
2026-11-07 17:43:02 -08:00