Commit Graph
3351 Commits
Author SHA1 Message Date
bflatastic 5e374712dc i18n(bg): Reset Tracking and Kerning 2026-10-16 02:56:55 +07:00
dotCooCoo d726f6596c Layers: Ctrl/Cmd+Alt-click on an eye switches the other layers between Outline and Preview
A Ctrl/Cmd+Alt-click on a row's eye in the Layers panel ran Hide Others
or Show All Layers, as an Alt-click does. It now runs Outline Others for
the row's layer (layer.outlineOthers), or Preview All Layers
(layer.previewAll) when every other top-level layer is in Outline
already. Each is one undo step. Alt-click and Ctrl/Cmd-click on an eye
keep their actions.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:56:35 +07:00
sanyamsingh001andCodex 98511401a0 G1: Fix intermediate overflow in adjoining width adjustments
Co-authored-by: Codex <noreply@openai.com>
2026-10-16 02:56:35 +07:00
Marco Arnulfo f2f1945973 Paste in Place: art copied from off the artboards goes back where it was (fixes #1924) 2026-10-16 02:56:35 +07:00
dotCooCoo d35ed26b0f Fix #1692: tabbing through a hex field no longer resets the color
In the Color Picker, Tab out of an edited field gives the next field the
focus in the same frame, and the picker applies the edit once it has
drawn its fields. The # field after Y took the focus showing the color
from before the edit and kept that text while it had the focus, so the
next Tab committed it and reset every other field. Shift+Tab into the #
field from K had the same effect. Swatch Options and New Swatch draw a
copy of the field, and Tab from the last component field into it had the
same effect there.

widgets::hex_field and the Swatch Options copy now show the current
color until something is typed into them, also while they have the
focus, and commit only what was typed. The change covers the # fields of
the Color Picker, Swatch Options, New Swatch, the Color panel, Color
Themes, Recolor Artwork, Document Setup and Define Perspective Grid.

widgets::opt_field, which draws the Stroke panel's dash and gap fields,
kept its text the same way: passing through a blank gap after typing a
dash committed the blank, and the panel applied the same dash pattern
again. It gets the same change. When its value changes while it has the
focus and nothing has been typed into it, all of the new value is
selected, as on keyboard focus.
2026-10-16 02:56:35 +07:00
dotCooCoo aaa7359c7b Part of #1692: Tab in the Color Picker goes through one color model's fields before the next
The Color Picker drew its value fields as one grid, row by row. Tab
moves through the fields in the order they are drawn, so it went H, L,
S, a, B, b, R, C, G, M, B, Y, # and K, from one color model to the
other on every row.

HSB, RGB and the hex field are now in one grid, and Lab and CMYK are in
a second grid beside it. Tab goes through H, S, B, R, G, B and #, then
L, a, b, C, M, Y and K. The dialog looks as before: K's row is as tall
as the hex field's row beside it.
2026-10-16 02:56:35 +07:00
Marco Arnulfo 7608639021 Type: a colour picked while typing keeps what was typed (fixes #1950)
The Color panel and the Color Picker preview a colour in an interaction.
While typing, that interaction joined the open typing session and
previewed from the document before the typing, so new type came back as
its placeholder text. Typing in progress is now kept as its own step
before another interaction begins, as Undo already does.
2026-10-16 02:56:35 +07:00
Gert Pellin e6170de1a2 Transform commands: a string for an optional number is an error (#1952)
object.setBounds read x, y, width and height with f64_or, which falls back
to the current value for anything that isn't a JSON number, so
{"width": "60 mm"} returned ok and changed nothing. object.move,
object.nudge, object.rotate, object.scale (sy) and object.shear did the
same with their optional numbers. A value that is neither a number nor
null is now a BadParams error naming the key, as a missing required
number already is.
2026-10-16 02:56:35 +07:00
MarekSpodBiedry a0f8a88e82 Paint: picking another pattern swatch keeps the pattern transform (#1970) 2026-10-16 02:56:35 +07:00
Ayberk A. 6d898558fd i18n: the notes for an EPS or .ai whose layers weren't read are translated (#2035)
The importer wraps its reason in "the file's layers weren't read ({why}):
it comes in as its page, in one layer" (and, for an .ai, "... from its
editing data ({why}): they come from its PDF part"). The reasons had @msg
rows, the wrappers none, so after "Opened with 1 note(s):" the whole note
stayed English. The two templates are now in every language that
translates messages (and pt-br, which has the reasons), and the reason
inside is translated through them. These notes are pushed as warnings, so
the message scan doesn't see them: a test checks them in each language.

"<Clip Group>" already has a French entry on main; layer names such as
"Layer 1" are stored names and stay as they are.
2026-10-16 02:56:35 +07:00
Ayberk A. 5ff34782cb color.loadProfile: say that a loaded profile lasts for the run (#1955)
The profile is registered in the process's colour registry only, so the
next `vectorcraft-cli run` (or the next launch of the app) doesn't know it
and edit.colorSettings falls back to the built-in profile. The command's
description now says so, and that the profile has to be loaded again in
each run that names it. Keeping profiles across runs would need a place in
the preferences to remember them, which is left for the maintainers.
2026-10-16 02:56:35 +07:00
Ayberk A. 07c6b938bc Shortcuts: + selects the Add Anchor Point tool (#2142)
The tool's default key is "+", and `shortcuts::parse` split a bare "+" as a
separator with nothing on either side: the key part came out empty, so the
shortcut didn't parse, `tool_for_key` found no tool for a typed "+" and the
key did nothing. "-" (Delete Anchor Point) has no separator to trip on, which
is why it worked. A bare "+" is now the key itself, the "=" key, as "Cmd++"
already was, so "+" typed with Shift on a US layout, by a layout's own "+"
key or on the numpad selects the tool.
2026-10-16 02:56:35 +07:00
dotCooCoo cb9522f7cd Type: Cmd/Ctrl+Alt+Q resets tracking to 0 and kerning to Auto
The reference app resets tracking and kerning to 0 with Cmd/Ctrl+Alt+Q.
VectorCraft had no command for it.

type.resetTracking (Reset Tracking and Kerning, default Cmd+Alt+Q) sets
tracking to 0 and kerning to Auto, which the Character panel shows as 0,
in the text the Type tool has selected, or else in all the text of
every selected type object. At a caret it resets only the kerning of
the character before it, where Alt+Left/Right kerns. It is one undo
step. The range logic of type.step moves into a shared type_ranges.

When a call gives ids, type.step and type.resetTracking change all the
text of those type objects even while the Type tool edits text, as
paint.setFill does. Before, type.step changed the Type tool's selected
text instead.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:56:35 +07:00
dotCooCoo 0155eaf468 3D and Materials: Inflate builds its edge index once
inflate built an edge index (Edges) of the shape's rings, and
Membrane::solve, which inflate calls first, built a second index of the
same rings. Membrane::solve now takes the index that inflate builds.

In a release build, one Edges::new takes 0.08 ms of the 27 ms that
inflate takes on a ring of 2,000 points, and 2.0 ms of 219 ms on a ring
of 1,000 spikes whose 2,000 edges fill 693,816 bucket entries. The
facets do not change: for nine shapes, three views and 96 combinations
of the Inflate settings, the Debug text of all 2,592 results is byte
for byte the same on main and with the change.
2026-10-16 02:56:34 +07:00
dotCooCoo 5a83bcfafa Export: the export commands' params and the MCP export tool list the .ai format
document.export, document.serialize and document.exportSelection write
a PDF-compatible .ai (format: "ai"), and document.formats lists ai as
writable, but none of their params texts named it. The params text of
document.exportSelection named only png, jpg, webp, svg, svgz and pdf,
although the command writes all 20 writable formats. The description
of the MCP export tool and its row in docs/mcp.md named every format
of the tool's format enum except ai, and the range text and the row
said that only PDF writes several artboards.

The three params texts now list every format their command writes,
and document.export's range and useArtboards texts name .ai beside
PDF. The MCP export tool's description names ai, and its artboard and
range texts say that .ai, like PDF, writes one page per artboard. The
range text and the row also say that SVG, SVGZ and EPS write one file
per chosen artboard, and that rasters, DXF, EMF and WMF do so with
options.useArtboards: true.

Docs: docs/mcp.md (the export tool).
2026-10-16 02:56:34 +07:00
dotCooCoo 66dbe40ea6 Export for Screens: the params text and the format error list every format a row writes
The document.exportForScreens params text listed png, png8, jpg, webp,
gif, svg, svgz and pdf for a row's format, and its settings keys left
out svgz. A row of another format VectorCraft writes, such as eps, was
refused with "Export for Screens writes png, png8, jpg, webp, gif, svg,
svgz or pdf". Rows of tiff, bmp, tga and psd are written too, and the
Export for Screens dialog offers TIFF rows.

The params text, its settings keys and the error now list the 12
formats a row writes: png, png8, jpg, webp, gif, tiff, bmp, tga, psd,
svg, svgz and pdf. Which formats a row accepts does not change.
2026-10-16 02:56:34 +07:00
Marco Arnulfo 53c59f06de PDF: an untitled document saved to a file exports titled after it (fixes #2166) 2026-10-16 02:56:34 +07:00
dotCooCoo 513685a221 Docs: the Print section describes printing through the system's print service
The Print section of docs/mcp.md said that File → Print writes the job
as a print-ready PDF and that there is no printer driver. The desktop
app sends the PDF to the system's print service, the web build opens
the browser's print dialog on it, and the PDF File entry in the
Printer list saves it. The section now says so, points to Printing,
and marks the `file.print` reply that follows as the headless one.
2026-10-16 02:56:34 +07:00
dotCooCoo 4a1323ad07 i18n: Russian Isolate and Sublayer rows use Cyrillic letters only
The Russian catalog's Isolate row began with a Latin "A" (U+0041),
"Aизолировать", and the Create New Sublayer and New Sublayer… rows
spelled "подслой" with a Latin "c" (U+0063). The rows now read
"Изолировать", "Создать новый подслой" and "Новый подслой…".

A new test fails when a word in any catalog's translation holds letters
from more than one of the Latin, Cyrillic and Greek scripts.
2026-10-16 02:56:34 +07:00
Justin Willhite 8da47f11f8 PDF export: a CMYK page without blending through is an isolated group (#2156) 2026-10-16 02:56:34 +07:00
bflatastic 18f8a1bdcf PDF import: a damaged array is refused as damaged, not as functions built on one another 2026-10-16 02:56:34 +07:00
Claude 95d27fe390 PDF.R1: never panic on a damaged array or dictionary in an imported PDF
hayro-syntax's Dict::entries still unwraps each value it reads, so a
dictionary cut short (or with a stray token) aborts the import. The
craft-libs ArrayIter now stops at the first unreadable item instead of
panicking, which also means a hostile item after a self-referencing
function or colour space is silently dropped from nests_without_end.

- import_color: `readable(array)` checks every item parses; `items()`
  yields nothing for an unreadable array; `entries()` reads a dictionary
  key by key through get_raw, skipping values that don't parse.
- pages/editing/import_scan: use the helpers instead of raw_iter() and
  Dict::entries().
- nests_without_end refuses a file with an unreadable array.
- Tests: two refusal cases in pdf tests and an .ai cut inside an array
  or dictionary in engine/tests/import_fuzz.rs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcHisW1vt6pkVPYbzL6Cpg
2026-10-16 02:56:34 +07:00
bflatastic ff7f47119d i18n(bg): rows for the strings this batch adds (Darkness, Isolate Selected Object, Keep selected, Sample Color from Screen, Tritanopia, Panels Except Tools and Control) 2026-10-16 02:56:32 +07:00
BFlat 01c66e51b7 Merge pull request #2016 from EricKuck/fix/ai-pattern-phase
AI import: a pattern fill tiles from its tile's lower left, wherever the definition drew the tile
2026-10-16 02:38:08 +07:00
BFlat 23a0a5f857 Merge pull request #2015 from EricKuck/fix/ai-non-native-art-orientation
AI import: non-native art stays upright unless its placement mirrors it
2026-10-16 02:37:52 +07:00
BFlat 651e2e1a57 Merge pull request #2014 from EricKuck/fix/export-art-over-artboard-groups
SVG export: a chosen artboard's file leaves out the art of other artboards inside a shared group
2026-10-16 02:37:38 +07:00
BFlat 5a5bb73aa9 Merge pull request #2062 from dotCooCoo/fix/page-knockout-group-backdrop
Render: a knockout group drawn over its backdrop counts an element's partial coverage once
2026-10-16 02:37:22 +07:00
BFlat 88e950a0d7 Merge pull request #2073 from dotCooCoo/feat/outline-object-nested-type
Effects: type in a group with a geometry effect keeps its transparency, mask, effects and visibility
2026-10-16 02:37:05 +07:00
BFlat 99bdcc6633 Merge pull request #2232 from MarekSpodBiedry/issue-2086
Menus: keep scroll bar clear of shortcuts and stop above the status bar (#2086)
2026-10-16 02:36:44 +07:00
BFlat ef53890c28 Merge pull request #2109 from dotCooCoo/fix/area-type-rows-columns-overflow
Type: area type opens and lays out with at most 100 rows and 100 columns
2026-10-16 02:36:23 +07:00
BFlat 011a1a0314 Merge pull request #2108 from dotCooCoo/fix/issue-1770-rosette-spurs
Fix #1770: Shape Builder merges leave no spur or split outline when shared edges differ by rounding
2026-10-16 02:36:05 +07:00
BFlat 17c512f5c9 Merge pull request #2055 from dotCooCoo/fix/copied-artboard-free-id
Artboards: copied artboards take a free id and keep their own guides
2026-10-16 02:35:47 +07:00
BFlat ec602d7e27 Merge pull request #2114 from dotCooCoo/fix/moved-rasters-test-fonts-steady
Render tests: two moved raster tests no longer run alongside the tests that add fonts
2026-10-16 02:35:30 +07:00
BFlat bb5f16c101 Merge pull request #2118 from dotCooCoo/fix/alt-drag-tests-created-stamp
Tests: the Alt-drag tests no longer fail when their documents are made in different seconds
2026-10-16 02:35:13 +07:00
BFlat b8041d3745 Merge pull request #2235 from Messhias/fix/2094-create-outlines-shortcut
Fix #2094: Cmd+Shift+O Create Outlines while typing
2026-10-16 02:34:55 +07:00
BFlat 0205a3332f Merge pull request #2227 from Messhias/fix/1910-reflect-axis
fix(G11): Reflect X/Y flip the correct axes (#1910)
2026-10-16 02:34:42 +07:00
BFlat a7425f27c1 Merge pull request #2176 from Silvertree2010/fix/2078-when-i-click-pixels-the-unit-of
Make New Document preset cards follow the chosen units
2026-10-16 02:34:25 +07:00
BFlat fb803dbdae Merge pull request #2158 from Silvertree2010/fix/2084-the-orientation-selection-is-wro
Fix New Document orientation on category tabs and preset cards
2026-10-16 02:34:08 +07:00
BFlat 81420b87c8 Merge pull request #2204 from Silvertree2010/fix/h1429-svg-export-writes-control-charac
Make SVG export of type with control characters well-formed
2026-10-16 02:33:51 +07:00
BFlat 57b5931c83 Merge pull request #2022 from Silvertree2010/fix/h797-negative-picas-points-value-2p6
Apply the sign of a picas+points value to the whole value
2026-10-16 02:33:32 +07:00
BFlat ce02a42f35 Merge pull request #2069 from Silvertree2010/fix/h839-field-arithmetic-rejects-followe
Evaluate * and / left to right in field arithmetic
2026-10-16 02:33:15 +07:00
BFlat 4b60fcead7 Merge pull request #2209 from Silvertree2010/fix/h1425-field-arithmetic-rejects-a-negat
Accept a negative operand after an operator in field arithmetic
2026-10-16 02:32:59 +07:00
dotCooCoo 3bef84aaa6 Type: type drawn as glyph outlines keeps its underline, stroke weight, gradients and patterns
Type is drawn as its glyph outlines under a geometry effect, in EMF and
WMF export, in DXF export when it is written as outlines, through
Flatten Transparency with Convert All Text to Outlines, and inside an
envelope. Those outlines differed from the type the canvas draws in
four ways. They had no underline or strikethrough bars. A character
stroke on scaled type kept its unscaled weight. An unplaced gradient was
fitted to the glyphs it painted instead of the layout box. A run's
pattern tiles stayed in document space instead of scaling and turning
with the type. A group's or layer's own fills and strokes also painted
the glyphs of the type in it without the bars.

The outlines now include each run's underline and strikethrough bars,
as the canvas has drawn them since #850. Each run's path is made in
text space with its unplaced gradients fixed to the layout box, then
placed by the type's transform, which also scales its stroke weight
and dashes and transforms its pattern tiles. The type's own fills and
strokes have their unplaced gradients fixed to the bounding box of the
layout box in the document, where the canvas fits them.

Geometry effects that move or scale the outlines move and scale the
type's gradients with them: the type's own effects, also inside an
envelope and after Flatten Transparency has converted the type to
outlines, and the effects of a group, symbol instance or envelope that
holds the type. outline_text marks its group with a new node field,
type_outlines, which is saved with the document. reshape_leaves maps
the placed gradients in each marked group through the map that takes
the group's bounds before the effects onto its bounds after them. A
scale out of proportion stretches a radial gradient into an ellipse
and turns the isolines of a diagonal linear gradient, as it does on
type scaled by its own transform. Placed gradients outside those
groups stay where they are.
2026-10-16 02:26:15 +07:00
dotCooCoo 98d1d738a2 Export Selection: the crop takes in what each object's effects draw
Export Selection, Asset Export, the Assets tab of Export for Screens
and the rasterized art of CSS Properties cropped each object to its
visual bounds, which leave out live effects. A path with an 8 pt drop
shadow was exported with the shadow cut off at the path's edge, and
a group's own shadow was cut off the same way.

The crop is now the bounds the renderer measures for what it draws
(`vectorcraft_render::effect_bounds`): `painted_bounds` without its
room for brush art. A brushed path is cropped to its visual bounds as
before, and a Shaper group leaves out its transparent source art as
its visual bounds do. `assets.list` reports the same crop. The
renderer measures a shadow's or glow's reach on every side of the
object, so the image also has transparent room on the side away from
an offset shadow.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:26:14 +07:00
dotCooCoo 1c30cbf88c Print: Print and Print Preview warn when type prints in the fallback font
PDF, EPS, EMF/WMF and raster exports, and SVG exports that outline or
embed fonts, warn when type is set in a font family that isn't
available, but file.print and print.preview gave no such warning. A job
in a missing family printed in Source Sans 3 with no warning, and the
Print dialog's Summary showed none.

Both commands now add the exports' warning for the type that prints:
visible type that fits its frame or path, on the layers that Print
Layers picks, where what it paints (its effects applied) lands on the
paper of an artboard or tile that prints, inside its clipping paths.
The type in the symbols, inline graphics, patterns, scatter brushes,
opacity masks and placed documents of the art that prints counts too.
file.print reads the fonts before raster effects and Print as Bitmap
turn type into images, for PDF and PostScript output.

Docs: docs/mcp.md (Print), docs/target-app-parity.md (Fonts).
2026-10-16 02:26:13 +07:00
dotCooCoo d069f0fe98 Variables: selecting a data set sets the objects inside a group that it shows
dataset.select applied a row's values in the order the variables were
defined, and it skips an object inside a hidden group. When a row
showed a group and set an object inside it, and the object's variable
was defined first, the object was skipped while the group was still
hidden: one selection applied the row in part, and selecting the row
again applied the rest. A text variable on type inside the group was
skipped the same way, and a row that hid a group skipped the objects
inside it whose variables were defined after the group's.

dataset.select, dataset.next and dataset.prev now apply the visibility
values that show objects first, outermost object first, then the other
values in definition order, then the visibility values that hide
objects, innermost object first. Of two variables of the same kind on
one object, the one defined last still wins.

Docs: docs/mcp.md.
2026-10-16 02:26:11 +07:00
dotCooCoo 01b3599227 Variables: binding errors name the command and say when a layer is hidden
Binding art in a hidden group or layer failed with "node N is locked",
even when nothing was locked. The error now says whether the object is
locked, is in a locked group or layer, or is in a hidden group or layer.

Make Text Dynamic and Make Visibility Dynamic reported their errors under
`variable.makeDynamic`, which is not a command id. They now name
`variable.makeTextDynamic` or `variable.makeVisibilityDynamic`.
2026-10-16 02:26:09 +07:00
dotCooCoo 00b02c1bdb Variables: Update Data Set writes only the variables the active data set holds
A data set can hold values for only some variables: `dataset.new` and
`dataset.set` store only the values they are given, and selecting the
data set leaves the other variables alone. Update Data Set replaced the
active data set's values with the values the art reports for every
variable. After one Update, a data set that set only a visibility
variable also held the current text of the bound type, and selecting it
again wrote that text back. Update also dropped a variable's value when
its objects were locked or inside a hidden or locked layer or group,
because such objects report no value.

`dataset.update` now writes the art's current value for each variable
the active data set holds, and keeps the data set's value when the art
reports none. A variable that the data set does not hold stays out of
it, including one defined after the data set was captured;
`dataset.update {all: true}` also adds the other variables that the art
reports a value for. The reply adds `left`, the variables it did not
write. The Variables panel's Update Data Set button and menu item run
it without `all`.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-16 02:26:08 +07:00
dotCooCoo ff33e0556f Pen: the new-path asterisk leaves the pointer once a path is started
The Pen's pointer showed the new-path asterisk before the first anchor
and also while a path was being drawn, where a click adds the path's
next anchor. A new Cursor::PenDrawing, the pen nib without a badge, now
shows there. The close, convert and join badges still show over the
first anchor, the last anchor and another open path's end. The asterisk
shows only where a click starts a new path.

The Continue, Add and Delete badges now also show after another path
was selected while the Pen was still drawing (for example with a
Cmd-click). A click there already continued the path or added or
deleted an anchor, but the pointer showed the asterisk.
2026-10-16 02:26:06 +07:00
dotCooCoo 35f43d631f View: Proof Setup simulates tritanopia
View › Proof Setup had two color blindness items, protanopia and
deuteranopia. A third item, Color blindness – Tritanopia-type, proofs
the canvas with a tritanopia simulation, and the Separations Preview
panel's Proof Setup list offers it too. `view.proofSetup` takes
`target: "tritanopia"`.

The simulation follows Brettel, Viénot and Mollon (1997): a color
keeps its L and M cone responses, and its S response is replaced so
that it lies on one of two half-planes. Both contain the neutral
axis; one passes through the 485 nm color and the other through the
660 nm color. The neutral axis runs to the display's white, so grays
keep their value. The matrices are computed at
compile time from the published inputs cited in the code. The
protanopia and deuteranopia matrices are unchanged.
2026-10-16 02:26:04 +07:00