Commit Graph
351 Commits
Author SHA1 Message Date
dotCooCoo 89ca9d9a25 Batch: keep an existing artboard file of a one-artboard Separate Artboards save
A save in the native, template or .ai formats with separateArtboards writes an
artboard file even when the document has one artboard, so an existing file with
that name now gives the data set the next free suffix too.
2026-11-01 01:22:06 +07:00
dotCooCoo 5dcc65d69a Batch: keep existing artboard files when choosing data set file names
When a step can write one file per artboard (Save in the native, template or
.ai formats, or Export in SVG, EPS, the raster formats, DXF, EMF, WMF or .ai)
and the document has more than one artboard, an existing file with one of the
stem's artboard file names now gives the data set the next free -2, -3 suffix,
as the stem's own file does. Names a data set's outputs will take are also
reserved for the later data sets of the same batch.
2026-11-01 01:22:06 +07:00
dotCooCoo 483c191e24 Batch: keep a backslash in a source file name on Linux and macOS
Destination and Override Save/Export paths reject only the current platform's
path separators, so a folder batch writes `a\b.svg` into the destination
folder on systems where that is one file name.
2026-11-01 01:22:06 +07:00
dotCooCoo c55e4a226e Batch: pass the data set naming choice in the batch run
The naming choice reaches batch_datasets as a field of the run, and the short
Batch entry in docs/mcp.md is back to main's text; the longer Batch section
names datasetFileNaming and when an existing output gets a suffix.
2026-11-01 01:22:06 +07:00
dotCooCoo 1b4c050bd3 Batch: check existing data set outputs only in a folder the batch may read and write
The `-2`, `-3` suffix for a data set output that already exists is now
looked up only when the destination folder and the output path pass the
read and write access checks, so a call cannot learn which files exist in
a folder it may not use.

The lookup uses only extensions of formats the app knows, without path
separators or `..`, and only paths whose parent is the destination folder.
A step whose `format` is unknown keeps its own path instead of being
pointed into the destination folder. A data set or document named `.` or
`..` gets the `Untitled` stem with its three-digit number.
2026-11-01 01:22:06 +07:00
dotCooCoo f8bba3388e Batch: data set files name by File + Number, File + Data Set Name or Data Set Name
The Batch dialog has a File Name choice for a Data Sets source, starting
with File + Number (`source001`), as well as File + Data Set Name
(`source_Alpha`) and Data Set Name (`Alpha`). `action.batch` takes it as
`datasetFileNaming`; a call without it keeps File + Data Set Name, and a
folder source ignores it. An unknown value is an error.

A data set output whose name already exists in the destination folder
now takes the next free `-2`, `-3` suffix instead of replacing the file,
and a Windows device name under Data Set Name gets a trailing `-`.
2026-11-01 01:22:06 +07:00
dotCooCoo 777ad6765c File formats: JPEG 2000 images open and place
File › Open and File › Place read JP2 files and raw JPEG 2000
codestreams (.jpf, .jpx, .jp2, .j2k, .j2c, .jpc) through hayro-jpeg2000,
and store the pixels as PNG. Gray, RGB, CMYK and ICC-tagged images, with
or without alpha, are converted to RGBA. Several codestreams, JPX
composition or layers, other color spaces, inconsistent or oversize
palettes, inconsistent channel definitions, oversize tile grids and
images past the pixel allowance are refused with a message before the
pixels are decoded.

The capture and display resolution boxes size the image: the box that
comes last sets each axis, and an axis without a usable value uses
72 ppi.

IccProfile::to_srgb_into converts a pixel with the cached transform and
no heap allocation.
2026-11-01 01:22:05 +07:00
dotCooCoo bb6cbfb2b4 Images: SVG and EPS export write colorized grayscale and 1-bit images in their fill color
SVG export ignored an image's colorize paint and wrote the image's own
black, white and gray pixels. EPS export flattened every colorized image
into a process-color raster, so a spot color lost its plate. Both now
write the image in the fill color, with the coverage the canvas and PDF
export use (darkness times alpha), so white and transparent pixels take
no color.

- SVG writes a coverage PNG as a mask over a rectangle in the color,
  shared between repeated images while each keeps its own color. Linked
  mode writes the coverage PNG as a file, and SVG Tiny 1.2 writes a PNG
  in the color. A colorized image whose pixels are missing, or whose
  grayscale pixels can't be decoded, is left out with a warning.
- EPS writes fully covered or uncovered pixels as an imagemask in the
  color's process or Separation ink (Registration on every plate), and a
  grayscale image as ink samples scaled by coverage. Level 3 masks its
  uncovered pixels out and Level 2 writes them white; gray midtones are
  written over white, with the existing warnings.
- EPS flattening no longer counts a solid colorize paint on a grayscale
  or 1-bit image as transparency by itself. Opacity, blending modes,
  opacity masks, effects and overlapping transparent art still flatten
  it, and so does a colorize paint the writer can't draw.

The svg crate's tests turn on resvg's raster-images feature to render
the embedded PNGs.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-11-01 00:35:27 +07:00
dotCooCoo a32a7cbb0d PDF/EPS export: EPS export and PostScript printing expand pattern tile images once
EPS export expanded the pattern fills whose tile images need flattening and
then flattened the result with flatten_document, which expanded them again
with a new object limit. PostScript printing did the same, on the layers
shown rather than the layers that print. EPS export now flattens the
expanded document without expanding it again (flatten_expanded), and
PostScript printing leaves the expansion to the flattener, which runs on
the layers that print, and only checks for such pattern fills to warn.
2026-11-01 00:35:23 +07:00
dotCooCoo 7b4d6282f1 Flattening: an option leaves colorized images to writers that draw them
Flattening counted every colorized image as transparency, in the art and
in pattern tiles. A new flattener option, keeps_colorize, stops counting
an image colorized with a solid paint whose pixels are grayscale or 1-bit,
for a format whose writer draws such images in their ink. Opacity,
blending modes, opacity masks and effects still flatten them, and pattern
tiles are still expanded for those. The option is off, so exports flatten
as before; the EPS writer that draws colorized images turns it on.

The EPS pattern tile tests now check the exported colors, and a tile
group with opacity for the warning.
2026-11-01 00:35:23 +07:00
dotCooCoo 33ab5adbc2 PDF/EPS export: flatten see-through and colorized pattern tile images
PDF 1.3, PDF/X-1a, PDF/X-3, EPS and PostScript printing flatten
transparency with `flatten_document`, which only looked at art in the
layers. An image with see-through pixels, a colorize paint, opacity or
a blending mode drawn only through a pattern fill stayed in the pattern
definition, so the flattener returned early and the writer drew the tile
opaque and uncolorized (PDF 1.3 export failed on the transparency left).

`flatten_document` now first expands pattern fills whose tile image
needs flattening into their tiles, as legacy .ai save does. See-through
tile pixels count only where the caller counts see-through images (PDF
1.3); EPS level 3 already masks them out. The EPS and PostScript print
transparency warnings now see those tile images too.
2026-11-01 00:35:23 +07:00
dotCooCoo a48ab7f8c6 Fix #3325: art nested in Affinity text is clipped to the characters
An Affinity text object whose children are nested in it (an image dragged
into artistic text, for example) opened as a plain group, so the image
was drawn over the text. The text now becomes a clip group whose
clipping path is the type, as a shape with children already does: the
nested art shows only inside the characters, with the characters' fill
behind it and their stroke over it.
2026-10-31 23:29:39 +07:00
bflatastic b8e5f7088f Export for Screens: Export PDFs As Single file writes the artboards as one PDF's pages 2026-10-31 21:43:13 +07:00
dotCooCoo d318a77560 DXF export: Alter Paths for Appearance outlines only strokes a CAD line can't draw
Alter Paths for Appearance wrote every stroke as its filled outline, so a
1 pt circle became two circles and a hatch, and an open arc became a
closed outline and a hatch: two cut lines where a CAM program expects
one. The option now outlines only the strokes a line can't draw: the ones
Preserve Appearance outlines, strokes wider than the widest lineweight
(2.11 mm, to the hundredth), and every stroke in R13 and R14, which have
no lineweights. Other strokes stay CIRCLE, ARC, SPLINE and LWPOLYLINE
entities with their lineweights; R12 keeps them as POLYLINEs with their
width.

A dashed stroke whose offset starts part way into its pattern is now
outlined with Preserve Appearance and with Alter Paths: a linetype always
starts at the start of its pattern, so the dashes were written in the
wrong place.
2026-10-31 19:43:15 +07:00
dotCooCoo 06e8d711ae DXF export: an open path of one straight segment is written as a LINE
An open path with a single straight segment was written as a two-vertex
LWPOLYLINE (POLYLINE before R14). It is now a LINE entity from its start
to its end point. A stroke with a weight in R12, R13 and R14 keeps its
polyline, since a LINE has no width. Closed paths and paths with more
than one segment are unchanged.

Part of #1299.
2026-10-31 19:43:15 +07:00
dotCooCoo 7584b6335e Legacy .ai: colorized image pattern tiles keep their color
Saving a legacy .ai with transparent images flattened expands pattern
fills whose tile image is see-through or has opacity or a blending mode,
so the tile images flatten with the art under them. A pattern whose tile
is an opaque grayscale image with a colorize paint was not expanded, so
the writer drew the source gray pixels over white. A colorize paint now
counts too.
2026-10-31 06:10:51 +07:00
Don Omondi 66c797a59b .ai import: outlines from the editing data keep their look and threads; mesh corners with non-numbers are unread
Follow-ups to the review of #4697 and #4698:

- With textAs: outlines, a .ai saved without PDF compatibility, read
  from its editing data alone, has its type outlined too, as an empty
  page or a text-only placeholder already did.
- Threaded type read from the editing data holds its story in its first
  frame: it now flows through its frames before it is outlined, so each
  frame's outlines show the text it holds, and the outlined frames
  leave their threads.
- Each outline group keeps its type's opacity, blend mode, isolation,
  opacity mask and effects (the same carry-over the flattener uses).
- A gradient-mesh corner (/N) holding anything but numbers is unread,
  instead of being read with the numbers after it shifted into its
  colour and opacity.
2026-10-30 23:55:32 +07:00
dotCooCoo 6c9933666b PDF: colorized images count as transparency for PDF 1.3 and PDF/X
A colorized grayscale or 1-bit image draws its white pixels transparent and is written to
PDF with the image as a soft mask. Node::shows_transparency did not count it, so a PDF 1.3,
PDF/X-1a or PDF/X-3 export whose only transparency was a colorized image was not flattened
and kept the soft mask. A legacy .ai save also wrote such an image as opaque.

- shows_transparency counts a visible image with a colorize paint.
- The legacy .ai writer flattens or leaves out colorized images like other see-through images.
2026-10-30 23:44:15 +07:00
Don Omondi 4f5d270ae9 .ai import: an empty page with skipped content isn't empty; outlines asked for are given
Two follow-ups to the empty-page fallback:

- A page counts as empty only when nothing on it was skipped: its only
  notes are the page's own (art off the artboards, art past the page). A
  page whose only content couldn't be read, such as an image that can't
  be decoded, keeps its notes and goes through the usual check against
  the editing data, instead of being replaced by it unchecked.
- With textAs: outlines, the document read from the editing data alone
  has its type outlined. The usual path takes outlines from the page,
  but an empty page (or a text-only placeholder, #1387) has none to give.
2026-10-30 19:44:45 +07:00
bflatastic f36dad59b3 Place: a photo's EXIF orientation turns it upright (#4284) 2026-10-30 17:15:38 +07:00
bflatastic beb0ae6515 SVG export: rectangles, circles, ellipses, lines and straight paths as SVG shape elements (#4652) 2026-10-30 09:45:59 +07:00
Don Omondi 87cb19c589 .ai import: a file whose PDF part is an empty page opens from its editing data
Squashed from #4651 onto main.
2026-10-30 08:15:16 +07:00
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