Commit Graph
1015 Commits
Author SHA1 Message Date
dotCooCoo 35d70f5531 i18n: Edit › Undo and Redo name the step as the History panel does
Edit › Undo and Edit › Redo translated the step's name with its plain
catalog row only. Nine of the 22 catalogs have no plain row for
Feather, only the effect's menu row with an ellipsis, so in Japanese
Edit › Undo read "Featherを取り消す" after Feather while the History
panel listed the step as ぼかし.

The menu now names the step with panels::history::step_name, as the
History panel does, and keeps the name as recorded when the catalog
has no row for it. The in-window and native menus both show the new
label.

Docs: docs/target-app-parity.md.
2026-10-18 01:27:14 +07:00
dotCooCoo 23dd9a1f2a Fix #2054: bounding-box resize handles snap to the grid, pixels, points and ruler guides
A bounding-box handle dragged with the Selection or Free Transform tool
snapped only with Smart Guides on, and then only to the other objects
and the artboards. View > Snap to Grid, Snap to Pixel, Snap to Point
with Smart Guides off and the ruler guides did not move it, while a
move snaps to all of them.

The corner or side a handle moves now snaps as a dragged point does.
With Snap to Grid on it lines up with the nearest gridline, and with
Snap to Pixel on it lines up with whole pixels. With Smart Guides off,
Snap to Point lands it on the other objects' anchors and on ruler
guides within its distance. With Smart Guides and Snap to Point both
on, it lines up with the ruler guides as well as with the other
objects. Shift still keeps the proportions. Without Alt the opposite
side stays put, and with Alt the box still scales about its center.

Docs: docs/target-app-parity.md.
2026-10-18 01:27:14 +07:00
dotCooCoo 20590e778c Linux Display Server: XWayland when a pen is attached, where a drawing tablet's pen draws
On a Wayland session a drawing tablet's pen could not draw or click:
winit 0.30 does not bind the Wayland tablet protocol, and neither KWin
(since Plasma 6.3) nor GNOME's Mutter turns tablet input into pointer
input. Linux Display Server's X11 (XWayland) choice let the pen work as
a mouse, but it opened the window through XWayland on every start, also
without a tablet.

Linux Display Server has a third choice, X11 (XWayland) When a Pen Is
Attached (x11WithPen). It opens the window through XWayland on a
Wayland session only when /proc/bus/input/devices lists a pen as the
app starts: a device with the BTN_TOOL_PEN key and the ABS_X and ABS_Y
axes, the check PhotoCraft's tablet.rs makes. Automatic stays the
default. On Linux the Performance page shows a note for tablet pens
that don't draw.

README.md named KDE alone; the KWin commit's message says Mutter does
not emulate tablet events either, so README.md and docs/development.md
now name GNOME. The Pen pressure row of docs/hardware-parity.md said
Linux has pressure through XWayland; winit 0.30 reports none on X11
either.

Docs: README.md, docs/development.md, docs/hardware-parity.md,
docs/target-app-parity.md.
2026-10-18 01:27:14 +07:00
dotCooCoo 2ccb147403 Variables: Import Data Sets… defines the variables a file's header names
dataset.import refused every header cell that wasn't the name of a
variable the document defines. A data source file could only fill
variables made beforehand, and `#` and `@` in a header were read as part
of a variable's name.

A header cell that names no variable now defines a new, unbound
variable in the import's undo step. `#Shown` defines a visibility
variable `Shown`, `@Photo` defines an image variable `Photo`, and a
cell without a prefix defines a text variable. A prefixed cell whose
name is already a variable of the prefix's kind names it. A cell that
is a variable's exact name keeps naming it, even when the name starts
with `#`, `@` or `%`, so files that dataset.export writes import back
as before. The import fails, naming the column, on a prefixed cell
whose name is a variable of another kind and on a `%` cell that names
no variable (graph data files). While the variables are locked, a
header that would define a variable fails the import with "variables
are locked", as New Variable does. The reply's `defined` lists the new
variables.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:27:13 +07:00
dotCooCoo a12f5b0f20 Export: Save for Office, CSS and Copy warn when an image couldn't be decoded or a font is missing
Save for Office Documents, the pictures that css.export rasterizes and
the PNG and PDF that Copy offers other apps were encoded with
fileio::encode, which drops the encoder's warnings. When the art held
an image that the renderer could not decode, the PNGs lacked it; type
in a font that is not installed came out in the fallback font in the
Office PNG and Copy's PNG and PDF. No reply or status message said so.
The app also dropped the warnings that document.exportSelection
returns.

document.exportForOffice, clipboard.exportPng and clipboard.exportPdf
now return `warnings`, and css.export adds the notes of its pictures to
its `warnings`, each once. Save for Office Documents and
document.exportSelection show the first note in the status bar in Save
As's message, and the CSS Properties panel's Export Selected CSS… and
Export All… show it in Export As's message. After Copy or Cut, once
the PNG and PDF are on the system clipboard, the status bar shows their
first note. The files are written as before.

Docs: docs/mcp.md (Clipboard, CSS Properties), docs/control-protocol.md.
2026-10-18 01:27:13 +07:00
dotCooCoo 919970a467 Export for Screens: exports warn when an image couldn't be decoded or a font is missing
Export for Screens wrote each file with fileio::encode, which drops the
encoder's warnings, and its replies had no `warnings` key. When the art
held an image that the renderer could not decode, or type in a font
that is not installed, the files lacked the image or used the fallback
font, and neither the reply nor the status bar said so. document.export
returns the same notes in `warnings`, and Export As shows the first in
the status bar.

document.exportForScreens and assets.export now collect the warnings of
every file they export, each once, and every reply carries them in
`warnings`. The export still writes or returns every file. When there
are warnings, the Export for Screens dialog and the Asset Export panel
show the first in the status bar as Save As shows a save's notes.

Docs: docs/mcp.md (Export for Screens), docs/control-protocol.md.
2026-10-18 01:27:13 +07:00
dotCooCoo 6b13dd776b Export: names typed into file names follow Windows' naming rules
Export for Screens, Batch over data sets, Save for Web and the
Libraries panel name files after text the person typed: a prefix, a
suffix, a name pattern, the name of an artboard, asset, layer, slice,
data set or library, or the document's title. The prefix, the suffix,
name patterns and Batch's data set names had only /, \ and : replaced,
so on Windows a name holding <, >, ", |, ?, * or a control character
failed to save, and Save for Web without a path named its files after
the document's title as it was. A row folder name could end in a
period, which Windows does not allow, and names such as CON, NUL, COM1
or LPT1, which Windows reserves for devices, were written as they
were.

file_part, which Export for Screens' prefix and suffix and Batch's
data set names use, now writes all of those characters as -, and name
patterns use it in place of their own copy. A new safe_file_name runs
a whole file or folder name through file_part, removes trailing
periods and spaces, and adds - after a device name that is the whole
name or comes before its first period (CON.png becomes CON-.png).
Export for Screens uses it for each file, folder and zip it names, and
keeps item names distinct after it. Save for Web uses it for slice
images and, without a path, for files named after the title. Library
files use it too. This happens on every platform. Other names are
written as before, except where a changed name now equals another
item's name, which then gets a number added.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:27:13 +07:00
dotCooCoo aeb9314e2c Part of #2558: a drag draws zoomed-in raster effects at a lower resolution until it ends
When zoomed in, an object with a texture, a blur or a shadow was slow
to drag. Filtered content whose on-screen area passes the 8.4 million
pixel raster budget keeps no raster, and a shadow or glow larger than
4 million pixels keeps none either, so every frame of the drag ran the
effects again over up to 8.4 million pixels. A drag that scales or
rotates an object runs them again at any zoom.

The canvas now renders a tool's drag with RenderOptions::dragging. In
such a frame the renderer draws filtered content within 1,000,000
pixels, at a lower resolution where it would need more, unless it can
draw the content from an earlier frame's raster. On the multithreaded
pipeline it does the same for drop shadows, outer glows and inner
glows. No raster made that way is kept. They count as moved rasters,
so a raster made over them is made again after the drag, and the
desktop canvas renders the frame after the drag on the render worker.
That frame renders them as a still frame does. A dialog's preview,
exports and printing render as before.

zoomed_drag_perf times a drag's frames at 100% to 6400% with and
without dragging.

Docs: docs/target-app-parity.md.
2026-10-18 01:24:35 +07:00
dotCooCoo 7f858a7e59 Actions: Batch's Override Save and Export name files with the format's extension
With Override Save or Override Export on, action.batch named each
step's file in the destination folder with the step's format exactly as
written. The save and export commands read the format in any case and
with a leading dot, so a step with "format": ".svg" wrote
"Untitled_a..svg", "SVG" wrote ".SVG", "png8" wrote ".png8" and
"template" wrote ".template".

The file now ends in the format's extension: the extension as written,
in lower case and without the dot, when it is one of the format's
(".JPEG" gives ".jpeg"), and otherwise the format's first extension, the
one Export and Save use for a file name ("png8" gives ".png", "template"
gives ".vctemplate"). An unknown format ("xyz") keeps its spelling, and
the step fails with the command's error as before.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-18 01:24:35 +07:00
dotCooCoo dc48ce6c74 .ai import: the warning that the layers differ from the page gives the art's share or names the type
When a .ai or EPS opens from its editing data and its layers draw a
little differently from the file's own page, the warning gave the share
of all the pixels that differ, type included, and blamed brush strokes
and live effects. Type that the import lays out again from the file's
text, or draws in another font, raised it on its own: a line that the
page sets in pieces gave "about 1.5% of it (brush strokes, live effects
and the like ...)" with no art differing.

The comparison now also counts the pixels that differ away from type.
The warning gives that share, and when the art away from type does not
differ by more than the warning's limit, the warning names the type
instead. The warning appears in the same cases as before, and the
limits that make the file come in as its page are unchanged.

Docs: docs/target-app-parity.md.
2026-10-18 01:24:35 +07:00
dotCooCoo ceddd8185f Artboards: Ctrl/Cmd+A in the Artboards panel selects every artboard with the Artboard tool
With the Artboard tool in use, Ctrl/Cmd+A after a click on a row of the
Artboards panel selected all the art. The panel now takes the keyboard
when one of its rows is clicked, as the Layers panel does for Delete,
and gives it up when the pointer is pressed outside its list or the
panel is not drawn.
While it has the keyboard and the Artboard tool is in use, Select All's
keys, and Select All in the macOS menu bar, select every artboard: the
active artboard stays active and the art's selection does not change.
Otherwise Select All still selects the art.

Docs: docs/target-app-parity.md.
2026-10-18 01:24:35 +07:00
dotCooCoo 85fce6852e History: the panel menu has Step Backward, Step Forward and Set History Limit…
The History panel menu held only a disabled History Options… item. The
reference app's History panel menu has Step Backward and Step Forward,
which undo and redo a change, and Set History Limit, which sets the
number of History States.

The panel menu now has Step Backward and Step Forward. They run
edit.undo and edit.redo, and each is disabled when there is no step to
undo or redo, as Edit › Undo and Edit › Redo are. Set History Limit…
opens Preferences at Performance, where History States is set.

Docs: docs/target-app-parity.md.
2026-10-18 01:24:34 +07:00
bflatastic a209996da2 Docs: file-format-parity counts Word .docx among the formats placed (#2766) 2026-10-18 01:24:34 +07:00
dotCooCoo 1c4ead3703 Place: Word .docx files place as area type with their formatting
File > Place could not place a Word document: a .docx file was "not a
format VectorCraft reads". The reference app's File > Place reads
Microsoft Word files as text.

File > Place now reads .docx files, written from ECMA-376 Part 1
(WordprocessingML, Transitional and Strict), and sets their text as
area type the way a text or RTF file is placed. The package is read
through its relationships. The formatting comes from the document
defaults, the paragraph style (else the default paragraph style), the
character style (else the default character style), the styles each
is based on, and direct formatting. Bold, italic, caps, small caps,
strikethrough and hidden text are toggle properties: a style that
turns one on reverses the state the styles before it give.

Each run keeps its font (theme fonts from the theme part), size, bold
and italic, underline, strikethrough, all caps, small caps,
superscript or subscript (in Document Setup's proportions), raised or
lowered text and color. Each paragraph keeps its alignment, start,
end, first-line and hanging indents, space before and after, and
direction; the start indent of a right-to-left paragraph is its right
indent. Table cells are separated by tabs and each table row is a
paragraph. Text in insertions, hyperlinks, content controls and
fields' results is read. Hidden and deleted text, field instructions,
pictures, text boxes, headers, footers, notes and list numbers are
left out.

A ZIP archive named .docx, or one that holds word/document.xml, is
read as a Word document. fileio/zip.rs gains a reader for stored and
deflated files that checks each file's CRC. A package with more than
10,000 files, a part that unpacks to more than 16 MiB or nests
elements more than 256 deep, and a document with more than 4 MiB of
text or 100,000 paragraphs or runs are refused with an error. The
Place dialog lists Word files, and file.place, file.place.info and
file.place.queue report the format `docx`. .doc files are still not
read.

The Word document tests in tests_textimport.rs fail without the
change, and text_import_fuzz.rs places random and damaged packages.

Docs: docs/file-format-parity.md, docs/mcp.md,
docs/target-app-parity.md.
2026-10-18 01:21:31 +07:00
dotCooCoo dd466d79e4 TIFF: CCITT Group 3 compressed files open and place
1-bit TIFFs compressed with CCITT Group 3 did not open, place or
relink: the image decoder reads Group 4 only and refuses Compression 2
(modified Huffman) and Compression 3 (ITU-T T.4).

They are now decoded with hayro-ccitt, the CCITT decoder that EPS
import uses. Compression 2 rows start at a byte and have no EOL codes.
Compression 3 rows follow an EOL code and are one-dimensional, or, with
T4Options bit 0, one- or two-dimensional as each row's tag bit says.
Fill bits, FillOrder 2, either PhotometricInterpretation, strips and
tiles are read.

A file whose pixels would take more than the decoding allowance as
RGBA is refused as too large to draw. A strip or tile whose data ends
before its last row is refused as damaged, and so is a tile larger
than the allowance. The strips and tiles together are read from at
most as many bytes as the file holds.

Part of #1466.

Docs: docs/file-format-parity.md, docs/target-app-parity.md.
2026-10-18 01:21:30 +07:00
dotCooCoo 8fbd515d6c Shaper: a marquee does not select a Shaper Group by the original shape of an erased face
A Shaper Group keeps its original shapes as a transparent first child.
Clicks skip them outside Construction mode, but a marquee did not: one
drawn over the outline of an original shape whose face the group had
erased selected the group, where nothing is shown. Group Selection's E
marquee selected an original shape that lay inside it, and Direct
Selection's marquee and clicks selected the anchor points of the
original shapes.

The marquee now skips a Shaper Group's original shapes with the same
check clicks use, which moves into shaper_hides, and Direct Selection's
walk over the art it can pick skips them too. In Construction mode, with
the original shapes isolated, the marquee still selects them and Direct
Selection still selects their anchor points.

Docs: docs/shaper.md, docs/target-app-parity.md.
2026-10-18 01:21:28 +07:00
dotCooCoo c44fb242aa Type: Optical Margin Alignment is a setting of the whole thread
Optical Margin Alignment was stored and set per text object, so the
frames of one thread could differ. Turning it on in the first frame of
a thread left the lines in the other frames flush with their margins.

Type › Optical Margin Alignment and type.opticalMarginAlignment now set
every frame of a selected frame's thread, in one undo step, and the
reply's ids list them all. After any edit to a thread, its frames take
the setting of the frame that the edit turned on or off, else the
first frame's. Threading frames gives the thread its first frame's
setting, and frames taken out of a thread keep it. A file whose frames
of one thread differ opens with the first frame's setting in all of
them. This holds for native files and for documents restored from the
editing copy that an EPS, SVG, PDF or .ai file carries.

Threading text from an out port into existing area type
(text.thread.add with to) now flows the story into that frame at once.
Before, the story reached it only at the next edit of one of the
thread's frames.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-18 01:21:27 +07:00
dotCooCoo e5945c6795 Type: sizes, scales, leading, indents and spacing out of range in a file are read in range
A .vectorcraft file's type opened with the character and paragraph
values it held. With a size of 0, the characters were not drawn. With
a size, leading, horizontal scale, left indent or space before of
1.797e308, point type's bounds were infinite or over 1e308 wide or
high, and moving or scaling it failed with "result would exceed the
canvas".

These values are now read into range when a file opens, in the type of
the layers, symbols, graph designs, patterns and the pattern definition
that Cancel puts back in pattern editing mode, and in character and
paragraph styles. A size, horizontal scale or vertical scale of 0 or
less becomes 12 pt or 100%, and a negative leading becomes Auto. A
size, leading or scale past 4,000,000 (MAX_COORD) becomes 4,000,000,
and a baseline shift, indent or space before or after past 4,000,000
either way becomes 4,000,000 or -4,000,000. A tab stop is kept to the
0 to 100,000 pt that text.tabs.set takes. A leading of 0, which legacy
.ai import stores, and the sizes, scales and indents within these
limits that SVG, PDF and .ai import keep outside the panels' ranges
open as saved.

Docs: docs/mcp.md (Paragraph attributes per paragraph).
2026-10-18 01:21:25 +07:00
dotCooCoo 81574b1648 Type: superscript, subscript and small caps proportions out of range in a file are read in range
document.setup accepts a superscript and subscript size of 1 to 500%
and a position of -500 to 500%, and a small caps size of 1 to 200%.
text.setFormat copies these proportions into the characters it
formats. A .vectorcraft file's characters, character styles and graph
label styles opened with the proportions they held. With a superscript
size of 0, the superscript characters were not drawn, and with a
superscript or small caps size of 1.797e308, moving the type failed
with "result would exceed the canvas".

These proportions are now read as the nearer end of each range when a
file opens: in the type of the layers, symbols, graph designs, patterns
and the pattern definition that Cancel puts back in pattern editing
mode, in graph label styles and in character styles. Proportions in
range and the other character attributes open as saved.

Docs: docs/mcp.md (Small Caps, Superscript and Subscript).
2026-10-18 01:21:24 +07:00
VictorandClaude Opus 5.5 f7382b2844 G1: Contextual Task Bar: Join, Edit Path, Release and type conversion by selection
The task bar offers more next steps for each kind of selection:
- an open path: Join (closes it) and Edit Path (the Direct Selection
  tool); a closed path: Edit Path;
- a compound path: Edit Path and Release Compound Path;
- a clipping group: Edit Clipping Path and Release Clipping Mask
  instead of Ungroup and Isolate;
- an image selected with vector art: Make Clipping Mask instead of Unite;
- point or area type: Convert To Area Type / Convert To Point Type
  beside Create Outlines.

Buttons may now carry command parameters. "Edit Path" is translated in
every catalog; the other labels reuse the menu's. cargo xtask ci passes
all 9 steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:11:39 +07:00
alpha-rock 039a467dcb M9: add multiple colored lights to 3D effects
Add opaque RGB light color and up to ten independently adjustable lights to
Revolve, Extrude & Bevel, Inflate and Rotate. Keep light selection, Add/Delete,
selected-light gizmos and preview transactions on the existing command path.

Preserve legacy white-light Inflate polygons, paint composition and seam joins,
including the 0.05 seam minimum. Use shared smooth normals for colored Inflate,
and batch solid-fill RGB channel paths behind one surface clip. Keep source
paths editable and SVG/PDF output vector artwork.

Rebase onto main f7de3d6a, retaining both sides of progress docs and all 22
catalogs. Shade freeform points once through the shared paint mapper. Document
release measurements and a reproducible CLI fixture, including the remaining
source-gradient cost and the one-light timing tradeoff.

Validation: full cargo xtask ci passed all 10 steps; 7,978 tests passed, zero
failed and 11 ignored. Four legacy white-light PNGs match unchanged main byte
for byte. Three paired release bench/perf repetitions completed without busy
warnings; all six standard perf runs passed every budget. Two-light solid
Inflate improved from 136.2 to 96.8 ms/frame at 1200x1200, default eight threads.
2026-10-18 01:11:39 +07:00
makesomethingshit 45c2b0b363 fix(blend): complete contribution checks (#2544) 2026-10-18 01:11:39 +07:00
makesomethingshit eeeb9645c1 docs(blend): document Anchor Point tool spine conversions (#2544) 2026-10-18 01:11:39 +07:00
makesomethingshit e1a6e373d5 docs(blend): document Anchor Point tool spine conversions (#2544) 2026-10-18 01:11:39 +07:00
MarekSpodBiedry 949473f1e3 Magic Wand: only select objects inside the isolated group (#2655) 2026-10-18 01:11:38 +07:00
MarekSpodBiedry b5164825d6 Write legacy .ai layer names in Windows Latin-1 and warn when a character is replaced (#2693) 2026-10-18 01:11:38 +07:00
bflatastic 6a42b3bf10 Fonts: Han and kana keep the bundled fallback order; only the weight and slant of fallback glyphs change (#2491) 2026-10-18 01:11:38 +07:00
vangel76andClaude Opus 5.5 43356a1cfb Fonts: fallback glyphs keep the weight and slant, Bold Italic of a missing family keeps the bold, Han and kana prefer an installed sans (#2491)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 01:09:54 +07:00
vangel76andClaude Opus 5.5 b55d277b49 Fonts: a variable font's default face is named and weighed by its default instance (#2521)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 01:09:53 +07:00
Guillem Hermida 2a5dfa6a94 Fix #1876: Artboard Options has X and Y with a reference point
Artboard Options showed Name, Width and Height only: the generic form
hides x and y, which in the shape dialogs are the click position. The
artboard's position could be dragged but not typed.

The dialog now draws its own rows after the form's, in the same grid:
the reference point locator the Transform panel, the Control bar and
Properties share, then X and Y. They show where the reference point
lies, counted from the ruler origin (#1212), as the Transform panel and
the Control bar show an artboard's position. A typed X or Y moves the
artboard so that its reference point lands there, and a new Width or
Height keeps the reference point in place. The fields still hold the
top-left corner in document points, so OK runs artboard.setProps as
before, in one undo step. The shape dialogs keep their x and y hidden.

form::grid_then lets a dialog add rows to the generic grid.
2026-10-18 01:09:52 +07:00
VictorandClaude Opus 5.5 6190525482 G4: Paragraph panel menu › Roman Hanging Punctuation
The Paragraph panel menu's Roman Hanging Punctuation was a disabled stub.

It is now a per-paragraph attribute: in a left-to-right paragraph of point
or area type with it on, the punctuation Optical Margin Alignment hangs
(quotation marks, hyphens, full stops and commas wholly; asterisks,
tildes, ellipses, en and em dashes, colons and semicolons by half) hangs
outside the margin when it starts or ends a line, on the same sides
(left-aligned lines at their left, right-aligned at their right, centered
and justified at both); letters don't hang. With Optical Margin Alignment
on as well, letters hang too. East Asian punctuation keeps hanging only
through burasagari. The layout shares Optical Margin Alignment's hanging
code, given the share of each glyph that hangs.

text.setFormat takes romanHangingPunctuation; it is off by default, saved
only when on, carried by paragraph styles (New and Redefine capture it)
and turned off by Reset Panel. The panel menu item is a checkbox enabled
with type selected.

Docs: docs/mcp.md, docs/type-parity.md, docs/gaps.md.

Closes #2756.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 01:09:34 +07:00
BFlat c1c677bc76 Merge pull request #2409 from dotCooCoo/feat/live-rasterize-effect
Effects: Effect › Rasterize draws an object as pixels while its paths stay editable
2026-10-17 20:02:59 +07:00
dotCooCoo 5ad04faff3 Contextual Task Bar: direct-selected anchors offer Connect, Corner and Smooth
With anchor points selected with Direct Selection, the Contextual Task
Bar's anchor items were Remove Anchor Points and Cut Path. The
reference app's bar also offers to connect the selected end points and
to convert the selected anchors to corner or smooth.

The bar now offers Remove Anchor Points, Connect, Cut Path, Corner and
Smooth. Connect runs Join (path.join), and Corner and Smooth run
path.convertAnchors with `to` set to corner or smooth. These are the
commands of the Control bar's anchor buttons. "Connect" has a row in
every catalog; "Corner" and "Smooth" use the existing rows.

The reference app's Smooth item, which adjusts a path's curvature, is not
offered, because VectorCraft has no Object > Path > Smooth. Simplify,
which the reference app's bar also offers here, is still offered only
while no anchors are selected.

Docs: docs/target-app-parity.md.
2026-10-17 19:38:48 +07:00
dotCooCoo 80b9753866 Paths: Reshape Segment, Convert Anchor Point and the Pencil refuse values of 1e9 pt or more
path.reshapeSegment refused only a delta that was not finite. It moves
a segment's two handles by the delta divided by 3t(1-t), which is
0.1425 at t 0.05 or 0.95, so a finite delta above about 2.5e307 wrote
infinite handles into the path. object.blend.spine.reshapeSegment
refuses a delta of 1e9 pt or longer.

path.reshapeSegment now refuses a delta of 1e9 pt or longer, with its
"invalid delta" error. Two other path commands took a point with no
bound. path.convertAnchor with x, y at 1e308 put the two handles 2e308
apart, so the path's control box was infinitely wide, and path.freehand
through such points with keepSelected off fitted a path whose bounds
reached infinity. Both now refuse a point whose x or y is 1e9 pt or
more in size, with "x and y must be finite", the error
object.blend.spine.moveAnchor gives for the same bound.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 19:38:47 +07:00
dotCooCoo 4712fb2c2f Character: variable fonts get a slider for each axis
VectorCraft listed a variable font's named instances as styles, but
type could not be set between or past them. The reference app's
Character panel has a Variable Font control that adjusts a variable
font's axes, such as Weight, Width and Slant.

Type now keeps Variable Font settings by axis tag
(CharStyle::font_axes, saved as fontAxes only when set). text.setFormat
and text.setRangeStyle take fontAxes: each value is clamped to its
axis's range, a tag the font has no axis for is left out, and null
goes back to the style's own setting. Another font or style clears
them, including one from a character style that does not set them. A
character style captured from type sets them, with {} for none.
text.fontList {family, style} lists a font's axes.

Layout, outlines, the substituted-glyph highlight and PDF export use
the font instanced at the settings, and neighboring runs at different
settings are shaped apart, as runs in different fonts are. SVG export
writes them as font-variation-settings, the wght setting as the weight
and the wdth setting as font-stretch, and embeds the font instanced
there; SVG import reads font-variation-settings back. Legacy .ai export
warns that the settings are left out.

The Character panel shows a Variable Font button for a variable font.
It opens a slider and a field for each axis the font does not hide,
labeled with the font's name for the axis, and screen readers get each
slider under that name. A drag is one undo step. Reset Panel leaves
the settings, as it leaves the font and style.

Missing Glyph Protection now keeps the settings and the font version
on the characters that keep their font; before, it kept only the
family and style. A character style applied or redefined with another
font now clears the old font's version, as text.setStyle does, unless
the style sets one. SVG export with embedded fonts warns when faces of
one weight, style and width share characters, which browsers then draw
in one of the faces.

Docs: docs/mcp.md, docs/target-app-parity.md, docs/type-parity.md.
2026-10-17 19:38:46 +07:00
andeecollardandClaude Opus 5.5 1e06c82800 Image Trace: Auto Color keeps rare but distinct colours
The Automatic palette counts flat art's colours by area (a colour needs
about 1% of the picture, 0.5% at Colors 100) and fits that count with
median cut and area-weighted k-means. A stem, a highlight, or a near
white beside a pale background (within 24 levels, so in one box) was
averaged into a large neighbour; mixed-colour merging and Noise could
then remove what was left.

On the flat route only, after the count is fitted, rare_colours finds
the colours too rare or too close for it and keeps those that are
distinct (at least 2.5 in Oklab x 100 from every palette colour and from
the main colour each stands for), flat (mostly one histogram bin) and
drawn (a 4-connected region of their own unblended pixels of at least 8
pixels, or three of at least 4); a colour lying between two others, as
anti-aliasing mixes them, must also have an inside. At most six, largest
region first. They stay fixed while the other colours are refitted
around them; merge_mixed leaves them alone; denoise_keeping keeps their
regions of 4 pixels or more whatever Noise is (smaller specks still go).
Full Tone, Limited and Automatic's continuous-tone route are unchanged,
and denoise without kept labels is as before.

Methods cited in the code: median cut (Heckbert 1982), k-means (Lloyd
1982), Oklab (Ottosson 2020), salient-colour preservation (Fukuda et al.
2019, doi:10.1117/12.2521607). The rule and its numbers are VectorCraft's
own; Adobe does not document how its palettes are chosen.

Tests (code-drawn): a rare stem or patch over 2-8 flat colours, hard
and anti-aliased at 1x and 2x; near-white highlights on #FFF4E0 kept and
still paths at Noise 16; a 3 px #909090 stem on #808080; scattered
single pixels of the same areas add no colour; denoise_keeping at Noise
0-32.

Measured on 66 synthetic flat logos (22 designs x hard 1x, anti-aliased
1x and 2x; local inputs): every key colour and no spurious one in 66
(was 39); missing key colours 0 (was 43), spurious 0 (was 2), merged
pairs 0 (was 46); rendered dE00 0.348 -> 0.138. Four public-domain scan
crops take the continuous-tone route and are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
2026-10-17 19:38:43 +07:00
andeecollardandClaude Opus 5.5 17a602d1dc Image Trace: large images trace with a warning; anchor limit 8,000,000
The public documentation says Image Trace warns about images larger
than 1500 pixels on a side but traces them, only more slowly. VectorCraft
traced them silently, and refused any trace of more than 2,000,000
anchors, which a 4000 x 3000 photo at High Fidelity Photo nearly reaches
(1.9 million on a code-drawn photo) and a noisier one passes.

- imageTrace.make and makeAndExpand return `warnings`: one for an image
  over 1500 px on a side (traced, but more slowly; a lower-fidelity
  preset or a smaller copy is quicker), one for an image over 64 MP
  that was traced from a scaled-down copy. The app shows the first in
  the status bar.
- MAX_TRACE_ANCHORS is 8,000,000 natively (2,000,000 on wasm, where a
  page has at most 4 GB). Measured through imageTrace.make on
  code-drawn photos: about 90 bytes an anchor kept in the document;
  7.9 million anchors kept 685 MB with an 876 MB peak (4000 x 3000,
  Full Tone at Colors 100, Paths 100, Noise 1, 37 s).
- The refusal keeps its translated advice: raise Noise, lower Paths or
  Colors, or pick a lower-fidelity preset.

Tests: a_large_image_traces_with_a_warning (1600 x 40 and 1500 x 8
code-drawn images, make and make-and-expand),
trace_warnings_name_a_large_or_scaled_down_image, a compile-time check
of the limit's range, and a UI test that the status bar shows the note.

Source:
https://helpx.adobe.com/illustrator/using/image-trace-results-optimization.html

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
2026-10-17 19:38:18 +07:00
andeecollardandClaude Opus 5.5 a2e2eba2e2 Image Trace: Raster::new returns an error for a buffer of the wrong length
Raster::new checked its RGBA length with assert_eq!, so bytes of the
wrong length (from a file, a plug-in or a control message) would abort
instead of reporting an error. It now returns Result<Raster, TraceError>
and gives TraceError::Decode naming the byte count and the size (the
existing variant, whose text is already translated, instead of a new
message every language would need). The size product is checked for
overflow.

Raster::pixel indexed the bytes directly; it now gives transparent
black outside the raster, or past the end of a raster whose public
fields were set to a size its bytes don't have (which mosaic reads).

The public signature changes: every caller in the workspace was a test
(trace unit tests, Flat Logo tests and the quality harness), updated to
unwrap. No shipped code built a Raster with new; decode, decode_to_fit
and from_fn always make the right length, and trace_within already
refuses a raster whose fields disagree.

Test (code-drawn): a_raster_of_the_wrong_length_is_an_error_not_a_crash
(short, long, empty, zero-width and overflowing sizes; pixel, trace and
mosaic on a raster whose fields disagree).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
2026-10-17 19:38:06 +07:00
0594ffe30f M12: build stroke skeleton graphs with junction merging
Place junction clusters at their deepest pixel and prune terminal branches
by significance beyond their maximal discs. Merge overlapping junctions
and split crossings with continuing outer arms, then join degree-two runs.
Bound nodes, branches and simplification work, using checked data access.

Keep whole-blob selection, ink refinement, cap detection and fitting rules.
Test junction degrees, crossings, 24-angle lines, pruning, graph caps and
random-blob determinism. Record improved junction degrees and remaining
spur and extra-junction limitations in the centre-lines measurements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI <noreply@openai.com>
2026-10-17 19:37:31 +07:00
abb7c1351b M12: use exact crop distances and Guo–Hall stroke thinning
Compute exact squared Euclidean distances per blob with separable parabola
lower envelopes. Thin with bounded Guo–Hall active sets and retain diagonal
ends during staircase cleanup. Keep the whole-blob width rule and existing
graph, ink, cap and fitting rules, with checked access in the moved module.

Verify EDT against brute force, active sets against plain rounds, and both
diagonal slopes for length and connectivity. Record the centre-lines rerun
and its remaining limitations without changing feature scores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI <noreply@openai.com>
2026-10-17 19:37:28 +07:00
c9f0ed14f0 M12: add centre-line scenes and baseline quality measurements
Measure Create Strokes on exact code-drawn scenes in aa and hard variants,
including centre/width error, topology, ends, spurs and painted coverage.
Keep the existing allocator report in one ignored test and document the
current baseline without changing tracing behaviour or dependencies.

Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:37:25 +07:00
bflatastic f38f6fafc3 Fix #2603: a panel per swatch library, with Persistent
Each swatch or graphic style library now opens in a panel of its own,
`library:<kind>:<library id>`, in the shared docking system: several can be open
at once, and they float, group as tabs, dock and close like any panel.
Opening a library that is open brings its panel forward; Previous / Next
Library show the neighbouring library in the same panel.

The library panel menu gains Persistent. The panels' view, Show Find Field
and Persistent are kept in the UI preferences (`library_panels`); at startup
the persistent panels reopen where the saved layout had them, a library
loaded from a file read again from its path (a missing file is skipped with
a status-bar note). `ui.libraryPanel {panel, library?, view?, showFind?,
persistent?}` reaches all of it; docs/mcp.md documents the panels.
2026-10-17 19:13:49 +07:00
bflatastic 88567ee46c Part of #2572: clicking an area type's out port threads its text into a path or a new frame 2026-10-17 19:13:22 +07:00
dotCooCoo 04b0be2c9f Merge main into feat/live-rasterize-effect 2026-10-17 04:09:12 -07:00
bflatastic 2879c206a8 Fix #2541: installed plug-ins stay installed after a restart
Install Plug-in (plugin.install) now keeps the plug-in's module in a
Plug-ins folder next to the preferences, as Illustrator keeps plug-ins in
its Plug-ins folder, and the desktop app and vectorcraft-cli install the
ones there at startup (a module that doesn't load is skipped with a log
record, at most 512 MB are read). Remove Plug-in deletes it from the
folder. The Additional Plug-ins Folder preference is still loaded from
where it is. Confined MCP servers and VECTORCRAFT_NO_PREFS keep nothing,
as with the PDF preset and colour profile stores.

Docs: docs/mcp.md, docs/plugins.md, docs/target-app-parity.md.
2026-10-17 17:49:38 +07:00
dotCooCoo 7930a0456b i18n: the History panel translates the undo step names the sources write as literals
The History panel looked up each step's name with tl!, which reads only
a plain catalog row. 95 of the 427 names that the engine, the tools and
the shell give their undo steps had no such row in at least one complete
catalog, and 83 had no row at all (Make Blend, New Guide, Pen, Reshape
Spine and others). A step named after an effect has the effect's menu
name without its ellipsis. For many effects, and for steps such as New
Layer, the catalogs hold that name only with the ellipsis. The panel
listed those steps in English.

Each complete catalog now has a row for every step name, written in the
catalog's own terms: 1,437 rows in the 19 catalogs. The History panel
reads a step's row, or else the row of the same name with an ellipsis,
without the ellipsis, and shows the name as recorded when the catalog
has neither. A new test reads the step names from the sources, as the
tl! and message tests read theirs, and requires a row for each in every
complete catalog.

Docs: docs/target-app-parity.md.
2026-10-17 17:22:15 +07:00
bflatastic 546b8846b6 Part of #2441: Group Selection picks and moves one path of a compound path
A click with the Group Selection tool on a compound path selects the path
under the pointer (its outline within the pick tolerance, else the innermost
path whose area holds the click), so the outer shape and each hole can be
selected on their own; Shift-click adds another, and a click on a selected
path adds the compound path, as a click on a group member climbs to its group.

A path selected this way is an object of its own for Move, Transform, Rotate
and the other transforms, as it already was inside an isolated compound path:
the hole of a ring moves without the ring. Paint and the other commands still
go to the compound path, which holds the paint.

Docs: docs/target-app-parity.md, docs/ui-parity.md.

# Conflicts:
#	docs/target-app-parity.md
2026-10-17 17:13:03 +07:00
dotCooCoo 1401051603 Artboards: Shift-click in the Artboards panel selects a range of artboards
With the Artboard tool in use, a Shift-click on a row of the Artboards
panel selects the artboards from the last row clicked without Shift to
the clicked row, and the clicked row's artboard becomes the active one.
When the artboard of the last row clicked without Shift is no longer
selected, the range starts from the active artboard, and later
Shift-clicks start from there too. Ctrl/Cmd-click still adds or removes
one artboard, and a plain click selects one. The public Select
artboards page lists Shift-click in the Artboards panel for selecting
several artboards.

A Shift-click used to add or remove one artboard, as Ctrl/Cmd-click
does (#1139).

Docs: docs/target-app-parity.md.
2026-10-17 17:13:03 +07:00
dotCooCoo 14a3740e51 Export: PDF, EPS, SVG, EMF, WMF and image exports warn about type that doesn't fit its frame
Print and Print Preview name the area type and type on a path that
prints without the text past the end of its frame (#2308). The exports
that draw type gave no such warning: a PDF, EPS, SVG, EMF, WMF or
raster export of a frame too small for its text answered with no
warning about it.

These exports now add the same warning, with "isn't drawn in the
export" in place of "doesn't print". Each export counts the visible
objects that it draws. Those are the objects on the pages or artboards
it writes, or all of them in an EPS of the visible art and in an SVG
without chosen artboards. A PDF leaves out layers that don't print
unless includeNonPrinting or createLayers is set. The objects are found
with the walk that fonts::printed_type runs for Print, and typecmd.rs
builds the message with the code that builds Print's. A .ai file keeps
the whole text in its editing data and gives no warning.

Docs: docs/mcp.md (Fitting area type), docs/target-app-parity.md.
2026-10-17 17:13:03 +07:00
dotCooCoo 30c5bd3589 Guides: Lock Guides keeps guides deselected after Undo, Exit Isolation Mode and Paste
With View > Guides > Lock Guides on, Session::select drops the paths
made into guides from the selection (#1534), but commands that set the
selection another way skipped that filter and left a locked guide
selected, so the next move moved it. object.exitIsolation selected an
isolated guide path. Undo, Redo, a canceled undo group, a canceled
drag or dialog preview and a failed command.batch brought back locked
guide paths and ruler guides in the selection. Unlock All, Show All
and Paste selected the guide paths they unlocked, showed or pasted.
Locking the guides deselected them in the active document only.

The filter is now deselect_guides, which also clears the selected
ruler guides. Session::select and Session::edit run it while Lock
Guides is on, and so do object.exitIsolation, Undo, Redo,
Session::cancel_interaction, a canceled undo group and a failed batch.
Locking the guides deselects them in every open document. With Lock
Guides off, the selection is set as before.

Docs: docs/mcp.md.
2026-10-17 17:13:03 +07:00