Commit Graph
23 Commits
Author SHA1 Message Date
Brian TorresandClaude Opus 5.5 945fa338d0 PDF/X-1a and X-3: flattening keeps a CMYK document's inks where it rasterizes (#2160)
Exporting a CMYK document to PDF/X-1a or PDF/X-3 flattens transparency
first. Where the flattener has no single colour (an image with see-through
pixels over a flat fill, gradients, masks, raster effects) it renders the
area into an image, and that image was always an RGB PNG. The PDF writer
then separated it back to CMYK, so CMYK art behind the image came back with
other inks: a C100 M6 Y41 K3 panel printed C75 M14 Y35 K5 under a
transparent logo, with a hard edge against the rest of the panel, and K
moved into C, M and Y.

For files without transparency the flattener's images are opaque and
clipped to their regions anyway, so in a CMYK document it now draws them
as ink amounts over the paper (Renderer::render_region_inks) and stores a
CMYK TIFF, which the PDF writer keeps as DeviceCMYK samples. An area too
large for a CMYK image falls back to the PNG it made before. In-app
Flatten Transparency and other flattening, whose images keep alpha, are
unchanged.

Test: tests_pdfx::pdfx_1a_flattening_keeps_the_inks_behind_an_image_in_a_cmyk_document
exports a CMYK panel under a half see-through PNG to PDF/X-1a, opens the
file again and checks the inks behind the see-through half.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 12:32:05 +07:00
bflatastic 58066428c6 Artboards: the Artboards panel locks an artboard and the art on it
Squashed from #2592 onto main.
2026-10-21 23:38:02 +07:00
dotCooCoo 133fd73feb Rasterize, Flatten Transparency: commands that replace art with a rendering warn about an undecodable image
Object > Rasterize, Crop Image, Flatten Transparency, Expand
Appearance of raster effects and Vector Halftone draw the art with the
renderer and replace it with the result. When the art held an image
that could not be decoded, the renderer drew nothing for it, and the
result lacked the image with no message. A raster export of the same
art warns that "an image couldn't be decoded and is missing from the
picture".

These five commands now compare decode_failures before and after they
draw, as raster exports do, and their replies carry that text as
`warning`. They still make their result in one undo step. The app
shows a command's `warning` in the status bar: after a menu command,
after OK in a dialog with a preview, and after a tool runs a command
(Crop Image's Apply).

Docs: docs/mcp.md (Flatten Transparency, Expand Appearance, Document
Raster Effects Settings, Vector halftone).
2026-10-17 16:38:42 +07:00
vangel76andClaude Opus 5.5 9122ed0a7e MCP: cancel long exports, answer while one runs, report progress (#2165, #2171, #2172)
The MCP server read one line, ran it and answered it before reading the next,
so notifications/cancelled was never seen during a call and every request
(even ping) waited behind a running export.

- vectorcraft_doc::cancel: a thread-local cancel token and progress sink that
  long work polls. Checkpoints: PDF pages, PDF flattening, geometry bake,
  raster-effect flattening, drawing objects, raster artboards, and before any
  file is written. A cancelled command returns EngineError::Cancelled and
  changes nothing.
- Server::serve reads on its own thread: notifications/cancelled drops a
  queued request or stops the running one (no reply, as the MCP spec says),
  engine-free requests (ping, the list methods, prompts/get) are answered at
  once, a request that would wait behind a call running for 2 s is refused
  with -32003 naming that call, and _meta.progressToken gets
  notifications/progress (page n of m). Queue and output are bounded.
- The remote backend stops waiting for the app when the call is cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 13:49:48 +07:00
dotCooCoo 88bd4812e0 Part of #1429: path, Pathfinder and live-object commands add and remove many objects in one pass
Offset Path, Outline Stroke, the Pathfinder panel, Clean Up, Make
Blend, Rasterize and the other commands that #1429's table marks 3,
except Split Into Grid, added, removed or replaced their objects one at
a time, and so did Remove Anchor Points, Redefine Symbol and the
removal of Shaper groups whose source art is gone. Each insertion or
removal moved the later objects of the layer or group. The lookups by
id that followed walked the tree, and every fourth walk rebuilt the
lookup cache. On 25,000 rectangles a release build of vectorcraft-cli
took 29.1 s for Offset Path.

These commands now make their edits with Document::wrap,
Document::insert_beside and Document::remove_many, in one pass over the
children of each layer or group, and Outline Stroke and Expand write
each new object into the place of the object it replaces. A list that
holds an object twice, or an object and another inside it, and that was
edited without an error before, is still edited one object at a time.
The documents, selections and undo steps are the same. On the same
rectangles Offset Path now takes 1.3 s.

Docs: docs/target-app-parity.md.
2026-10-13 05:21:31 +07:00
bflatastic 3acc29f8e0 Fix #1442: an unknown parameter key is an error naming it and the nearest accepted key 2026-10-13 02:54:33 +07:00
arhammusheer 5beac4f122 #1244: smaller objects: Node 352 -> 152 bytes, NodeKind 200 -> 80, and a cheaper undo estimate
Rarely set fields move into one optional boxed NodeExtras, and large NodeKind payloads (images,
live shapes, blends, envelopes) are boxed. Every object, and every undo step that copies it,
shrinks: a 50k-path move keeps ~24.5 MB per step instead of ~40 MB. Files stay byte-identical
(Node keeps its flat JSON through a borrowed Serialize and a from-wire Deserialize, no flatten),
and loading is slightly faster. Undo and redo reuse their step's size estimate instead of
walking the document again (8.3 -> 5.6 ms for a 50k-path move).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc

# Conflicts:
#	crates/engine/src/cmd/graph.rs
#	crates/svg/src/export.rs
2026-10-12 11:02:32 +07:00
arhammusheerandClaude Opus 5.5 0724aafbb6 #1172: share an object's appearance with its undo history until it changes
Every edit of an object copied its whole appearance (its fills, strokes and effects, about
540 bytes for a basic object) into the new node, though a move leaves it as it is. Over 45
whole-selection moves of 10,000 paths, undo history grew by 589 MB; now 349 MB (41 % less), and
a nudge or a drag of the selection takes 12 ms instead of 15.

- Node::appearance is a vectorcraft_doc::Shared<Appearance>: copy-on-write. A clone shares it,
  and the first change through DerefMut copies it (Arc::make_mut). Reads, writes, equality (also
  against a plain Appearance) and the saved form are those of the Appearance itself, so call
  sites read and change it as before. Sites that build or assign a whole appearance add .into(),
  and the few that need a plain copy take (*n.appearance).clone().
- Node::transform changes the appearance only when it has gradients to transform, and pattern
  paints are transformed only when there are some: a move no longer copies an appearance it
  doesn't change.

The workspace suite (5,301 tests) and clippy pass with the change; the doc and engine suites
pass again after the transform guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
2026-10-11 23:27:21 +07:00
bflatasticandClaude Opus 5.5 1e7f5084d8 Part of ROADMAP gap 2: Radial Blur, Smart Blur and Unsharp Mask on a pixel-filter pipeline
Effect › Blur › Radial Blur (spin/zoom, draft/good/best) and Smart Blur
(Normal mode), Effect › Sharpen › Unsharp Mask: Photoshop-style filters in
vectorcraft_effects::pixel, run by the renderer on the object's pixels in
stack order with Gaussian Blur and Feather, cached while panning; lengths in
points, bounded work per pixel. PDF, Expand Appearance and SVG export write
the effected objects as images. Dialogs with preview (method/quality
dropdowns, fields in documented order), menu entries, translations, tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:30:31 +07:00
haven 6d56195eda Placed documents: the object, drawn exactly and kept as art by every exporter 2026-10-08 12:20:51 -07:00
bflatasticandClaude Opus 5.5 79581c7ff5 M4.87: PDF 1.3 compatibility: transparency flattened with the chosen preset
compatibility "1.3" writes a %PDF-1.3 file (its XMP metadata says 1.3
too) without transparency: a copy of the document is flattened first
with flattenerPreset (stored with PDF presets; empty is High Resolution)
and the flattener options over it, see-through image pixels counting as
transparency and rasterized areas clipped to their regions so no soft
mask is needed. Page groups are left out and the writer refuses a file
that would still have transparency. PDF/X-1a and PDF/X-3 share this
path (their version is now PDF 1.3; 1.4 is still accepted and written
as 1.3), and with it the preset, the header and metadata code and the
transparency check. Passwords at PDF 1.3 encrypt with 40-bit RC4
(revision 2). The Save PDF dialog lists PDF 1.3 and picks the flattener
preset under Advanced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 18:05:36 +07:00
bflatasticandClaude Opus 5.5 a825aed763 M4.84: PDF/X-1a, PDF/X-3 and PDF/X-4 files written as their standards say
document.exportPdf {standard: pdfX1a|pdfX3|pdfX4} and the PDF/X presets now
write conforming files instead of being refused:

- every PDF/X file gets a /GTS_PDFX output intent (by default the CMYK
  profile in effect, embedded), TrimBox, Trapped, GTS_PDFXVersion (and
  GTS_PDFXConformance for X-1a), a title and dates; editing data,
  passwords and, in X-1a/X-3, PDF layers are refused
- PDF/X-1a converts every colour and image to a CMYK destination,
  untagged, with CMYK spot alternates; X-3 and X-4 tag colours with ICC
  profiles
- X-1a and X-3 are written as PDF 1.3 and their transparency is flattened
  first (High Resolution preset; images with see-through pixels count,
  rasters stay opaque); X-4 keeps transparency, is PDF 1.6 at most and
  names the standard in its XMP metadata
- the written file is checked against its standard (soft masks,
  transparency groups, opacity, blend modes, RGB in X-1a, fonts not
  embedded) and refused rather than mislabelled
- choosing a standard sets the latest compatibility it allows and turns
  off what it forbids (engine and Save PDF dialog)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:19:11 +07:00
bflatasticandClaude Opus 5.5 4c4850e745 M4.76: EPS export (PostScript Level 2 and 3) with EPS Options, TIFF previews and the document embedded
New crates/eps (L3): a hand-written EPS writer. A DSC header with
%%BoundingBox and %%HiResBoundingBox (y up from the origin), %%LanguageLevel
and spot colours; paths filled and stroked with caps, joins, miter limits
and dashes (strokes a plain line can't draw as filled outlines, brushes as
their art), clipping groups, linear and radial gradients as smooth
shadings at Level 3 and stepped fills at Level 2 or with compatible
gradient printing, patterns as tiles, freeform gradients and placed images
as images (Flate with masked transparent pixels at Level 3, run-length at
Level 2), type as glyph outlines, spot colours as Separation spaces and
overprints when preserved; RGB written as CMYK on request. A TIFF preview
(1-bit or colour, transparent or opaque) behind the binary header, a PNG
thumbnail and the native document in comments (eps::native and
eps::thumbnail read them back for a later import).

document.export {format: eps} and document.exportEps: transparency is
flattened first with a flattener preset (built-in or saved) and options;
the visible art's bounds, or with useArtboards one file per artboard named
{stem}_{artboard}.eps. Export As lists EPS, then EPS Options
(ui.epsOptionsDialog for agents, remembered between exports).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:50:50 +07:00
bflatasticandClaude Opus 5.5 2925e7f9b5 M4.63: Links panel with status, Relink, Go To, Update, Edit Original, Embed/Unembed, Link Info and Placement Options
The Links panel lists every image (thumbnail, name, page, missing/modified/embedded
badge) with Show filters, sorting and thumbnail sizes; its buttons and flyout relink
(to a file or a folder), go to, update, edit the original, embed and unembed, show in
folder and open Placement Options. Link Info shows format, colour space, location, ppi
and effective ppi, dimensions, scale, rotation, size and dates. Edit > Edit Original
and the Properties panel's image section reach the same commands.

Engine: links.list, links.goTo, links.embed, links.unembed, links.info and
links.placementOptions; links.relink and links.update now place the new file as the
image's Placement Options say (transforms, bounds, file dimensions, fit, fill, the
alignment point, clip to the old bounds). Model: ImageObject.placement.
UI: links.editOriginal, links.reveal, ui.placementOptionsDialog; Services.open_file
and pick_folder on the desktop.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:40:05 +07:00
bflatasticandClaude Opus 5.5 2e5ec7b251 M4.53: Linked images remember their file and keep a preview; missing links ask Replace or Ignore, modified ones follow Update Links
A linked image (`ImageObject.link`, now a LinkInfo: absolute path, path relative to the document,
size, modification time, hash, page; old path strings still load) keeps a low-resolution preview
(`ImageBlob.proxy`, at most 256 px a side) that a native save writes in place of the pixels of
images only linked images show. Opening a native document reads the linked files again, looking
for each at its path, then relative to the document and by name in its folder: missing files
show their preview, modified ones are read again only with Update Links: Automatically.
`document.open` reports `missingLinks`, `modifiedLinks` and `updatedLinks`.

links.check reports each link's status, links.update reads modified files again and
links.relink points images at another file or at same-named files in a folder (each keeps its
bounds; one undo step). In the app, a document opened without its linked files asks about each
(Replace…, Ignore, Apply to All), then offers to update modified links when the preference asks.
The renderer caches previews apart from the file's pixels; the Eyedropper samples previews in
the object's pixel space.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:07:10 +07:00
bflatasticandClaude Opus 5.5 94f0f1b0d2 M4.56: Document Raster Effects Settings with colour model, background, anti-alias, clipping mask and room around
document.rasterEffectsSettings now also takes and reports colorModel,
background, antiAlias, clippingMask, addAround and preserveSpotColors in one
undo step (Document.raster_effects, RasterEffectsSettings, next to the
existing raster_effects_ppi so old files load unchanged). The raster effect
images of PDF export and Expand Appearance get the extra room and are
finished by the settings (hard edges, grayscale or bitmap, opaque on
white, white only under the art with a clipping mask). Rasterize takes
them as defaults (params override), and its clipping mask puts the image
in a clip group with the art's outline. The settings have their own dialog
(rasterEffectsSettings, ui.rasterEffectsSettingsDialog) and Rasterize…
opens its options prefilled from them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 09:55:48 +07:00
bflatasticandClaude Opus 5.5 7fb07bb3db M14.4: as_chunks for the pixel loops main gained since the no-panics branch
Same treatment as the branch's `as_chunks` commit (Rust 1.99's
`chunks_exact_to_as_chunks` lint) for the flattener's coverage pass, the
CMYK ink compositor, the Flattener Preview highlight and four test helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:40:34 +07:00
bflatasticandClaude Opus 5.5 1a5f7f6441 M14.4: no panics in the shipped code main gained since the no-panics branch
The M3/M4 work added ten panic sites the new workspace lints deny:
- doc: the lens/taper width profiles fall back to a uniform profile
  instead of `expect` (the presets are a constant table, covered by tests).
- engine: `FlattenOptions::default` builds the medium preset directly
  (`builtin`), `apply` skips an empty group instead of `expect`, the
  writable-format fallback reports an error instead of `expect`, and the
  new-art template's stroke comes back as an `Option` (it always has one)
  instead of `expect`.
- tools: the freeform gradient drag returns no actions for a grab it never
  starts (was `unreachable!`), and the gradient tool's release takes the
  gesture with `if let` instead of `expect`.

Behaviour on the success paths is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:39:18 +07:00
Brandon ThomasandClaude Opus 5.5 3e323bfa2a Robustness: VectorCraft never crashes — no panics in shipped code
Shipped code no longer unwraps, expects or panics: every one of the 124
unwrap()/expect()/panic!/unreachable! sites outside tests now handles the
failure (Result/Option, ?, let-else, defaults) and reports errors to the user.

- Lints: workspace clippy denies unwrap_used, expect_used, panic, todo,
  unimplemented and unreachable; clippy.toml allows them in tests, and
  integration tests, examples and testkit opt out explicitly.
- Safety net (vectorcraft_engine::guard): a panic in a top-level command rolls
  the document back and returns EngineError::Internal; a panicking tool is
  reset; the MCP server answers with an internal error and keeps serving; the
  UI frame and each control request are guarded.
- APIs that hid failures now return them: Rendered::to_png/to_jpeg/to_webp
  return Result (JPEG/WebP errors used to write empty files),
  FontDb::face returns Option, Region::top returns Option.
- Fuzzing: engine/tests/import_fuzz.rs feeds garbage, hostile and mutated SVG
  and PDF through import → render → export (30k cases locally, no panics).
- Docs: AGENTS.md/CLAUDE.md rule, docs/development.md "Robustness" section,
  README pointer and a PR template checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:28:59 +07:00
bflatasticandClaude Opus 5.5 9537eca5f7 M3.90: blend modes and opacity composite inks in CMYK documents
A CMYK document that shows transparency is drawn once per ink plane
(complemented C, M, Y; complemented K as a grey) so every blend mode,
opacity, group, knockout and effect composites inks, PDF-style; the
planes' inks are shown through the working CMYK profile (cached 4-D XYZ
table). Cyan multiplied over magenta now shows C100 M100. Opacity masks
keep screen luminance; RGB colours and placed images are separated
first. RGB documents and opaque CMYK documents keep the screen-colour
path.

PDF export of CMYK documents writes its transparency groups with
/CS /DeviceCMYK (luminosity mask groups stay RGB) and puts top-level
transparency in a non-isolated page-wide group so it blends in CMYK too.
Flatten Transparency composites CMYK documents ink by ink and makes CMYK
regions. Non-separable modes no longer give NaN for greys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 04:30:06 +07:00
bflatasticandClaude Opus 5.5 953024cc35 M3.94: Window > Flattener Preview shows what flattening would rasterize, outline and change
flattener.preview {highlight, overprints?, preset?|options?, ids?} reports, without changing the
document, the transparent and affected objects, patterns, outlined strokes and type, rasterized
complex regions, all rasterized areas and flat-colour regions (FlattenReport, worked out by the
flattener's own plan without rendering images). The panel previews a snapshot taken on Refresh,
greyed with the chosen highlight in red, zoomable (click, Alt-click, drag, double-click); its
Overprints, preset and options (Show Options) set how it flattens, and its menu saves them as a
preset. ui.flattenerPreview drives it; the settings persist with the UI state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:36:43 +07:00
bflatasticandClaude Opus 5.5 1dc158cafe M3.93: Edit > Transparency Flattener Presets… manages built-in and saved flattener presets
Saved presets live in Prefs.flattener_presets (persisted with the preferences) beside the
built-in High, Medium and Low Resolution ones, which stay read-only. New commands:
flattener.presets.list / save / delete / import / export (a .vcflattener JSON file; opening one
imports it). Any saved preset name works as `preset` wherever flattener options are taken
(Session::flatten_options). The manager dialog lists the presets and edits the selected saved
one in place; the Flatten Transparency dialog lists saved presets and gains Save Preset….

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:36:28 +07:00
bflatasticandClaude Opus 5.5 f75d7628c7 M3.91: Object > Flatten Transparency turns transparent art into opaque art that looks the same
object.flattenTransparency {preset?|options, ids?} splits the targets into groups of overlapping
objects. Groups with transparency become atomic regions (faces of the arrangement of every fill,
outlined stroke and clipping path) filled with the colour the renderer composites there: blend
modes, group opacity, isolated and non-isolated groups, knockout groups and knockout shapes, over
white or (Preserve Alpha) over nothing. Regions reached by gradients, patterns, images, opacity
masks or raster effects are rendered into one image per group, under the regions, clipped to
them with Clip Complex Regions. The raster/vector balance rasterizes whole groups past its region
budget (all at 0). Options and presets live in FlattenOptions; single visible paints keep their
colour and overprint; text and strokes can be outlined everywhere. One undo step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:36:18 +07:00