Session::replace_document on a tab other than the current one cleared
the bounds of type set in a font named like a composite font and left
that type with estimated bounds until the document came to the front.
- The replacement's type without bounds is measured at once, in the
document's own composite fonts, lent for the measuring. The front
document stays the one whose composite fonts are registered.
- When the document then comes to the front unchanged, its type in its
own composite fonts is not measured again. Type set in a composite
font only another document defines is still measured again, as
missing, and so is type set in a composite font name that another
thread's loan held with a different definition.
Spot Color Objects listed every spot color linked from a gradient's stops
once any stop was above opacity 0. A spot stop at opacity 0 between other
clear stops, at the end of the ramp after a clear stop, or meeting an
opaque stop only at one offset was listed though it prints no ink, and so
was an opaque stop out of order whose pairs have no width.
A stop now counts when it bounds a stretch of some width that the
gradient sampler mixes and either stop of that stretch is above opacity
0, or when it is the first or last stop and above opacity 0, since those
color the offsets before and after the others. Stops in any order are
judged by the stretches the sampler uses. A freeform gradient keeps the
old rule, since it draws from its points. Type runs follow the same rule.
The document.info params text, docs/mcp.md and the progress line
describe it.
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.
Several threaded-text paths in threads.rs searched a list once for each
frame, so their work grew with the product of the frame count and the
list length:
- leave_threads (Create Outlines, Expand Appearance, Rasterize, Flatten
Transparency, symbol replacement, opening with text as outlines)
searched the threads for each frame that stayed and ran continued over
that frame's whole thread again, about n^2 frame visits for a thread of
n frames. It now marks each changed thread with one continued call,
and keeps the frames that left in a map instead of a list.
- has_thread_selection (the availability of Release Selection and Remove
Threading), with_threads, keep_shown_text and cut_to_shown, release and
remove test ids against a hash set instead of a list.
- create finds the thread of each selected frame from a table built
once, and each frame still takes in the first thread not yet merged
that lists it.
- release writes each thread back at its own index instead of searching
the thread list for an equal thread.
Marks, thread membership, frame order, text and overflow are unchanged.
A 256-frame thread that loses one frame visited 65,025 frames before
this change and at most 256 now. Six tests on many threads count the set
lookups: with a list in place of the set they make 67,650 to 258,226.
Another test compares the marks with a scan of the frames before each
one.
With repeated names numbered when a brush library is read, a stroke that
names a repeated brush draws the first brush of that name, and a later
brush of the name is read as "Name 2". Three tests built a library with a
repeated name and expected the later brush:
- Document Info lists a name two stored brushes share with the first
brush's type (art), not the last one's.
- The two render memory tests add their own `Dots` scatter brush beside
the default library's `Dots`. They now remove the default `Dots` first,
so the stroke draws their brush.
- The .ai writer test that leaves out a Pattern brush "X" gets the
Calligraphic brush after it back as "X 2", the name it had in the
document.
With the selection as its scope, a fix that removes or unwraps objects
(Needless Groups, Stray Points, Empty Text and the others) kept the list
of selected objects current by searching it and splicing it once for each
object it changed. Removing many selected objects above a long run of
others took time that grew with the square of their number.
The list is now rebuilt in one walk after each pass: each changed object
gives way to what replaces it, in its place, and the others keep their
order. The objects found, the counts, the document and the undo step are
the same as before.
Empty Artboards deleted each empty artboard with its own Vec::remove and
walked every ruler guide again for each one. It now drops the empty
artboards in one walk and deletes their guides in one more. The same
artboards and guides are deleted as before.
Tests cover the order after unwrapping nested and empty groups, Fix All
reaching an unwrapped member on a later round, the rebuild itself, the
number of objects looked at for 150 and 300 removals, and the number of
walks over the artboards and guides when 40 empty artboards are deleted.
A command now counts as taking a file or folder when its params
description (with the commands its `…` entries name) or its schema takes
a file key as a param. A word in the description's prose or result no
longer counts; a description whose keys are not known keeps the text
scan. The code of the call-time scan of arguments is unchanged, but it
reads the changed exemption tables. The confined run is unchanged.
- NOT_FILES drops seven entries that named no param of their command
(object.setLiveShape path, path.reverse paths, object.pathfinder.options
paths, object.maskImage path, imageTrace.make paths,
svgInteractivity.info files, addon.cleanup.rejoin paths). A top-level
`path` to object.setLiveShape or object.maskImage, `paths` to
path.reverse, imageTrace.make or addon.cleanup.rejoin, and `files` to
svgInteractivity.info are now refused.
- Image Trace's path fidelity setting is exempt only inside `params` and
`savedPreset` of imageTrace.make and imageTrace.makeAndExpand, so a
call with it is no longer refused.
- every_command_is_sorted checks that each exemption names a param its
command takes, and that each nested exemption starts at one.
- effect.expandAsWireframes is reviewed as allowed and
stroke.arrowheads.list as refused.
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
- The clipping path of a clip group or a clipped layer takes no part,
the members of a compound clipping path included. Before, only a
plain clipping path was left out.
- In isolation mode only paths inside the isolated object are trimmed;
paths outside it still count as crossing.
- A rectangle or line that reaches so many paths that comparing their
boxes would take more than 50,000,000 tests is refused before any
work starts.
- A tool drag that goes 3 screen pixels or more from the press stays a
rectangle or line when it ends near the press. Before, it ran as a
click and could remove a whole path the final rectangle did not
touch.
Docs: docs/mcp.md.
While the Smooth tool drags with a path selected that it can smooth, a
dashed outline shows the band its release smooths: every point within
the tool's radius (12 screen pixels) of the drag, round at both ends.
Alt smoothing with the Pencil, Paintbrush and Blob Brush shows the same
band. A selection with no path the tool can change shows none.
- The band is kept in squares eight radii wide, and each step of the
drag adds its area only to the squares it reaches. A piece of a
square's outline along a side is left out only where the square
across holds band area.
- A drag segment too long for the squares (a jump drawn zoomed out and
viewed zoomed in) keeps its band near each end in the squares and
shows the rest as the two sides of its band. A step that cannot be
added whole is added one segment at a time.
- A coarser grid of cells lists the squares and long segments, so a new
one is checked only against those near it, and an outline is made
again only where the band near it changed.
- After a zoom change mid-drag the band is outlined again a few hundred
points per frame, and the work on long segments stops for the frame
after a fixed budget. Tools report unfinished overlays with
Tool::overlays_pending, and the canvas then draws again on the next
frame, so the band covers the whole drag even while the pointer holds
still.
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.
Brushes, Spot Color Objects, Pattern Objects and Gradient Objects left
out a fill or stroke that was hidden or at an opacity of 0, but still
listed an object that was itself hidden or at an opacity of 0, and the
objects inside such a group or layer. They now leave those out, their
characters' paints and colorized images included. With Selection Only,
an object selected inside a hidden group or layer, or one at an
opacity of 0, is left out too. Objects still counts every object.