A PDF-compatible .ai whose PDF part's pages are cropped to its art (each
page the box of all the art, the editing data's bounding box) rather
than to its artboards came in as that PDF part: the editing data's
artboard wasn't the page's size, so its layers were refused. With
several artboards every page held all the art, so the document had a
copy of everything per page.
When the artboard isn't the page's size but the editing data's bounding
box is, the layers are compared with the page on that box and keep
their artboards. The comparison of their art with the page still
decides whether they are used.
An 8-bit PCX file whose image data is followed by the palette marker
(0x0C) and fewer than 768 color bytes was read as gray levels. The reader
now notes where the run-length encoded image data ends, and when the
palette marker follows it without all 768 colors, refuses the file as cut
short. An 8-bit file with nothing after its image data still reads as
gray levels, and a gray level of 12 that falls 769 bytes before the end of
such a file is read as a pixel instead of as the palette marker.
A new reader, written from the ZSoft PCX Technical Reference Manual, decodes
the run-length encoded scan lines of 1-bit images in the header palette's
first two colors (black and white when the version has no palette or the two
colors are the same), 1-bit images in two to four planes and 4-bit images
with the header's palette (the EGA colors for versions 0 and 3), 2-bit CGA
images in the colors that the header's CGA background and palette settings
pick, 8-bit images with the 256-color palette after the image data (gray
levels without one) and 24-bit images in three planes. The image is stored as PNG at the resolution of the header's HDpi and
VDpi fields.
Open and Place list PCX, and pasting a copied .pcx file and Glass's Load
Texture… read it by its header. A file of another depth or plane count, one
cut short and one whose pixels as RGBA pass the pixel allowance are refused
with a message before its pixels are decoded. The desktop packaging does not
associate .pcx files, and no export writes them.
Docs: docs/file-format-parity.md, docs/target-app-parity.md, docs/mcp.md,
docs/releasing.md.
Preview, Embed ICC Profiles, Create PDF-Compatible File and the other VectorCraft Options went back to their defaults for every document not saved yet. A native Save or Save As now records the options it used in the preferences (not the artboards it picked, nor Use Compression, which its preference decides), and file.formatOptions offers them for a document not yet saved as a native file. A document saved before keeps its own.
svg_darkness_uses_rendered_shadow_in_non_srgb_working_space runs itself
again in a child process with --exact and checked only the exit status.
libtest exits 0 when --exact matches no test, so a rename of the test
would have left the parent passing without running the non-sRGB check.
The parent now also requires "1 passed" in the child's output, as
layout_cache_follows_fonts_added_later in crates/tools does.
Main replaced the glob import of the command helpers in fileio/eps.rs
with an explicit list; the EPS Save path's selectedOnly check uses
bool_or, so it is added to that list.
A .ai file whose editing data has no layers, as swatch libraries are
saved, opens from its PDF part, and the PDF part has the default
swatches. layered_ai applied the editing data's palette only when it
built the document from the layers, so Other Library… listed the
default swatches for such a library, and File › Open gave the file the
default swatches as well. A .ai whose layers can't be used (an image
too large for the editing data's reader, or a read error) lost its
palette the same way, and so did an EPS that opens as its page.
The reader now reads the palette on its own (ai::palette, which shares
palette_swatches with the layer reader), and its swatches and color
groups replace the default swatches in those three cases. Spot and
pattern swatches of the PDF part's art that the palette does not have
follow the palette's swatches.
Fixes#4100. Part of #3349.
The Type tool heard of Undo and Redo only when they ran as top-level
commands. As steps of command.batch, they left the caret where it was,
and the tool took the batch for an outside edit. Session::execute now
tells the active tool about each nested edit.undo and edit.redo.
command.batch also tells the tool when it begins, ends or rolls back.
The Type tool keeps its caret and its edit records at the start of each
batch and puts them back when a failed batch rolls the documents back.
GIF, WebP and Targa files hold no resolution field, so document.formats and
the export docs now say that ppi, and the resolution that width or height
gives, are stored in the file except in those three formats.
PNG, PNG-8, JPEG, WebP, GIF, TIFF, BMP, Targa and PSD exports take
`width` or `height` in pixels. Each file is scaled so that its own
artboard, `rect` or art bounds is that wide or high, so with
`useArtboards` every file gets its own scale. `width` wins over
`height`, and either wins over `ppi` and `scale`, in the order an
Export for Screens row reads its size. The scale stays within 0.01 to
64 pixels per point and the file stores the resolution that results.
A value that is not a positive number is refused.
The per-file scale comes from `ScreenSize::scale_for`, which Export for
Screens rows already use. Rows keep their own size: their format
settings still drop `width` and `height`. `document.formats` lists the
two options for every raster format.
The check after the last file returned the cancelled error for every
export. A folder export without a ZIP has written each file by then, so
a cancel that came during a final step that writes nothing (a row whose
file repeats the one before it) reported a finished export as
cancelled. The check now runs only for exports that still hold their
files: those without a folder and those that make a ZIP.
A cancel that came after the last file's check was not seen: an export
without a folder returned its files or its archive anyway. The export now
checks once more after the last file and again after deflating the
archive, so a cancel returns the cancelled error and nothing is written
or returned.
zip::deflate takes a `stop` callback that it calls before each 1 MiB of
a file. Export for Screens passes the cancel state, so deflating stops
at the next MiB. file.package passes one that never stops, so its
behavior does not change.
With `zip: true`, document.exportForScreens deflated nothing: every file
went into the archive stored. Each file is now deflated, or stored when
deflating does not make it smaller, as file.package's archive does. The
file list, the names and the reply's fields stay the same; `bytes` and
the archive's contents change. Entries keep the fixed 1980-01-01 date, so
the same files still give the same archive.
The deflating writer moves from package.rs into fileio/zip.rs as
`zip::deflate`, beside `zip::store`, and file.package calls it there with
its own date and error text. The test reader `zip::entries` reads
deflated entries too. The `zip` option's text in docs/mcp.md,
docs/control-protocol.md and the command's params says the files are
deflated.
A PNG, JPEG, GIF or WebP image that takes more than the decode
allowance as RGBA (2 GiB, 512 MiB in the web app), such as a 24000 ×
24000 JPEG, was placed with the warning "too large to draw" and then
drew as a crossed gray box on the canvas and was left out of raster
exports. A 16-bit PNG whose decoded samples were past the allowance
was shown the same way, with the same warning, although its RGBA fits.
The renderer now decodes such an image at the largest size, keeping its
proportions, that fits the allowance as RGBA. Each pixel of the result
is the mean of the image's pixels it covers. A PNG that isn't
interlaced is read a row at a time; other images are decoded whole when
their decoded samples fit the allowance. A 16-bit PNG that isn't
interlaced and whose RGBA fits is read a row at a time at its own size.
An image of more pixels than the allowance has bytes still draws as the
crossed box, and so does one whose decoded samples are past the
allowance, other than a PNG that isn't interlaced. The renderer
decodes an image at reduced size only while the result and the memory
reading it takes (the image decoded whole, or the PNG reader's row
buffers) fit beside the images being painted; otherwise the image is
not decoded on that render (on the canvas it draws as the crossed box)
and is tried again on the next render. The document keeps the whole
file for saving and export. A placed document's saved preview past the
allowance is decoded the same way, at the size it is drawn at or at the
reduced size when that is smaller.
Open, Place and the Links panel say "an image of W × H pixels is shown
at reduced size" for an image drawn smaller. They say nothing for an
image drawn at its own size, and they say "too large to draw" for one
that is not drawn at reduced size.
Docs: docs/mcp.md, docs/target-app-parity.md (the crossed box's line
now names an image over the allowance that can't be drawn at reduced
size).
A command.batch that edited a document with an undo group and then a
document without one was dropped from the journal as a whole when the
group was canceled, so a replay lost the second document's edit, which
the session kept. Each batch step is now noted in the undo groups it
belongs to, by the same rule as a top-level command, and a cancel takes
only those steps out of the batch's journal entry. A step that changed
the active document becomes that change. Undo groups that stay open
follow the entry and step numbers that a cancel changes.
Found while testing crates/engine/src/cmd/fileio/screens.rs:168-177 (safe_file_name)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/engine/src/cmd/fileio/screens.rs:49 (ScreenSize::parse)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
useArtboards: false writes one file of the visible art even when artboards are named. With useArtboards absent, naming artboards writes the same files as useArtboards: true, including the no-art exception for the central file. The option description and the document.exportEps params text now say so, and the EPS test covers both cases.
A misspelled parameter key got a did-you-mean suggestion, but a
misspelled command id only got "unknown command `select.al`", through
the CLI, MCP and the control channel alike. Session::execute now names
the nearest command id, using the same matching as parameter keys
(ignoring case), and action.batch does the same for an unknown step. An
id with no near match keeps the plain error.
The matching skips candidates whose length differs by more than the
allowed number of edits, so a very long unknown command id or parameter
key is answered at once instead of being measured against every command
id or accepted key.
The new message has a row in every catalog, and docs/mcp.md's
Parameter keys section shows it.
In a file name pattern, <filename> is documented as the document's name
without its extension, but it expanded to the File Info title whenever the
document had one, so a saved my-logo.vectorcraft titled "Logo: final
version" exported as Logo--final-version__Artboard 1.png. The Full
Document file and the .zip took the same title.
Export for Screens and the dialog's Example line now take <filename> from
the document's file, as Save names it (save.rs), and fall back to the
title only for a document that has not been saved.
A paragraph split between two threaded frames took its first-line
indent again at the top of the next frame, and a list paragraph drew
its bullet or number there again. Each frame is laid out on its own,
and its first paragraph kept every paragraph attribute there.
A text object now marks a frame whose first paragraph continues from
the frame before (first_para_continues, not saved). The thread re-flow
sets the mark where each frame's share starts inside a paragraph and
measures each share with it, and a restored document reads the marks
from its saved text. Area type and type on a path set the first line of
such a paragraph as a wrapped line: no first-line indent and no bullet
or number. The paragraph after it takes the next number. Release
Selection, Remove Threading, Convert To Point Type and copies of a
frame clear the mark, so the first paragraph there has its indent and
bullet or number again. When Create Outlines, Expand or Expand
Appearance takes frames out of a thread, a frame that stays keeps the
mark only while it is threaded and the frames before it end inside a
paragraph. A frame whose mark changes lays out its first line again,
and when it is still threaded, its thread re-flows. When the outlined
frame is the last one, the text it has no room for is measured as the
frame lays it out: without the first-line indent when it continues a
paragraph, and with its numbers counted on from the frames before it.
The frame before it now takes exactly the text the last frame has no
room for. Duplicates, copies dragged in the Layers panel, the copies
that Release to Layers (Build), Release Opacity Mask and Edit Opacity
Mask make, and Draw Inside's clipping copy then have their cached
bounds computed again, as paste already does for pasted type.
Docs: docs/mcp.md, docs/target-app-parity.md.
With Use Artboards and two or more artboards, a document with no visible art gets only the artboard files. The useArtboards and range descriptions, the document.export and document.exportEps params text, the control protocol and the parity line now say so.
A document opened or created between edit.undoGroup.begin and
edit.undoGroup.end opens an undo group of its own with the begun
group's name, so an atomic command_batch that starts with file.new or
document.open is one undo step in the new document. The group opens
after the command that opens the document: when that command also
edits it (a command.batch), those edits stay the command's own undo
step, and a canceled group keeps them, so the journal still replays.
Both commands also run with no document open, and a command.batch that
fails rolls back a begun group. Switching to a document that was
already open does not open a group in it.
A save (document.save, file.saveAs, file.saveCopy) passes its options to
the export encoders, so an option an encoder refused was reported under
document.export: file.saveCopy with format pdf and range "5" on a
document of three artboards, or with format ai and compress "yes", or
document.save to a .vectorcraft path with compress "yes", failed with
"invalid parameters for `document.export`". The error now names the
save command that was run, for PDF, SVG, .ai, .ait and native files.
document.export's own errors are unchanged.
A native save was pretty-printed when the document had at most 2,000
layers and objects and its compact JSON was at most 1 MiB. Pretty
printing puts every coordinate on its own indented line, so traced art
with few paths and many anchors could pass that check and be written at
about five times the size of its compact JSON.
The 1 MiB limit now applies to the pretty-printed document, indented as
the file holds it and in the form the save writes: version 1 anchors,
and placed documents expanded for older versions or Include Linked
Files. Documents whose pretty-printed JSON is over 1 MiB are saved as
compact JSON, even when their compact JSON is under it. The 2,000-object
rule and compressed saves are unchanged. docs/mcp.md describes the rule.
The editing data's palette (%AI5_BeginPalette) was skipped, so a .ai file
opened with the default swatches plus only the spot colours its art used:
inks the art doesn't use (a Pantone set kept in the file) and colour groups
were lost. The palette is now read in order (process, spot in CMYK, RGB or
Lab, and global colours, gradients and patterns, colour groups; versioned
content read once) and replaces the default swatches.
Xs/Xz (Registration at a tint, as printer's marks use it) are read as the
Registration swatch: before, art painted with them stopped the editing data
from being read at all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Use Artboards and two or more artboards, EPS export now writes a
central file under the path's own name before the file of each artboard.
The central file contains the whole document, and its bounding box is
the visible art, as in an export without Use Artboards. An export of a
single chosen artboard still writes one file at the path's name, and
Export for Screens still writes one EPS per artboard. When no art shows,
only the artboard files are written.
Docs: the useArtboards params text, the MCP export tool's range text,
docs/mcp.md, docs/control-protocol.md and docs/target-app-parity.md.
The title field of document.inspect and the document.open reply is the
document's tab name, which comes from the file name once the document
is saved to or opened from a file. A client had to call file.info or
document.info to read the File Info title, so reading a saved
document's title from these replies gave the file name instead.
Both replies now add fileInfoTitle, the title file.info reports, and
keep title as it was. docs/mcp.md's File Info section describes both.
A save without options reuses the options the document was last saved
with in that format. Their artboard, artboards and range name artboards
by number, so once artboards were deleted or moved, the save wrote
other artboards or was refused: with three artboards, a PDF saved with
range "2" wrote the third artboard after the first was deleted, and one
saved with range "1, 3" was refused after the third was deleted. The
same held for SVG saves, a legacy .ai, and .ai and native files saved
with separateArtboards. file.formatOptions and SVG Options showed the
same numbers.
The document now keeps the ids of the artboards it had when it was
saved, and the remembered pick follows them: an artboard deleted since
is left out, one that moved is named by its new number, and the rest of
the pick is kept. A range open at its end ("2-") is written out as the
artboards it named once artboards are added. When none of the artboards
the save used is left, the pick is left out, and the save writes the
artboards it writes when none is named. A range of "all" stays as it
is. An artboard given to the save is still refused when the document
does not have it.
A new artboard no longer takes the id of an artboard deleted earlier in
the same document (Document::retire_artboard_id), or of one an undo took
away: the highest id given out is shared with the undo history, so a
new artboard is not taken for an earlier one.
file.revert keeps the pick only when the file read back has the
artboards the document had at the save, with the same ids in the same
order. A PDF saved with a range holds only those pages, and one saved
without preserveEditing reads back with new ids in page order, so their
picks are left out.
* Links: use resolve_absolute for stored paths
* CI: release only with git dependencies from storytold
* Release: check git sources from the parsed Cargo.lock and cargo config
The grep let any character stand for the dot in github.com, passed a missing or stale
Cargo.lock, and missed source replacement in .cargo/config.