Commit Graph
329 Commits
Author SHA1 Message Date
Don Omondi 0f1b70f8b1 .ai import: a PDF part cropped to the art no longer hides the layers
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.
2026-10-30 08:15:04 +07:00
dotCooCoo dc9106939e File formats: a PCX palette cut short is refused
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.
2026-10-30 00:15:56 +07:00
dotCooCoo f32699bea7 File formats: PCX (.pcx) files open and place
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.
2026-10-30 00:15:56 +07:00
bflatastic d3d1e31219 Format 2026-10-29 19:57:58 +07:00
bflatastic a353ad3e7a Fix #4523: a new document's VectorCraft Options start as the last native save used them
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.
2026-10-29 19:57:57 +07:00
dotCooCoo c8a7420b93 Place: an image whose pixels can't be read is refused instead of placed blank
Squashed from #4257 onto main.
2026-10-29 18:54:38 +07:00
dotCooCoo cfdeda35c4 Tests: the non-sRGB Drop Shadow test fails when its child test does not run
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.
2026-10-29 18:45:54 +07:00
dotCooCoo dfa9f883c0 Fix Drop Shadow Darkness in source color space 2026-10-29 14:36:41 +07:00
Don Omondi 1196d9a335 fix(eps): import bool_or after main's explicit command imports
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.
2026-10-29 13:00:49 +07:00
Don Omondi c6070f633e Support original-format EPS Save and Save As 2026-10-29 13:00:47 +07:00
dotCooCoo 754051c26a Fix #4036: a .ai swatch library without layers loads its palette, not the default swatches
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.
2026-10-29 10:10:58 +07:00
dotCooCoo 43ecaaa62a Type: Undo and Redo in a command batch put the caret where the step was
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.
2026-10-29 10:10:58 +07:00
dotCooCoo 512036a81d Export: the ppi, width and height descriptions name the formats that store no resolution
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.
2026-10-29 10:10:58 +07:00
dotCooCoo 530cca3322 Export: document.export takes a pixel width or height for raster files
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.
2026-10-29 10:10:58 +07:00
dotCooCoo 1cecdfb985 Export for Screens: a folder export cancelled after its last file returns its files
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.
2026-10-29 10:03:27 +07:00
dotCooCoo 2731ffacdd Export for Screens: a cancel while the archive is made returns no archive
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.
2026-10-29 10:03:27 +07:00
dotCooCoo 3b01769d46 Export for Screens: a ZIP export deflates its files
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.
2026-10-29 10:03:27 +07:00
Dylan M. Taylor 90ef99cb2c Simplify file import and export checks 2026-10-28 16:44:05 +00:00
aiongg fa1848be4c refactor(engine): move cmd/fileio.rs items to cmd/fileio/{access,commands,export_names,formats}.rs 2026-10-26 22:01:25 +00:00
aiongg 2a90e82736 refactor(engine): move cmd/fileio/affinity.rs items to cmd/fileio/affinity/{document,import,layout,master_copies,nodes,paint,text}.rs 2026-10-26 22:01:25 +00:00
aiongg 62261b17c9 refactor(engine): move cmd/mod.rs cmd/fileio/mod.rs cmd/findfiles/mod.rs cmd/place/mod.rs to foo.rs files 2026-10-26 18:15:21 +00:00
dotCooCoo 6ed4608d82 Images: an image past the 2 GiB decode allowance draws at reduced size instead of a crossed box
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).
2026-10-26 21:41:42 +07:00
nrq6993-a11y fff6227351 #4122: File → Open starts in the last opened file's folder
Squashed from #4263 onto main.
2026-10-26 20:34:53 +07:00
nrq6993-a11y c640f40ce1 G4: draw loaded-text cursor for threaded overflow
Squashed from #4229 onto main.
2026-10-26 13:09:22 +07:00
dotCooCoo 056274f5d6 Undo: canceling an undo group keeps a batch's steps in other documents
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.
2026-10-26 09:09:45 +07:00
Brandon Thomas c5c44e0dfb Merge main into fix/h2473-safe-file-name-leaves-a-windows
Combines this branch's screens.rs test module with the one #4123 added.
2026-10-25 10:38:39 -04:00
Brandon Thomas 70a05ba09b Merge pull request #4068 from dotCooCoo/fix/eps-no-art-central-file-text
EPS: the help text says no central file is written when no art shows
2026-10-25 10:38:12 -04:00
Brandon Thomas dda90f0f4f Merge pull request #4060 from dotCooCoo/feat/unknown-command-suggestion
Commands: an unknown command id is answered with the nearest command id
2026-10-25 10:37:54 -04:00
Brandon Thomas 774314a351 Merge pull request #4050 from dotCooCoo/fix/screens-filename-is-file-name
Export for Screens: <filename> takes the saved document's file name
2026-10-25 10:37:45 -04:00
NetterSilverandClaude Haiku 5.5 80363c7e60 Protect Windows device names followed by a space in safe_file_name
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>
2026-10-25 11:52:29 +01:00
NetterSilverandClaude Haiku 5.5 50b067c089 Refuse signs, exponents and non-numeric text in Export for Screens sizes
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>
2026-10-25 11:16:58 +01:00
dotCooCoo 4210c2447f EPS: the useArtboards help text separates false from absent
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.
2026-10-24 14:00:32 -07:00
dotCooCoo 6e6fbbac1d Commands: an unknown command id is answered with the nearest command id
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.
2026-10-24 11:20:27 -07:00
dotCooCoo 0dcb52c7cd Export for Screens: <filename> takes the saved document's file name
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.
2026-10-24 09:53:53 -07:00
dotCooCoo b998ae623c MCP: save_file takes the native save options compress, version and preview
Squashed from #3955 onto main.
2026-10-24 23:43:54 +07:00
dotCooCoo 0520e9da86 Type: threaded frames do not repeat a continued paragraph's first-line indent or list marker
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.
2026-10-24 21:43:44 +07:00
dotCooCoo df49ed25f1 EPS: the help text says no central file is written when no art shows
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.
2026-10-24 07:09:33 -07:00
dotCooCoo 6cd57cfa1b Undo groups: an open undo group also groups the edits in a document opened or created before its end
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.
2026-10-24 20:10:04 +07:00
dotCooCoo d2de74e12a Save: option errors name the save command that was run
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.
2026-10-24 19:02:09 +07:00
dotCooCoo 0635357eb9 Native format: a document is saved pretty-printed only when its pretty-printed JSON is at most 1 MiB
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.
2026-10-24 19:02:08 +07:00
Fabian GrajalesandClaude Opus 5.5 1adbf259f7 Open .ai files with their swatches: the palette, its colour groups and unused spot colours
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>
2026-10-24 17:46:20 +07:00
bflatastic 381b788f56 Slices: Lock Slices and Hide Slices deselect the slices and keep them deselected
Squashed from #2841 onto main.
2026-10-24 17:46:19 +07:00
dotCooCoo 640ddbd4c0 EPS: Use Artboards also writes a central file with every artboard
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.
2026-10-24 17:46:14 +07:00
dotCooCoo c5d010e7d7 MCP: document.inspect and document.open report the File Info title as fileInfoTitle
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.
2026-10-24 17:29:33 +07:00
Taha-Mahmoodi cab62cbb18 Fix #3964: Create Outlines, EPS, EMF/WMF and legacy .ai keep the ruby where the canvas draws it 2026-10-24 17:29:32 +07:00
dotCooCoo 11c61c97b1 Save: a remembered artboard pick follows the artboards it named
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.
2026-10-24 17:20:48 +07:00
Dylan M. Taylor e95e8418ed Audit: automation roots follow file.showPackage, the Place queue, headless file methods, clipboard files, placed-document workers and caches, folder searches and the recovery folder 2026-10-24 05:16:20 +07:00
BFlat e0c1548049 Merge pull request #3896 from storytold/fix/3869-svg-type-features
SVG export: type with small capitals, superscript, subscript or tab leaders is written as outlines (#3869)
2026-10-24 04:51:54 +07:00
aiongg 6877d6535f Links: resolve stored paths with craft-docpath's resolve_absolute (#3267)
* 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.
2026-10-23 17:51:28 -04:00
bflatastic 166afce360 SVG export: type with small capitals, superscript, subscript or tab leaders is written as outlines (#3869) 2026-10-24 04:49:08 +07:00