Without a plug-in store, plugin.install read the process grant once
for its later check and again for its approval. A grant another session
recorded between the two reads approved the module; when that session
revoked it before the install, the check saw the grant it first read
and the install recorded the revoked grant again. The approval now comes
from the first read.
plugin.install published the plug-in before it wrote the module and its
grant to the Plug-ins folder. A plugin.remove in another session in
between removed the plug-in, its file and its grant, and the install then
wrote both back, so the plug-in loaded with its grant at the next launch.
A session attaching the folder in between loaded the old version, which
kept running while the folder held the new one. plugin.remove, loads of
the stored plug-ins and loads of a plug-ins folder now wait until an
install has written the folder, and an install waits for them.
Docs: docs/plugins.md, docs/target-app-parity.md.
Assigning to a named child counts its list of the matching children, and
assigning through an empty XMLList counts the chain of targets it climbs,
before either changes the tree. copy(), toXMLString(), normalize(), the
`..` selector and the depth check on insertion hold the stack they keep
for each open level of the tree. Inserting an XMLList, and assigning one
to an item of an XMLList, hold their copy of the list's items.
copy() reads attributes in place, and toXMLString() reads attributes and
namespace declarations in place, instead of copying the lists. copy()
charges the declarations it copies before it copies them. Deleting by
name or assigning an attribute compacts the remaining links in place
instead of building a second list.
toXMLString() holds the set of prefixes a namespace lookup passes, and
picks a new nsN prefix in one pass over the declarations in scope
instead of rescanning them for each candidate.
The parser holds its open elements and their names, the text it gathers,
each tag's attribute values, the sets that check declarations and
attribute names, and its one copy of an element's namespaces, and it
charges the strings of the namespace declarations it makes. The
allowance it holds from the source's length covers the copy a value
takes as it becomes a string.
Comparing XML values, converting them to strings, assigning an XMLList
to an attribute, and reading a name, a qualifier, a namespace URI or
embedded text from a string built by joining strings flatten it with
flat_charged. An attribute name read from a string reserves its copy,
and an element name shares the string.
Docs: docs/target-app-parity.md.
The previous commit took the spans from how far each piece bends away
from the split whenever both handles there were within ONE_CURVE of the
anchor. A curve that barely moves at the split but doesn't bend there
(a straight one, say) has short handles and no bend, so that gave 0/0
and the side fell back to the polygon shares. Now the spans come from
whichever pair is the longer: the handles, or the bends (which go with
the squares of the spans). The check of every piece against the
rebuilt curve is unchanged.
Test: a_split_curve_keeps_the_top_object_patch_where_it_barely_moves_and_runs_straight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
$.fileName gave the file being run, so a function defined in a file
that #include inserted, in a file $.evalFile ran, or in an earlier run
of a named engine reported the calling file instead of its own.
- vectorcraft_js: HostItem::SiteGetter gives a getter the CallSite
(source tag, line, column) of the code that reads it.
- $.fileName maps that place through the run's source maps, or the
maps a named engine kept, to the file the code is in. It is empty in
code from a script given as source and in kept code whose map was
dropped. Code that eval or Function compiled still gets the file
being run.
Docs: docs/mcp.md, docs/target-app-parity.md.
affinity-corpus.yml ran a local job; it now calls craft-common-actions
gate.yml with the same toolchain (1.95), environment and commands, and a
cache of its own. release.yml's git-sources copy is removed: the shared
release.yml runs the same check before any build.
The sweep filtered by transparency kept every open box in one tree, so
a box without transparency met every open box without transparency it
overlapped, though such pairs never join. Both sweeps closed an open
box early only when a later box held it, so in a nest of boxes that
share their left and top edges and get smaller to the right and
bottom, each box met every earlier box around it. One see-through
object inside 512 nested opaque objects took 130,817 meets in the
unfiltered sweep and 131,328 in the filtered one.
The filtered sweep now keeps open see-through boxes and open opaque
boxes in separate trees. An opaque box looks only in the see-through
tree; a see-through box looks in both.
In both sweeps, a box inside an open box it met, ending no further
right, is not opened when that box joins everything it overlaps (any
box without the filter, a see-through box with it). Whatever later
overlaps the inner box overlaps the outer one.
The nest above now takes 512 meets in each sweep, and a nest of 512
boxes alone takes 511 in the unfiltered sweep where it took 130,816.
The groups are unchanged.
The Brush libraries row named four libraries. It now names the nine in
BUILTIN order (crates/engine/src/cmd/brushlib.rs), as the Window ›
Brush Libraries menu shows them, and says nine built-in libraries.
A double-click on a paragraph style's row and the panel menu both open
Paragraph Style Options (ui.paraStyleOptions, paraStyle.edit) with its
seven sections. The type-parity and ui-parity rows still called it the
rename dialog and named ui.paramDialog with paraStyle.rename; they now
name the dialog, its sections and its tests.
The target-app-parity entry said each section has a Reset Panel. Reset
Panel is unavailable in General, and the entry now states that.
An image placed several times in an EPS file (the pieces of an envelope
or a Warp effect, copies of one image, a symbol's instances) wrote its
whole ASCII85 data at every placement: 200 copies for one envelope at
the default fidelity, enough for a large photo to pass the 256 MB page
limit and lose pieces.
The samples of such an image are now written once in the setup as
VCim<i>, and each placement reads them from there:
- Level 3: a ReusableStreamDecode stream, rewound with resetfile and
read through a new FlateDecode filter at each placement.
- Level 2: an array of strings of at most 49,152 compressed bytes, fed
to RunLengthDecode by a procedure.
An image placed once is written where it is drawn, byte for byte as
before. The page limit counts a repeated image's samples once, and a
colorized image is reused per ink.
A paragraph format given to the empty paragraph after a frame's last
paragraph break was lost when the story flowed again: the paragraph is
shared with the next frame's first paragraph, and the next frame's old
style replaced it, in that frame and in the frames after it.
- The thread reflow also notes which frames' last paragraph changed
style, and story_paras_restyled keeps that style against the next
frame's unedited first paragraph, across unedited empty frames too.
When the same edit formats the next frame's first paragraph, that
format applies, as before.
- story_paras keeps its behavior for its other callers.
- Tests cover area type and type on a path on either side, a next frame
holding text, three frames, undo and redo, a format that changes
nothing, and numbered list counts.
plugin.install wrote the module and its grant to the Plug-ins folder,
attached the plug-in's settings and cache and turned a disabled plug-in
on before the registry's last approval check. When another session
revoked the grant, or recorded one for other bytes, after the command
recorded its own grant and before that check, the install was refused
but left the new module and grant in the folder for the next launch.
A plug-in another session installed under the same id between the
command's checks and the install took no part in the decision, so the
install replaced it without `replace: true`.
- registry::install_checked records the grant (or forgets it) and
installs the plug-in as one step, after a caller check that sees the
plug-in installed under the id and its grant; a refusal changes
nothing.
- plugin.install commits through it before any store write, refusing
with the existing messages when another plug-in now needs `replace`
or the grant its approval came from changed. The store writes, the
put-back on a failed write and the disabled-list update now run after
the commit; their logic is unchanged.
- The grant call-site audit also counts registry::install_checked.
Docs: docs/plugins.md, docs/target-app-parity.md.
A whole pick from a source with no fill row, or no stroke row, removes
that row from the target. When the target is a graph series mark whose
row had paint, the pick now replaces the row opacity and blend mode the
graph keeps for that kind of row in the series. A row that had no paint
before the pick still leaves the kept values as they are, and a pick of
some attributes from such a source is unchanged.
Text wrap can split a line into spans that share a baseline. The spans
after the first were measured across the height of the line set in the
first one, so a taller line set in a later span could run into a wrap
object the shorter line stayed clear of. Each further span is now
measured again across the height of the line that goes in it. The line
gets the part that is free, and a span with no free part, or with a part
narrowed to less than the line's first word, is left empty. The
Every-line Composer and the line loop both place lines through
`Pen::place`, so they agree.
The Every-line Composer also keeps a line start whose line found no room
while a longer line from it would be taller. A taller line steps down
the frame by its own leading and can find room the shorter line's steps
missed.
Tests: a_further_span_is_measured_across_the_height_of_its_line,
composition_tries_a_longer_line_after_one_that_cannot_be_set.
Part of #2224.
A Smooth tool drag only refitted the anchors in its reach within
Fidelity, so a wobble wider than Fidelity stayed, and a second drag over
the same place changed nothing. Repeated refits also drifted away from
the path, and a drag over a whole closed path left a corner at its first
anchor.
- path.smoothRegion now smooths the stretch in reach along its length
with a Gaussian whose spread is a third of the reach, then refits it
within Fidelity. The run's outer end points and handles stay fixed.
- Each refit stays within twice the distance the smoothing moved the
path (at least a tenth of Fidelity), so repeated drags do not drift
(SimplifyOptions::smooth_moved_only).
- A whole closed path is smoothed across its start as a loop, so a
bump at the first anchor is smoothed like any other and no corner is
left. Object > Path > Smooth smooths a closed path across its start
the same way.
Split where the curve stops (a cusp), both handles at the split lie on
the anchor, so their lengths say nothing of how the curve's parameter
divides between the two pieces, and the side fell back to the polygon
shares. When both handles lie within ONE_CURVE of the anchor, the
spans now come from how far each piece bends away from it (the second
derivatives, in proportion to the squares of the spans). The existing
check of every piece against the rebuilt curve is unchanged.
Test: a_split_curve_keeps_the_top_object_patch_when_split_at_a_cusp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A group's or layer's geometry effects now run after its members' own:
the geometry effects of each visible path and compound path in it and
of their fills and strokes, and a nested group's geometry and
Pathfinder effects, are applied first, at any depth, and the group's
effects reshape the result. A symbol instance's geometry effects run
after those of its symbol's members in the same way. Before, the group
reshaped the members' outlines and their own effects ran afterwards:
a 100 pt square with its own 10 pt Offset Path in a group with a 50%
Transform drew 70 pt wide, and it now draws 60 pt wide. Existing
documents with such groups draw differently.
The canvas, export (including groups that keep their type), Expand
Appearance and the art that Pathfinder operations and compound shapes
take from such a group use the same order. The reply of
effect.expandAppearance names such a group and no longer names the
members whose effects the group's art already holds.
The renderer now drops the art it keeps for a group when a plug-in is
installed or removed and a member of the group has a plug-in effect.
Docs: docs/target-app-parity.md. Not yet: the own geometry effects of
images, symbol instances, paths with a linked opacity mask or a 3D
effect, and nested groups with a 3D effect in a group still run after
the group's.
With Real-time Drawing off, a command that ends a drag first (Undo, Redo,
Clear History, a plug-in command and the others) applies the drag's
deferred preview, but dropped the warning that preview gave, which
releasing the drag shows in the status bar.
- The applied preview's warning is now kept while a top-level command or
the tool's actions run. A command's result carries it as
`previewWarning`, and as `warning` when the command has no warning of
its own, so the status bar shows it.
- Typing kept as its own step when a tool's drag begins, or when Cmd
lends the Selection tool, shows the warning in the status bar.
- The Cmd-lend path no longer ignores an error from keeping the typing.
Docs: docs/mcp.md (undo/redo row), docs/target-app-parity.md.
A text chunk placed by an absolute x was measured from the pen of its
first character to the farthest glyph end. The first character of a
right-to-left chunk is drawn at its right, so a chunk of several such
characters was measured as that one character: anchored by middle or
end, or starting a line beside absolutely placed characters, it opened
off its position by the width of its other characters.
- A chunk's left and right edges are the smallest glyph start and the
largest glyph end over all its characters, as SVG 2 anchors a chunk
(https://www.w3.org/TR/SVG2/text.html#TextLayoutAlgorithm). The first
character's pen is placed at its own distance from the left edge.
- The layout that measures the chunks has the direction the imported
text gets: left to right unless the text's direction is rtl. Text
starting with a digit and holding right-to-left letters was measured
right to left.
- The chunks are measured in the first of the layouts that kern the
characters onto their x, so anchored text is laid out once less.
A live-effect plug-in that paints its pieces draws each piece's brushed
stroke along the piece's path at the piece's stroke width. Culling left
room for brush art from the object's own stroke width around the
pieces, so brush art of a moved or wider piece was cut off at the edge
of the view. Culling now leaves 16 x the piece's stroke width + 8 pt
past each brushed piece, and does so for a symbol instance whose art
holds such a path (measured once per symbol) and for a blend, envelope
or repeat that holds one. Object > Rasterize measures the brush art
drawn along each piece and leaves out the object's own brush art, which
is not drawn.
Groups and layers now leave room for their members' brush art. The
cull bounds of a group or layer united its members' bounds without the
room each brushed member leaves, so the painted bounds of a group, and
the area redrawn when it changes, left out brush art past its members'
strokes. That applied to plain brushed paths as well as painted pieces.
Each member is now measured as the renderer measures it when it draws
the group, in the members of a group with a fill, stroke or adjustment
of its own and in a group with crop marks or Vector Halftone too.
measured_arclen returned kurbo's arc length unchecked. For a quadratic
segment whose three points coincide, kurbo 0.13.1's QuadBez::arclen
multiplies 0 by infinity and returns NaN. measured_arclen now returns 0
when the measurement is not finite, the same value it gives a segment it
does not measure. Finite results are unchanged.
Offset Path now turns the overlap sweep the way Outline Stroke and
Unite do. When the estimate for its fill, the ring stroked around it
and the outline of its open subpaths is under two thirds as large for
the parts turned a quarter turn, the faces are found on the turned
parts, the faces within the offset distance of the path are dropped by
testing their points turned back, and the result is turned back.
On an open path of 10,000 anchors in rows of 1,000, offsetting by 2 pt
takes 1.0 s of processor time instead of 1.8 s in a release build.
The offset of a long, low path has as many subpaths as the offset of
the same path stood on end, and the area where the two differ is no
larger than the refit precision times the outline's length.
An Eyedropper pick that includes transparency now counts a fill or stroke
row that had paint before the pick and has none after it when it stores
the series' row opacity and blend mode on the graph, as graphic styles
already do. Before, such a pick left the opacity and blend mode another
mark of the series had stored, and the series was drawn with them again
after its data changed. A row that had no paint before the pick still
leaves the stored values as they are.
A top-object envelope gives each piece of a side a share of the content
in proportion to its control polygon's length. When anchors split a
curve without changing its shape, the pieces' polygons don't divide the
side where the curve's own parameter does, so the envelope bent its
contents differently although its outline was the same: Add Anchor
Points on a top object moved the art, and a top object whose .ai mesh
saves each side's curve split into thirds drew its type up to about
half a point off its drawn art, which follows the whole curve's
parameter.
Coons::from_top_object now checks whether a side's pieces, from corner
to corner, are all pieces of one cubic. The pieces' spans of that
curve's parameter come from the handle lengths either side of each
split, the curve from the outer ends and handles, and every piece must
lie within 0.02 pt of the curve's piece over its span (each control
point). Such a side runs along that curve, by its parameter. Any other
side keeps the polygon shares, including a side only partly made of one
curve and a smooth side drawn in many pieces that isn't one curve as a
whole. Corners found from the outline can still move when anchors are
added, as before. Gradient meshes made from a path are unchanged.
Existing top-object envelopes with such sides redraw.
a_split_curve_moves_the_top_object_share_to_its_new_anchor, which
pinned the earlier behaviour, becomes a_split_curve_keeps_the_top_object_patch.
Tests: a_split_curve_keeps_the_top_object_patch,
a_top_object_joins_a_split_curve_saved_with_rounded_coordinates,
a_top_object_keeps_smoothly_joined_curves_apart,
a_top_object_side_partly_of_one_curve_keeps_the_polygon_shares and
a_top_object_side_of_many_arcs_stays_on_them (vectorcraft-doc),
envelope_top_object_keeps_its_look_when_anchors_are_added
(vectorcraft-engine) and
a_top_object_whose_top_is_one_curve_split_in_thirds_opens_live
(vectorcraft-eps).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A PDF-compatible .ai saved with Include Editing Data for Other Apps now
writes warp, mesh and top-object envelopes as live envelope records in
its editing copy: the grid or contents, Envelope Options, Fidelity,
Edit Contents, a warp's settings and the contents' box, beside the
distorted art, in the form the .ai envelope reader reads. The file
opens with the same live envelopes.
Envelopes the reader would open in another shape or with other
settings are written as their distorted art with a warning: type in
the contents, a turned frame, an old warp map, a mesh laid out another
way round, a top object whose sides do not pair up, a Fidelity that is
not a whole number, bounds margins kept with Distort Appearance off,
envelopes in opacity masks, pattern tiles, clipped art or another
envelope's contents, on a large canvas, and after the first 256.