Commit Graph
10704 Commits
Author SHA1 Message Date
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
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
dotCooCoo 5508f9eccc xtask: test262 help lists the built-in tests and json2.js 2026-11-07 17:38:18 -08:00
dotCooCoo d21ab8f4e2 JavaScript: join with an empty separator does not walk the holes of a sparse array
Array.prototype.join wrote the separator once per index between the
elements it found, so new Array(4294967295).join("") ran four billion
empty writes without taking fuel. Separators are now written in one step
per gap, and an empty separator writes nothing.
2026-11-07 17:38:17 -08:00
dotCooCoo 906fd4cfad Merge main into feat/plugin-menu-commands 2026-11-07 17:34:08 -08:00
dotCooCoo ed5de5edcb Plug-ins: a plug-in adds menu commands of its own under Object › Plug-ins
A filter or live effect may declare up to 32 `commands` in its manifest,
each `{id, label, params?}`. While the plug-in is installed each one is the
command `plugin.<id>.<command>`: listed by `command.list` and in the
plug-in's `plugin.list` entry, run through `run`, MCP and `command.batch`
with its parameters checked before the plug-in runs, and recorded in
actions. A run goes through `vc_run` on the selection with
`_context.mode` "command" and `_context.command`, under the plug-in's
permissions, as one undo step named after the command. A failing run
leaves the document as it was, and its error starts with the plug-in's
name and the command's label. Removing or replacing the plug-in removes
its commands.

In the app, a plug-in with commands is a submenu named after it under
Object › Plug-ins (Effect › Plug-ins for a live effect), holding its own
item and then its commands; a `menu` key in a manifest is ignored. A
command with parameters opens a dialog for them from its menu item or a
shortcut. Edit › Keyboard Shortcuts lists the commands for the user to
give a shortcut; a manifest cannot set one. Labels and names are shown
with control and bidirectional characters escaped, and a malformed entry
is refused by name when the module is installed.

Docs: docs/plugins.md, docs/mcp.md, docs/target-app-parity.md.
2026-11-07 17:32:54 -08:00
dotCooCoo aa3bcc4c77 JavaScript: each NativeError prototype is an Error object
Object.prototype.toString gives "[object Error]" for TypeError.prototype
and the other NativeError prototypes, as ECMAScript 5.1 section 15.11.7.7
defines them. Before, only Error.prototype had the Error class.
2026-11-07 17:22:26 -08:00
dotCooCoo 3e09521b1f JavaScript: sort keys and replacer names count toward the heap limit
Array.prototype.sort without a comparison function and the property list
of a JSON.stringify replacer array keep a string for each element. A key
that comes from flattening a concatenation is a new buffer: it is now
charged to the heap, and keys that are not elements are rooted so that a
collection counts them. Keys that share an existing string's buffer are
not charged again.
2026-11-07 17:22:06 -08:00
dotCooCoo 263c7cb926 JavaScript: arguments from apply stay alive while a native function runs
A native function called through apply could run JavaScript that started a
collection, which freed the arguments apply had read from the array-like
object. The native call now roots its receiver and arguments.

The parsing entry in docs/target-app-parity.md no longer says that nothing
runs scripts.
2026-11-07 17:10:48 -08:00
dotCooCoo bdf6db8d19 Merge main into feat/js-builtins-core 2026-11-07 17:10:42 -08:00
dotCooCoo 1470dd5ca9 JavaScript: built-ins keep a new value rooted while they reserve room to store it
Array methods that store an element (map, filter, slice, splice, concat,
shift, unshift and sort), the JSON.parse reviver walk and JSON.stringify
reserved heap for a value that only native code held. When the heap was
near its limit, that reservation ran a collection that freed the value
before it was stored, and the script later failed with "object out of
range". The value now stays on the temporary roots until it is stored,
and stringify roots an object from toJSON or a replacer before it writes
the separator and key.
2026-11-07 17:09:47 -08:00
Luis 21249399c6 Release: refresh contributor credits for each release (#5997) (#5998)
About > Contributors still showed an old snapshot, because nothing
regenerated contributors/contributors.json before a release. Opt in to
the shared refresh from craft-common-actions: a read-only credits job
regenerates the snapshot for the release commit, and the shared release
workflow installs it before every source build. The snapshot is not
committed back.
2026-11-08 02:07:42 +01:00