SVG export ignored an image's colorize paint and wrote the image's own
black, white and gray pixels. EPS export flattened every colorized image
into a process-color raster, so a spot color lost its plate. Both now
write the image in the fill color, with the coverage the canvas and PDF
export use (darkness times alpha), so white and transparent pixels take
no color.
- SVG writes a coverage PNG as a mask over a rectangle in the color,
shared between repeated images while each keeps its own color. Linked
mode writes the coverage PNG as a file, and SVG Tiny 1.2 writes a PNG
in the color. A colorized image whose pixels are missing, or whose
grayscale pixels can't be decoded, is left out with a warning.
- EPS writes fully covered or uncovered pixels as an imagemask in the
color's process or Separation ink (Registration on every plate), and a
grayscale image as ink samples scaled by coverage. Level 3 masks its
uncovered pixels out and Level 2 writes them white; gray midtones are
written over white, with the existing warnings.
- EPS flattening no longer counts a solid colorize paint on a grayscale
or 1-bit image as transparency by itself. Opacity, blending modes,
opacity masks, effects and overlapping transparent art still flatten
it, and so does a colorize paint the writer can't draw.
The svg crate's tests turn on resvg's raster-images feature to render
the embedded PNGs.
Docs: docs/mcp.md, docs/target-app-parity.md.
Blends, envelopes, repeats and Intertwine groups that hold a pattern fill
with a transparent tile image were each expanded under a limit of their
own, so many of them together had no limit. Their art now counts toward
the same limit as the pattern tiles, and an object whose art would pass
it stays live.
EPS export expanded the pattern fills whose tile images need flattening and
then flattened the result with flatten_document, which expanded them again
with a new object limit. PostScript printing did the same, on the layers
shown rather than the layers that print. EPS export now flattens the
expanded document without expanding it again (flatten_expanded), and
PostScript printing leaves the expansion to the flattener, which runs on
the layers that print, and only checks for such pattern fills to warn.
The expansion counted the objects a pattern fill made only after making
them, so one shape could clone up to 250,000 tile positions times the
tile art's objects past the limit. Each fill now counts its tiles before
they are made, and after outlining strokes and effects for turned or
sheared tiles, and keeps room for the copy and group of the fills after
it. A fill that does not fit stays a pattern fill.
A pattern fill in a pattern's tile art that paints a see-through or
colorized image was not counted, so PDF 1.3 export kept it as a
pattern and failed on its transparency. Detection now follows pattern
fills in tile art down to the three levels the canvas draws, skipping
hidden tile art, and the tiles the expansion makes are expanded again
for them. Expansion stops once the expanded art holds MAX_GENERATED
objects.
Blends, envelopes, repeats and Intertwine groups that hold a see-through
or colorized pattern tile image are expanded into the art the writers
draw for them before the tiles are expanded, so those tiles are
flattened too. Random Transform effects in that art are baked first.
Hidden shapes, shapes in hidden groups or layers and template layers are
skipped: expanding a hidden shape made its tiles show in the file, and
art that is not written no longer makes a flattened copy of the document.
Flattening counted every colorized image as transparency, in the art and
in pattern tiles. A new flattener option, keeps_colorize, stops counting
an image colorized with a solid paint whose pixels are grayscale or 1-bit,
for a format whose writer draws such images in their ink. Opacity,
blending modes, opacity masks and effects still flatten them, and pattern
tiles are still expanded for those. The option is off, so exports flatten
as before; the EPS writer that draws colorized images turns it on.
The EPS pattern tile tests now check the exported colors, and a tile
group with opacity for the warning.
PDF 1.3, PDF/X-1a, PDF/X-3, EPS and PostScript printing flatten
transparency with `flatten_document`, which only looked at art in the
layers. An image with see-through pixels, a colorize paint, opacity or
a blending mode drawn only through a pattern fill stayed in the pattern
definition, so the flattener returned early and the writer drew the tile
opaque and uncolorized (PDF 1.3 export failed on the transparency left).
`flatten_document` now first expands pattern fills whose tile image
needs flattening into their tiles, as legacy .ai save does. See-through
tile pixels count only where the caller counts see-through images (PDF
1.3); EPS level 3 already masks them out. The EPS and PostScript print
transparency warnings now see those tile images too.
objects_nested_in_text_are_clipped_by_the_type now also nests a 4 x 4 Rstr pixel layer under the TxtA and checks that it is read as an image in the clipping group, next to the shape.
An Affinity text object whose children are nested in it (an image dragged
into artistic text, for example) opened as a plain group, so the image
was drawn over the text. The text now becomes a clip group whose
clipping path is the type, as a shape with children already does: the
nested art shows only inside the characters, with the characters' fill
behind it and their stroke over it.
Homography::then_after returned products whose entries could overflow to
infinity or become NaN. A move, transform or grid reproject of type or a
symbol instance whose stored projection has large finite entries stored such
a product, and the type was then drawn flat, since a projection that is not
finite reads as none.
- Homography::checked_then_after returns None when an entry of the product
is not finite.
- project_node composes each type and symbol instance projection with it and
returns false, leaving the object as it is, when one would not be finite;
moves and transforms in perspective and Define Perspective Grid with
reproject then return the horizon error and leave the document unchanged.
- transform_map, attach_homography and size_homography return None for such
a product.
- Edit Text returns the horizon error, and does not enter isolation mode,
when the type cannot be shown flat where it is drawn.
effect.apply now reports the item of the first selected object, whose row
the active item is. With nothing selected, an explicit aligned item returns
the command's error instead of an internal error.
The Appearance panel's active row is a row of the first selected object's
stack, but fill, stroke, gradient and effect edits matched it against the
object that paints first. When the selection was made top object first and
the objects' stacks differ, an edit changed another row: with the red fill
of [red, white, Stroke] active above a [white, Stroke] object, Fill set the
white row instead.
These edits now match the active row against the first selected object (or
the compound path holding it), as Add New Fill and Reduce to Basic
Appearance already do. Gradient edits also read whether the active row is a
fill or a stroke from that object.
A built-in add-on in Object › Plug-ins › Arrange Tools (#4830, #4831, #4832, #4833, #4834):
- addon.arrange.grid lays the top-level selected objects out in rows and
columns, a row or a column (stacking order from the back, visual bounds,
each object centred in its cell), from the selection's top-left or
centred on the artboard.
- addon.arrange.swap swaps two objects' bounds centres, and with `size`
their widths and heights.
- addon.arrange.replace puts a copy of the topmost selected object centred
on each of the others, in its stacking place, kept, stretched or fitted
in proportion; the replaced objects are deleted unless `keep`.
- addon.arrange.size gives each selected object an exact width and/or
height about a reference point.
- addon.arrange.zoomSelection (a UI command) fits the active view to the
selection.
Places and sizes are measured as object.setBounds measures them (its
transform, now also available per object), strokes scale as Scale Strokes
& Effects says, each command is one undo step, and Arrange in Grid,
Replace with Top Object and Set Size of Each open previewed dialogs.
Catalog rows, command sweep cases, docs/mcp.md, parity docs and the
ROADMAP progress log.
Found while testing crates/engine/src/cmd/variablecmds/csv.rs:146 (csv_escape) and :158-163 (cell_to_value)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
run_guarded put back only the active document's art, selection and interaction.
An undo step pushed before the panic stayed (Undo then stepped to a state equal
to the current one) and Redo was lost; other documents a command touched kept
their half-applied edits; the active document was restored by index.
It now snapshots every open document with its history and restores them, and
the active document by uid.
Fixes#3599
With write root ...\out, a save to ...\out\x.svg:evil was accepted: it stays inside the root, but writes a hidden stream no folder listing shows. A ':' in any name after the drive is now refused with the device names. The check is a plain name test, so it is covered on every platform; the check-then-open race (item 1 of the report) is a documented limitation and is not touched.
library_file_name kept CON, NUL, COM1 and the like, which Windows treats as
devices, not files. It now adds a - after such a name (CON-.json).
Part of #3602
Each patch of a gradient mesh was cut into 8 x 8 flat quads for SVG export,
Object > Expand and the other outputs that cannot shade a mesh, whatever its
colours, so a patch of one colour became 64 identical pieces. Neighbouring
quads are now joined into square blocks wherever the colour changes by no
more than about one 8-bit step across the block; the block's outline runs
through its quads' corners, and a patch that folds over itself is not joined.
The move drag remembers the plane it places loose art on. Pressing 4
during the drag, or dragging on after it, previews and commits the move
on that plane instead of dropping it. A 1-3 key pressed before the drag
threshold also sets the plane the move uses.
perspective.move takes constrain: true instead of a plane-space
direction. For each object, the command snaps the move to the nearest
axis or 45-degree diagonal of that object's own plane, so a selection on
several planes keeps every object on a line of its plane. The tool sends
the raw pointer with the flag.
On a diagonal, Snap to Grid now moves along the diagonal by the nearer
per-axis correction, so that edge lands on its gridline.
perspective.move takes an optional constrain: [dx, dy], a plane-space
direction. For an in-plane move, Snap to Grid's correction is projected
onto that direction, so a move the Shift key constrains to a plane axis
or a 45-degree diagonal stays on that line when an object edge lies near
a gridline. The Perspective Selection tool sends the direction for
Shift moves and copies. Moves without the param behave as before.
Holding Shift while dragging objects with the Perspective Selection tool
snaps the move, in the plane's own coordinates, to the nearest plane axis
or 45 degree diagonal. This applies to plain moves and to Alt and
Ctrl+Alt copies. The preview, the final move and the measurement label
use the snapped point. Pressing or releasing Shift, pressing 5 and
pressing 1 to 4 during the drag recompute the preview from the pointer;
a move perpendicular to the plane is not constrained.
The targets are in paint order, so the first selected object is not always the first target. The active row is now matched against the first selected object wherever it is among them.
Reduce to Basic Appearance kept the topmost visible fill and stroke even
when another fill or stroke row was selected in the Appearance panel.
It now keeps the selected row in place of the topmost one of its kind,
and in each other selected object the row equal to it. With ids, or
with no selected row, it keeps the topmost ones as before.
With a fill row selected in the Appearance panel, Add New Fill now
inserts a copy of that row directly above it and selects the new fill.
Rows already above the selected one stay where they are. Before, the
new fill was always a copy of the topmost fill, added at the top.
The selected row belongs to the first selected object. In a mixed
selection, an object that shares that row gets the copy above its own
equal row; an object that does not share it gets a copy of its top
fill on top, as before. With a stroke row selected, or with `ids` or
`target: "contents"`, Add New Fill works as before.
The panel drops rows that were Cmd- or Shift-clicked together when the
active row changes outside the panel, so after Add New Fill (button,
menu or shortcut) only the new fill is selected.
transparency.makeOpacityMask with two or more layers below the mask
object (or a layer and objects of other layers) wrapped the layers
themselves in a new top-level group, so a layer ended up inside a
group. A layer among several masked items now stands for its art: the
masked group takes the layer's visible, unlocked objects, a hidden,
locked or template layer gives none, and the layers stay where they
are. One layer under a mask object, or one layer alone, is masked as
before.
Blob Brush Tool Options gain Angle (-180 to 180 degrees, 0 by default)
and Roundness (0 to 100 percent, 100 by default), with a preview of the
tip and three dabs; a drag in the preview square sets the Angle. The
elliptical tip keeps its angle on the page along a stroke, a 0 percent
tip is drawn 0.1 percent as wide as it is long, and Size set to
Pressure still varies its size. path.blob takes angle and roundness,
and both values are kept with the tool's options.
While a non-round tip drags, the Blob Brush shows the outline of the
area it covers with its length label, and an Alt drag that smooths
shows the Smooth tool's thin line.
Alter Paths for Appearance wrote every stroke as its filled outline, so a
1 pt circle became two circles and a hatch, and an open arc became a
closed outline and a hatch: two cut lines where a CAM program expects
one. The option now outlines only the strokes a line can't draw: the ones
Preserve Appearance outlines, strokes wider than the widest lineweight
(2.11 mm, to the hundredth), and every stroke in R13 and R14, which have
no lineweights. Other strokes stay CIRCLE, ARC, SPLINE and LWPOLYLINE
entities with their lineweights; R12 keeps them as POLYLINEs with their
width.
A dashed stroke whose offset starts part way into its pattern is now
outlined with Preserve Appearance and with Alter Paths: a linetype always
starts at the start of its pattern, so the dashes were written in the
wrong place.
An open path with a single straight segment was written as a two-vertex
LWPOLYLINE (POLYLINE before R14). It is now a LINE entity from its start
to its end point. A stroke with a weight in R12, R13 and R14 keeps its
polyline, since a LINE has no width. Closed paths and paths with more
than one segment are unchanged.
Part of #1299.
The art that replaces an outlined or expanded path did not take the
path's hidden, locked or Knockout Shape state. Outline Stroke on a
hidden path, or Expand on a group with a hidden member, made the new art
visible (a filled and stroked path became half visible: the outline
shown, the fill copy hidden), and a locked member's art was unlocked.
The replacement now takes the path's visibility, lock and Knockout Shape
state, and the fill copies and clipping paths inside it are shown and
unlocked, so showing or unlocking the new art shows or unlocks all of it.
This covers Outline Stroke, and Expand's strokes, gradient fills and
pattern fills.
select.font (the Find Font dialog's Find button) selected every text
object using the font, including hidden or locked text and text in a
hidden or locked group or layer. It now selects only text that is
visible and unlocked through every group and layer, and not on a locked
artboard. Text on a visible, unlocked template layer is still found. A
font used only by hidden or locked text selects nothing.
Select > Same > Fill Color and Fill & Stroke from a face of a Live Paint
group now select the matching faces of that group only, and from any
other object they select no Live Paint face. Before, they matched faces
of every Live Paint group and paths outside them alike.
With a blend selected, the Contextual Task Bar's Corner and Smooth
buttons failed with "select paths or anchor points", since
`path.convertAnchors` only read paths. They now convert every point of
the blend's spine, as they convert every anchor of a path selected
whole, in one undo step. The key objects stay where they are.
With Direct Selection, the Control bar and the Properties panel now
show their Convert buttons for a selected blend too.
With Document Swatches as the library, traced fills and strokes take the
library color without a swatch link. Global, spot and tint swatches no
longer link the trace, so editing a swatch later leaves the trace
unchanged, and swatch order no longer decides whether a fill is linked.
Each region still takes the nearest library color, and a tint traces at
the color the tint swatch shows.
Path Precision Tools gains the Shared Edge Merge Tool, kept out of the
toolbars (All Tools drawer and Object › Plug-ins › Path Precision Tools,
no shortcut), and Keep Shared Points Together:
- Shared Edge Merge Tool (sharedEdge): over two paths touching at two
coincident anchors (tolerance 0 to 3 pt) hovering shows the two
stretches and the merged edge; a click replaces both with it, half way
between them, or following the upper (Shift) or lower (Alt) path. Both
paths stay closed or open, anchors either stretch has inside are
matched on the other, one undo step.
- Making ends: near anchors meet on a click and an anchor one path lacks
is added to match; Shift+Alt asks for this inside a stretch.
- Keep Shared Points Together: an option that makes Slide Anchor Along
Path, Extend Path and Set Anchor take coincident anchors along. Nothing
else in the app changes.
- Shared Edges section in the Anchor Inspector: brush diameter,
coincidence and the option.
- Commands addon.pathtools.mergeEdge and movePointsTogether, params
validated and covered by the command sweep.
- Cursor stand-in, catalog rows in every language, docs and tests.
With the Live Paint Bucket, Alt-click on vector art samples its fill,
stroke and stroke weight into the paint defaults, and on a placed image
it samples the pixel as a solid fill with no stroke. Alt+Shift-click
samples only the color under the pointer, including an intermediate
gradient color or image pixels, into the active fill or stroke. Neither
click paints or changes the selection or selected art, and the next
Bucket click paints with the sampled paint. While Alt is held, the
Bucket shows the Eyedropper cursor and no face or edge highlight.
appearance.copyFrom takes `defaultsOnly: true` to update only the paint
defaults, and paint.sampleColor takes `clearStroke: true` to clear the
stroke default after an image sample into the fill.
With a path selected that the Smooth tool can change, Alt held at a
Paintbrush press now switches the drag to the Smooth tool: it smooths the
selected paths under the drag with the Smooth tool's radius and Smooth Tool
Options' Fidelity, and the cursor shows the Smooth tool before the press.
Alt pressed after a stroke begins still closes the path on release. With no
such path selected (nothing, only text or an image, or only a two-point
line), Alt at the press draws the closed stroke as before.
The Paintbrush no longer reads the Pencil's "Alt key toggles to Smooth Tool"
setting: its Alt close and Alt smoothing do not depend on it.
Saving a legacy .ai with transparent images flattened expands pattern
fills whose tile image is see-through or has opacity or a blending mode,
so the tile images flatten with the art under them. A pattern whose tile
is an opaque grayscale image with a colorize paint was not expanded, so
the writer drew the source gray pixels over white. A colorize paint now
counts too.