The legacy reader skipped `bindrepeat Tg` (section 15.3.4 of the version 7
file format specification). It now reads the binding punctuation bit:
1 and 3 set the paragraph's Burasagari to Force, and 0 and 2 set it to
none, which is VectorCraft's own mapping. The value is modal, lasts into
the next text object, and an operand other than 0 to 3 leaves it as it
was. The repeat character, `TG`, `Xu` and `Tu` are still skipped.
docs/mcp.md and the `ParaStyle::burasagari` doc comment name the legacy
`.ai` case as the exception to imported text keeping `none`.
Add Used Colors, New Color Group from artwork and Select All Unused
looked up each linked paint's swatch by searching every swatch. They now
build one map of swatch names first, with the built-in Registration
swatch, and the first swatch of each name as before.
New tests and test cases: New Color Group from artwork with repeated,
near-equal and -0.0 colors, a Registration link and names that need
numbering, and New Swatch and Edit Color Group with near-equal and
-0.0 colors; each checks the names, their order and one undo step.
New Swatch with several colors, New Color Group and Edit Color Group's
extra colors searched every swatch and group for each candidate name
of each new swatch. They now build the set of taken names once
(FreeSwatchNames, with take_new for New Swatch's default names and
insert for the group name and the group's own swatches) and keep it up
to date as they add swatches. The names they give are unchanged.
swatch.addUsedColors and swatch.unused searched lists inside loops over
colors and swatches, checking each color against every color or swatch
before it. They now keep the same ordered lists and use hash sets and
maps for membership and lookup. A color's key is its model and the bits
of its components with -0.0 read as 0.0, so the key agrees with `==`; a
color with a NaN component matches nothing, as before.
Add Used Colors names its new swatches through
Document::free_swatch_names, which collects the taken swatch and color
group names once and gives the same names free_swatch_name gives when the
swatches are added one at a time. The added swatches, their names, order
and links, the undo step, and the swatches Select All Unused lists are
unchanged.
An envelope mapped type from its laid-out box: ascent to descent for
point type, the frame for area type. The distorted glyphs then filled
less of the envelope than other art does, with room above the capitals
and below the baseline.
The envelope now maps type from the bounds of its glyph outlines,
underlines and strikethroughs when the type is on its own, in groups
and layers, or used as a clipping path. Inline graphics count by their
boxes. Rotated type and type in perspective map from their outlines as
they are drawn. Make with Mesh,
Make with Top Object and Reset with Mesh without Maintain Shape use
those bounds. Type that draws nothing keeps its laid-out box, and so
does type inside any other object, such as a blend, a repeat or a
symbol instance. Shrink Text to Fit type is measured as it is laid out
on the page.
vectorcraft-text's envelope_ink measures the type, and
register_envelope_ink registers it with the document crate. The engine
session, Renderer::new, live evaluation in vectorcraft-effects and the
.ai reader register it. The document crate keeps the measured boxes per
thread, by object and by a hash of the content, and measures again when
the type, the fonts, the hyphenation exceptions or the transform's
scale or rotation change.
This is VectorCraft's own rule. Envelopes of type made in VectorCraft
and in saved files map from the glyph outline bounds instead of the
type's laid-out box, so they redraw differently.
Smooth Color now counts the steps between each pair of keys from the
keys' colors and visible bounds, which changes the step count of most
Smooth Color blends. The counts are VectorCraft's own values:
- When the keys' solid fills or strokes differ (in groups, member by
member; in text, run by run), the count is 255 times the largest
change of one channel as a fraction of its range, a half step
rounding down, from 1 to 254. An RGB channel's range is 256 values,
CMYK and gray count in percent, and two tints of one swatch by their
tints. It is at most eight per point of the largest of the keys'
widths and heights and the distance between their centers. Black to
white was 128 steps and is now 254.
- When the colors match, a solid paint meets no paint, or a key holds
a gradient or a pattern, the count is the largest distance between
corresponding visible bounding-box edges divided by the keys' mean
width or height, rounded, less one, from 1 to 1000. Two 20 pt
squares 200 pt apart were 100 steps and are now 9.
Keys without bounds make the color count when their colors differ,
and 1 step otherwise.
A blend nested in another blend's keys counts toward the step limit
by its color steps when its colors differ, and as up to 1000 steps
when they match (256 before). An outer blend of 200 steps between two
such same-color Smooth Color blends now draws 99 steps.
A Smooth Color blend in an .ai file whose drawn steps follow this rule
now opens as a live blend.
Blend Options: https://web.archive.org/web/20241217212108/https://helpx.adobe.com/illustrator/using/blending-objects.html
Docs: docs/mcp.md, docs/target-app-parity.md.
A save wrote the path relative to the document only for linked images and
placed documents in the layers. Links inside symbols, graph designs,
patterns and opacity masks kept only their absolute path, so after the
document and its files moved to another folder together, those files were
not found.
- The save sets the relative path of every link the document holds.
- On open, a link inside that art whose file was found away from its path
takes the new path, as a link in the layers does, so the next save
writes the right relative path.
- Document::update_links_where changes only the links it picks and copies
only the nodes on the way to them.
Docs: docs/mcp.md (Linked images), docs/target-app-parity.md.
The Shape Builder, Live Paint and run_around tests in planar/tests.rs
measured elapsed time against a 5 s limit, and the large edit tests
(large_edits.rs, with the Lock and Hide All Artwork Above and perspective
Alt-drag tests that share `soon`) against 20 s. On a loaded machine they
failed without any change in the code.
- pathops: a test-only counter adds up the items `RectIndex::at` reads
and the segments `nearest_u` searches. The four planar tests assert a
bound on that count.
- doc: `tree_walks` counts, per thread, the walks of a document's tree
that lookups by id make. `soon` fails when a command walks the tree
more than 64 times, and keeps a 300 s wait for a hang. The tests that
named a 20 s limit are renamed.
Looking objects up one at a time in Delete or Offset Path, or scanning
the whole index or ring in the planar code, makes these tests fail.
A paste relinked every copy to the document's resources even when no
resource had another name or color and the objects linked no graphic
style. It now leaves those copies as they are.
Finding the pasted type looked each id up and compared every text object
with the ones already found. Text commands and the bounds refresh after
a paste now look all ids up in one walk and keep the found ids in a set.
Giving a pasted group new ids builds its map of old to new ids only for
an Objects on Path group, which is the only kind that reads it.
A pasted solid paint with no swatch link and a tint other than 1 now
keeps that tint when no relinking runs. Saved art carries a tint of 1 on
such a paint.
The width between two width points was a straight line, so a variable-width
stroke turned a corner at every width point.
- Each side of a width profile now follows a monotone cubic (Fritsch-Carlson)
through its points: no kink at a point, flat at a point wider or narrower
than both neighbours, never past either point of a span. Points on one line
stay on it and two-point tapers are unchanged; stacked points still step.
- widest() counts each span's wider left and wider right, since the two sides
needn't peak together.
- The outliner samples straight runs between width points closely enough to
follow the curve to the drawing tolerance (at most 64 samples per span and
4,096 per piece); straight spans get none.
- Tests that expected straight-line widths now expect the curve. The stroke
gradient coverage test uses Teardrop instead of Wave until #5964 is fixed.
When Distort Appearance is on and the size a `.ai` saves for an
envelope's contents (`AIDeformArtWidthKey`, `AIDeformArtHeightKey`)
matches the contents' bounds with strokes, the reader now maps the
contents from that stroked box. It used to map them from the geometric
bounds, half a stroke inside that box, so an envelope of stroked art
could differ from its drawn art and keep the drawn art with a note.
Envelope Options hold the box's margins as `boundsMargin`, shares of the
geometric bounds at the top, bottom, left and right. Native files write
them only when they are not zero, and `object.envelope.info` reports
them then. Leaving Edit Contents measures them again when the strokes
reach differently than when Edit Contents began, and the node holds
what they measured then until it leaves. New envelopes have no
margins, so their results do not change. Contents with no width or no
height, such as one straight line, get no margins.
Docs: docs/mcp.md, docs/target-app-parity.md.
The walk measured every object's visual box to leave out what is off
the page. A plain group or layer is measured as the union of its
members, and each member then measured itself again when it was
written, so an object in plain groups was measured once for each level
it sits at.
Measuring a plain group or layer now keeps its members' boxes in a
tree, and each member uses its box when it is written. A container
written whatever its box is (a PDF layer's group, placed symbol art) is
not measured. A knockout group's elements are measured for the group's
box and again for the list of elements on the page, and that second box
is used for the element and for its mask. Objects in a live container
(Blend, Envelope, Repeat, Intertwine) are still measured with the
container and again after it is expanded. Node::visual_of_members holds
the formula that Node::visual also uses, so the boxes and every decision
are unchanged.
The output is byte for byte the same on one open path of 200,000
anchors, 20,000 rectangles in 200 groups of 100, and 5,000 point type
objects, and on a document of plain, see-through, clipping and knockout
groups on and off the page, each exported with default settings, with
uncompressed text and with PDF layers.
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
A linked 1-bit image mask from a .ai file keeps its fill color as a
colorize paint, so the image takes the color once its file is relinked.
Until then the image shows the missing-link placeholder, and the canvas
and PDF export colorized that placeholder too: its light tile turned
nearly transparent and its red cross took the fill color.
ImageBlob::takes_colorize is false for a preview whose pixels are not
grayscale. While it is false, the canvas draws the image and PDF export
writes it as it is, printed separations separate it as an uncolorized
image, and transparency flattening (EPS, PDF 1.3 and PDF/X-1a export,
Flatten Transparency) does not count its paint as transparency. The
paint stays on the image, and a grayscale preview is still colorized.
SVG and EPS export already write only grayscale images colorized.
VectorCraft now reads `CropAreaActive`, an `/Int` in the dictionary of
the editing data that holds the `ArtboardArray`, as the index of the
active artboard in that array. The reader ignored it before, and every
file opened on its first artboard.
The opened document starts with that artboard active, so the view the
document opens at and the Artboards panel use it. The index counts
every entry of the array, so an artboard that is left out (one with no
area) does not shift it. When the entry is missing, is not an `/Int`,
or names an artboard that does not open, the first artboard is active,
as before. A file that comes in as its PDF part, with its pages placed
where its artboards are, opens on the page of the active artboard, also
when the reading stops before the artboards. The same reader serves the
editing data of EPS and PDF files.
The active artboard is kept on the opened document only; native files
do not save it.
A linked image read from a version 7 `.ai` file can keep its path in
the classic Mac OS form, with colons between folders
(`Macintosh HD:Images:photo.tif`). The link's name was the whole path,
so the Links panel showed it as one long name, and Relink to Folder,
the search of the document's folder and Find Linked Files looked for a
file with that whole string as its name.
A path with a colon and no `/` or `\` is now read as a classic Mac OS
path. Its name is the part after the last colon, and `links.info` gives
the rest as `location`. Relink to Folder, the document-folder search and
`links.findFiles` look for that name, and Find Linked Files compares the
path's folders with the folders of the files it finds. The link keeps
the path as stored. Paths with a `/` or `\` keep those as separators,
so a file name may still hold a colon (`/Users/a/x:y.png`).
Docs: docs/mcp.md (Links), docs/file-format-parity.md (version 7
linked images).
The editing copy of a PDF-compatible .ai wrote every image as its outline
filled with its average color. Embedded images are now written with their
pixels, and linked images as placed files with a preview:
- Embedded images are rasters in the form of the version 7 specification
(section 8, https://13thmonkey.org/documentation/fonts/AI7FileFormat.pdf):
%AI5_BeginRaster, the color space, the image matrix and the XI operands
of current files, then the samples in binary in a %%BeginData section.
Gray, RGB and CMYK images keep their samples (CMYK images their ink
amounts), and see-through images their alpha channel after the color
samples. The matrix is written to ten decimals.
- Linked images are placed files (section 16): the matrix, the box of the
file's pixels and its path, %%IncludeFile, the folder relative to the
document's (when the relative path ends in the path's file name), and a
preview of at most 256 pixels a side.
- Images that can't be decoded or have more than 33,554,432 pixels (the
most the reader reads) are written as their outlines filled mid gray,
images of 16 bits a channel (CMYK TIFFs included) at 8 bits, and linked
images in opacity masks and pattern tiles as embedded images, each with
a warning.
Each of these is written in the form VectorCraft's .ai reader reads, and
the tests read the written data back through that reader.
The reader now gives an image its name and opacity mask when the
operators that end its raster (XH, Xr and the N after %AI5_EndRaster) come
before its dictionary, reads an empty relative folder as the document's
own folder, and looks for the %%EndData that ends an image's samples
after the alpha channel its XI operands give it, preferring one whose next
line is XH, so samples that hold the marker's bytes are read whole.
Tests: the round trip of every kind of image and its state, a second write
of what is read back giving the same bytes, the raster and placed-file
forms, previews of large linked images, and the engine's editing copy with
every save option; a fuzz case for damaged images and placed files.
Docs: docs/file-format-parity.md.
A legacy .ai in the version 7 form names a linked image's file with
`(path) modified XG` and draws the image with `XF`, which takes the
operands `XI` takes and has no samples after it (Adobe Illustrator File
Format Specification 7.0, sections 8.2 and 8.3). The reader ignored both
operators, so the image was dropped without a warning.
- `XF` after `XG` becomes a linked image of that file, its pixels placed
as an `XI` image of the same operands is placed. The file keeps no
pixels of it, so a placeholder stands in until the file is relinked in
the Links panel, with a note in the open result. No pixels are read,
so the image is not held to the pixel limit of `XI`'s samples.
- An `XF` without a file name before it, or with operands `XI` couldn't
read (no matrix, too few numbers, no pixels, or samples of other than
1 or 8 bits), is left out with the existing "an image VectorCraft
can't read was left out" warning. An `XG` applies only to the image
after it in the same raster object; `() 1 XG` before an embedded `XI`
keeps that image embedded.
- A PostScript page draws nothing for `XF`, so there the placeholder's
area is left out of the comparison of the layers with the page, and
the file keeps its layers. A PDF part draws the image's pixels, so a
file with one is compared with it there as before, and its layers are
not used without the PDF part.
- Linked images of different files get image keys of their own even
when they show the same pixels (the placeholder, or two identical
placed-file previews), so updating or exporting one file's image
reads that file only.
- `vectorcraft_doc::links::missing_placeholder` draws the placeholder: a
light tile, crossed out, as SVG import shows for a missing file.
Tests: reader tests for the placement, the image keys and the cases left
out, an end-to-end test of a legacy file with a linked image, a test of
a file with a PDF part, and a fuzz case for damaged files with `XG` and
`XF`. The new note has a row in each catalog that translates the
placed-file note.
Locking or hiding the layer (or a group) around the isolated object left
its art selectable with the Selection tool: the isolation branch of the
hit test only checked for a locked artboard, and the marquee's checked
nothing. Both now pass over isolated art that isn't editable.
The enabled check of artboard.deleteEmpty built the full list of empty
artboards, measuring the visual bounds of the art against every
artboard, each time the Artboards panel menu was drawn. It now returns
false at once in a document with one artboard (that artboard is never
deleted). With two or more, it asks the new
Document::any_artboard_without_art whether any unlocked artboard has no
art, which is the same answer as a non-empty list.
Document::any_artboard_without_art builds the symbol boxes once for all
the artboards and first tests each path and compound path by a quick
box: its control points' box grown by the farthest its strokes reach
(Appearance::outset), with a margin for rounding. An artboard that no
quick box meets is reported empty without measuring along any path.
Only when every artboard may hold art is each one measured.
Document::artboard_has_art also skips measuring an object whose quick
box misses the artboard; its answers are unchanged.
The check of gradient.presets.save found the proxy paints through
Node::proxy_paint, which measures the object's geometric bounds for a
gradient box the check does not use. Node::proxy_paint_only picks the
same paint without measuring, and the engine's proxy_paint uses it, so
the Fill and Stroke proxies and the mixed-paint test no longer measure
each object either.
With an open stroked path of 200,000 anchors selected, the two checks
take under 0.01 ms instead of 21.9 ms and 17.2 ms in a release build, and
Delete Empty Artboards' check takes 0.85 ms instead of 42.1 ms with a
second, empty artboard.
Tests compare each check with what its command does (one artboard, all
empty, one empty, locked empty, nested art; with and without a
gradient), compare any_empty_artboards with the previous check and with
measuring every object on 120 seeded documents, compare the quick boxes
with the visual bounds on seeded strokes, and compare proxy_paint_only
with proxy_paint on seeded objects.
Docs: docs/target-app-parity.md, one line under Delete Empty Artboards.
The renderer keeps the path of a path or compound path of 1,024 anchors
or more by object id, with the bounds of its segments and of its miter
spikes in runs of 128 segments. When anchors move and the subpaths and
anchor counts stay the same, the next frame rewrites only the path
elements of the segments that start or end at a moved anchor, and
measures again only the runs that hold them and the runs of the
segments with length before them. Other edits make the path again as
before. The pixels and the cull bounds are the same.
- StrokeLayer::bounds_on is unchanged. The miter tip of one joint
(miter_tip) and the stroke box with given tips (bounds_with_tips) are
new functions beside it, and Appearance::stroked_bounds_by takes the
box of each shaped stroke, so the cull bounds of a kept path use the
kept tips.
- geom::bez::has_length is the predicate reach.rs used for segments
that draw, made public.
Docs: docs/target-app-parity.md.
An instance of a symbol whose art holds a 9-slice instance draws that
instance through its guides with the transform of every instance above
it. When the outer symbol has no guides, the quick box of an outer
instance that scales or turns its art was its art box placed by its
transform, and the renderer could cull art the nested guides move past
that box.
Symbol art boxes now record whether the art holds an instance of a
symbol with 9-slice guides at any depth. For such art, quick boxes
place the art by the instance's transform and measure it, once for each
symbol and linear part. One set of boxes places at most
MAX_SYMBOL_NODES objects this way, the art of opacity masks included.
Leaving opacity-mask or symbol editing now removes the editing layer
wherever it is, so a save or export after Layers moved it into another
layer no longer writes it. An editing id that names anything other than
a layer is left alone.
symbol_edit is no longer read from files: a value in file data is
dropped on load, so the layer it names is kept and saving does not
write the value again.
An object in 9-slice art that crosses a guide and draws symbol art is
placed by the map of the cell its center is in, so it can draw outside
the instance's transformed art box. Bounds measured without placing the
art (the renderer's culling, and placements past the object limit) used
that box alone, so the displaced art could be culled and not drawn.
The fallback now takes in the art box placed by each of the nine cells'
maps (NineSliceMap::placement_bounds), grown by the art's reach as
before.
#5139 (in 0.21.0) made a positive Twist angle turn clockwise, but
documents saved by earlier releases stored their angles with the other
sign and reopened twisted the other way.
The document version (FORMAT_VERSION) goes from 1 to 2. Opening a
version 1 document negates its Twist angles (in layers, opacity-mask
art, Shaper edits, symbols, graph designs, pattern swatches, graphic
styles and brush definitions), unless the file is of the current file
version and its generator is VectorCraft 0.21.0 or later. The document
opens and saves as version 2. A save for an older file version, which
only apps before 0.21.0 read, writes a version 1 document with the
angles negated back. Graphic style libraries (.vcstyles) go to version
2: a version 1 library's Twist angles are negated when it is read.
Readers do not check either version, so older builds open the new files
as before.
In a threaded frame whose first paragraph continues from the frame
before, the text at the frame's start is not the start of a paragraph,
and the frame draws no list marker for it. Typing "* " or "1. " there
removed the characters and set a list that never showed. typed_list_at
now returns None for that paragraph.
A graph keeps the paint and stroke weight given to a series' marks, but not
the opacity of their fill or stroke row. An opacity set with
`transparency.set {item}` or `appearance.setItem {opacity}` on a column,
bar, line or other series path was lost the next time the graph was drawn
(a data, type or option change).
- `SeriesPaint` gains `fillOpacity` and `strokeOpacity`. A setItem opacity
on the topmost fill or stroke of a series path stores it, in the same
undo step, and the generator puts it on the topmost fill and stroke of
every path directly in that series' group. Graph designs keep their own
opacity. A value read from a file is held to 0..1.
- The Appearance commands that change the opacity of a series path's
topmost fill or stroke (add, remove, duplicate and move a row, Clear
Appearance, Reduce to Basic Appearance, `appearance.transfer` and the
Eyedropper) update the stored opacity. They do not change the stored
paint, which only `paint.setFill` and `paint.setStroke` set.
- An edit that leaves a series path fully opaque stores nothing for a
series the graph keeps nothing for.
Add Anchor Points builds the new anchor list in one pass, Swatch Library add looks names up in sets, full_documents dedups placed keys with a set, width profile lookups and mark insertion use binary search, and the symbolism tools cap and thin the previews of a long drag as Liquify does.
A projection now counts as invertible when its determinant, or its
adjugate's determinant, is more than 1e-15 times the largest of the six
products that determinant sums. The adjugate of the adjugate is the map
times a number, so the inverse of a projection that inverts also inverts,
is stored and reads back. The adjugate's cofactors are formed with exact
products, so the inverse of that inverse gives back the original map up
to scale, within 1e-6 of each row's largest entry, and a map whose third
row is the sum of its first two is still refused.
Homography::conjugated and Node::transform_projection judged the affine
map by an absolute determinant of 1e-15, so a scale by 1e-8 left type and
symbol instances in perspective with their old projection. Both now use
Homography::affine_is_invertible, which compares the determinant with
the two products it sums at any scale of the map. An affine map whose
determinant or inverse is not finite, such as a scale by 1e200 or
1e-200, is inverted through Homography::inverse. When the direct product
underflows to a map that is not invertible, as for a projection stored
at a subnormal scale, conjugated forms it again with its powers of two
kept apart.
- crates/geom/src/projective.rs: the_inverse_of_an_invertible_map_is_invertible,
conjugating_does_not_depend_on_the_scale_of_the_affine,
conjugating_a_map_at_a_tiny_scale_gives_an_invertible_map
- crates/doc/src/node/tests.rs: a_projection_follows_the_transform_or_is_removed
covers an invertible scale by 1e-8 and a singular map at that size
A compound path was assembled from its members in every frame it was
drawn, and Node::stroke_path grew its buffer by doubling, copying the
path into ever larger buffers for a compound path of many anchors. The
renderer's geometry cache was swept only once it held more than 1,024
entries, so while an object was dragged (a new node, and layer, in
every frame) the old copies stayed alive with all their members.
Now:
- a compound path is drawn from the path the renderer keeps for it in
its geometry cache, as a plain path already was, and is assembled
only when the node changes;
- Node::stroke_path sizes a compound path's buffer up front from its
members' segment counts (the same elements, in the same order);
- unused geometry entries are dropped after a few frames however few
entries there are.
Tests:
- crates/render/src/tests/memory.rs: the second frame draws the kept
path and the same pixels as the first and as a new renderer; after a
five-frame move the renderer no longer holds the compound path drawn
before it. The test fails without the renderer change.
- crates/doc/src/node/tests.rs: a compound path's path is its members'
paths in order (open, closed, empty and single-anchor members).
Not timed; vectorcraft-cli perf was not rerun.
Every click and hover measured each path and compound path along its
curves to cull it (Node::reach_bounds_with), and a compound path was
then assembled whole to test its outline and fill. For a compound path
of thousands of members that took milliseconds per click.
Hit testing now culls paths and compound paths by the box of their
control points (which holds the curves), grown by their strokes' reach,
so the box is never smaller than the reach box it replaces. A compound
path's outline and fill are tested against only the members whose
control box, grown by the tolerance, holds the point: a member outside
that box is farther than the tolerance and winds zero times round the
point. Painted strokes still follow the whole compound path (alignment,
dashes, width profiles) and are measured along all of it, as before.
Tests (crates/doc/src/hit/tests.rs):
- a 2,000-member compound path hits exactly as the whole-path test did,
at random points, on and next to anchors and on the edges of the
members' boxes, for both fill rules, in Outline view and with Object
Selection by Path Only;
- wide strokes painting past the control points are still hit, for a
path and a compound path;
- the cull box holds the reach box for paths and compound paths.
Measured in a debug test build (one run, machine not idle): a click on
a 20,000-member compound path went from about 18 ms to about 1 ms.
vectorcraft-cli perf was not rerun.
With one Objects on Path group of two or more objects selected, the
Selection and Objects on Path tools show a square Space widget beyond the
last object, ahead along the path. Dragging it moves the last object
along the path, around its bends, as far as the pointer moves along it,
and the objects between follow evenly while the first one stays. With
Alt (Option on macOS) the objects spread from both ends toward or away
from the center of an open path, or of the first anchor of a closed one.
The distance between the objects shows next to the pointer in the
document's units; the drag is one undo step, and Escape cancels it.
- object.objectsOnPath.options takes spacing (points, or "auto", the
default even spread) and start (points from the first anchor). A
spacing the path has no room for is drawn smaller and kept, so a
longer path brings it back. Older files read as Auto.
- Moving the group, Pivot, Rotate, path edits, copies and saving keep
the Space. Scaling, rotating or shearing the group keeps the first
and last objects where the transform takes them on the new path, live
shapes included; a value that cannot be kept that way scales with the
path's length.
- The Rotate All widget follows the first object when Space moves it.
Behavior: https://helpx.adobe.com/illustrator/desktop/manage-objects/arrange-objects/rearrange-and-edit-objects-on-a-path.html
and https://helpx.adobe.com/illustrator/using/objects-on-path.html.
Character Style Options and New Character Style get a Character Color
section and an OpenType Features section.
Character Color sets the fill and stroke (None or a document swatch;
a global swatch or a tint of one stays linked, with a Tint from 0% to
100%), the stroke Weight, and Overprint Fill and Overprint Stroke. A
gradient or pattern paint the style already has stays until another
paint is chosen.
OpenType Features sets Figure, Position, Standard and Discretionary
Ligatures, Contextual, Stylistic and Titling Alternates, Swash,
Ordinals, Fractions and Stylistic Sets 1 to 20. A changed control
replaces only its own tags in the style's feature list, so other tags
stay, and Default Figure and Default Position are stored as their
features turned off, distinct from unset.
Empty controls stay unset, and each section's Reset Panel clears only
its own attributes.
charStyle.new, charStyle.edit and charStyle.setAttrs check a changed
fill, stroke, stroke weight or feature list: tints from 0 to 1, links
to global swatches of the document (the color follows the swatch),
weights from 0 to 4000000, and known feature tags, at most 256. Values
the style or the text it is made from already has are kept.
charStyle.duplicate keeps the copied style's values in the same way, so
a style with a language tag that is not in the list can be duplicated.
Editing a global swatch now also updates the fill and stroke stored in
character styles, so their text no longer shows as overriding the style
and text styled later takes the new color. swatch.unused counts a swatch
a character style links to as used.