The measurement that runs before the style cascade and usvg now covers
the inputs that still got past it:
- usvg matches a selector without keeping what it found, so one whose
leftmost part matches nothing tests every set of ancestors in turn.
The measurement counts those tests for each copy and stops at
50,000,000; past that the SVG is refused with "the SVG's style sheets
take too long to match". An SVG image's matching stops at what the
SVG showing it left.
- A test counts one more for every 16 attributes a lookup scans and
every 16 bytes of the value it compares, so many classes against a
long class list count the work they do, in usvg's matching and in
the import's own cascade.
- The declarations the import's own cascade keeps, once per element,
count toward the 64 MB, including those of `:not()` rules, which usvg
drops. Each `:not()` alternative counts as a test.
- A linked SVG file is measured before its cascade runs. The linked SVG
files share one measurement: once they pass the limits together, the
files after that are shown without their links. The tests a file past
the limits made still count.
- SVG images share the element limit and the selector tests with the
SVG showing them, as they already shared the 64 MB.
The svg_filters reader measures its SVG before the cascade too.
A WMF without a placeable box picks its scale from the map mode its
drawing records are played in. Lines and shapes drawn under R2_NOP or
R2_XORPEN, and PATBLT records the reader does not fill, no longer count,
with the mix mode saved and restored with SAVEDC and RESTOREDC.
An open stroke that reaches far outside the picture is left out before
it joins the strokes waiting to be joined, so the strokes drawn before
it are still added.
Effect › SVG Filters and Apply SVG Filter list 14 default filters in
every document, before the document's own. The set, the names and the
values are VectorCraft's own, written with SVG 1.1 filter primitives
(crates/effects/src/svg_filter/defaults.rs).
A document holds none of the defaults until one is applied or edited
there. svgFilter.apply with a default's name copies it into the document
in the same undo step, and svgFilter.define with replace naming a default
adds the edited copy under that name. The copy is listed in the default's
place. svgFilter.delete refuses a default the document holds no copy of,
and deleting the copy lists the default again.
svgFilter.list also returns the defaults. svgFilter.apply takes index and
item to put the filter in the place of an applied SVG filter, which the
dialog's OK uses when it is opened for an applied effect; an index that
is not an applied SVG filter, null included, is an error.
EMF and WMF import now plays the records with the shared craft-metafile
reader (storytold/craft-libs#778, at the craft-libs rev the SVG, EPS and
DXF crates already use) and maps its picture onto the document here:
paths with their fills, strokes, caps, joins, miter limits and dashes,
clipping groups, images and point type. The EMF and WMF record players in
crates/metafile/src/import and the DIB decoder they used are removed. The
writers are unchanged, and the PICT reader keeps its own player.
What changes for a file being opened, placed or pasted, and for an EPS
file's Windows metafile preview (read when its PostScript can't be):
- An EMF that draws with EMF+ records is read from them. It opened empty,
with a warning that EMF+ is not read. A file with both kinds of record
is read from its EMF+ records too.
- Hatched and pattern brushes fill with the average color of the brush's
tile, translucent where the tile has gaps. Hatches filled with the hatch
color and pattern brushes with gray.
- Translucent fills, strokes and EMF+ text keep their alpha as opacity.
- Text keeps a stretch across from the world transform; it kept only the
turn. Text the file clips to a rectangle is clipped to it, and an
underlined or struck-through font gives underlined or struck-through
type.
- A clip path whose bounding box holds the whole picture is kept unless
it is that rectangle; a diamond or a bow tie around the picture was
dropped.
- Images drawn with SRCAND, SRCPAINT or SRCINVERT are drawn as they look
over white, and images drawn with other raster operations are drawn as
a plain copy. They were left out with a warning.
- Excluded clip rectangles and the other clip region modes are applied.
A clip path combined with RGN_OR, RGN_XOR or RGN_DIFF is ignored
without the warning it gave.
- An image past the reader's size or pixel limits, or one that can't be
decoded, is left out without a warning of its own. A file past the
reader's limits (more than 32 nested clip paths, 200,000 drawn items,
256 MiB held) is read up to that point without a warning.
- An EMF record whose size is not a multiple of 4 ends the file, with the
"ends early" warning.
Tests: an EMF+ only picture imports its fill (it failed before), a dual
EMF+ file draws its translucent EMF+ fill and not its fallback, hatch and
pattern brush colors, clipped and underlined text, excluded clip
rectangles, a shared clipping group, an odd record size, WMF at 1440
units an inch without a placeable header and at its own units with an
inverted box, an EMF without a frame, diamond and bow-tie clips, text with a
stretch, clips nested past the stack and 200,000 clip records; the fuzz
test gains random EMF+ records.
With Window › Arrange › Tile or floating document windows, every document
other than the front one was drawn through the profiles the front document
is assigned, since the active color settings are process-wide.
- cms::with_active puts other settings in place of the active ones on one
thread, fast paths included, and puts them back after (also on a panic).
cms::for_document gives the context a document's assigned profiles make,
the same one each time for the same profiles and working settings.
- Each pane renders its document's art through colormgmt::doc_cms, on the
render worker and on the UI thread alike, and draws its paper, artboard
colors and transparency grid through the same settings.
- Loading or removing a profile redraws the documents assigned it, the
ones behind the front one included.
- The ink and separation tables keep the tables of up to four color
settings, so documents with different CMYK profiles side by side do not
rebuild them on each redraw.
Placed documents inside a document behind the front one still draw through
the front document's profiles.
A graphic style from a .ai or EPS library whose fill, or the fill or
stroke of a style of one paint, paints with one of the file's gradients
by name was left out of the library. Such styles now load with that
gradient at the style's angle. The style stores only the gradient's name
and angle, so the gradient is fitted to the bounds of each object the
style is applied to; this placement is VectorCraft's own rule.
- A style of one paint reads a gradient instance's fill or stroke as
that gradient; a gradient outside an instance is still not read.
- A fill filter paints with its last paint: a color, a gradient or a
pattern replaces whichever of the others came before it.
- A style naming a gradient the file doesn't define, or painting with a
pattern, is still left out.
Docs: docs/mcp.md, graphicStyle.loadLibrary params, progress doc.
With Smart Guides on, the Dimension tool's second point snapped onto the
construction guides through the first point (Smart Guides › Construction
Guides), so a click beside a segment straight below a first point that
was not on an anchor stayed on the vertical guide instead of landing on
the segment.
The second point now has no construction guides through a first point
that is not on an anchor. After a first point picked on an anchor, the
construction guides through it still apply. The second point still snaps
to anchors, centers and alignment lines, and with Snap to Perpendicular
or Snap to Tangent on it lands where the line from the first point meets
a segment square or grazes a curve.
Docs: docs/dimensions.md, docs/target-app-parity.md.
Two windows or processes using one Plug-ins folder could interleave their
writes: an install from one could leave its module with the other's grant,
a grant written for one plug-in could drop one written for another, and
plug-in settings and cache changes could lose each other's keys or pass the
cache caps together.
Each change to the folder now runs in one turn on disabled.lock, the file
the disabled list was already locked with:
- plugin.install takes the turn before it reads the stored grants and holds
it through the module, grant, put-back and disabled-list writes;
plugin.remove holds it through grant removal and storage deletion.
- Grant writes, the disabled list, loading the folder, and the plug-ins'
settings and cache changes read and write in a turn.
- When disabled.lock can't be locked, plugin.install and plugin.remove fail
and change nothing, plugin.setEnabled keeps its change in the process, and
loading the folder writes nothing to it.
Docs: docs/plugins.md, docs/mcp.md, the plugin.install and plugin.remove
params text, docs/target-app-parity.md.
An unlinked aligned dimension that was reflected on its own kept its
offset, which is measured along the left normal of a to b. A reflection
turns that normal to the other side, so a line 30 pt above two level
points stayed 30 pt above them after a flip top to bottom instead of
going below them. The offset now changes sign when the transform
reverses orientation, as a reflection or a scale by a negative factor
does. Other transforms leave it as before, and linked, horizontal and
vertical dimensions keep their own rules.
Docs: docs/dimensions.md.
Object › Plug-ins › Cutting Tools › Nest Shapes… (`addon.cutting.nest`)
moves the selected objects close together onto a sheet or a roll of
material. The sheet starts at an artboard's top-left corner and is the
artboard's size or a set width and height; a roll is a set width with
no end. A margin (18 pt by default) is kept inside the sheet's edges and
a spacing (9 pt) between objects.
Objects are packed by their bounding boxes, with their strokes and
effects (the reach of shadows, glows and blurs included) or by their
geometry: the tallest first, each where its bottom edge is highest and
then furthest left. With Rotate, an object may turn 90° clockwise where
that puts its bottom edge higher or where only the turned object fits;
a turned object is measured after the turn. A path of a pixel-aligned
compound path is not turned. Objects that do not fit go in a column to
the right of the sheet, in stacking order. Objects on the pixel grid
move by whole points and take 1 pt more room, so they stay on the grid
and keep the margin and spacing. A path of a compound path selected on
its own moves alone. Objects without bounds, such as an empty group,
are not moved and are listed in the result.
The command is one undo step, and its result gives the objects placed,
put aside, turned and skipped and the length of material used. The
dialog has Preview.
The new Nest Shapes and Roll catalog rows are in every catalog.
Docs: docs/mcp.md (Cutting Tools add-on), docs/target-app-parity.md and
ROADMAP.
Under a colored light, Inflate's facets were antialiased one by one.
Where two facets met, their edge pixels added up to less than full
coverage. The solid-color composite then showed its black channel
backdrop there, so internal edges drew darker at 1:1, and the per-facet
gradient art let the background through.
- The solid-color composite draws, in each channel, an underlay of the
facets with 2 pt round seams between the black backdrop and the
facets themselves.
- The per-facet routes put each run of consecutive faces of one surface
over an underlay of its own faces, clipped to the union of the faces
and their seams. Runs that carry a gradient's opacity mask are sealed
with that mask. A face clip's footprint keeps only the outer contours
of its seam stroke.
- The paint-copy allowance for cut faces counts 12 copies per facet.
At 1:1, inside the surface each color channel of an opaque fill is
within 3 levels of the same art drawn at 3x and averaged down, and
every pixel is opaque. Before, 2,267 channels differed by up to 10
levels on the solid Inflate, and 1,088 pixels of the gradient Inflate
were below full alpha. Under white light, the rendered pixels, the SVG
text, the PDF read back and the expanded art of Inflate, Extrude,
Rotate and Revolve are the same as before.
Docs: docs/three-d.md (Seams), docs/target-app-parity.md.
The location sweep listed imageTrace.make's `paths` as a document part
at the top of its params, so catalog_path_keys_match_location_policy
failed: Image Trace's path fidelity `paths` is exempt only inside its
tracing settings, and a top-level `paths` names a file, as it already
does for imageTrace.makeAndExpand.
A legacy .ai graph (%_Gs ... %_GS) opened as plain art, with its table,
type, axes and settings left out. A grouped column, stacked column, line
or area graph now keeps them on its group when every record has the
shape and values the version 7 specification's appendix A gives, the
table holds at least one number, the drawn art strokes the plot's left
and bottom edges, and each series group is tagged once as a whole
column, holds only paths and paints them with one fill and one stroke.
The saved art stays as it is on open; Graph Data redraws the graph from
its data afterward, with each series' fill and stroke kept.
Any other graph (pie and scatter graphs, designs, customizations, record
shapes of later versions, tables past 10,000 categories, 256 series or
2,600,000 cells) keeps its art as plain objects with the existing note.
Saving a graph to .ai, in either form, now warns that its data and
settings are not written.
Object › Plug-ins › Cutting Tools › Banner Hems and Grommets…
(`addon.cutting.banner`) marks where a printed banner is hemmed and where
its grommets go. The banner is the bounds of the selected art, or each
artboard when nothing is selected. On each chosen side the command adds a
hem line a set depth inside the edge (1 in by default) and grommet marks a
set inset inside it (0.5 in): one at each end of the side and the rest
spread evenly, as few as keep them at most a set spacing apart (24 in). A
corner that two chosen sides share gets one grommet. Each grommet mark is a
circle (27 pt by default) with a cross through its center. The marks are
stroked in a chosen swatch ([Registration] by default) with no fill, in one
group named Banner Marks per banner on top of the current layer, selected,
as one undo step. While a clip group is edited in isolation, the group goes
outside the outermost clip group, as Weed Box and Lines does.
The command refuses every side off, no hem and no grommets, and bad params
with a message. It also refuses a hem or inset that reaches the far edge of
the banner when one of two opposite sides is chosen, or that meets the marks
of the opposite side when both are chosen, an inset of more than half a
chosen side's length, and more than 100000 grommets. The dialog has Preview.
The grommets' distance from the edge is `grommetInset`, so the dialog shows
it as Grommet Inset with its own catalog rows. The new Diameter catalog
rows also translate the Diameter field of the `shape.flare` command dialog,
which had no rows.
Docs: docs/mcp.md (Cutting Tools add-on), docs/target-app-parity.md and
ROADMAP.
Under a light that is not white, an opaque Inflate fill or stroke in a
solid color was drawn as one composite of all its facets, so the
object's solids were drawn whole, one after another: the stroke's faces
covered every face of the fill, nearer or not.
When an object's solids share one order (all opaque with Normal
blending, or one member's under its opacity mask), each facet of such a
solid is now drawn on its own, with the composite's paint, channel
light ramps and seam width, and takes its place in that order as the
faces of a gradient already do. A lone solid, and the solids of an
object with a translucent or blended fill or stroke, keep the one
composite.
A face drawn on its own is about 16 nodes, against about 6 per facet
in the composite of a whole surface. These faces go in one order while
they number at most 30,720 together, counted again after the cuts.
Past that, each such solid is drawn as one composite.
Docs: docs/three-d.md, docs/target-app-parity.md.
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.
The new Trim at Crossings Tool removes stretches of paths between the
places where they are crossed by another path or by themselves. A click
removes the stretch under the pointer, a drag removes every stretch its
rectangle touches, and an Alt-drag removes every stretch its freehand
line crosses. It works on every path that can be edited, selected or
not, and each gesture is one undo step.
- `addon.pathtools.trim {at, tolerance?}`, `{rect}` or `{points}` is the
command behind the tool. A closed path that loses a stretch becomes
open, an open path that loses a middle stretch becomes two paths with
its appearance in its place in the stacking order, and a path that
nothing crosses is removed whole. The stretches that stay get no new
anchors.
- Hidden and locked paths, guides and clipping paths are not trimmed and
do not count as crossing. Only the paths the gesture reaches are
searched for crossings, and one work limit covers every path the
command trims.
- `crossings_on` and `crossings_on_within` in `vectorcraft-pathops` find
the crossings on one path and compare it only with the segments in
its box.
- The new tool shares the crossing search with Add Points at Crossings
and Split at Crossings, so those change too: two segments of a path
that meet at an anchor, directly or across segments with no length,
now count as crossing where they cross again away from that anchor,
and do not count as crossing at the anchor itself. With Self
Crossings, a curved segment that loops over itself counts as crossing
itself. Before, both crossings were missed.
- The tool is an add-on tool: Object › Plug-ins › Path Precision Tools
and the All Tools drawer list it, it has no shortcut, and neither
shipped toolbar shows it.
Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
A param key names a file or a folder by a reviewed list of whole names,
read in the context of the command it is for (ParamsPlace): the
plug-in call check, the control server's reply hiding and recorded-step
filtering, and the sweeps' temp-folder redirection all use it.
- `url` (a web link) and `folders` (an export's grouping) no longer
count as files or folders, and keys that only end like one
(alignToPath, wholePaths, sortPaths, linksFolder and others) are no
longer path params in control replies or recorded steps.
- `dir`, `directory` and the folder preferences that prefs.set takes in
`values` (fontsFolder, pluginsFolder, recoveryFolder,
templatesFolder) are folders everywhere, so a relative path under one
outside the read root is taken out of a recorded step.
- A `command.batch` step, an `engine.execute` request and a recorded
step keep their own command's exceptions below the top level, so the
sweeps no longer replace a nested web link or export grouping.
- A new test sorts every catalog key that reads like a file or folder
key into the reviewed lists and checks every classifier against them.
The profiles Edit > Assign Profile gives a document stayed the active
color settings after another document came to the front, so a new,
opened or switched-to document drew and converted its colors through the
previous document's profile, and closing the last document left it in
use.
The session now puts the front document's profiles in use (the working
spaces where it has none, or no document is open) after a new or opened
document, a tab switch, a close, File > Revert, every command including
each batch step and Undo or Redo of Assign Profile, a failed batch or
plug-in call, a script's rollback and slice boundaries, a canceled
preview, undo group or undo scope, and for each document Export All Open
and the update of placed documents work in. The active settings are
rebuilt, and the documents redrawn, only when they change.
Clear, path.deleteAnchors, Cut, Copy and Cut Path scanned every selected
anchor or segment of a path once for each of its subpaths, so a path with
thousands of subpaths took time that grew with the square of their count.
The selection sets are ordered by subpath, so a new helper, subpath_refs,
reads one subpath's entries with a range lookup instead. The results are
unchanged.
With one selected interior anchor on each of 8,000 open subpaths of one
path, Clear took a median of 74 ms of thread CPU time before and 3 ms
after (release build, five runs each; the timings are noisy because the
CPU was shared and loaded).
Tests in delete_path_pieces.rs check exact surviving subpaths and their
order for anchor and segment selections across open and closed subpaths,
an untouched subpath, references to missing subpaths and indexes, one
Undo/Redo step and a second Clear, and Cut Path on several subpaths.
The eye and lock on a guide's row in the Layers panel hide or lock that
guide alone, in one undo step, and a drag down either column takes guide
rows along with the other rows it passes. A hidden guide is not drawn and
does not snap, and its row is dimmed; a locked guide is drawn and snaps
but can't be selected, moved or deleted. Both can be changed while the
guide's layer is hidden or locked, and the guide shows or can be edited
only when its layer allows it as well. The state is kept through save
and reopen.
- layer.setProps takes ruler guide node ids; every other command still
refuses them.
- guide.list rows carry the guide's node id and its own hidden and
locked state.
- Alt-click and Ctrl/Cmd+Alt-click on a guide row's eye or lock act on
the other layers, as on any row in that layer.
- Object > Lock > Selection and Object > Hide > Selection lock or hide the
selected ruler guides with the selected art, in one undo step, outside
isolation mode. Unlock All and Show All, document scope, also unlock or
show guides locked or hidden on their own and select them as guides.
- Guide rows dropped on the Layers panel's trash delete their guides
(guide.remove {indexes}). A click on a guide row clears the highlighted
rows, so Delete deletes the guide; with no row highlighted the trash
deletes the selected guides.
- guide.remove and guide.setLayer refuse index together with indexes and
an indexes value that is not a list, and count a repeated index once.
guide.select, guide.remove and guide.setLayer refuse an indexes list of
more than 10,000 entries; guide.setLayer cut such a list short before.
- Moving, deleting or moving to another layer a guide that is hidden or
locked on its own fails with "the guide is hidden or locked"; a guide
on a hidden or locked layer still fails with "the guide is on a hidden
or locked layer".
Docs: docs/mcp.md, docs/target-app-parity.md, docs/ui-parity.md.