Commit Graph

423 Commits

Author SHA1 Message Date
BFlat 39a7af7eba
Merge pull request #111 from storytold/pr/m4-save-for-web
M4.90: Save for Web (Legacy)
2026-10-05 15:32:03 +07:00
BFlat df4b84048e
Merge pull request #110 from storytold/pr/m4-tiff-bmp-tga
M4.67–M4.69: TIFF, BMP and Targa export
2026-10-05 15:31:49 +07:00
BFlat db7164a228
Merge pull request #109 from storytold/pr/m4-emf-wmf
M4.94–M4.95: EMF and WMF
2026-10-05 15:31:36 +07:00
BFlat 6017846ef6
Merge pull request #108 from storytold/pr/m4-dxf-import
M4.93: DXF import
2026-10-05 15:31:22 +07:00
BFlat c816f16bad
Merge pull request #107 from storytold/pr/m4-data-recovery
M4.37–M4.38: Data Recovery and native save options
2026-10-05 15:31:08 +07:00
BFlat 88b9eee4ad
Merge pull request #106 from storytold/pr/m4-pdf-security
M4.85: PDF security
2026-10-05 15:30:53 +07:00
BFlat 2bab433c92
Merge pull request #105 from storytold/pr/m4-print-engine
M4.75: print engine
2026-10-05 15:30:39 +07:00
BFlat 1e09016b80
Merge pull request #104 from storytold/pr/m4-ft3
M14.4: flaky paste fuzz test and batch journaling
2026-10-05 15:30:26 +07:00
bflatastic c6450f6f70 M4.90: Save for Web (Legacy) previews and saves optimised GIF, JPEG, PNG-8 and PNG-24 images, slices and HTML
File > Export > Save for Web (Legacy) (Ctrl/Cmd+Alt+Shift+S) opens a dialog with Original,
Optimized, 2-Up and 4-Up views showing each file's size and download time, and settings for
colour reduction, colours, dither, transparency and matte, interlacing, web snap, lossy GIF,
JPEG quality, the colour table (lock, map to transparent, web shift, sort), image size and
anti-aliasing, Clip to Artboard, sRGB, metadata, slices (all or selected), HTML output and
presets. Preview in Browser opens the HTML page.

Engine: document.exportForWeb, document.exportForWeb.preview (its byte count is the size of
the file Save writes), webExport.settings and webExport.presets.list/save/delete. Saved presets
and the remembered settings are stored in Prefs. Render: quantize_locked, and encode::web for
web snap, colour table edits, lossy GIF and GIF/JPEG comments. PNG files get an sRGB chunk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:51 +07:00
bflatastic b578c32f4d M4.69: Targa export at 16, 24 or 32 bits per pixel
document.export {format: "tga"} writes uncompressed true-colour Targa files
through the raster export path (resolution, background, anti-aliasing,
artboard ranges): depth 16 (5-5-5 with a one-bit alpha), 24 (flattened on
white) or 32 (8-bit alpha), rows bottom-up, with the version 2 footer.
Export As offers it with the tgaOptions dialog (Depth).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:51 +07:00
bflatastic f2fc8782c7 M4.68: BMP export at 1 to 32 bits, Windows or OS/2, with RLE and flipped rows
document.export {format: "bmp"} writes BMP through the raster export path:
depth 1 (black and white), 4 or 8 (a palette by reduction and dither,
optionally RLE4/RLE8), 16 (5-5-5), 24 or 32 (with alpha, a version 4
header); colorModel rgb or gray; fileFormat windows or os2 (a core header);
flipRows writes top-down rows (a negative height). Impossible combinations
are refused as bad params. Export As offers it with the bmpOptions dialog,
which keeps its choices writable together; BMP moves from read-only to read
and write (tests that used .bmp as the unwritable format now use .dwg).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:35 +07:00
bflatastic d01b2fbd8a M4.67: TIFF export in RGB, CMYK or grayscale, with LZW, either byte order and the profile
document.export {format: "tiff"} writes baseline TIFF through the raster
export path (resolution, background, anti-aliasing, artboard ranges):
colorModel rgb (transparency as unassociated alpha), cmyk (ink amounts,
CMYK colours keep their inks) or gray, lzw (weezl), byteOrder little (II)
or big (MM), embedIcc, and the resolution tags. Export As offers it with
the tiffOptions dialog; TIFF moves from read-only to read and write.

The TIFF writer is shared: EPS previews now use it (and its PackBits)
instead of their own copy, so eps depends on render (layers allow it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:29 +07:00
bflatastic 961ca75a28 M4.95: EMF and WMF open, place and paste as vector objects
crates/metafile import: EMF and WMF (placeable or not) records play on a
small device-context model (objects numbered as GDI does, saved DCs, world
transforms and mapping modes, path brackets, the current position) into one
artboard (the picture's frame) and one layer: paths with fills and strokes
(pens with caps, joins, dashes), clipping groups for clips, images from DIBs
(StretchDIBits, BitBlt, StretchBlt, AlphaBlend, StretchDIB, DIBBitBlt) and
point type in the font the file names. Lines drawn one after another join
into one path; a clip around the whole picture is dropped; objects far
outside it are left out; unknown records are skipped with one warning.

document.open and file.place read .emf and .wmf through the shared loader
(Open and Place filters list them). clipboard.importEmf loads a picture into
the clipboard, and Paste takes image/emf from a system clipboard service
ahead of text and bitmaps. The import fuzzer covers mutated and random-record
metafiles opened, placed and pasted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:27 +07:00
bflatastic ab1c6b0516 M4.94: EMF and WMF export from Export As and document.export
New crates/metafile (L3, pure Rust): hand-written EMF and WMF writers over
one shared document walk. EMF keeps Bezier paths, solid brushes, geometric
pens (width, caps, joins, miter limit, user-style dashes), clipping masks as
path clips, images (StretchDIBits, or AlphaBlend with transparency) placed by
a world transform, type as outlines; gradients become images clipped to their
shape and pattern fills their tiles clipped to it. The header's frame is the
artboard in 0.01 mm on a 100 px/mm reference device. WMF flattens curves into
polygons in 16-bit units behind a placeable header (with its checksum),
numbers objects as players do, and reports what it leaves out (clipping,
transparency, gradients, rotated images).

document.export {format: emf|wmf} (and serialize, export) writes one artboard,
or one file per artboard with useArtboards (false: the art's bounds); raster
effects are rendered to images first, as for PDF. Export As lists EMF and WMF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:24 +07:00
bflatastic 2e6585d7ea M4.93: DXF drawings open and place with layout, scale or fit, centring and merged layers
ASCII DXF of any version opens (document.open) and places (file.place) through
a new reader in vectorcraft-cad: lines, LW/2D/3D polylines with bulge arcs,
circles, arcs, ellipses, NURBS splines (exact Beziers up to cubic, fitted
otherwise), solids, solid and pattern hatches, TEXT/MTEXT/ATTRIB as point
type, named blocks as symbols (one per look for by-block art), anonymous
blocks and dimensions as groups, MINSERT arrays, OCS extrusion; layers keep
off/frozen/locked/plot state; ACI and true colour, lineweights, linetypes and
transparency resolve through layers and blocks. The `dxf` option object picks
the layout, ratio or fit, lineweight scaling, centring and merged layers;
document.dxfInfo lists layouts, layers and units. Binary DXF and DWG answer a
clear error. The DXF Import Options dialog (dxfImport) asks on open and place
with a layout preview. Fuzzed in import_fuzz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:21 +07:00
bflatastic bf4614669e M4.38: native and .ai save options: each artboard to its own file, linked files, ICC profiles, PDF content
- document.save / Save As / Save a Copy to a native or .ai file take
  separateArtboards + range (also write <name>-<artboard>.<ext> per artboard
  beside the master file, holding that artboard and the art touching it; the
  result lists every file), includeLinked (linked images keep their files' own
  pixels), embedProfiles (ICC profiles loaded from files that the document is
  tagged with; opening installs them where missing) and pdfCompatible (native:
  also carry a PDF of every artboard; .ai: false writes blank pages around the
  native document). .ai files also take compress (compression.compressText).
- File > Save As to a native or .ai file shows the options dialog (VectorCraft
  Options) after the save panel: Save each artboard to a separate file (All or a
  range), Include Linked Files, Embed ICC Profiles, Create PDF-Compatible File,
  Use Compression, Preview; the web's Save As dialog shows them too. Save, Save a
  Copy and Save as Template reuse the options as last saved.
- Format: optional "profiles" and "pdf" members (load_file, pdf_content),
  SaveOptions.include_linked/profiles/pdf; files without them are unchanged.
- Changed test: tests_aisave::save_as_ai_writes_a_pdf_that_reopens_editable now
  confirms the options dialog Save As .ai shows before writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:19 +07:00
bflatastic d8d1d472cf M4.85: PDF security: encryption at every compatibility level, permissions, either password opens
- pdf/src/encrypt.rs encrypts a written PDF with the standard security handler: 128-bit RC4
  (PDF 1.4, revision 3; PDF 1.5, revision 4), 128-bit AES (1.6) or 256-bit AES (1.7/2.0,
  revision 6), every string and stream, plaintext XMP metadata when asked, the permission bits
  (printing none/low/high, five change levels, copy, screen readers). Crypto from RustCrypto
  (aes, md-5, sha2: MIT OR Apache-2.0, pure Rust, wasm).
- PDF/A and PDF/X refuse passwords; equal passwords and passwords the algorithm can't take are
  refused.
- Opening: the permissions password opens RC4/AES-128 files too; editing data comes back from
  an encrypted PDF opened with its password.
- Save PDF › Security: the passwords, permissions and plaintext metadata, the encryption level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:16 +07:00
bflatastic 975067b44a M4.37: Data Recovery keeps copies of modified documents and offers them after a crash
- Every Preferences > File Handling > Data Recovery interval, modified documents
  changed since their last copy are written to the recovery store in the
  background (the same snapshot and encoder as Background Save; the status bar
  shows it). Complex documents (over 20,000 objects) are skipped while Turn off
  Data Recovery for complex documents is on.
- A copy goes when its document is saved, reverted, closed or undone back to its
  saved state, and when the app quits with nothing unsaved.
- Each running app keeps its copies in an area of its own: a sub-folder whose
  .lock file it keeps locked for its lifetime (File::try_lock), or on the web a
  heartbeat refreshed every minute. Only areas nobody holds (lock free, or no
  heartbeat for three intervals, at least three minutes) are offered, and an
  area is held while it is restored or discarded, so a second running instance
  never offers or deletes a live instance's copies. Empty leftover areas are
  tidied away.
- The next launch after a crash asks with the Recover Documents dialog (Restore /
  Discard / Cancel): restored documents open as "<name> [Recovered]", unsaved, and
  Save asks where to save them, suggesting their original file; the copy moves
  into the new app's area.
- Store: the recoveryFolder preference, else Data Recovery beside the desktop
  app's preferences; browser storage on the web (Services::recovery_store);
  tests use an in-memory store (with in-memory locks, or heartbeats).
- Engine: file.recovery.save, file.recovery.list, file.recovery.restore {file?},
  file.recovery.discard {file?}; DocState.recovered and DocState.recovery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:24:16 +07:00
bflatastic b379f02527 M4.75: File → Print writes the job as a print-ready PDF: paper, scale, tiles, marks and bleed, separations
file.print {settings?, path?} lays the document out on paper and writes one PDF page per sheet:
copies (collated, reversed), artboards (all, a range, ignored), blank artboards skipped, paper
size and orientation (or turned per artboard), transverse, Print Layers (template layers never
print), placement on the imageable area, no scale / fit / custom / tiles of the paper or of the
imageable area with overlap and a tile range; printer's marks and bleed as PDF export draws
them; composite or separations (one grey plate per ink that prints, overprints honoured, spots
as process), emulsion down, negative; rendering intent and preserve numbers. print.preview lists
the pages, tiles, inks and warnings; print.setup saves the settings with the document
(Document.print_setup, one undo step).

PDF export and print share one page writer; the ink separation helpers move from the renderer to
vectorcraft_doc::inks and the range parser to vectorcraft_doc::range so print can use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 15:13:10 +07:00
bflatastic 3d87b71ed4 M14.4: a batch that opens a new document is journaled
command.batch kept its steps in one interaction of the document active
when it began and journaled itself by committing that interaction. A
step that opened, closed or reverted a document broke both: the switch
cancelled the interaction (throwing away the steps so far), and the
commit then ran in the new document, which had none, so the batch was
never journaled and a replay was missing the new document. An error
after the switch rolled back only the active document.

- Each step runs inside an interaction of the document active when it
  starts; a switch leaves it open while a batch runs, so every document
  the batch edits gets one undo step when it succeeds.
- The batch journals itself, whichever document it ends in.
- On error everything rolls back: documents the batch opened close, the
  ones it closed or reverted come back (Session::batch_stash), each
  one's interaction is undone, and the active document and the
  Untitled-N count go back too.
- DocState::keep_interaction / undo_interaction share the undo-step and
  rollback code with commit_interaction / cancel_interaction.
- Tests: a batch that opens a new document is journaled, replays to the
  same documents and keeps one undo step in each; a failed batch closes
  the document it opened and restores the one it closed; a failed batch
  brings back a document it reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:22:36 +07:00
bflatastic 766b612070 M14.4: a batch journals the dates and preferences its steps used
Session::note_journal only kept values noted by the top-level command, so
a command run as a step of command.batch lost them: a save in a batch
stamped File Info's modified date from the clock without recording it,
and object.scale's strokes/corners came from the preferences again on
replay. Replaying such a journal a second later (or with other
preferences) gave a different document.

- Session::execute_step runs a batch step with its own journal notes
  (note_depth) and returns its params with what it noted; command.batch
  writes those params into its step of the batch's journal entry (steps
  that note nothing stay as given).
- journal_for_action strips the replay-only dates from a batch's steps
  too, so an action recorded with one still dates the document when it
  plays.
- Tests: a save in a batch records its date and a replay with the
  recorded date moved lands on it, while an action leaves it out; a scale
  in a batch records Scale Strokes & Effects and replays alike with the
  preference off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:05:09 +07:00
bflatastic 0312f1ea55 M14.4: the pasted-PDF fuzz test accepts a paste that refuses out-of-range art
pasted_pdf_never_panics failed at random (about one run in four at 256
cases): a mutated digit turns a curve coordinate like 245.18535 into
245018535, so the pasted art spans ~1e8 pt, beyond MAX_COORD, and
edit.paste rightly refuses it with "result would exceed the canvas",
leaving the document unchanged. The helper unwrapped that result. The
test is about never panicking, so paste_from_elsewhere now ignores an
Err from edit.paste, with a comment saying why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 14:05:09 +07:00
BFlat e2e36f221b
Merge pull request #103 from storytold/pr/m4-eps-export
M4.76: EPS export
2026-10-05 14:02:45 +07:00
bflatastic 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
Brandon Thomas 90e022b0c0
Merge pull request #102 from storytold/pr/release-0.2.0
Release: VectorCraft v0.2.0
2026-10-05 02:41:45 -04:00
Brandon Thomas 8b50a2604d Release: VectorCraft v0.2.0
Bump the workspace version 0.1.1 -> 0.2.0 (cargo xtask version set 0.2.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:41:32 -04:00
BFlat 819a0cb9b4
Merge pull request #101 from storytold/pr/m4-svg-encoding-tiny
M4.97–M4.98: SVG encoding, Tiny 1.2, font subsets and freeform gradients
2026-10-05 13:35:39 +07:00
bflatastic 33023c5a2b M4.98: freeform gradients export to SVG as images clipped to their shapes
SVG has no freeform shading: an object's freeform fill or stroke is now
written as an <image> of the colour field over its painted box, sampled
on the grid the canvas uses (twice the box's size in points), clipped to
the shape or to the stroke's outline, with the fill or stroke opacity and
blend mode on the group. Linked image mode writes the PNG next to the
SVG. Character paints keep the linear approximation (with a warning);
gradients along or across strokes stay vector slices. The grid sizing
moved to the colour crate so the canvas and the export share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:25:16 +07:00
bflatastic fa3dcab411 M4.97: SVG Options choose the encoding, SVG Tiny 1.2 and embedded font subsets
SVG export writes UTF-8, UTF-16 (big-endian with a byte order mark) or
ISO 8859-1 (other characters as character references) and SVG/SVGZ
open in any of them. The Tiny 1.2 profile (simplified) writes
presentation attributes only and leaves out filters, masks, blend
modes and symbols with warnings. embedFonts embeds the fonts type uses
as @font-face subsets of the characters used, honouring the fonts'
fsType (whole when subsetting is forbidden, left out when embedding
is). New options: encoding, profile, embedFonts (SVG Options dialog,
document.formats); document.serialize also returns dataBase64 for
non-UTF-8 files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:25:15 +07:00
BFlat eacc83e05b
Merge pull request #100 from storytold/pr/m4-export-screens
M4.51–M4.52: Export for Screens downloads, zip and options
2026-10-05 13:24:40 +07:00
bflatastic c96bf40d18 M4.52: Export for Screens options: full document, bleed, sub-folders, sizes, presets, format settings
Artboards: All, a range or Full Document (one file per format: a PDF of
every artboard, other formats the bounds of all art), Include Bleed
(artboards grown by the document's bleed). Rows size files as a factor,
100w, 100h or 72ppi (suffixes @2x, @100w, @100h), and choose PNG, PNG 8,
JPG 100/80/50, WebP, GIF, SVG or PDF. Create Sub-folders (by size, by
format, or a row's own folder) and the presets Mobile 1x/2x/3x and
Density buckets (ldpi…xxxhdpi sub-folders). The gear opens Format
Settings: per-format options for every row of that format (the raster
and SVG options dialogs' rows, the PDF preset). Open Location after
Export shows the first file. The dialog has Artboards and Assets tabs.

The params of the last export are saved in the document
(Document.export_settings, not an undo step), read with
document.exportSettings, and the dialog reopens on them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:18:10 +07:00
bflatastic 902d95173e M4.51: Export for Screens downloads on the web, picks folders, zips
document.exportForScreens takes zip: true to pack every file into one
store-only .zip (returned as dataBase64 without a folder, written there
with one); without a folder the files come back as base64. The engine's
Export for Screens moves to fileio/screens.rs, with a small ZIP writer
(fileio/zip.rs).

The dialog starts in the user's Desktop (else home, on every OS), has a
folder picker button (the Services.pick_folder the Links panel uses) and
stays open when the export fails. On the web it has no folder: OK reads
"Download" (DialogSpec.ok_label) and the files download, several as one
zip. file.exportForScreens with params goes through io::export_for_screens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:18:10 +07:00
BFlat 8ab7237443
Merge pull request #99 from storytold/pr/m4-pdf-boxes-marks
M4.71–M4.72: PDF bleed and trim boxes, printer's marks
2026-10-05 13:17:15 +07:00
BFlat 522f05c540
Merge pull request #98 from storytold/pr/m4-pdf-import-fidelity
M4.61–M4.62: PDF import fidelity and layers
2026-10-05 13:17:00 +07:00
bflatastic b0e39d0912 M4.72: PDF export draws printer's marks in Registration around each page
document.exportPdf {marks} now draws trim marks (Roman or Japanese, at the
trim mark weight and offset, outside the bleed), a registration target at the
middle of each side, colour bars (process inks, their overprints, the
document's spot inks and black tints) and the page information (title,
artboard, export date in UTC). Marks print in [Registration] (/Separation
/All), the MediaBox grows to hold them and the art is clipped to the
BleedBox. The geometry is the shared vectorcraft_doc::marks::PrinterMarks
(built on TrimMarks) so print can draw the same marks. The "not drawn yet"
warning is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:05:20 +07:00
bflatastic 8fb1733f05 M4.71: PDF pages carry the bleed and trim boxes; non-printing layers are left out
document.exportPdf applies the bleed (the document's with bleed.useDocument,
else the custom values): each page's TrimBox is its artboard and its BleedBox
and MediaBox the artboard grown by the bleed, so art in the bleed is kept.
Layers whose Print option is off are left out unless includeNonPrinting (new
setting, Save PDF › General) or createLayers is on. The bleed warning is gone;
the Marks and Bleeds section shows the document's bleed when it is used.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:05:19 +07:00
bflatastic 8046b9be84 M4.62: PDF/.ai layers from optional content: named layers with visibility, print state and lock; art that is off as hidden layers
Optional content groups become layers (in paint order, shared by every page)
with their name, default visibility, print usage and lock; art outside them
goes to a layer per page. Art that is off isn't lost: an update appended to
the file drops its /OCProperties so the interpreter draws every group, and
the marked-content sequences found walking the content (checked by tag) route
the art to its group's layer, which comes in hidden. A form hidden by its own
/OC keeps the file as it is. document.open layers: false keeps one layer per
page of only what shows (Save then asks for a name).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:05:12 +07:00
bflatastic a51083733e M4.61: PDF import fidelity: opacity masks, isolate/knockout, pattern swatches, editable text, gradient meshes, stop opacity and Extend
Luminosity soft masks become opacity masks (alpha masks as a white copy of
their art; backdrop colour gives Clip, an inverting transfer function Invert;
the PDF export's backdrop and Difference covers read back as the options; a
constant alpha mask is opacity). Transparency groups keep their /I and /K
flags (read by walking the content as the interpreter does). Tiling patterns
become pattern swatches (one per colour for uncoloured ones). Text becomes
point type, one object per line with a run per font and paint, fonts named
from the file (document.open textAs: text | outlines). Patch and triangle
mesh shadings become gradient meshes; gradients drawn through their grey
opacity mask get stop opacity back; shadings that don't extend clip the art.
A rectangular clip around all its art (a form's box) is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 13:05:12 +07:00
BFlat 66e92bbd4c
Merge pull request #97 from storytold/pr/m4-slices
M4.88–M4.89: slices and the Slice tools
2026-10-05 13:05:06 +07:00
bflatastic fbc7549c31 M4.89: Slice and Slice Selection tools draw, pick, move, resize and delete slices
The Slice tool (Shift+K) drags out a user slice (Shift square, Alt from the
centre, snapping to ruler guides) as one undo step through
object.slice.create. The Slice Selection tool selects slices (Shift
toggles, object.slice.select), moves them (object.slice.move; object
slices move their objects), resizes a user slice by its handles
(object.slice.setRect), opens Slice Options on a double click and deletes
with Delete (object.slice.delete). Hidden or locked slices can't be
picked. Both tools have cursors drawn in code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:52:15 +07:00
bflatastic 271b0c6c25 M4.88: Object → Slice makes, edits and lays out user, object and auto slices
Document.slices holds user slices, Node.slice makes an object an object
slice that follows its bounds, and Document.slices_clip_to_artboard picks
the region auto slices fill (the artboards, or the art and the slices).
Object → Slice: Make, Release, Create from Guides, Create from Selection,
Duplicate, Combine, Divide… (dialog), Delete All, Slice Options… (dialog)
and Clip to Artboard; View → Hide/Lock Slices; Select → Object → Slices;
slice.list reads the numbered layout. The canvas draws the slices in the
Slices preferences' line colour with their numbers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:52:15 +07:00
BFlat d767fa22e0
Merge pull request #96 from storytold/pr/m4-links-package
M4.63–M4.64: Links panel, Placement Options and Package
2026-10-05 12:49:30 +07:00
bflatastic 8d8ca11e50 M4.64: File > Package collects the document, its linked files and fonts with a report; Document Info categories and Save
File > Package (saved documents; unsaved changes are saved first) writes
<folder>/<name>/<document>.vectorcraft, the linked files in Links/ (relinked in the
packaged copy), the fonts its type uses in Fonts/ (leaving out fonts whose licence
doesn't allow embedding) and <document> Report.txt, then offers Show Package. Without a
folder (the web, agents) the same files come back as a zip.

Document Info's flyout picks one category (document, objects, styles, spot colours,
patterns, gradients, symbols, fonts, font details, linked and embedded images) and Save…
writes the text report Package shares.

Engine: file.package {folder?, name?, copyLinks?, linksFolder?, relink?, copyFonts?,
report?}; document.info gains category, format: text and sections. Text: FontFace
path, fs_type and embeddable. UI: ui.packageDialog (dialog `package`), file.showPackage,
docInfo.save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:40:05 +07:00
bflatastic 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
BFlat 171de62fde
Merge pull request #95 from storytold/pr/m4-native-format
M4.35–M4.36: native format v3 and background save
2026-10-05 12:37:53 +07:00
bflatastic db0a7ffd8b M4.36: Background Save and Export write a snapshot off the UI thread with status bar progress
- With Preferences > File Handling > Enable Background Save / Export on, File >
  Save, Save As, Save a Copy and exports encode and write a snapshot of the
  document on a worker thread; the call returns at once ({path, background}).
- The status bar shows a spinner and what is being written; ui.inspect lists
  the running jobs under `background`.
- The document takes the path and counts as saved as of the snapshot once the
  file is written: edits made meanwhile keep it modified.
- Quit and Close wait for running saves; the Save Changes dialog saves at once.
- The host gives a thread-safe writer (Services::write_shared, atomic on the
  desktop); without one (web, tests) everything runs at once as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:27:06 +07:00
bflatastic 65cdc45ed9 M4.35: native files compress, save atomically, drop unused images, keep newer keys and save down to v1
- Format v3: optional gzip (Preferences > File Handling > Use Compression, or
  document.save {compress}); compressed files open by their magic bytes, legacy
  ones too.
- Every file write goes to a temporary file renamed over the target (engine
  writes and the desktop app's writer), so a failed save keeps the old file.
- Only the images the document uses are written.
- Document keys from newer versions are kept (Document.extra, flattened).
- Assigned colour profiles are a real field (Document.color_profiles),
  migrated from unknown.colorProfiles on load.
- document.save {version: 1|2} writes files older versions open; {preview}
  embeds a PNG of the first artboard of at most 256 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:27:02 +07:00
BFlat 7aee3bd487
Merge pull request #94 from storytold/pr/m4-svg-import-live
M4: SVG import keeps symbols, patterns and filters live; linked images, nested clips, reflected gradients (M4.41, M4.42)
2026-10-05 12:25:30 +07:00
bflatastic 303fc3cbca M4.42: SVG import reads linked images from the SVG's folder, intersects nested clips, keeps hidden objects and expands reflected gradients
document.open {path} (and File > Place) find an SVG's relative <image>
links in its folder: raster files stay linked images (LinkInfo with size,
time and hash), SVG files and data: SVGs import as vector art, and a file
that can't be read becomes a placeholder in its box that keeps the link,
with a warning and a missingLinks row (Update Links / Relink read it
later). The SVG library itself reads no file unless given a reader.
Nested clip paths become clip groups in clip groups; undisplayed objects
(display:none) come back hidden; spreadMethod reflect and repeat are
expanded into stops over the painted area (matching resvg). Midpoint stops
and focal points keep folding back as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:12:06 +07:00
bflatastic 0d75df4da8 M4.41: SVG import keeps symbols, patterns and blur/shadow filters live
<symbol> with <use> imports as a symbol (named after data-name or its id)
with one instance per <use>; a <use> that shows the symbol differently
(inherited paint, a viewport that cuts it) or that the canvas would paint
differently under its transform stays plain art. feGaussianBlur,
feDropShadow and the usual shadow chains (offset, blur, flood or colour
matrix, composite, merge) become Gaussian Blur, Drop Shadow, Outer/Inner
Glow and Feather effects; other filters are still reported. Export records
the symbol name and the shadow/glow blend mode so export -> import keeps
them. <pattern> keeps importing as a pattern swatch (now tested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 12:11:27 +07:00