A path that keeps its shape because the effect would pass the geometry
budget holds a group's own placed gradients in place. A hidden path in a
layer, which the layer's bounds leave out, no longer holds them, so the
layer's gradient moves with its visible art. The art's own gradients
follow the same rule.
A freeform gradient whose points were edited keeps its vector fitted to
the bounds, so it did not count as placed and its points stayed behind
under a Transform effect and Free Distort unless another placed gradient
was in the same appearance. Its points now move like a placed gradient.
docs/file-format-parity.md held 35 copies of the paragraph under
"Illustrator's private editing data" after #6202 left one, each copy
carrying a different set of the changes merged since. The section now
holds one copy with every clause from the others: placed PDF and .ai
pages as placed documents, Bas Relief among the single-effect art
styles, Bas Relief's settings block in type's art style, the fill a
clipping path keeps with its exceptions, paths with an Art brush, the
spot colors in brush art, symbols defined before the art styles
section, the Type on a Path options, and the artboards the type of a
PDF's later pages goes to.
Align to Path moved the whole line by the largest ascent and descent
among its characters. Each character now moves by its own font, size
and vertical scale. Ascender runs the top of the font's em box along
the path: the ideographic em box that the font's BASE table gives, or
in a Chinese, Japanese or Korean font the one that its typographic or
hhea box, a one-em box-drawing cross or its ideographs give; else an
em box centered on the cap height. Descender runs the bottom of the font's
bounding box along the path, and Center runs a line at half the height
of the top of the font's H along it. In vertical type, Ascender and
Descender move the characters half an em, and Center leaves them on
the path.
Existing documents with Align to Path set to Ascender, Descender or
Center lay their type out at a new distance from the path.
Generic CMYK's tone curves now pass through the CGATS TR 001 tone value
increase of each ink at 40% and 50% (C 20.0, M 17.6, Y 16.8, K 21.6 at
50%), and its 16 Neugebauer primaries mix linearly, so a single ink's
tints gain as TR 001 gives. The 300% ink limit stays. The primaries, the
black-generation curve and the black point are VectorCraft's own values.
- The black point is 90% black under neutral CMY that fills the ink
limit: sRGB black separates to C75 M68 Y67 K90 instead of C67 M67 Y67
K100.
- Black follows a curve over the gray component of the black-free
solve, raised by how far that solve stays lighter than the target.
Grays from sRGB 177 up carry no black, and sRGB 128 gray separates to
C50.5 M42.2 Y42.0 K7.5 instead of C38.8 M26.3 Y29.9 K24.4.
- Display of CMYK fills, swatches and imported CMYK values and the
separation of RGB and Lab colors use the same model.
Paper white still separates to C0 M0 Y0 K0, but other separations
change, and so does the display of CMYK colors with ink. Tests that held
numbers of the old model are updated.
plugin.install wrote the grant and the module as two separate writes,
and either could fail with only a log record. With a grant tied to the
module's hash, the stored pair then no longer matched and the plug-in
was skipped at the next start.
The module is now written first and its grant only once it is there.
When the grant can't be written, the store gets its old module back, or
loses the new one on a first install, and a disabled plug-in stays
disabled. A stored module that can't be read back is not replaced by
one that needs a grant.
A composite (Type 0) font with the 8/8 mapping picks a descendant font
and a code in it with each pair of bytes (PLRM 3rd ed., 5.10). The reader
set its strings as type in a font of the composite font's name, the
bytes read as Latin-1 and at a thousand times the size, so EPS from
pdftops of a PDF with embedded TrueType fonts showed nothing, or one
black shape, where its type was.
Characters now go to their descendants: a Type 42 font's glyphs are drawn
from the TrueType outlines its sfnts strings carry (Adobe Technical Note
5012), by the glyph index CharStrings gives their names, spaced by their
own widths or the show operator's numbers; charpath and stringwidth read
them too. A Type 3 descendant runs its procedures, and a descendant found
by name sets its character as type at the composite font's size.
The boolean's answer went through orient(), which judges each ring's
side from its first point. The boolean often starts a contour where it
touches another, so a triangle whose tip rests on a square's edge was
dropped, and an even-odd hole touching its outline was filled. The
boolean already runs holes against their outlines, so its rings are now
only turned as a whole.
Other fixes:
- A bowtie drawn as one contour has no area of its own. It was dropped
before the meet check and built nothing, although it fills both lobes.
Sampling now keeps rings with no area, and orient() drops them after
the check. Flat rings, every point on one line, fill nothing and are
still dropped, so a stray flat contour doesn't send separate art
through the boolean.
- An open subpath was closed along its end handles when the profile went
through the boolean, but straight otherwise, as the renderer fills it.
It is now closed straight before the boolean too.
- An empty union was taken as a refusal. Contours that cancel (a square
drawn twice under even-odd) built a square. They now build nothing.
- Contours meet within twice the boolean's own sweep tolerance,
computed by its rule from every anchor and handle of the path it is
given. pathops snaps anchors onto nearby heights before the sweep
joins edges within that tolerance, so contours up to about 1.4 times
it apart can come out as one; within twice it, every contour it
might join goes through it, even far from the origin, and the few it
leaves apart cost a boolean that builds the same shape.
- A one-anchor subpath draws nothing, so it is skipped, as the renderer
and the boolean skip it.
- A profile the boolean refuses reuses the rings already sampled.
Tests:
- touching parts and holes;
- an open subpath with handles, alone and beside crossing bars;
- a flat contour beside separate squares;
- contours joined far from the origin, including by handles alone,
and contours a few millionths apart near it kept apart;
- triangles snapped together and joined at 1.4 times the tolerance,
the same triangles left apart and kept whole, and the factor pinned
between 1.9 and 2.2 times, with the worst case just past twice the
tolerance left apart by pathops;
- a 24-bar grid: too complex for Extrude and Inflate, whole under Rotate;
- the merged rings turned as a whole;
- a one-anchor loop;
- the bowtie and the cancelling squares under all three effects;
- a 3,001-point star that the boolean refuses (TooComplex), built as
drawn.
Docs: the pathops README's Limits section now says only the try_
functions refuse, lists OpenPath and the caught panic, and describes
unite_all's answers, and which calls take a precision. three-d.md and effects-parity.md (the 3D row, its
history and status line) describe the merge and its limits.
The boolean that merges crossing contours cost about as much as the rest
of a plain extrusion, and it ran on every profile. Most art has no
contours that meet, so the profile is now sampled first (its point limit
checked there, before any boolean) and resolved only when two edges
cross, touch, run along each other or fold back, in one contour or
across two. Edges are swept by their left ends, within a pair budget past
which the boolean decides. Touching counts: squares sharing an edge
inflate as one puff, not two with a seam.
On a release build, Extrude of 120 separate contours takes 0.31 ms
instead of 4.18 ms; profiles that do cross cost what they did.
Tests: the meeting cases, squares sharing an edge, the fallback for a
profile the boolean refuses, Inflate's extent and area for the crossing
k, and the coincident circles' area. The hairline test now holds a
crossing pair so that its profile still goes through the boolean.
Docs: the merge is described once for all three effects, linking to a new
Limits section in the pathops README instead of repeating its limits,
and the point limit notes that a merged outline can have more points.
A module that lists no permissions now removes the grant from grants.json only after it has replaced the stored module. When the write fails, the old module stays stored and keeps the grant it needs at the next launch.
`plugin.install` accepted `grant` and `allowHeavy` from any client, so
the program installing a plug-in could answer its own permission
prompt.
The desktop app now shows an approval dialog that names the module,
its hash and what it asks for. Confirming it gives the engine a
one-use approval tied to that module id, hash and permission list. A
control or MCP request and a frame of synthetic input cannot confirm
the dialog, and Enter does not confirm it.
Without such an approval, `grant` and `allowHeavy` are refused. The
new `--allow-plugin-grants` flag on `mcp` and `run` lets a person who
starts a headless session accept them from requests. The flag cannot
be combined with `--connect`. A module already approved in the desktop
app still installs through its stored grant.
The editing data of a .ai or .ait holds every artboard, so a multi-page
file has no pages to pick. Opening one now goes straight to it, as a
file carrying its VectorCraft document already does. The dialog stays
for PDFs, a .ai that is only a PDF, a password, and Place.
Two problems in the Character Styles panel and its options dialog.
Opening the options took the panel menu's item, which is easy to miss; a double-click on a style's
row did nothing beyond applying the style a second time. A double-click on a user character style's
row now opens its Character Style Options (`ui.charStyleOptions`), as the menu item does. A single
click still applies the style, the second click of the double-click does not apply it again (the
Normal style's included), and with Shift, Cmd/Ctrl or Alt held the double-click only follows the
click rules. The built-in Normal style opens no dialog. Double-clicking a paragraph style's row similarly opens
its options, which are the rename dialog the Paragraph Styles panel menu opens (`ui.paramDialog`
running `paraStyle.rename`): paragraph styles have no full options window yet.
The dialog then grew with its content: the General section lists every attribute of the style, so
the window covered the screen and its buttons could sit below it. The content now scrolls both ways
inside a window at most 420 points tall (less on a short screen) and never wider than the points
left beside the section list, with one scroll offset per section. The dialog keeps a fixed width of
720 points, enough for the widest section's controls, in every section. The content's height is the
space the footer leaves, measured from the footer drawn last frame, so a large interface scale's
taller footer is reserved too and OK and Cancel stay visible. The section list stays outside the
scroll area, so scrolling a long section never moves the navigation; the section is read after the
list handles a click, so a newly picked section gets its own scroll offset in the same frame.
Tests:
`panels::text_styles::tests::double_clicking_a_character_style_row_opens_its_options` selects text,
double-clicks a character style's row and checks the dialog opens with the style's name, one apply
ran from the first click and the second click applied nothing; a paragraph style's row opens none.
`double_clicking_the_normal_style_opens_no_options` checks the Normal style opens none and is
applied once.
`dialogs::char_style_options::tests::the_sections_scroll_in_a_window_shorter_than_the_general_list`
draws the dialog on a 900 by 560 point screen and checks OK and Cancel stay on it, the dialog keeps
its 720-point width in two sections, and the General list is taller than its scroll area.
`every_section_and_the_buttons_fit_the_smallest_window` checks the app's minimum 800 by 500 point
window in all five sections: every section label, OK and Cancel are on the screen.
`a_scrolled_section_keeps_its_offset_after_switching_away_and_back` scrolls General, clicks Basic
Character Formats and then General again, and checks General''s content returns to the same place:
the section is read after the list handles the click, so no other section stores its offset.
`every_section_and_the_buttons_fit_the_smallest_window_at_twice_the_scale` checks the same in the
400 by 250 points the minimum window holds at 2x UI scale, before and after a wheel scrolls the
content.
Docs: type-parity (a panel row and the revision history), ui-parity (a panel row and the revision
history), target-app-parity (two evidence bullets and the revision history) and roadmap (the
revision history), with the status lines updated.
The remembered selection is now tied to the document it was made in, by its
uid, instead of being cleared on every tool reset: another view of the same
document, or closing an inactive tab, kept the anchors picked but made Copy
take the whole path.
A lent gesture that leaves the selection as it found it (a Cmd+Shift marquee
over nothing) is no longer recorded as a pick, so the Pen's last anchor stays
its own and Copy takes the whole path.
Also records #6123 in target-app-parity.md.
With the Pen or Curvature tool, Copy and Cut ignore the anchor selection,
so that the last anchor the Pen leaves selected doesn't make Copy take just
that anchor (#2122). Anchors picked with the Direct Selection that Cmd/Ctrl
lends those tools were ignored too, and the whole path was copied.
The session now remembers the selection the lent tool leaves when it gives
the tool back. While that is still the selection, Copy and Cut take the
picked anchors and segments, as with Direct Selection chosen.