Commit Graph
4392 Commits
Author SHA1 Message Date
dotCooCoo 6e4fb6e4e7 Links: a link on a drive mapped to another share is not followed
craft-docpath follows a stored path only on this computer or on the
share the document is on, but its check is lexical and cannot see
mapped drives. fileio::document_path therefore followed a link stored
as Y:\img\logo.png, where Y: is mapped to \\server\other, from any
document: opening the document, links.check, Link Info and the
linked-file watch read or stamped the file on that server.

On Windows, document_path now reads the final path of the root folder
of a link's drive (std::fs::canonicalize). Windows opens that root
folder on the drive's server to answer, so the check still contacts
the server once per lookup; no file on it is read, stamped or watched.
When the final path names a share, the link is checked against the
document's path on its share, and a link on another share is refused
as on another computer, as a link stored with that share's own path
is. Links on this computer's drives and on the document's share are
followed as before, and a link on the document's own drive is not
looked up. A document opened as \\?\Z:\... is looked up on its share
as Z:\... is. Other systems are unchanged.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
dotCooCoo 82a78e3d58 Save for Web: browser previews go only to a folder the current user owns
Preview in Browser wrote each preview into `VectorCraft Save for Web`,
and before each preview it removed every file and folder there that
was an hour old or older. Where the temporary folder is the shared
/tmp, another user could make that folder first, writable by everyone,
and the app wrote its previews inside it, in folders that user could
read and replace. Where the system follows other users' links in /tmp,
the folder could also be a link, and the app removed the old entries
of the folder it named.

A preview now uses that folder only when it is a folder and not a
link, and on Linux, macOS and FreeBSD only when the current user owns
it and no other user can write to it. On those systems the folder and
each preview's folder are made with mode 0700. The cleanup removes
only preview folders, on those systems only those the current user
owns. When the shared folder can't be used, the preview goes to a new
folder in the temporary folder. A later preview removes such folders
once they are an hour old.

Docs: docs/target-app-parity.md.
2026-10-21 15:42:35 +07:00
BFlat 0f39505d12 Merge pull request #3251 from MarekSpodBiedry/issue-3229
Warn on export when a font style is drawn in another style (#3229)
2026-10-21 15:41:57 +07:00
BFlat 31734a0dcb Merge pull request #3285 from dotCooCoo/fix/legacy-ai-radial-gradient-stop-order
Legacy .ai export: a radial gradient's color stops are written from ramp point 0 to 100
2026-10-21 15:41:38 +07:00
BFlat c5f6443e0c Merge pull request #3203 from rahul05ranjan/fix/offset-path-preview-memory
Offset Path's Preview checkbox keeps its last state across opens (#3195)
2026-10-21 15:41:23 +07:00
BFlat 0d387fb16c Merge pull request #3304 from HEOJUNFO/fix/pattern-guides-in-export
Leave the pattern tile edge and swatch bounds out of exports
2026-10-21 15:41:06 +07:00
BFlat 153bcb2ec7 Merge pull request #3284 from MMexicann/fix/css-swatch-stop-precision
M3.57: preserve fractional CSS gradient stop positions
2026-10-21 15:40:50 +07:00
BFlat d5682167ec Merge pull request #3331 from dotCooCoo/fix/user-library-file-panic-is-skipped
Libraries: a User Defined library file whose reading panics is skipped
2026-10-21 15:40:35 +07:00
BFlat 975db53e30 Merge pull request #3286 from lehni/fix/automation-args-timeout
Root-argument tests: give the app 30 seconds to exit, not 5
2026-10-21 15:40:18 +07:00
BFlat a06950b30c Merge pull request #3323 from Silvertree2010/fix/h1183-gpl-palette-with-a-blank-name-li
Keep fallback library name for blank GPL Name line
2026-10-21 15:40:02 +07:00
BFlat a437b9ae63 Merge pull request #3230 from Silvertree2010/fix/h857-measure-tool-shows-angle-0-0-for
Normalise negative zero in Measure tool angle
2026-10-21 15:39:44 +07:00
BFlat ffc7e7272e Merge pull request #3225 from Silvertree2010/fix/h842-xml-id-object-name-round-trip-dr
Encode trailing space as _x20_ in XML ids to keep round trip
2026-10-21 15:39:25 +07:00
BFlat 528f94c4cf Merge pull request #3254 from Silvertree2010/fix/h934-css-font-family-left-unquoted-fo
Quote CSS font-family names that are not plain identifiers
2026-10-21 15:39:08 +07:00
BFlat 0e7cb4afd0 Merge pull request #3240 from Silvertree2010/fix/h876-eps-writer-emits-inf-as-a-number
Fix EPS writer emitting inf for huge finite numbers
2026-10-21 15:38:53 +07:00
BFlat c1a823b34d Merge pull request #2955 from nrq6993-a11y/stream/vc-feat-g2-sketch-water-paper
G2: Effect › Sketch › Water Paper raster filter
2026-10-21 15:28:29 +07:00
MarekSpodBiedry 24638c409b Warn on export when a font style is drawn in another style (#3229) 2026-10-21 15:20:19 +07:00
dotCooCoo bda3c14f72 Legacy .ai export: a radial gradient's color stops are written from ramp point 0 to 100
The version 7 .ai writer wrote the %_Bs color stops of every gradient
from ramp point 100 down to 0. The Adobe Illustrator File Format
Specification version 7.0 lists a linear gradient's stops in that order
(section 1.4.3). Its radial definition, "White & Purple Radial", lists
them from 10 to 100 (section 3.5.2). As reported on #2807, the
reference app reads both stops of #2687's radial gradient, written from
100 down to 0, at ramp point 100 and draws the circle in #1b2a6b. It
reads a radial gradient with three stops written that way in reverse
order.

The writer now lists a radial gradient's stops from the first to the
last. Each stop is written with its own midpoint to the next stop, and
section 3.5.2 says the last stop's midpoint is ignored. Linear
gradients are unchanged.
2026-10-21 15:20:18 +07:00
Rahul Ranjan 9ce5ffd244 Offset Path's Preview checkbox keeps its last state across opens (#3195)
The Offset Path dialog remembers the values it was last confirmed
with (#1152), but the Preview checkbox was not among the kept
fields, so every open started unchecked and Preview had to be
ticked manually each time. Preview now rides along with offset,
joins and miterLimit in the fields kept on OK, as Simplify's
retained fields already keep theirs.

Unchanged: a very first open (nothing kept yet) still starts
unchecked, and Cancel keeps nothing.

Adds a regression test beside offset_path_remembers_its_last_values:
open, tick Preview, confirm, reopen - the checkbox is still on.

Verified by reading the flow on current main (menus.rs opens on
app.ui.offset_path when it is an object, d.bool("preview") defaults
to false when the field is absent, form::preview_result ticks the
checkbox into d.fields) and by tracing the existing #1152 test;
no Rust toolchain on this machine, so the test was not executed
here - saying so plainly for the reviewer.

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-10-21 15:20:17 +07:00
HEOJUNFOandClaude Opus 5.5 5569ae97a6 Leave the pattern tile edge and swatch bounds out of exports (#3261)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 15:20:16 +07:00
Mex 97d133d9d4 M3.57: preserve fractional CSS gradient stop positions 2026-10-21 15:20:15 +07:00
dotCooCoo 8712be97b0 Libraries: a User Defined library file whose reading panics is skipped
The desktop app reads the User Defined swatch and graphic style
libraries while it creates its window: set_user_dir runs
Libraries::rescan, which read and parsed every file in the folder
outside the panic guard that commands have. A parser bug that panicked
on one file stopped the app before its window appeared, at every
launch while the file stayed in the folder.

rescan now reads and parses each file inside guard::catch_panic and
skips a file whose reading panics, as it skips one that can't be read.
The other libraries are listed.
2026-10-21 15:20:15 +07:00
Jürg Lehni 494343c274 Fix: the root-argument tests give the app 30 seconds to exit, not 5, which the binary's start alone took on a loaded machine (the workspace's other tests running meanwhile), so they failed there; 30 still catches a desktop that started instead of exiting 2026-10-21 15:20:14 +07:00
NetterSilverandClaude Haiku 5.5 acee6cccd8 Keep fallback library name for blank GPL Name line
Found while testing crates/color/src/palette_io.rs:373

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 15:20:13 +07:00
NetterSilverandClaude Haiku 5.5 89c1448777 Normalise negative zero in Measure tool angle
Found while testing crates/tools/src/xform/measure.rs:20 (shown at :66, reported at :80)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 15:20:12 +07:00
NetterSilverandClaude Haiku 5.5 781d68a971 Encode trailing space as _x20_ in XML ids to keep round trip
Found while testing crates/doc/src/xml_id.rs:80 (strip_unique), triggered by from_object_name at :32

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 15:20:11 +07:00
NetterSilverandClaude Haiku 5.5 7c8e772b46 Quote CSS font-family names that are not plain identifiers
Found while testing crates/svg/src/css.rs:112

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 15:20:11 +07:00
NetterSilverandClaude Haiku 5.5 2b0d71dbea Fix EPS writer emitting inf for huge finite numbers
Found while testing crates/eps/src/ps.rs:36

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 15:20:10 +07:00
bflatastic 15babbcacc Water Paper: no Adobe address in the shipped effect description, clippy (#2955) 2026-10-21 15:17:05 +07:00
bflatastic 55df023b65 Water Paper: rustfmt (#2955) 2026-10-21 15:17:04 +07:00
nrq6993-a11y 078e67a386 G2: Effect › Sketch › Water Paper raster filter (#2955) 2026-10-21 15:17:04 +07:00
Dylan M. Taylor bc1ae08252 Merge pull request #3224 from Matoo125/fix/emf-pattern-tile-budget
EMF export: pattern tiles stop at 250,000 in all
2026-10-20 12:17:30 -04:00
Dylan M. Taylor cdc4ecc67e Merge pull request #3223 from Matoo125/fix/nested-repeat-budget
Repeat: nested Repeats stop at 250,000 instances in all
2026-10-20 12:17:16 -04:00
Dylan M. Taylor 0c42504069 Merge pull request #3194 from Matoo125/fix/mesh-remove-lines-malformed
Gradient meshes: deleting mesh lines leaves a malformed mesh as it is
2026-10-20 12:17:01 -04:00
Dylan M. Taylor 31eafe3f8d Merge pull request #3193 from Matoo125/fix/mesh-point-count-overflow
Gradient meshes: a grid whose point count overflows is malformed
2026-10-20 12:16:47 -04:00
Brandon ThomasandClaude Opus 5.5 2c1391cf40 Skip CodeRabbit reviews on Dependabot PRs (#3247)
Adds .coderabbit.yaml so CodeRabbit no longer reviews or comments on PRs opened by dependabot[bot].

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 11:38:32 -04:00
Dylan M. Taylor ae663f53aa Merge pull request #2923 from dylanmtaylor/docs/add-sonar-coderabbit-badges
docs: add SonarCloud and CodeRabbit badges to README
2026-10-20 11:31:29 -04:00
Martin Vlach dd7991cb20 EMF export: pattern tiles stop at 250,000 in all
One pattern fill tiles at most 250,000 times (PatternDef::offsets_covering),
but Scene::fill writes each tile's own pattern fills again, down to the
nesting limit of 8, with nothing bounding the total. A pattern whose tile
art is filled with another pattern (or with itself) multiplies the tile
count at every level: a small document makes EMF export (the engine's
"emf" format, also reachable over MCP and the control channel) run out
of memory or never finish, and the unsaved document is lost with it.

The scene now keeps one tile budget for the whole walk. A fill whose
tiles don't fit in what is left, and every pattern fill after it, is
written as one colour like WMF writes patterns, with a warning.

The test tiles squares filled with dots (over half a million fills before)
and a pattern filled with itself, and checks both stay under the budget.

Reported-by: BS.ehlas.cz
2026-10-20 14:26:00 +02:00
Martin Vlach 03b2960f4d Repeat: nested Repeats stop at 250,000 instances in all
One Repeat makes at most 250,000 instances (a 500 x 500 grid; radial
counts stop at 1,000), but its source may hold another Repeat and every
instance draws that one again: expand() clones the inner Repeat once per
instance, and the renderer, the exporters (expand_deep) and hit testing
evaluate each copy. Nothing bounds the product, so a document under 1 KB
with a 500 x 500 grid of a 500 x 500 grid asks for 62.5 billion
instances: opening it freezes the app or runs it out of memory, and the
unsaved work in other documents is lost with it.

A Repeat now counts the instances the Repeats nested in its source make
in all and keeps only as many of its own as fit in 250,000, the budget
pattern fills already keep (at least one, so the source still shows).
A Repeat with nothing nested keeps all its instances, as before.

The test nests a 500 x 500 grid in a 500 x 500 grid and in a mirror,
counts the instances from the transforms (62.5 billion before) and
checks they stay within the budget, while one grid and a small nest keep
all of theirs.

Reported-by: BS.ehlas.cz
2026-10-20 14:23:50 +02:00
Martin Vlach da8e6af7e8 Gradient meshes: deleting mesh lines leaves a malformed mesh as it is
GradientMesh::remove_point_lines (the Mesh tool's Alt-click,
object.mesh.deletePoint) only checked that the clicked point exists,
then indexed the points above and below or left and right of it by the
mesh's rows and columns. A gradient mesh read from a document is not
checked against its rows and columns, so one with fewer points than
they need (rows 2, columns 1 and 3 points instead of 6) indexes past
the end and panics: the engine guard rolls the command back with an
internal error, and the web app, which has no guard, aborts.

It now returns false for a mesh that isn't valid, as locate (and with
it adding mesh lines), the outline and the mesh lines already do, and
the command reports that nothing could be deleted.

The test Alt-clicks an interior row's point and an interior column's
point of two such meshes and checks nothing is deleted and the mesh is
unchanged.

Reported-by: BS.ehlas.cz
2026-10-20 09:20:24 +02:00
Martin Vlach bf6692ae82 Gradient meshes: a grid whose point count overflows is malformed
GradientMesh::is_valid and the two mesh envelope checks (envelope_grid
and the envelope's surface) compare the number of points with
(rows + 1) × (cols + 1), multiplying the document's u32 rows and
columns unchecked. With u32::MAX rows and columns the product is 2^64:
debug builds panic on the overflow and release builds wrap it to 0, so
a mesh or a mesh envelope without points passes as valid. Selecting
it, Outline view, hit testing and export then index points that aren't
there (the mesh lines, outline and edges), tessellating it asks for an
impossible capacity, and a mesh envelope with handles to fill in loops
2^64 times making them until memory runs out. On wasm32, where usize
has 32 bits, 65,535 rows and columns are enough, and a panic there
aborts the web app.

The three checks now share grid_len, which counts the points with
checked arithmetic and gives None when the count doesn't fit in a
usize: a grid that can't be counted matches no point list, so it is
malformed like any other wrong count.

The test checks that a u32::MAX × u32::MAX mesh without points isn't
valid and has no lines, outline or quads, that envelope_grid refuses
those counts, and that a mesh envelope with them leaves art in place.

Reported-by: BS.ehlas.cz
2026-10-20 09:18:33 +02:00
Brandon ThomasandClaude Opus 5.5 ad0566943a Release 0.14.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.14.0
2026-10-19 22:18:48 -04:00
BFlat 9e7929ee89 Merge pull request #3173 from KaboomFox/p6-effect-paint
Plug-ins: live effects read their object's fills and strokes (G17)
2026-10-20 08:49:13 +07:00
Louise FoxandClaude Opus 5.5 2e4666a364 Plug-ins: live effects read their object's fills and strokes (G17)
A live-effect plug-in was given only its object's geometry. An effect
that draws as wide as the stroke it sits on, or tells its fills apart,
had to ask for them again as parameters, which then went stale when
the paint changed.

A live effect's input object now holds the object's fills and strokes
too, read only and in the form a filter gets them (one encoder,
objects::paint_lists, for both), and _context.item says where the
effect sits: "object", or {"fill": i} / {"stroke": i} for an effect in
a fill's or a stroke's own effects. effects::GeomContext carries the
object's appearance and the item, and the plug-in cache keys on the
paints, so a change of paint runs the plug-in again. Hosts from before
this give geometry only; nothing in the output changes.

Expand worked a fill's geometry effects out in the object's context, so
a plug-in effect there would be told "object" and a gradient fill was
expanded into a shape the canvas doesn't draw: fill_shape now gives them
the fill's context, as drawing does (expand.rs).

Siblings named with a declared prefix, the other half of such context,
are left out: the effected geometry is worked out from the object alone
wherever it is drawn, measured, hit-tested or expanded.

wat::when_given is a plug-in whose output depends on whether its input
or parameters hold a text. tests_plugins.rs uses it to check that an
effect sees the stroke's width and the fill, runs again for a thinner
stroke, and on a second stroke is told so; effect.rs checks the item
counting; a gradient fill under such an effect is drawn and expanded
into the same triangle. CRAFT_PLUGIN_PAINTS_WASM=<folder> saves a test
effect to try in the app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
2026-10-20 08:39:41 +07:00
BFlat ee4564c586 Merge pull request #2954 from nrq6993-a11y/stream/vc-feat-g2-sketch-note-paper
G2: Effect › Sketch › Note Paper
2026-10-20 08:39:16 +07:00
bflatastic c9d769ad4e Note Paper: grey tones on white like the other Sketch filters, rustfmt (#2954) 2026-10-20 08:30:05 +07:00
bflatastic 62e8474caa i18n and docs: Note Paper's catalog rows, raster effects 46 of 57 (#2954) 2026-10-20 08:29:26 +07:00
nrq6993-a11y 301783c480 G2: Effect › Sketch › Note Paper raster filter (#2954) 2026-10-20 08:28:51 +07:00
BFlat 64699f5212 Merge pull request #3172 from KaboomFox/p2-rich-params
Plug-ins: labels, units, groups, help and conditions for parameters (G17)
2026-10-20 08:27:36 +07:00
BFlat bea46e63ff Merge pull request #3171 from KaboomFox/p1-exporter-plugins
Plug-ins: exporters write file formats of their own (G17)
2026-10-20 08:27:26 +07:00
Louise Fox c755909d67 Plug-ins: labels, units, groups, help and conditions for parameters (G17)
A plug-in's dialog labelled each field with its parameter's name spaced
out ("Line_spacing_mm:"), showed numbers without their unit and listed
at most 64 parameters in one column. A plug-in with many settings, such
as a generator's or an exporter's, could neither explain nor sort them,
and its dialog showed settings that don't apply to the choices made.

Each parameter may now say how dialogs show it, in keys that hosts
from before them ignore (plugins::dialog):

- label; unit, after the value ("0.25 mm"); help, on the label; and
  helpUrl, an https:// page opened from a button after the field.
- group: the parameters outside any group come first, then each group
  as a section that folds, in the order the groups first appear, the
  fields lined up down the dialog.
- visibleWhen: {"pattern": ["lines"]} shows a parameter only
  while each checkbox or choice it names, declared before it, has one
  of the values listed and is shown itself. Manifest::visible decides
  every parameter in one pass, in declaration order. A hidden parameter
  still goes to the plug-in with its value, so a run never depends on
  what the dialog showed.

Two types join number, int, bool and choice: string, a text field cut
to maxLength, and numberList, numbers typed separated by commas or
spaces, each clamped, minItems to maxItems of them. A plug-in declares
up to 256 parameters, at most 64 outside groups. The filter, live-effect
and exporter dialogs use every key. The Appearance panel's editor of a
plug-in effect uses the labels, units, help and conditions, and reads a
typed number list as the dialog does.

dialog.rs tests parsing, the round trip of a manifest written out,
sections, visibility (also of 256 overlapping conditions, which a
recursive check would take forever on) and the keys refused;
manifest.rs the new types and limits. tests_plugin_fields.rs draws a
dialog with kittest: labels and units, sections in order with their
fields lined up, visibleWhen hiding and showing rows, and the help
button opening its page.
appearance.rs checks the editor's labels, units, conditions and a
typed number list, and form.rs units after numbers (a unit starting
with a digit is spaced from it) and typed lists.

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

# Conflicts:
#	docs/gaps.md
#	docs/target-app-parity.md
2026-10-20 08:17:53 +07:00