Object > Path > Remove Anchor Points removed each selected anchor with
its own Vec::remove, shifting the rest of the subpath every time, so
removing k anchors from an n-anchor subpath took O(k*n) moves.
- pathops: remove_anchors removes a subpath's anchors the last first,
reading each anchor's current neighbors, handles, length and closed
state as removing them one at a time does, and compacts the anchor
list once. remove_anchor and remove_anchors share one refit, so the
result is bit for bit the same.
- engine: the command groups the selected anchors by subpath, works out
the next selected anchor with each subpath reading only its own
selected anchors, and takes emptied paths out of the selection
together (Selection::remove_all).
- Tests compare remove_anchors bit for bit with remove_anchor over 600
generated subpaths and focused cases, count the anchors moved (2001
anchors, every other one removed: at most one move per anchor left,
500,500 one at a time), and compare the next-anchor selection and
the command on many paths and subpaths with removing one at a time.
An <image> whose EXIF orientation or TIFF Orientation tag turns or flips
its pixels, linked or as a data URL, is turned before usvg reads it: the
pixels are stored as they are shown, the width and height swap, and the
box fits the turned image under preserveAspectRatio, as CSS
image-orientation: from-image describes. A turned CMYK JPEG or TIFF keeps
its inks as a CMYK TIFF, as does a CMYK TIFF that is not turned.
- Data URLs of type image/tiff, and untyped TIFF bytes, are read.
- TIFFs with extra channels, CCITT Group 3 TIFFs and planar CMYK TIFFs
are read as Open and Place read them.
- Converted images decode within the decode allowance, and a TIFF that
claims more strips or tiles than the decoder can count is refused
before any decoder opens it.
- exif_orientation moves to vectorcraft-doc so Open/Place and SVG import
share it.
Tests: crates/svg/tests/it/image_orientation.rs.
Spine::at_length walked the first subpath's segments from the start
until one ended at or past the arc length, so placing every blend step,
spread key, blend widget and Objects on Path attachment cost one
comparison per earlier segment. It now searches the anchors' arc
lengths, which never decrease, and picks the same segment: the first
whose end is at or past the length, so an exact anchor length and
repeated lengths from zero-length segments keep their segment. The
point and tangent evaluation is unchanged.
Tests compare the search with the old walk on open and closed spines,
one segment, curves, zero-length segments, every anchor, both ends,
out-of-range and non-finite lengths, and check that a 4,096-segment
spine needs at most 13 comparisons.
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>
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 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.
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>
The kept grid now records whether a type measure was registered, so a
grid built before registration is not reused when the registered inputs
number happens to be 0.
With both guides of an axis past the same edge, the art keeps its size
from the other edge of the instance, or shrinks to the instance's width
or height when the instance is smaller than the art. The module doc,
AxisMap::new and the docs/target-app-parity.md entry now say so.
A selected warp envelope now shows the points of its surface on a 4x4
grid (8x8 or 16x16 where 4x4 strays more than 0.1% of the warp's
diagonal from it). object.mesh.movePoint, movePoints, addLine,
deletePoint and object.envelope.setMeshPoint accept a warp: the first
edit turns it into a mesh envelope on that grid and applies the edit as
one undo step, so Undo or Escape gives the warp back with its style and
bend. An edit that moves no point leaves the warp and adds no undo
step. A warp that no such grid follows (some version 0 maps) shows no
points and its edits fail without changing anything.
movePoints and setPointColor on a gradient mesh record no undo step
when they change no point.
The Paragraph panel's Mojikumi Set lists None, YakumonoHankaku,
GyomatsuYakumonoHankaku, GyomatsuYakumonoZenkaku and YakumonoZenkaku,
then Line-end Punctuation Half Width, and text.setFormat {mojikumi}
takes yakumonoHankaku, gyomatsuYakumonoHankaku, gyomatsuYakumonoZenkaku
and yakumonoZenkaku besides none and lineEndHalf.
The spacing of each set is VectorCraft's own rule, built on the help
page's one-line description of each set:
- YakumonoHankaku sets every bracket, comma and full stop half width,
takes a quarter em off each side of a middle dot, and puts a quarter
em between Japanese and Latin letters or digits.
- GyomatsuYakumonoHankaku keeps punctuation full width inside a line,
sets an opening bracket starting a line flush and a closing mark
ending it half width, and takes the quarter em after a middle dot
ending a line off.
- GyomatsuYakumonoZenkaku keeps punctuation full width and adds the
quarter em between Japanese and Latin text.
- YakumonoZenkaku sets characters as None does.
Both composers and vertical type measure each line as it is set. A
right-to-left paragraph, whose line ends are measured untrimmed, also
draws them untrimmed, and a right-to-left line that ends between a
Japanese and a Latin character is measured without the quarter em the
set adds there. With a Desired glyph scaling other than 100%, the
space a set takes off before an opening bracket or middle dot, and the
Japanese-Latin space recorded at a line's end, scale with the glyph.
The sets save with the document and paragraph styles; new type and
Reset Panel keep Line-end Punctuation Half Width. SVG export lays type
with middle dots out again for its positioned spans. A .ai save
reports a paragraph with one of the new sets as left out.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md,
docs/gaps.md.
Copy now remembers the whole chain of layers around each object, not only
the name of the nearest one. With Paste Remembers Layers on:
- In the same document, art goes back into the sublayer it came from while
that sublayer exists, even after it was moved, so two sublayers of the
same name in different layers stay apart. A deleted sublayer is made
again inside the layer that held it.
- In another document, the sublayer and the layers around it are matched
by name from the top level down; of repeated names, the topmost chain
that matches the most names wins, a shown and unlocked one first. A
missing sublayer is made inside its layer, and a missing layer on top
with the sublayer inside it, in the paste's undo step.
- When the matched layer, or a layer around it, is hidden or locked, the
paste fails with "the target layer is locked or hidden" and nothing is
pasted, instead of putting art in a hidden layer or making a second
layer of the same name.
The series fill and stroke and the label fill and stroke a graph keeps
for its next redraw are now in the paint traversal, so Delete Pattern
and canceling a new pattern make them no paint and a Pattern Options
rename renames them. Before, a later graph.setData drew the paint of a
missing pattern again. Global swatch edits reach the same paints.
The progress-doc line now says that only canceling a new pattern clears
its paints; canceling the edit of an existing pattern puts the
definition, and after a rename the old name, back.
A 9-slice guide can now be placed outside the symbol's art, by dragging
it past the art's edge in symbol editing mode, by symbol.options
nineSliceGuides, or by a Symbol Options field below 0% or above 100%.
A scaled instance then scales by the cells its art falls in.
- Each guide is a fraction from its edge in -16..16, and the two guides
of an axis keep their order (left + right < 1, top + bottom < 1).
- A guide past an edge cuts the art at that edge, so that side's outer
cell holds no art. With both guides of an axis past opposite edges,
the art scales as a whole along it. With both past the same edge, the
art keeps its size in the outer cell at the other edge.
- Symbol Options shows the guides with three decimals, and OK keeps a
field that was not changed at its exact value.
- Native files keep the guides exactly. SVG and .ai write such an
instance as its placed art, without guides.
Docs: docs/mcp.md, docs/target-app-parity.md, docs/gaps.md.
- The link watch and links.updateChanged look at a DCS file's plate files
of their own, so a changed plate file is read again.
- links.update and links.relink apply the file's clipping path to the
images in symbols, graph designs, patterns and masks too. When the new
file has no clipping path, a clip group holding the old file's clipping
path loses that path and stays as a plain group of the image, keeping
its id, name, visibility and transparency. The image keeps that path with its plates to tell it from a
clipping mask made in the document; a clipping path given another fill
rule in the document stays. An even-odd clipping path in the file is
kept as the same area under the nonzero rule.
- links.update and links.relink return the swatch warnings file.place
gives, including two files that add one ink in two colors.
- links.relink with copy fails for a DCS file with plate files of its own.
- file.place answers the image id of a clipped DCS placement as image,
and checks page and crop for a DCS file as it does for an EPS file.
- Print separations and Separations Preview take a spot plate's color
from the document's swatch of its ink, and a reduced image's plates
keep their coverage.
- Show DCS Transparency Interactions counts a transparent DCS image and
characters filled or stroked with a transparent gradient.
- %%PlateFile names keep their spaces, and header bytes that are not
UTF-8 are read as Latin-1.
- A plate program that draws more than one image, art besides its image,
or its image through a clipping path is an error naming the plate
instead of printing the wrong ink.
- Nested clipping paths around the composite clip the image together.
- A kept image with more plates than are read, or more TIFF directories
than its composite and plates, is the CMYK image of its composite
only, not a set of plates cut short.
- swatch.unused counts a spot or process swatch as used only while an
image the art shows has a spot plate of its name.
Cancel on a pattern that Make created for the edit now removes every
swatch of it, a color group's included, and turns every paint of it
left in the document into no paint: art outside the editing layer,
opacity mask art, type runs, symbol definitions, graph designs, other
patterns' tiles (a copy Save a Copy made included), graphic and
character styles, brush art, and the fill, stroke and appearance new
art takes. Delete Pattern clears its paints in the same places, and a
Pattern Options rename renames them there; both reached only the
layers before. Delete's `unpainted` counts the document paints it
cleared.
Cancel after a Pattern Options rename during the edit gives the
pattern its old name back with its swatches and paints, unless another
pattern, swatch or color group has taken that name; before, the
definition went back to the old name while its swatches and paints
kept the new one.
Undo and Redo restore new art's paints with the document, including
the appearance inherited from the selection with New Art Has Basic
Appearance off. A rename preview that is replaced or canceled starts
again from the paints the interaction began with, and a canceled
preview takes the selection's appearance again.
The symbol instances the picker reads now share one limit of 16,000,000
placed objects, charged through charge_symbol_art and counted as the
renderer counts them, so deeply nested or heavily repeated instances
stop there. An instance whose art would pass the limit is left out with
its own paints, as the canvas draws none of it. Pattern tiles carry the
count in and out, and their art is read one level deeper than the
object the pattern fills, so a chain of patterns shares the depth limit
with the art around it; a pattern cut short there is read again where
it is used less deep.
The pixels read inside clip groups are now tested against at most
4,000,000 clipping-path elements in all. The clipped reads of one image
share the work, a read outside clip groups keeps its own pixel budget,
and when the work left can't test a pixel of every clipped read, the
reads that fit are kept in order and the rest are left out.
ImageBlob::sample_pixels takes a pixel budget for each pick.
locate_within measured every boundary edge of the mesh at full accuracy
for each lookup, including points well inside the mesh. It now skips an
edge whose control points all lie farther than the distance it is
looking for: a millionth of a point for the outline, or the tolerance
for a point outside.
A point exactly on a gradient mesh's outline located on the top and left
edges but not on the bottom and right ones, where the nonzero winding
number is 0. object.mesh.addLine and Create Gradient Mesh at a point there
reported the point outside the mesh, and the Eyedropper did not sample it,
while the hit test selects the mesh there.
locate_within now takes the nearest point of the edge for a point within
a millionth of a point of the outline, with any tolerance, and returns
nothing for a point that is not finite. A point just outside still needs
a tolerance.
The Color Theme Picker button beside the Assign and Edit tabs turns on
picking from the canvas. A click takes the color palette of the object a
Selection tool click takes there, a drag takes the palette of the art
inside its marquee, and Shift adds more picks. The rows that are not
excluded take the palette as their new colors and the art previews them;
OK keeps the recolor as one undo step.
- recolor.pickTheme {picks, colors?} returns the palette of the art
picked: visible fills and strokes, gradient stops, text, mesh points,
pattern tiles, symbol art, colorize colors and image pixels. Clip
groups, clipping opacity masks and isolation mode limit it to what
shows, and it changes nothing.
- Each object read weighs the same; similar colors merge (k-means in Lab)
into at most `colors`, most used first. Image reads are bounded (65,536
pixels a pick, 1,048,576 pixels and 64 images in all), and past 4,096
distinct colors, close colors merge before clustering.
- The dialog's themePicker and themePicks fields drive it from
ui.dialog.set. A new Colors count keeps the picks.
A click just past the end of two segments that meet with almost no
turn, as the segments of a warped edge do, missed a beveled or mitered
stroke: the joint has no bevel, and the check counted that as a miss.
It now counts as painted, so the eyedropper samples an envelope's
stroke there again.
A miter join on a stroke whose dashes have round or projecting caps
still paints the miter spikes the limit keeps, and the visual box
includes them. object.setBounds with Use Preview Bounds did not count
such a stroke as one whose margins follow its segments' directions, so a
closed path at a slant missed the size typed when the width and height
were out of proportion.
A bevel join, and a miter join whose miter limit bevels a corner, end the
stroke at the outer corners of the two segments they join. The visual box
measured such a corner as if the stroke reached half its weight round it:
a 40 x 100 pt triangle with a 20 pt stroke and a miter limit of 2 measured
10 pt above its tip, where the stroke paints about 1.96 pt above it. Use
Preview Bounds, and the alignment and sizing that use it, took that box.
- With bevel and miter joins, closed paths and open paths with round or
projecting caps are now measured along their segments, as butt-capped
open paths already were, with the miter tips the limit keeps and the
caps added. Round joins still reach half the weight round each corner,
the round caps of dashes, which can end at a corner, reach half the
weight all round, and the projecting caps of dashes reach the diagonal
of their square. The visual box of
such a stroke can be smaller than before and is never larger. A path
with no width, such as a rectangle sized to 0 pt wide, turns back at
its ends, where the join bevels, so its box no longer reaches past them.
- object.setBounds with Use Preview Bounds measures an object as drawn
when a beveled or mitered corner, or a butt cap, is on a segment at a
slant. Before, a closed path with miter spikes the limit keeps missed
the size typed when the width and height were out of proportion.
- A click past a bevel or a butt cap, by more than the click tolerance,
no longer selects the stroke. Strokes with a width profile or with
dashes that have round or projecting caps, and the ends of curves that
bend more tightly than half the weight, keep the round reach.
- Paths measured along their segments skip the segments that cannot
reach past the box measured so far, which starts with the path's own
bounds.