When the desktop app opened a document on a thread of its own, that
thread parsed it, but open_read, which the app ran on the UI thread,
read the document's linked files, decoding BMP, PSD and RGB TIFF files
and storing them again as PNG, and laid out the type. In release builds,
a document with 12 linked 3000 x 2000 TIFFs kept the UI thread busy for
1.04 s, and one with 300 Auto Size area type objects, every second one
in three columns, kept it busy for 0.85 s.
read_for_open now reads the linked files and lays out the type on the
opening thread, with the path and the Update Links preference that
opening::start passes it, and open_read only adds the finished document.
open_read now takes 0.02 ms and 0.1 ms on those documents. For the
measured documents, the opened documents, results and undo states are
the same as on main.
Docs: docs/target-app-parity.md.
Every symbol instance was measured as its transform applied to the
20 × 20 square around the origin, which symbols made in the app keep
their art in. The SVG and DXF importers keep symbol art as drawn, so
the box of an instance of an imported symbol did not match its art.
The SVG Star instance at x 20, y 30 draws from (20, 30) to (40, 50)
and was measured from (10, 20) to (30, 40). Align moved such an
instance by the wrong amount, clicks and marquees beside its art
selected it, and the canvas, exports and prints left it out when its
art reached the view or the page and the square did not.
An instance is now measured by its symbol's art, which
Document::symbol_boxes looks up in the document each time
(Node::geometric_bounds_with, visual_bounds_with and
reach_bounds_with). With Use Preview Bounds on, the strokes in the art
count at their own weight, as the canvas draws them, and
object.setBounds counts them at that weight when it sizes an instance
or an object that holds one. When strokes do not scale and a width or
height of 0 or more is smaller than what a path's stroke adds,
object.setBounds now gives the path no width or height, as it does
for an instance whose art has strokes; before, it mirrored the path.
With Use Preview Bounds on, object.setBounds now measures the margins
that strokes add object by object, the members of groups and the
objects in symbol instances' art included, and solves for the width,
height and reference point asked for when the width and height are 0
or more. Before, it measured the margins of the whole selection and
could miss the size or the reference point asked for when strokes did
not scale and the objects' strokes added different margins, and when
strokes scaled and the width and height were out of proportion, even
for one path. A clip group, a Shaper Group, a compound path, type, and
a group or instance with effects or an opacity mask of its own are each
measured as one object, and so is an instance in perspective or one
turned or slanted relative to the bounding box, other than by quarter
turns. When the selection holds more than 4,096 objects to measure this
way, each selected object is measured as one. Instances inside symbol
art are measured by their own symbol's art. An instance is measured as
the square when its symbol is missing, when its symbol or a symbol
nested in its art holds an instance of itself, and when symbols nest 32
deep in its art.
Align and Distribute, the Transform panel, the bounding box and the
selection outline, clicks, the marquee, document.inspect and
document.node, Fit to Artwork Bounds and Fit to Selected Art, canvas
culling, thumbnails, the SVG filter region of an instance with a
raster effect, raster effect images, Export Selection, the SVG of an
artboard, printing, and the PDF, EPS, EMF/WMF and DXF writers use the
new box. The renderer also culls an instance by the effects and
brushed strokes in its art, and it culls a blend, envelope or repeat
by the brush art of what it holds and the effects in the art of the
instances in it. It culls an instance with geometry effects of its own
by the effects in its art after those geometry effects reshape the art.
It measures the effects in a symbol's art once for each symbol and
linear part of the transform that places it. For symbols whose art
holds instances, it measures at most 4,096 placements a frame, and at
most 4,096 more for each pattern tile it rasterizes and each placed
document it draws. Past that, it estimates the reach from the symbol's
reach with no transform, which it measures once. When the drawn
document's symbols change, the renderer drops the cached bounds of the
instances and of the objects that hold them.
The PDF and EPS writers, the SVG of an artboard and Skip Blank
Artboards grow the box of an instance whose art holds brushed strokes
by 16 times the widest stroke (at least 1 pt) of the objects in that
art that have a brushed stroke, plus 8 pt, as the renderer grows the
cull bounds of a brushed path. They keep an instance whose brush art
reaches the page. The SVG of an artboard also keeps an object that
holds an instance when what the canvas paints for it, the effects in
the symbol's art and the instance's own included, reaches the artboard.
Not yet: other commands that measure objects, such as Select › All on
Active Artboard, Transform Each and Convert to Artboards, still measure
an instance by the square.
Docs: docs/mcp.md, docs/target-app-parity.md.
The SVG import's style cascade tested every <style> rule against each
element it was asked about. The import asks about the objects outside
<defs> and the like when the file contains `display`, about each shape
when the file contains `non-scaling-stroke`, and about each <text>, its
spans and its ancestors. On a file with 2,000 class rules and 100,000
rectangles, `vectorcraft-cli info` took 3.54 s with one `display`
attribute in the file and 2.03 s without it.
The rules are now filed under the id of their selector's rightmost
compound, else under its first class, else under its type. An element
is tested against the rules filed under its id, its classes and its
type, and the rules filed under none of these, in source order. It
matches the same rules in the same order and gets the same
properties. The file with the `display` attribute now takes 2.10 s.
Docs: docs/target-app-parity.md.
A CMYK document that shows transparency is drawn as two ink planes,
and ink::compose turned them into screen colors one pixel at a time on
one thread. At 2880 x 1800 that took about as long as drawing one
plane of 50,000 paths. The renderer also searched a CMYK document for
transparency in every frame.
compose now splits the frame into bands, as many as the renderer has
threads and no more than one per 65,536 pixels. The calling thread
composes the first band and a scoped thread composes each other band,
with the same per-pixel code. The renderer keeps the answer of the
transparency test with the layers, symbol art and pattern art it was
found for, held weakly, and tests again only when the document holds
other art. With Overprint Preview on, a document whose layers or
symbols hold an overprinting fill or stroke is still tested in every
frame. FrameStats::ink_threads reports how many threads composed a
frame. On 8 threads at 2880 x 1800, a page of 300 shapes at 20% to 90%
opacity takes 30 ms a frame instead of 55 ms, and 50,000 paths with
one at 99% opacity take 258 ms instead of 361 ms, with the same
pixels.
Docs: docs/target-app-parity.md.
In Outline view the renderer made a new path from the path data of
each path and compound path it drew, in every frame. In Preview the
renderer draws plain paths from the path it keeps for each object
between frames. In Outline view the members of a clip group were
culled by bounds measured again in every frame, and the bounds of a
group inside a clip group were measured from every path in it. The
outlined shapes of an Image Trace object were culled and drawn the
same way.
In Outline view the renderer now draws paths and compound paths from
the paths it keeps for them, and culls the members of clip groups and
the outlined shapes of Image Trace objects by the bounds it keeps for
them. The live-effect geometry of those members is kept between frames
too. The frames have the same pixels as before.
With 50,000 paths at 2880x1800 on 8 threads, an Outline frame at 400%
took 47.1 ms on main and takes 16.7 ms when the paths are the members
of one clip group. It took 36.6 ms and takes 26.3 ms when the paths
are paired into compound paths.
Docs: docs/target-app-parity.md.
The renderer kept the evaluation of each blend, envelope and repeat
while the object and a hash stayed the same: the addresses of the art of
the symbols the object showed and of the patterns it painted with, and
the patterns' tile bounds. When anything else the evaluation reads
changed, such as a pattern's tile type, symbols in symbol art, the
fonts or the color settings, a renderer that had drawn the object went
on drawing the old evaluation. The quads of a gradient mesh were kept
the same way after a change to the color settings, and the art drawn
for a live object with an effect of its own was kept by the object's
address alone.
resources_key now also walks the opacity-mask, symbol and pattern art
it finds. It hashes what it finds, the font generation when it finds
type or a placed document, and the color settings. Mesh quads are made
again when the color settings change. The art drawn for an object's
effects is made again when the hash changes, if it was made from
evaluated live objects.
With other builds running, a temporary release test timed the hash for
400 live objects at 17 to 506 microseconds a frame, against 10 to 16
before.
Docs: docs/target-app-parity.md, docs/gaps.md.
A transparency group whose members blend with the art below it, and a
masked object with a blend mode, are drawn over a copy of that art. For
each such group in each frame, the renderer rendered everything drawn
so far into a new pixmap the size of its context (on the canvas, the
whole view), started that context again and drew that pixmap back into
it as an image, which the next copy and the frame rendered again. At
2880x1800, a document with 50 translucent groups, each holding a
Multiply rectangle, took 299 ms a frame on 8 threads.
Every render that composites in 8 bits, the canvas included, now keeps
that picture and renders what the context draws next over it. The
backdrop is still cut from the picture. The 50 groups take 65 ms a
frame, and the frames have the same pixels. Renders that composite in
floating point (raster exports, Save for Web, Object › Rasterize and
Crop Image) still draw the picture back.
Docs: docs/target-app-parity.md.
The server dispatched requests and notifications without ever checking jsonrpc, so a missing or wrong version could invoke tools or flip notification state; Remote accepted any reply with ok/result/error without comparing ids, so a faulty endpoint could attribute a stray result to the current call. handle() now requires jsonrpc 2.0 before dispatch (other envelopes get -32600, or are ignored for notifications, which have no id for a reply) and Remote::call rejects a reply whose id doesn't echo the request's, dropping the connection so the next call starts fresh, with the usual uncertainty error. Three new tests cover wrong/missing versions (incl. batch entries and a state-changing notification) and the mismatch-reconnect; docs/mcp.md documents both. cargo test -p vectorcraft-mcp passes.
The server read stdio lines and remote replies without any byte bound, answered batches of any size, and command_batch kept every step result: one faulty peer or batch could allocate and work without bound. Request lines are now read in bounded chunks (16 MiB; longer lines get a -32600 reply, are skipped, and framing continues), protocol batches hold at most 64 messages, control-channel replies past 256 MiB drop the connection with the usual uncertainty error, command_batch takes at most 256 steps and keeps at most 8 MiB of step results (past that the batch stops with the reason, still closing its atomic undo group). Eight new tests cover the line/batch/step/result caps and the atomic-group close; docs/mcp.md and the command_batch tool text document them. cargo test -p vectorcraft-mcp passes.
Fix#1726 with idle, absolute line and reply-write deadlines and connection-slot guards. Add real-loopback regressions and document reconnect behavior.
Validated with cargo xtask ci: all seven gates passed; 6156 tests passed, zero failed, seven existing ignored.
text.create, text.createInPath and the MCP add_text tool now take a
`justify` (auto|left|center|right|justifyAll, as text.setStyle). Point
type already laid out centred or right-aligned text around its origin;
it just could not be made that way, so scripts measured the bounds and
moved the text. Changing it afterwards stays text.setStyle {justify}.
The control server wrote each reply with writeln!(out, "{r}") on the
bare TcpStream. serde_json formats a Value one piece at a time, and
each piece (a brace, a quotation mark, a key, a colon, a number) was
sent as a write of its own: the 17 MB document.inspect reply of a
document with 50,000 objects on one layer was sent in 7,857,200 writes.
On Windows 11, sending that reply to a client over loopback took 34 s,
and sending the 28 MB document.json reply took 57 s.
vectorcraft-cli mcp reads every reply from a running app over this
connection.
write_line now writes each reply, each refusal and the "too many
control connections" line through a 64 KiB BufWriter, then flushes
it. The 17 MB reply takes 262 writes. Sending it now takes 0.09 s,
and sending the 28 MB reply takes 0.2 s. The bytes are the same.
Docs: docs/target-app-parity.md.
VectorCraft does not run scripts written for the reference app, and
File › Scripts held only the disabled Other Script…. File › Scripts ›
Check Script… picks a .jsx, .jsxinc or .jsxbin file, reads it without
running any of it, and shows a report: what the script needs (the
script language's own objects, the document model, files, menu
commands, dialogs, or features only the reference app has), what it
would do outside the document, the menu-command strings it passes, the
document-model member chains and type names it uses, its directives,
the files it includes, and the problems that would stop it loading.
Copy Report copies the report as JSON. File › Open and drag-and-drop
show the report for those files too.
- vectorcraft-scriptcheck, a new Standalone crate with no
dependencies, holds the checker: a lexer for ECMAScript 3 with E4X
literals and the #name and //@name directive lines, and an analysis
of the tokens that follows variables, function scopes and the files
a script includes. It never recurses: open brackets, the levels of
an XML literal and the function bodies being read are kept on stacks
capped at 4,096. It reads at most 8 MiB of a file and 32 MiB with the
included files, follows 64 included files 16 deep, looks up at most
1,024 paths for them, keeps 1,000 entries in a list, tracks 1,000
variables holding member chains and 1,000 holding File objects, and
cuts echoed text to 120 characters. A compiled script is reported as
compiled and isn't read.
- script.check (engine, a query) takes path, data or dataBase64. With a
path and includes: true, it reads the files the #include lines name
from inside the script's folder, through the automation read root,
and when the script or one of those files assigns a member chain or a
File object to a variable, the check treats the variable as holding
it in every file. It doesn't follow absolute names, names, folders or
links that lead out of the folder, names or folders longer than 4,096
bytes, or JSINCLUDE, and it reads only regular files.
- ui.checkScript shows the report (dialog scriptCheck). File › Open
lists Scripts (.jsx, .jsxinc, .jsxbin) and sends a script's path to
script.check, which reads at most 8 MiB of it.
The language rules come from ECMA-262 3rd edition and ECMA-357 2nd
edition. The directive, platform and document-model root names come
from the public JavaScript Tools Guide and scripting guide pages that
catalog.rs cites by URL, and executeMenuCommand, doScript, loadAction,
unloadAction and selectTool come from the public forum posts cited
there; only names are used. Every script in the tests is written from
scratch with invented names.
Docs: docs/mcp.md, docs/target-app-parity.md.
A scan's paper is a tenth darker in one corner than in another. Flat Logo found
each tone of it as a colour of its own and traced the bands and the specks
between them: on the scanned inputs 470 paths, 21 px from the art on average.
Two palette colours are now one when the pixels between them are no fewer than
a third of those at the lesser of the two: flat colours leave only their shared
edge between them, tones of one colour fill the gap. Whether the image is flat
art at all is still asked of the tones as found.
Flat Logo on its inputs, all sizes and variants: 3.106 -> 0.090 px off on
average, 88.5 -> 0.82 px at most, 69.9 -> 3.9 paths; clean, aliased, blurred
and noisy art as before.
Lock › All Artwork Above and Hide › All Artwork Above went through the
objects above each selected object in its top-level layer, one selected
object at a time. For each object they looked up in a list whether it
was selected or already found, and they computed its bounds again for
each selected object below it until one overlapped it. With 100 tiles
selected under 25,000 rectangles, a release build of vectorcraft-cli
took 6.4 s for Lock, and with all of 5,001 objects selected it took
8.8 s and locked nothing.
The commands now go through each top-level layer that holds a selected
object once, and check each object that is not selected against the
bounds of the selected objects below it. The selected objects and the
objects found are kept in sets, and each object's bounds are computed
once. The objects they lock or hide, the count, the selection and the
undo step are the same as before. On the same documents Lock now takes
under 0.1 s.
Docs: docs/target-app-parity.md.
The renderer kept one tile per pattern name and ink plane, and used it
only at the raster scale it was made at: the device scale rounded up to
a power of two and capped. Two objects that paint one pattern at scales
for which the raster scale differs, such as a pattern scaled to 25%
with Transform Pattern Tiles next to the same pattern at 100%, replaced
each other's tile, and every frame rasterized both tiles again.
Tiles are now kept per pattern name and raster scale, within the tile
cache's budget of 128 MiB per thread and ink plane. When the budget
fills, the cache drops the least recently used tiles first. A tile is
still used only while the pattern's art, tile, tile type and overlap
are the ones it was made from. At 760%, with a pattern of 576 stars in
a 288 pt tile on a rectangle that fills the view, and the same pattern
at 25% on a square, release builds of vectorcraft-cli bench at
2880x1800 took a median of 26.0 ms a frame on main and 8.8 ms with this
change, with 8 threads.
Docs: docs/target-app-parity.md.
Snap Curves To Lines made every straight piece within 2.5 degrees of level or
upright exactly so, whatever its length: a 200 px line 2 degrees off had each end
moved 3.5 px. Now a piece is levelled only when neither end moves more than
1.6 px; a longer line that far off is left as the image has it.
Technical Drawing (the preset with the option on) on its inputs: largest
distance from the art 4.21 -> 1.46 px (1.43 before levelling existed), mean
0.178 -> 0.126 px; on the askew inputs 98% -> 70% of the outline comes out level
or upright.
Gradients (params `gradients`, a checkbox in the panel for Grayscale and Color;
off unless asked for): an area whose colour changes evenly along one direction
is traced as one path filled with a linear gradient, not as bands of flat colour.
Gradients are found before the palette is made, so no colour is spent on their
bands: a connected run of smoothly changing pixels is a gradient when a plane
fits each colour channel with all three sloping the same way, the colours along
that way are at most six stops with ramps between them, and painting them back
gives the area's own pixels. Flat colour, soft edges between flat colours and
shading that goes two ways are left as they were.
The harness gains two linear-gradient inputs with known lines and stops, and
measures whether each is found, the angle of its line and the colours of its ramp.
Auto Grouping (params `autoGrouping`, a checkbox in the panel; off unless asked
for): each traced path that has others inside its outer contour comes out in a
group with them, the path first, nested as deep as the picture is. The path that
covers the whole image is nobody's group.
The grouped result paints the same picture: where paths can overlap (stacked
layers, Flat Logo, strokes) a path only joins a path below it, and only if that
moves it under nothing that shares any of its box.
The harness measures three presets with the option on ("grouped %"), painting
them in the grouped order.
Shapes (params `shapes`, a checkbox in the panel; off unless asked for): a filled
outline that is a circle, an ellipse, a rectangle or a regular polygon comes out
as that live shape. A shape is only made when every point of the traced outline
lies within 0.5 px (0.2% of the shape's size, if more) of it; an ellipse whose
radii differ by less than twice that is a circle of its area.
The outline before fitting: a corner's own pixel no longer pulls the two edges
that meet there, and a one-pixel step between two long edges is the corner where
they meet. Sharp corners kept: Default 69% -> 76%, the colour presets 86% -> 91%.
The harness gains plain-shape inputs and "shapes %".
Create: Strokes (the Outline preset, or the option) traced lines along the pixel skeleton: right to a pixel,
with a width counted in pixels, and fitted by a separate, older fitter.
- Each skeleton point is moved to the middle of the line's ink, read from the image across the line: every
pixel counts as much ink as its colour is the line's rather than the paper's, the ink's centroid is the
middle and its sum the width (an anti-aliased or blurred line says both to a fraction of a pixel).
- The stroke width is the median of those widths, no longer a whole number of pixels.
- Each end goes where the ink ends, less the half width a round cap adds.
- Centre lines are fitted as outlines are: a straight line is two anchors, corners stay corners, Snap Curves To
Lines levels them. The old Douglas-Peucker and simplify fitter, and the crate's pathops dependency, go.
Outline on the thin-line inputs, all sizes and variants (docs/image-trace-quality.md), before -> after:
- painted outline 0.310 -> 0.055 px off on average; 81 -> 14 anchors for five lines
- centre line 0.295 -> 0.052 px from the true centre line; stroke width 0.13 -> 0.02 px off; 18.4 -> 2.7
anchors a stroke (blurred, noisy and scanned lines: 2.0 each; aliased: 4.6)
- largest distance 1.84 -> 2.94 px: the square ends of a 10 px line under a round cap
Presets without Create: Strokes are unchanged.
The documented option "replaces slightly curved lines with straight lines and snaps lines near 0 or 90 degrees
to absolute 0 or 90 degrees" (helpx, Image Trace panel options); ours did the first half only.
- With Snap Curves To Lines, a straight piece at least 2 px long and within 2.5 degrees of level or upright is
made exactly so, by moving its ends across it to their mean (the page gives no angle: 2.5 degrees is our
choice). The pieces next to a moved end follow it.
- A junction other regions share stays where it is, so abutting regions still have one curve along their edge;
a line between a junction and a free end is levelled to the junction.
- The harness gains askew inputs (a rectangle, a plus sign and two lines, 1 to 2 degrees off level and upright)
and an "upright %" column: the share of the outline length that is exactly level or upright.
On the clean askew inputs (docs/image-trace-quality.md): Black and White Logo and Technical Drawing, the presets
with the option on, go from 0% to 91% and 95% of their outline exactly level or upright. Their outlines are then
0.49 and 0.59 px from the art as drawn where they were 0.05 (4.2 px at the far end of a 300 px side turned 1.5
degrees): that distance is what the option is for. Presets without the option are unchanged.
Against the documented panel (https://helpx.adobe.com/illustrator/desktop/manage-objects/traces-mockups-symbols/image-trace-panel-options.html):
- Ignore Color: "Ignores a specific color while tracing. Use the color picker to selectively ignore a color."
The panel's Ignore White is now Ignore Color with a colour button beside it (white until another colour is
picked); `imageTrace.make` takes `ignoreColor: "#rrggbb"` beside `ignoreWhite`, and leaves out the palette
colours within about 30 levels of it. Documents saved before have no such setting and ignore white as they
did. Flat Logo mode leaves out white only.
- Auto Color ("Creates a posterized image from a photo or artwork"): Color mode with the Automatic palette.
- Outline ("Simplifies the image to black outlines"): Black and White with Create: Strokes and no fills.
- The Outline preset's name is translated as a preset's name, not as the Outline view's.
Measured (docs/image-trace-quality.md): Auto Color on flat art is 0.140 px off on average with 4.0 paths (16
Colors: 0.141, 4.2). Outline on the line inputs, the first measure of Create: Strokes: 0.310 px off on average,
1.84 at worst, 81 anchors for 5 lines (Line Art's filled outlines: 0.055, 0.96, 46).
Black and White read every pixel as a blend of "black" and "white", and took for those the mean luminance of
the image's black and white pixels. In blurred or scanned line art most black pixels are part paper, so the
mean black was a gray: the middle of every line counted as more than all ink, and by how much changed as the
line crossed the pixel rows, which put a ripple on its edges that the fit then followed with anchors.
- Black is the image's darkest black (the 2nd percentile of its black pixels' luminance).
- White is the lightest pixel within a few pixels of each crack: the paper as it is there. A scan's paper is
darker in one corner than in another, and read against the image's lightest white it counted as part ink.
On each preset's own inputs, all sizes and variants (docs/image-trace-quality.md), before -> after:
- Line Art: 78 -> 46 anchors, outlines 0.093 -> 0.055 px off on average (blurred lines 168 -> 53 anchors,
scanned 199 -> 70); before sub-pixel edges it was 25 anchors and 0.158 px off
- Technical Drawing: 56 -> 46 anchors, 0.092 -> 0.084 px off
- Sketched Art: 0.197 -> 0.186 px off; Default 0.092 -> 0.090; Black and White Logo 0.063 -> 0.059 with
27 -> 29 anchors
Not kept: reading lines up to six pixels deep by their ink's centroid and width, and a looser tolerance for
straight runs, neither of which measured better.
The font scan read /usr/share/fonts, /usr/local/share/fonts, ~/.fonts and the
XDG data folders. Packages add folders outside those to fontconfig's
configuration (TeX Gyre under /usr/share/texmf/fonts), so fc-list and other
apps showed fonts VectorCraft didn't have. The scan now also reads the <dir>
folders of /etc/fonts/fonts.conf and the files it includes (conf.d, the user's
own), as the fontconfig documentation describes them. The files are read as
they are: no library is linked and no program is run.
A PDF-compatible .ai keeps its art past the artboard edge unclipped (#646),
and its pages are laid out in a row 36 pt apart. Art that a page draws far
to the left of its artboard (notes beside a design, say) then lay over the
page before it. There it was taken for that page's art: the check against
the editing data found art its layers don't have and opened the file as
its PDF part, without the art outside its artboards, and place_pages left
that art behind when it moved the pages.
Such a page now moves right until its art clears the art before it.
Plain PDFs (clipped to the page) keep the 36 pt gap.
Every colour layer took a mask, component labels, a fill stack and an edge map the size of the whole image,
and as many layers were traced at once as the machine has cores.
- A layer is traced within the box its pixels lie in.
- The stack that fills components holds `u32` indices (a mask has at most 64 million pixels).
- Layers are traced as many at a time as fit in half a gigabyte (about ten bytes a pixel of the largest
layer's box each), on however many cores.
Peak memory above the image itself, photo-like image (docs/image-trace-quality.md), before -> after:
- 16 megapixels: High Fidelity Photo 3,856 -> 880 MB (2.85 -> 3.65 s), Low Fidelity Photo 1,972 -> 754 MB
(1.49 -> 1.80 s), 16 Colors 1,625 -> 298 MB, Shades of Gray 852 -> 407 MB, Default 256 -> 199 MB
- 4 megapixels: High Fidelity Photo 981 -> 643 MB, 16 Colors 408 -> 122 MB, times unchanged
The traces are the same, path for path.
An image whose decode needs more than the 2 GiB allowance (512 MB on the web) is not drawn.
A raster export says so since #1517; on screen it was still blank with no word. Open and
Place now carry a warning for it, and the Links panel's row a note (links.list: note).
Paste read PDF only from the Windows clipboard and EPS nowhere, so on macOS a copied
equation or drawing pasted nothing new (the last thing copied in the app came back).
- macOS: the pasteboard's PDF, EPS and SVG are read, ahead of text and bitmaps.
- Windows: EPS is read beside PDF.
- Everywhere: an .eps file copied in a file manager pastes.
- clipboard.importEps loads an EPS into the clipboard as document.open reads one.
Complete Polish catalog (4,492 rows): every menu string, tl! literal,
plural and status/error message, enforced by the existing completeness
tests (complete_menus, KEEPS_MENU_NAMES, COMPLETE_MESSAGES).
- pl.tsv: clean-room translation from the English meaning, following
common Polish desktop-software conventions (obszar kompozycji for
artboard, obszar roboczy for workspace, obrys, wypełnienie, próbki…)
- plural_polish: one / few (2–4, not 12–14) / many (0, 5–21, 25…)
- every pl-* locale (pl-PL, pl_PL.UTF-8) resolves to it
- tests: Polish plural forms, vocabulary, nested message translation;
Polish letters in the bundled-font coverage test
- docs: README, development.md, localization-parity.md
Drafted with AI assistance; native review welcome.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33