A document's composite fonts were lent by writing them into the font
database's shared map, and a second lend of another definition of the same
name was skipped while the first was held. A document drawn in a tiled or
floating window while the front document drew, a document placed in one
that defines the name differently, and an export made while another
document is in front could draw their type in the other document's
definition. Registering the front document's fonts during a render could
also change the definition mid-render.
Lending now applies on the lending thread only: each guard pushes the
document's definitions onto a per-thread stack that lookups read before the
database's own fonts, latest loan first, and removes them when it drops,
also when the thread unwinds. The guard is neither Send nor Sync. Lending no
longer changes the database or its generation; while a thread lends
definitions other than the database's, FontDb::generation gives a number of
its own for them, so kept layouts and render caches made in one document's
definitions are not used for another's.
The test's second and third wheel turns step to whichever families the
process has loaded at that moment. When another test has loaded a test
font without Latin glyphs, such as Alias Jointest Sans from the Glyphs
panel tests, the turn applies it, missing glyph protection keeps "Hi" in
Source Serif 4, and the assert_ne at the second turn fails. The test now
turns the preference off, as the font menu tests in tests/font_menu.rs
do, so each turn changes the text's font whatever family it reaches.
With downsampling on, each piece of an image bent by a Warp effect was
resampled for its own placed size, so a warp wrote one image XObject for
each size a piece was placed at (four for a 4 x 4 warp that stretches
across, eight for a colorized one with its soft masks). The pieces are
now resampled for the widest and the highest size any piece places the
image at, and all paint that one image. Pieces off the page count too,
and another placement of the same image keeps its own size.
Without downsampling, a colorized image and a CMYK image with a grid
mask no longer key their cache by placed size, so each is decoded and
encoded once for all the sizes it is placed at, as plain images are.
When a recovery copy could not be written, the status bar said only
"Couldn't save recovery data: <error>", and a copy that failed on the
background worker showed as "Saving recovery data for <title> failed:
<error>". Neither said which folder was in use or how to pick another.
The message keeps the error text (and the document's title when one
copy fails), then names the folder copies go to, says whether it is
the default recovery folder or the folder set in Preferences, and
names the setting that picks another folder: Preferences → File
Handling → Data Recovery → Folder (recoveryFolder). The recovery code
writes the same line to the log. The folder is the one in use when the
round started, so a preference changed while a copy runs does not
change the folder the message names.
A copy that fails now reports its own message, so the job's result is
Ok rather than an error, and the job no longer adds "<label> failed:
<error>" over it. A store the host installed (browser storage) has no
folder to name: its failed copy shows "Saving recovery data for
<title> failed: <error>", as a copy on the worker did before, also
when the copy was written without the worker.
- engine: recovery::folder returns the folder in use and whether the
recoveryFolder preference names it; recovery::store uses it.
- Four @msg rows in each complete-message catalog, and in the bg, bs,
da and id catalogs, which translate the old message.
- Control replies to a client confined to read roots show a recovery
folder outside them, and the paths under it, by the folder's name, as
they already show other paths outside the roots. That covers the
folder in use and the last eight folders a failure message named.
- docs/target-app-parity.md: a line next to the Data Recovery Folder
preference.
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.
object.setLiveShape was in params::UNCHECKED, so a key it does not read,
such as `path`, ran the command with the key ignored. Its description
names every key it reads, so it now goes through check_keys like other
commands, for MCP, scripts and command.batch steps. Each of `items` takes
`id` and `corners` alone; another key is refused as
`items[0].corner`, with the nearest accepted key. Saved steps still
replay leniently.
A `sides` that is not a whole number from 3 to 1000 (2.5, -1, 2, 1001)
and a negative `radius` are now errors naming the key, and leave the
document and its undo history as they were; a radius of 0 still
removes rounding. The Transform panel's Sides field takes 3 to 1000,
the Corners dialog's radius field stops at 0, and the panels' corner
radius fields send 0 for a negative value typed in.
Break Text checked every thread of the document for each selected type
object to skip threaded frames, and Remove Breaks searched every thread
for each selected object to find its story, so the work grew with the
number of selected objects times the number of threaded frames.
Both commands now index the document's threads once per command: Break
Text tests membership in that index, and Remove Breaks takes the story
from the indexed thread through threads::story_in, the second half of
story_of. Split Text at Cursor, which takes one object, checks it once
as before. The results are unchanged.
Tests: Break Text on two threaded frames and six point type objects
skips the frames unchanged, breaks the rest in place as one undo step
and goes through the threads once; Remove Breaks reads the selected
story as one text and the other objects on their own, going through
the threads once; Split Text at Cursor refuses a threaded frame.
Offset Path took each selected path in turn, so offsetting many paths, or a
group of many paths, used one CPU. It now reads the selected paths first,
works out their offsets on as many threads as the computer has logical CPUs
(one thread on the web and for a single path), and then makes the new
objects one after another in the selection's order on the editing thread.
The new objects, their ids, their stacking, the selection, the messages and
the undo step are the same as before. When several paths fail, the error is
the one for the first of them in the selection's order, and the paths after
a failure that have not started yet are skipped.
A right-to-left line draws the text before a tab after it, but its width
was measured in the order of its text. A right-aligned line such as
"אב גד<tab>הו זח" with a stop at 150 pt in a 300 pt frame ended 5.59 pt
past the frame's right edge, and right, center and decimal stops aligned
the text before the tab instead of the text drawn after it.
- The line's width is measured in drawing order, each tab up to its stop
from where the glyphs drawn before it end. Right, Auto and centered
lines are aligned by that width. Stops are still measured from the
left edge.
- Right, center and decimal stops align the text drawn after the tab.
- Area type breaks a line with a tab where it fits as drawn, and wraps a
line that fits only in the order of its text. Once the line holds a
tab, it reads on while the least it can be drawn at fits, then ends at
the latest break that fits as drawn: up to 256 breaks are measured
from the latest back, and earlier breaks without a hyphen are
bisected. Hyphenation points are measured as drawn the same way, so
the line hyphenates at a point that fits.
- Only lines of horizontal paragraphs with right-to-left text keep a
list of their break candidates.
- A tab that ends a wrapped right-to-left line is drawn before the line
at its own width instead of pushing the line to the right.
Docs: docs/target-app-parity.md.
Type on a path whose first paragraph is aligned Justify All places its line
from the start bracket, the same as Left, so a threaded story is now measured
with the paragraphs it needs rather than the whole rest. A 1,500-paragraph
story through 40 paths aligned Justify All was laid out over 2,135,202 bytes
while measuring and is now laid out over 20,583. The frames take the same
text as before.
Center, Right, Auto right to left, hanging punctuation, Dynamic Text and
horizontal right-to-left text are still measured with the whole rest: a
centered or right-aligned line starts where the width of all its glyphs puts
it, and negative tracking can make an advance negative.
Tests compare every frame's share with the whole-rest share for Justify All
with spacing, on closed paths, with negative tracking and with punctuation
ending paragraphs.
Add Points and Divide into Dashes read each subpath's selected segments
with a range lookup in the path's ordered segment set instead of
filtering the whole set once per subpath. Add Points at Crossings and
Split at Crossings sort a path's crossing places into a list per
subpath once, in the order they come, instead of filtering the whole
list for every subpath. Places on a subpath that no longer exists are
still left out. The results are unchanged.
Tests cover paths with several subpaths, an empty subpath, an
untouched subpath and selection on the last subpath, for all four
commands, plus the anchor fallback and the grouping of out-of-order
crossing places.
An SVG shown as an image that could not be read (text that isn't XML,
an SVG without a size) still took one of the 256 nested SVG images an
SVG may show, so an image after 256 broken ones was left out. Only
images that decode now count toward that limit; a failed image gives
its place back.
Every nested SVG image read, decoded or not, counts toward new limits
of 1,024 reads and 512 MB of text per import, so a broken image shown
many times through <use> is still read a limited number of times. Past
them, nested SVG images are left out with a warning.
A nested SVG image with text that usvg then refuses no longer warns
that its text isn't imported.