- The 45° move assertion compares dx and dy with a small tolerance instead of
exact equality (the matrix's floats are rounded), keeping the non-zero check
and the message.
- The engine test helpers say what they do, so the docstring coverage of the
functions this pull request touches is complete.
The Selection tool's cursor now checks the ruler guides before a selected
object's centre point, as a press does. Tests: a ruler guide under the centre
point keeps the pointer and drags; on a box too thin for its centre to clear the
handles the handles take both; a shape-tool move from the centre snaps and shows
the hollow arrow; and Alt pressed mid-drag copies the shape through the engine.
The Selection tool's cursor now checks the bounding-box handles before a
selected object's centre point, as a press does (on a thin box the centre lies
within a handle's reach). The shape tools' tests cover a Live Corners widget
that lands on another selected shape's centre (the widget rounds), Alt pressed
mid-drag (the preview copies and the copy cursor shows), and the engine tests
check the copy's place and both gestures' Undo and Redo. The cursor catalog says
which arrow a drag of the selection shows.
- The Selection tool takes a Shift-press on a selected object's centre point
too: a Shift-drag moves the selection held to 45° steps, and a Shift-click
there takes the object out of the selection, as a Shift-press on the object
does.
- One rotate zone for the Selection tool and the shape tools
(`select::rotate_zone`): a press on a bounding-box handle is not a turn, so
the shape tools' zone matches the Selection tool's classification exactly.
- The shape tools' hover cursor follows the press order (corner and pie widgets,
then the centre point, then the rotate zone), and a move of the selection
shows the Selection tool's feedback: SelectionCopy with Alt, ArrowHollow while
it snaps.
- Engine-level tests for the whole gesture (`crates/engine/src/tests/shapetools.rs`):
repeated previews and one undo step, Alt copy, Escape then release, a turn
about the centre, the corner widget still rounding, and an unfilled shape
moved by the Selection tool.
- docs: evidence in target-app-parity.md's Shapes bullet and the M2 milestone,
and the Move cursor's entry names the copy and snap cursors.
A press on the centre point of the selected shape now moves the selection, as
asked (#5477), so the Constrain Angle test's repeated draws from the same point
moved the polygon drawn before them. It deselects first: nothing selected, the
press draws.
A new test binary opens a document of 13 type objects with the plans a
font face keeps and again while every shaped segment makes its own plan.
The document, its cached bounds and baselines included, and the first
frame's pixels must be the same. `test_fonts::keep_shape_plans` switches
the plans off; it exists only in tests and with the `test-fonts` feature.
The engine's open test now builds objects of one script and direction
that differ in kerning and language, and lays each one out in a font
database of its own, so a plan used for the wrong features fails its
comparison. It no longer sets Arabic, which could fall back to a system
font in one database and not the other.
Every shaped segment built its own harfrust shaping plan, although a
plan depends only on the face, direction, script, language and features.
Opening a document lays out each type object once, so a document of many
short objects built thousands of identical plans.
A font face now keeps up to 32 plans, oldest dropped first, and shapes
with the one that matches. Feature ranges still come from the shape
call, so text with the same features over other ranges shares a plan.
The glyphs, bounds and baselines do not change.
Docs: docs/target-app-parity.md.
The clipping path around a transparent image flattened for a version 7
.ai followed 2 pt cells, a visible staircase. It now follows the rendered
pixels (in blocks only past 4 million), grown by one pixel so the whole
anti-aliased edge is inside, through the middle of each pixel edge and
simplified with Douglas-Peucker, so slopes and curves are sloped lines.
A group whose style holds Effect › Pathfinder › Intersect, Exclude, Subtract,
Minus Back, Divide, Outline, Trim, Merge, Crop, Hard Mix or Soft Mix now opens
with its members editable and the effect live, as Add already did
(https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/combine-objects-using-pathfinder-effects.html).
The Command numbers 1 to 10 are VectorCraft's own reader rules, learned from
saved files. Soft Mix's number 11, and reading its Mix from 0.01 to 1 as a
Mixing Rate of Mix times 100, are also VectorCraft's own reader rules.
These still open as the drawn look:
- Trap, and the effect on a path, on type, on a layer or on a fill or stroke.
- Any operation whose saved Pathfinder Options (Precision, Remove Redundant
Points, Divide and Outline Will Remove Unpainted Artwork), ConvertCustom,
Mix or Trap settings differ from the values the reader accepts, although
only Soft Mix uses Mix and none of these operations uses the Trap settings.
The import fuzz test now also gives the style every option with any Mix, and
checks that a live Soft Mix's rate is from 1 to 100.
A .ai file that File > Export As, its dialog or a control request writes
to a path now names the view's active artboard, as document.export and
document.serialize do. The UI export records the view's artboard and
takes its document from fileio::export_doc, which applies
DocState::active_artboard to the export's copy; document.export and
document.serialize use the same function.
The app now records the view's active artboard before every command,
including the commands the UI handles itself, so a .ai copy from Save a
Timestamped Copy or Export All Open Documents names it too. Before,
these files named the artboard the document opened on, or the first.
The open document is unchanged.
The editing copy of a .ai or .ait file that VectorCraft saves or exports
now has `%_<n> /Int (CropAreaActive) ,` after the ArtboardArray in its
recorded part, and marks that artboard as the selected one. The app
records its view's active artboard before each command
(DocState::active_artboard; an index past the last artboard is the last,
as in the Artboards panel); without a view, the artboard the document
opened on (Document::opened_artboard) is written, else the first. A .ai
file read from its editing copy opens on that artboard. Before, the copy
named no active artboard and always marked the first as selected.
Docs: docs/mcp.md, docs/file-format-parity.md, the editingData option text.
Bulgarian was not among the text languages, so Bulgarian text could not
be tagged and fonts drew their Russian-style Cyrillic. Bulgarian (bg)
is now in the language list (Character panel, character styles,
text.setFormat by name or tag, the Hyphenation preference's default
language), with CLDR's quotes, „…“ at either level. Shaped as bg, the
fonts' locl forms for BGR apply: in the bundled Source Sans 3 Italic,
в, д, ж, з, к, л and ю take their Bulgarian forms.
Fixes#5945
Tab-separated rows with the same number of cells (quoted cells too)
become a group of point type, one per cell, in columns and rows over
0.5 pt gridlines; other text pastes as before. Fixes#5296.
The Eyedropper, the Live Paint check for paint to sample, the Appearance
panel's Characters chips and the colors the Libraries panel adds from
the selection found the paint behind the Fill or Stroke proxy through
Node::proxy_paint, which measures the object's geometric bounds. They
now read it with Node::proxy_paint_only.
- Eyedropper: the test for an empty fill reads the paint without
measuring. A sample measures the object only for a gradient or a
pattern, whose color depends on the point; a solid color gives its
color at once.
- Live Paint: the check for paint to sample reads the paint without
measuring.
- Appearance panel: the Characters row's two chips read the paint
without measuring.
- Libraries: adding a color or a pattern from the selection reads the
paint without measuring; a gradient is still measured for its
placement.
The paints and colors are the same as before. On an open path of
200,000 anchors, the Live Paint check and the Eyedropper's sample for a
point inside it take 0.004 ms together instead of 26.0 ms in a release
build.
Docs: docs/target-app-parity.md.
A 3D effect on a group or layer builds a solid from every path,
compound path and type in it, all turned about one shared axis, as the
public docs describe for groups. Fixes#4506.
perspective.grid.get reports define.name only while the grid still has
the preset's fields, as Define Grid's Preset menu shows [Custom]. Part of #5248.
The name remembered for a user library file stays only while the file still
holds the bytes that were written. A file a device name could make, which can
no longer be read, is kept too, and the save goes to a new file.
Co-authored-by: Adithya Shankaran <adikshan11@users.noreply.github.com>
After a restart an .ase file called COM¹- could have been saved as COM¹ or as
COM¹-, and the same goes for its numbered files. Such a file is kept until it is
saved here, so a library named like that gets a new file, once per run. Other
names go on replacing their own file.
An .ase file can't say whose it is, so without this COM¹ saved after COM¹- wrote
over it. The app now remembers while it runs. After a restart COM¹ gets a new
file, which costs a copy and loses nothing.
Find and Replace matched each style run on its own, and the whole-word
test looked only at the characters inside the run, so a run's end read
as a word boundary: with cat coloured red in catfish, a Whole Word
search for cat replaced it. The test now looks past the run's ends at
the characters next to it in the neighbouring runs. Matches still stay
inside one run, as the command documents.
Fixes#5901
all_texts walked every node of a visible, unlocked layer and checked
only each text object's own lock and visibility, so a text object that
was itself shown and unlocked inside a locked or hidden group was found
and replaced. It now stops at a hidden or locked group, as it stops at
a hidden or locked layer: Find and Replace and Find Next with no ids
leave that type alone, however deep the group is.
Fixes#5900
A library called COM¹ is saved as COM¹-, which can be the file of a library
really called COM¹-, and the numbered file one of them then gets can be asked
for by a third name. Saving one replaced the other. Each now gets a free file
name, and saving it again goes back to that file.
Each visible object with a URL (Attributes panel) is written as a Link
annotation with a URI action over its bounds on the page, trimmed to its
clipping groups. Hidden objects, non-printing layers and printed sheets get
none; PDF/X files leave them out with a warning. URIs are percent-encoded
to 7-bit ASCII.
A colorized image whose pixels are all black or white (or clear) is
written as a 1-bit image mask (ImageMask 1): its color as the fill, a 0
bit for each black pixel and a 1 bit for each white or clear one. The
legacy reader opens it colorized again with the same clear pixels, so
the engine no longer flattens or omits it. Other colorized images are
written with their color in their pixels, over white, with the note
for transparent images.
object.blend.spine.moveAnchor now refuses any supplied `with` (even an
empty list) together with `handle`. Direct Selection builds the blend
spine once when drawing the picked points and once per drag preview,
instead of once per picked point. Part of #5285.
When a frame's text ends with a paragraph break and the next frame is empty, restyling that empty frame was undone on reflow: an unedited empty frame after it gave the story's last paragraph its own style again. A restyled empty frame now keeps its style against unedited empty frames that follow it. Unedited empty frames before it, later frames with text and later restyled frames still give the paragraph their style, as before.
The remaining flags under crates/engine/src/cmd that read a boolean with
get(..).and_then(Value::as_bool).unwrap_or(..) now go through switch_or,
so a value such as "false" or 1 is an error instead of the default.
Omitting a flag or giving null keeps the old behavior.
- effect.move: copy
- aligned on the appearance.*, stroke.set, transparency.set and effect.*
commands, checked before any early return
- transparency.viewOpacityMask: on
- view.printTiling: on
- plugin.install: replace and allowHeavy, checked for every plug-in
- edit.clear: guides, and path.deleteAnchors' read of it
- text.create and text.createInPath: vertical (already refused by
check_switches, so their behavior is unchanged)
Loading a variable library deleted every variable of the document with its
bindings, including a text variable of the same name that the library
declares. Text made dynamic and then given a library's variable name lost its
binding, so selecting the library's data sets changed nothing. Such a
variable now stays bound, and the library's data sets change its text; other
variables are deleted as before.
The area an Intertwine member is drawn over another in was exactly the
overlap face, so its edge lay on the edge of the member below. Both edges
were anti-aliased, and the member below showed through as a thin line along
them. The area now reaches 2 pt past the face into the parts where only the
raised member paints (where drawing it again changes nothing), so its edge
lies inside that member.
Direct Selection kept only one picked point of a blend's spine. Shift-click
now adds a point of the same spine or takes it away, and dragging any picked
point moves them all. object.blend.spine.moveAnchor takes `with`, the other
points to move by as much, with the key objects on them, in one undo step.
The Join tool drew a segment between the open ends of two crossing paths
instead of trimming what overhangs the crossing. path.joinScrub now looks for
two crossing open subpaths whose overhanging ends the scrub runs over, cuts
both at the crossing and joins the kept parts there with a corner, in one undo
step. Without a crossing the endpoint join is unchanged.
The reader measured a symbol's definition only to find the page art an
instance covered, so every file with a placed symbol instance opened
with that instance as drawn art, or as its page.
- Each `%AI14_BeginSymbol` definition is read by a reader of its own into
a symbol of the document, with its art (in the symbol's space, y down),
the registration point its dictionary gives
(`AI13PatternRegistrationType`, 1 to 9 from the top left) and the
instances of other symbols in its art. A definition whose art isn't
read whole isn't kept: type, placed files, non-native art, pattern
fills, a gradient or an image the reader can't read or place, and
anything else the reader doesn't read. Its instances come in as the
page draws them, within the box of the art that is read. A definition
that holds an instance of a symbol the file doesn't have isn't kept
either, nor is a definition holding an instance of one of those, and
their instances aren't read. A definition after the first layer isn't
read.
- `%AI24_BeginSymbolList` orders the symbols as the Symbols panel lists
them. A name read twice is numbered ("Dot 2"), and instances name the
first symbol with the file's name.
- `/SymbolInstance` with `/SymbolRef` and `/RTransformMatrix` comes in as
a live instance, with the hide and lock set before it and the opacity,
blending mode, isolation and knockout of the `Xy` after it, as a
group's. An instance with appearance changes of its own
(`AI19SymbolAppearanceMap`), of a symbol that isn't read, or placed
only by `/Matrix` or `/Transform`, comes in as the page draws it.
- Symbol art takes the gradients and art styles read before it, and an
object in it linked to a listed graphic style keeps the link.
- In a symbol's art, the matrices of instances, gradients and images are
read in the canvas's space, y up, about the canvas point of the ruler
origin. Without `%AI3_TemplateBox` an image can't be placed, so the
symbol isn't kept.
- At most 4,096 definitions are read, and a symbol list names at most
16,384 symbols.
Tests: reader tests for each case above, and fuzz cases for damaged and
hostile symbol data.
links.findFiles hashes files up to MAX_HASHED (1 << 30 bytes) and compares larger ones by size only. The params text, the module doc comment and docs/mcp.md said 1 GB; they now say 1 GiB.
An art style with a Distort & Transform effect opened every object but
type as its drawn look. Objects other than type now open as themselves
with these effects live:
- Any object takes a Tweak that moves nothing and Pucker & Bloat at 0,
beside the effects it already took.
- A path, compound path or group takes Transform without copies, or a
Free Distort whose corners make a parallelogram, when it is the only
effect. Neither the style nor anything in the object may paint with a
gradient or pattern. Everything in the object must be a path, compound
path or group without a clip, an opacity mask or effects of its own.
When Transform scales, rotates or reflects, nothing in the object may
be stroked.
- An unstroked path or compound path whose segments are all straight
takes Twist when it is the only effect.
Other objects keep the drawn look, as do Zig Zag, Pucker & Bloat of
another amount, a Free Distort with other corners and Transform copies.
Docs: docs/file-format-parity.md, docs/target-app-parity.md.
Hovering looks up the bounds of every selected object, and each lookup
went through the memo key check, which compares the whole selection. That
made a hover quadratic in the selection: 1.5 s at 50,000 selected paths.
The walk now checks the key once and shares it across its lookups.
Part of #1280.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .ai or EPS graphic style library holds graphic styles and their list
in its editing data but no layers, so it came in as its PDF part or
page with the default styles, and Other Library listed those. A file
read as its PDF part or page, because it has no layers or its layers
can't be used, now gets the styles of its list, in list order, as a
file read from its layers does: File > Open shows them in the Graphic
Styles panel and Other Library lists them in a library panel.
Style stroke weights and effects are scaled by the Large Canvas factor
of an EPS, and for a .ai whose editing data is in the units of its
pages by the pages' UserUnit or the declared Large Canvas scale.
Part of #3349