A CSV file that holds NUL bytes and has no byte-order mark was refused.
It is now read as UTF-16 when at least half of its two-byte units hold
a NUL byte, in the byte order that reads a line break. A file with fewer
NUL bytes (UTF-8 or Windows-1252 text with stray NUL bytes, or UTF-16
text made mostly of characters above U+00FF) is still refused, and so is
a file that reads a line break in both byte orders, has an odd length,
isn't valid UTF-16 in that order or holds a NUL character. Refused files
get the same message as before.
object.distort warped the anchors of paths, groups, compound paths and
clip groups but left their bounding-box angle as it was, so a rotated
path kept its old box after a warp and an upright one stayed square to
the page however far the warp tilted it. Text, images and symbol
instances already took the angle of the warp's affine approximation.
- Each path, group, compound path and clip group now takes the angle
the warp turns its x axis to at its own center before the warp, the
members of a warped group and the art of a linked opacity mask each
at their own. An unlinked mask's art keeps its angle. Mirroring keeps
the nearer angle and a collapsing warp keeps the old one. Reset
Bounding Box squares the boxes again.
- Each subtree's bounds are measured once while it is warped.
- Corners that equal the corners of the box being warped change
nothing and add no undo step.
Docs: object.distort params text, docs/mcp.md, docs/target-app-parity.md.
A Smooth drag along the middle of a curved segment smoothed nothing when
neither of the segment's anchors was within the reach, and paths of two
anchors were never smoothed.
- path.smoothRegion searches each curved segment where it can come
within the reach, halving it down to pieces no longer than half the
reach, and adds an anchor in the middle of each stretch in reach; the
existing reach split then cuts the segment where the drag enters and
leaves the reach. A drag that only grazes a curve finds the place on
it nearest the drag. The search is bounded per drag and shared
equally by the segments in reach.
- Paths of two anchors are smoothed, open or closed.
- Alt with the Paintbrush smooths a selected two-anchor curve; a
two-anchor straight line still gets the closed stroke. Both sides use
one segment_is_straight test.
Part of #2224
In SVG and PDF output, each piece of a warped image whose pieces can show
through was drawn through a mask of every later piece that reaches it. On a
later piece's antialiased edge the viewer multiplies the earlier piece's
coverage by the mask and draws the later piece over it, so an image with
both opaque and partly transparent pixels came out lighter at its opaque
pixels along the triangles' edges (alpha 184 of 255 in the new SVG test).
A later piece now goes into an earlier piece's mask only when a pixel of
either piece's image within 2 pixels of their overlap is less than opaque
(vectorcraft_effects::WarpSeams). Where two pieces overlap over opaque
pixels, the later one is drawn over the earlier one without a mask.
Type with an inline graphic was resolved only in the layers, so type in
the art of another symbol, a graph design, a pattern tile or an opacity
mask kept the size its graphic had when it was made. The symbol holding
that type, its instances and the inline graphics that show it were
measured from the stale layout.
- Inline graphics are resolved in every tree that holds type. Symbols
are resolved in dependency order, so a symbol's type is laid out
before the type that shows that symbol inline. Edits, opening,
reverting and Refresh Font List use the same pass.
- The pass visits each object once: it resolves the children first and
copies a node only when something below it changed. It measures each
symbol's art once per pass.
- An inline graphic of a symbol whose art shows that symbol inline,
directly or through other symbols, is an empty slot. The symbols on
such a cycle are found once and cached until the symbols change.
- A changed inline graphic also clears the text's cached baselines.
Edit > Edit Colors > Convert to Indexed Color... (edit.colors.toIndexed)
stores the embedded images in the selection as indexed-color PNGs: a
palette of at most 256 RGB colors, each pixel the index of one. The
dialog previews live and OK is one undo step.
- The palette is chosen as for a PNG-8 export (selective, perceptual,
adaptive, web, gray, black and white), as evenly spaced levels of red,
green and blue (uniform), or from the document's swatches. Dithering
is diffusion, pattern, noise or none, with an amount.
- Pixels under half opacity become one transparent entry; the others
become opaque in their own color. Linked images, colorized images and
color glyph images keep their pixels.
- An image that is already an indexed PNG and would show the same pixels
is left as it is, and no undo step is recorded when no image changes.
- An image of more than 134,217,728 pixels (33,554,432 in the web app)
is refused, read from its header before decoding.
- With Select All, locked and hidden objects that hold an image that is
not an indexed PNG yet are counted in notChanged, without converting
them.
- The quantizer gains quantize_to, which maps pixels to a given palette.
Docs: docs/mcp.md, docs/target-app-parity.md.
file.newProfiles.edit and file.newProfiles.reset returned the automation
access check and file system errors with the profile file's whole path,
so a client confined to read roots that leave out the preferences folder
learned where the folder is. The errors now go through naming_in_error,
like other paths the client did not give: whole inside the read roots,
otherwise by file name. docs/mcp.md and the params text say so.
A document or brush library file could hold two brushes of one name.
Renaming the first in Brush Options moved every stroke of that name and
left the second brush unreachable, deleting by name removed both, and
the Brushes panel could not select the second row.
Reading a stored brush library now keeps the first brush of a name as
stored and numbers each later one with the first "Name 2", "Name 3" and
so on that the file does not already have. Strokes name their brush,
and a name always found the first brush, so every stroke keeps drawing
the brush it drew before. The rule applies to opened documents and to
brush library files; brushes added to a document that already has the
name keep today's numbering.
Docs: docs/mcp.md, docs/target-app-parity.md.
An envelope around type of only spaces and a rectangle mapped from the
union of the rectangle and the type's laid-out box, so the empty type
moved and scaled how the rectangle was distorted. Type that draws
nothing now adds no box while other content in the envelope has one,
in groups and layers and as a clipping path too. When nothing else in
the envelope has a box, the type keeps its laid-out box, so an envelope
of only empty type keeps its frame, Make with Mesh and Reset with Mesh
still work, and Release still gives back the warp shape.
This applies to Make with Warp, Make with Mesh, Make with Top Object,
Reset with Mesh, Edit Contents and the stroke margins. Type that has
not been measured yet keeps its laid-out box as before.
The PDF/.ai import's boxes for envelopes with Distort Appearance
measure each object as the whole contents measure it, so the margins
the import tries match the contents' geometric box. The three import
tests that measure type without registering the glyph measure now
register it first, so another test registering it while they run no
longer changes their result.
vectorcraft_testkit::temp_dir returns a bare path, so every engine test
that wrote into one left the folder in the system temporary folder.
- testkit: TempDir, a test's own folder made through temp_dir (same
private creation and ownership checks) under a tag no other TempDir in
the process uses, removed with its contents when dropped, also when
the test panics.
- Engine unit and integration tests hold a TempDir for the whole test
instead of calling temp_dir: graph, fileio, compare, journal, library,
shortcut set, spelling, store, web export, cancel, containers, build,
color settings, command sweep, font folder, removed font, model-based
and the font search fuzz.
- The DCS and swatch library tests' own helpers and the PDF editing
tests' shared vc-pdfedit-<pid> folder use a TempDir per test too.
- The font and link file search, store, color settings and font folder
helpers no longer delete the private folder temp_dir checked and make
it again with default permissions.
With automation read roots in force, svgInteractivity.info and the files
reply of addFile and removeFile returned every linked JavaScript file as
stored, including paths outside the roots. They now name such a file by
its file name alone, as document.json does; a file inside the roots and
a URL are named as they are, and the document keeps every path.
removeFile also unlinks a file outside the roots by that file name. When
the name fits more than one linked file, it returns an error that names
no stored path and unlinks nothing. addFile still treats only the same
text as already linked.
In a frame a request drives, the JavaScript Files dialog lists such a
file by its file name while its selection keeps the file as linked, and
ui.inspect gives the selected file the same way. The person's frames
show full paths.
The widget's Constrain button was grayed out in Free Distort, and
neither it nor Shift changed a Free Distort corner drag. Constrain is
now available in Free Transform and Free Distort, and stays unavailable
in Perspective Distort.
With Constrain on, or Shift held, a corner dragged in Free Distort
moves only along the line through the opposite corner, in the box's
own proportions, and the other three corners stay in place. On a
turned box the line runs through the box's own corners. A box with no
size leaves the corner where it is. Cmd/Ctrl+Alt+Shift on a corner
still drags in Perspective Distort. The measurement label shows how
far the corner moved.
A Transform effect whose result would pass the geometry budget (2 million
anchors or 8 million lines drawn) is left out and the art stays where it
was, but the raster effects listed before it still scaled and moved with
it: before a Transform effect that scales the art 1000 times, a Drop
Shadow's offset grew 1000 times and its blur went to the 1000 pt limit
around art that had not moved, and Expand kept those values.
The geometry pass now notes each Transform effect that moves the art once
and that it leaves out, in the Carry it already returns, and
Carry::hide_skipped hides those effects in the list the raster effects
are read from. The canvas (a path's own effects and its fills' and
strokes'), its bounds, bake (SVG and PDF export), shape art, Expand,
Expand Appearance and Outline Stroke use it, and a fill's Transform
effect inside a piece no longer scales the effects before it when the
budget leaves it out. The budget itself is unchanged.
A path drawn as its own art inside a group whose geometry effects run
after its members' own read its object effects whole. The reshaping pass
now notes a Transform effect that no path in the art took past the
budget, and the member's art hides it before its raster effects are read.
The bounds skip the extra geometry pass when no raster effect could
follow a Transform effect, and painted_reach runs it once for both
measurements.
A Fit to Artwork Bounds step of `command.batch` that gets no artboards from
the Artboard tool or the batch fits the first artboard, as the command does
by default, and its journal entry now records that `index`. Before, the
entry kept the step without it, so a replay with the Artboard tool choosing
two or more artboards fitted those instead.
A `command.batch` step with `"params": null` now reads as a step without
params. Before, it got neither the Artboard tool's selected artboards nor
the batch's `artboard`, so a Fit to Artwork Bounds step fitted the first
artboard and its journal entry recorded `null`.
When a Fit to Artwork Bounds step of `command.batch` takes the batch's
`artboard` as its `index`, the batch's journal entry now records that
`index`. Before, the entry kept the step without it, so a replay with the
Artboard tool choosing two or more artboards fitted those instead.
The params text, docs/mcp.md and docs/target-app-parity.md state that the
step uses the Artboard tool's selection only when two or more artboards
are selected, and the active artboard otherwise.
The Live Paint Selection tool always showed the plain arrow. Its cursor
now says what a click at the pointer selects, by the same rules as the
click (Alt, Select Fills, Select Strokes, locked, hidden and isolated
groups): the arrow with a filled square over a face, with a short line
between two anchor marks over an edge, and with an x where a click
selects no face or edge. From a press to the release the cursor keeps
the look it had at the press. Use Precise Cursors shows the crosshair.
- Cursor: LivePaintSelectFace, LivePaintSelectEdge and
LivePaintSelectOutside, with cursors.toml entries and drawn glyphs.
- Tests: live_paint_selection_cursor_shows_what_a_click_selects,
live_paint_selection_shows_the_arrow_with_a_face_edge_or_x_badge, and
the precise-cursor list.
Docs: docs/mcp.md, docs/target-app-parity.md.
An Overlapping layer covers the colors above it within its area, so an
area of another color that a shape enclosed was a hole in that shape
and in the shape of every layer around it, and its outline was fitted
once for each of them. In a code-drawn test, a white disc in a black
ring that four grays touch had six copies of its outline.
With Fills and without Create Strokes, each shape is now filled whole,
by a fill rule of VectorCraft's own. The area inside a filled hole is
traced on its own and drawn after the shapes around it, so its outline
is fitted once. An area of one color is drawn from the hole's outline.
A shape keeps a hole where the area holds a transparent pixel or a
color that Ignore Color or Keep Colors leaves out, and a hole smaller
than Noise is filled with the shape's color as before. Nested areas are
traced within a work budget of VectorCraft's own: four times the
image's pixels (counted as at least 65,536) times its colors, each area
costing its box times its colors. An area that costs more than what is
left keeps its holes and is not charged. Create Strokes and outlines
without Fills are unchanged.
Shapes have holes only in those cases, there are more paths, and the
stacking order changes. Expand by Color starts another group of a color
where an area is drawn above the shape around it. The trace's progress
counts finer steps across the rounds of enclosed areas, not one step
per color layer.
A layer's components without its own color are now left out after its
mask is traced, instead of in a labeling pass before it. Every route
finds its components through the same function.
file.close {index} on a tab other than the current one made the
document now at the closed index current and reset the tool, so the
document the user was working in went to the back. The current
document now stays current, found by its id as document.move does, and
its tool keeps its state. Closing the current tab still makes its
neighbor current.
Docs: docs/mcp.md, docs/target-app-parity.md.