Commit Graph
1570 Commits
Author SHA1 Message Date
bflatastic 04bb6d6f27 SVG import: an Inkscape page whose box can't be placed is skipped 2026-10-13 13:36:41 +07:00
Gert Pellin ef0ab7ba9d SVG import: Inkscape's pages become artboards
Inkscape 1.2+ saves a multi-page document as <inkscape:page> elements
in its <sodipodi:namedview>; VectorCraft opened it with one artboard
the size of the root, leaving the other pages' art outside any. Each
page with a size now becomes an artboard at its place (through the
viewBox, as the art is), named by its inkscape:label or "Artboard N".
Files without pages keep their one artboard.

Fixes #1601
2026-10-13 13:36:41 +07:00
Gert Pellin 546c81624a SVG import: an unsupported filter's warning names the filter and the object
A filter that can't become a live effect warned "filter on a group
ignored", even on a plain <rect>: usvg wraps every filtered element in
a group of its own, which the message named. The warning now names the
filter and its primitives and the element (by id, or "an object"), and
says it is drawn without it: "filter 'f' (feColorMatrix) on an object
isn't supported; drawn without it".

Fixes #1598
2026-10-13 13:36:40 +07:00
Gert Pellin 13bd24ba79 Open by a relative path finds relative links; convert reports import warnings
document.open (and file.newFromTemplate) with a path relative to the
working directory took the SVG's folder from Path::parent(), which is
"" for a bare file name and was then dropped, so relatively linked
images were "not found" (and, linked by their bare name, not listed in
missingLinks either). A relative path is now made absolute first: its
folder is the working directory, as for the absolute path.

vectorcraft-cli convert printed only the export's warnings; what reading
the input left out now comes as importWarnings.

Fixes #1604
2026-10-13 13:36:40 +07:00
Gert Pellin eb25a85d90 Type: stylistic sets, alternates, titling, figure styles, superscript and subscript
A character style's OpenType features took nine tags. OtFeatures now
also keeps ss01-ss20, salt, titl, pnum, lnum, sups and subs (off by
default, one bit each in `extra`): they are accepted by the type
commands, stored with the style and passed to the shaper, so a font
that has them uses them.

The Affinity import test used ss01 as its example of a feature kept but
not rendered; it now uses cv01 for that (character variants still
aren't rendered) and keeps ss01 as one that is.

Fixes #1607
2026-10-13 13:36:40 +07:00
bflatastic c49fb30032 Fix #1616: a tool flyout is as wide as its longest translated row
A flyout's rows were 250 pt wide whatever they held, so in Spanish (and
other languages with long tool names) a name ran into its shortcut. The
rows are now as wide as the longest name and shortcut need, and never
narrower than before.
2026-10-13 12:58:05 +07:00
bflatastic 0cc7e59df0 Save: a folder that refuses the temporary file or the rename gets the content copied in (part of #1620)
A save writes a temporary file beside the document and renames it over
it. Some network shares refuse one of those steps although the document
itself can be written. The save now falls back, without giving up what
the rename is for:

- the temporary file can't be created beside the document: it is made
  in the system's temporary folder (for its owner only);
- the rename is refused: the content, whole and flushed in the
  temporary file, is copied into the document itself;
- when that copy fails, the temporary file stays and the error names
  it; when the document can't be opened for writing, it is untouched
  and the first error is returned;
- permissions that can't be copied to the temporary file (SMB, FAT) no
  longer fail the save.

A folder that isn't there and a full volume fail as before.
2026-10-13 12:58:05 +07:00
Tomasz LewandowskiandClaude Opus 5.5 17314d6b61 i18n(pl): confirm the maintainer-added Polish rows
Reviewed the rows added after the Polish catalog was written (graph
legend and position options, Transform Objects, Load EPS into
Clipboard, the unknown-parameter and image-size messages, …): the
translations are correct and read naturally, so no wording changes;
drop the "not reviewed by a native speaker" marker above them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DukHNvUY45rjRnvtz91x33
2026-10-13 12:58:05 +07:00
dotCooCoo f0810ca58b Object: Lock and Hide › All Artwork Above go through each top-level layer once
Lock › All Artwork Above and Hide › All Artwork Above went through the
objects above each selected object in its top-level layer, one selected
object at a time. For each object they looked up in a list whether it
was selected or already found, and they computed its bounds again for
each selected object below it until one overlapped it. With 100 tiles
selected under 25,000 rectangles, a release build of vectorcraft-cli
took 6.4 s for Lock, and with all of 5,001 objects selected it took
8.8 s and locked nothing.

The commands now go through each top-level layer that holds a selected
object once, and check each object that is not selected against the
bounds of the selected objects below it. The selected objects and the
objects found are kept in sets, and each object's bounds are computed
once. The objects they lock or hide, the count, the selection and the
undo step are the same as before. On the same documents Lock now takes
under 0.1 s.

Docs: docs/target-app-parity.md.
2026-10-13 12:58:05 +07:00
dotCooCoo 35d6aa0a16 Render: pattern tiles at different scales are kept between frames
The renderer kept one tile per pattern name and ink plane, and used it
only at the raster scale it was made at: the device scale rounded up to
a power of two and capped. Two objects that paint one pattern at scales
for which the raster scale differs, such as a pattern scaled to 25%
with Transform Pattern Tiles next to the same pattern at 100%, replaced
each other's tile, and every frame rasterized both tiles again.

Tiles are now kept per pattern name and raster scale, within the tile
cache's budget of 128 MiB per thread and ink plane. When the budget
fills, the cache drops the least recently used tiles first. A tile is
still used only while the pattern's art, tile, tile type and overlap
are the ones it was made from. At 760%, with a pattern of 576 stars in
a 288 pt tile on a rectangle that fills the view, and the same pattern
at 25% on a square, release builds of vectorcraft-cli bench at
2880x1800 took a median of 26.0 ms a frame on main and 8.8 ms with this
change, with 8 threads.

Docs: docs/target-app-parity.md.
2026-10-13 12:58:05 +07:00
Marco Arnulfo 36dc6e0c11 Area type: a word too wide for the top lines of a shape moves down instead of breaking (fixes #1615) 2026-10-13 12:58:05 +07:00
Marco Arnulfo 791bd91d5c Strokes: a zero-length subpath draws its round or projecting cap as a dot (fixes #1613) 2026-10-13 12:58:05 +07:00
Marco Arnulfo 544b17ec33 Workspace: rustix is declared once in the workspace dependencies 2026-10-13 12:58:04 +07:00
Marco Arnulfo 5e8581e42f Save: a network drive on macOS that refuses the full flush gets a plain fsync (fixes #1620) 2026-10-13 12:58:04 +07:00
BFlat 5bfe774420 Merge pull request #1627 from storytold/u/trace-gradients
Image Trace: Gradients
2026-10-13 12:24:35 +07:00
bflatastic 250da4226e Image Trace: Gradients
Gradients (params `gradients`, a checkbox in the panel for Grayscale and Color;
off unless asked for): an area whose colour changes evenly along one direction
is traced as one path filled with a linear gradient, not as bands of flat colour.

Gradients are found before the palette is made, so no colour is spent on their
bands: a connected run of smoothly changing pixels is a gradient when a plane
fits each colour channel with all three sloping the same way, the colours along
that way are at most six stops with ramps between them, and painting them back
gives the area's own pixels. Flat colour, soft edges between flat colours and
shading that goes two ways are left as they were.

The harness gains two linear-gradient inputs with known lines and stops, and
measures whether each is found, the angle of its line and the colours of its ramp.
2026-10-13 12:16:03 +07:00
bflatastic 175ad4a1e3 Effects: effect.apply and effect.setParams refuse parameters the effect doesn't take (#1592) 2026-10-13 11:53:38 +07:00
bflatastic 95304ad7f6 PDF import: a held glyph stroke takes its turn in the page's overprint list 2026-10-13 11:53:38 +07:00
Hiroshi Inukai 331fc48b24 PDF/.ai import: type stroked glyph by glyph stays one object
Type on a path is written glyph by glyph, and a stroke added to it as each
glyph's outline stroked right after the glyph. The path in between ended the
line, so each glyph came in as its own text object. Such a stroke, in the
glyph's own paint, is now held with the line and written after it, where it
becomes the type's stroke as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	crates/pdf/src/tests_import_lines.rs
2026-10-13 11:53:38 +07:00
Hiroshi InukaiandClaude Opus 5.5 02da9bbdc9 PDF/.ai import: evenly tracked type on a path keeps its length
Straight type takes the tracking that keeps its length as the file set
it; type on a path took none, so letter-spaced type on a curve came back
closed up to the font's advances (an "OFFICIAL ISSUED" badge sat to one
side of its band, a condensed title ran off the end of its path). Type on
a path now takes the same tracking, its length measured along the path
from the first glyph to the end of the last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:53:38 +07:00
BFlat 8d2d838884 Merge pull request #1618 from storytold/u/trace-groups
Image Trace: Auto Grouping
2026-10-13 11:47:48 +07:00
BFlat bc5bdf47d1 Merge pull request #1591 from switch87/fix/1590-offset-path-message
Offset Path: an empty result says the offset leaves nothing
2026-10-13 11:29:17 +07:00
BFlat 6b99150aac Merge pull request #1589 from switch87/fix/1584-icc-tag-alignment
ICC profiles written here have their tags 4-byte aligned
2026-10-13 11:28:57 +07:00
BFlat 26bb452ba1 Merge pull request #1576 from excited-raccoon/palette-nearest-spatial-index
Palette nearest-colour lookup: branch-and-bound index over the linear scan
2026-10-13 11:28:37 +07:00
BFlat dbb8bdcaaa Merge pull request #1571 from excited-raccoon/select-behind-linear
Select Behind: test each object once instead of re-hitting the whole document
2026-10-13 11:28:08 +07:00
BFlat ecf4ae7a3a Merge pull request #1577 from switch87/fix/ids-without-selection
Commands: objects given as ids stand in for the selection
2026-10-13 11:27:30 +07:00
BFlat e518974913 Merge pull request #1581 from switch87/fix/1578-arc-warp-aspect
Warp › Arc: bend in the object's real proportions
2026-10-13 11:27:17 +07:00
BFlat a20b0308b8 Merge pull request #1569 from switch87/feat/svg-flowroot-area-type
SVG import: Inkscape flowed text (flowRoot) becomes area type
2026-10-13 11:26:57 +07:00
BFlat 3126e82ebd Merge pull request #1572 from switch87/fix/1570-pathfinder-strokes
Pathfinder: Trim, Merge and Crop remove strokes
2026-10-13 11:26:39 +07:00
bflatastic 9c42a2942a Image Trace: Auto Grouping
Auto Grouping (params `autoGrouping`, a checkbox in the panel; off unless asked
for): each traced path that has others inside its outer contour comes out in a
group with them, the path first, nested as deep as the picture is. The path that
covers the whole image is nobody's group.

The grouped result paints the same picture: where paths can overlap (stacked
layers, Flat Logo, strokes) a path only joins a path below it, and only if that
moves it under nothing that shares any of its box.

The harness measures three presets with the option on ("grouped %"), painting
them in the grouped order.
2026-10-13 11:15:02 +07:00
BFlat 36286cc783 Merge pull request #1609 from storytold/u/trace-shapes
Image Trace: Shapes, and corners whose pixel is mostly outside the shape
2026-10-13 11:02:42 +07:00
bflatastic bc343414d8 Offset Path: the new message in the catalogs that translate messages 2026-10-13 10:52:38 +07:00
Gert Pellin 7b0164e4e7 ICC profiles written here have their tags 4-byte aligned
moxcms's encode packs tagged elements back to back, so the built-in
sRGB profile embedded in JPEG, TIFF and PSD exports was 634 bytes with
`cprt` at offset 558, where ICC.1 §7.1.2 requires every tagged element
on a 4-byte boundary (stricter readers reject the files). encode now
lays the tag data out again with each element padded, updating the tag
offsets, the size and the profile ID (zeroed, "not computed"); tags
that share data keep sharing it. Reported to moxcms as
awxkee/moxcms#198.

Fixes #1584
2026-10-13 10:52:38 +07:00
bflatastic 670946ef75 Palette index: rustfmt 2026-10-13 10:52:38 +07:00
bflatastic bdb9963c73 Select Behind: one test of what may be hit for both walks, and random documents checked against the repeated hit test 2026-10-13 10:52:38 +07:00
bflatastic dc64aaa41b Commands: ids stand in for the selection where the params description names the key, checked over every command 2026-10-13 10:52:38 +07:00
Gert Pellin 5366cf689e Offset Path: an empty result says the offset leaves nothing
With paths selected but every offset empty (an inset larger than the
shape), Offset Path failed with "select paths", sending the user to
look for a selection problem. It now says the offset leaves nothing of
the selected paths; "select paths" stays for selections without paths.

Fixes #1590
2026-10-13 10:52:38 +07:00
Ayanprogrammer11 cca3a63e4a Palette nearest-colour lookup: branch-and-bound index over the linear scan
Indexed export (GIF, PNG-8, BMP, TIFF) finds each pixel's nearest
palette entry with a linear scan, and the k-means refinement in median
cut does the same once per cell per pass. With the 256-entry palettes
this code produces, that is 256 distance evaluations per query.

PaletteIndex is an exact nearest-neighbour kd-tree over the palette,
balanced at the widest weighted axis, with tight channel bounds in every
subtree so branch-and-bound can skip whole boxes. Small palettes (12 or
fewer entries) keep the scan, which is cheaper than tree traversal.

Results match the linear scan exactly, including ties: an equal-distance
entry with a smaller index still wins. The bound uses the same weighted
squared f32 form as dist() rather than a transformed space, so rounding
cannot change which entry is chosen.

Three tests: randomized parity against the original scan across palette
sizes spanning the cutoff, three weight vectors, duplicate colours and
fractional query points; explicit tie-breaking cases; and an opt-in
benchmark comparing 100k queries at both, behind -- --ignored.
2026-10-13 10:52:38 +07:00
Ayanprogrammer11 507c6e0e63 Select Behind: test each object once instead of re-hitting the whole document
objects_at() found Select Behind targets by running hit_test_skipping()
in a loop, skipping everything found so far. Each round is a full tree
traversal, so a stack of n overlapping paths costs O(n^2) hit tests
before the loop finds no further object.

The fast path walks each editable top-level layer once. Every selectable
object's subtree is visited only until its first hit, and a HashSet
records found ids so subtrees already covered by an earlier hit are not
searched again. Sublayers recurse the same way, so a stack of n paths
costs one traversal total.

The original traversal stays in charge where Select Behind semantics
differ from the plain front-to-back order: isolation (scope), and Edit
Contents, which exposes objects nested inside a top-level group. Non-
layer roots in malformed files also fall back. objects_at_repeated()
keeps the old loop for those cases.

Three tests: a 256-path stack asserts front-to-back order and
completeness; a mixed document (sublayer, groups, clipping group,
outline and path-only options) asserts the one-pass result equals the
repeated-traversal result; an envelope in Edit Contents asserts the
fallback preserves the original order.
2026-10-13 10:52:38 +07:00
Gert Pellin dfc1b846cc Commands: objects given as ids stand in for the selection
Commands whose description takes `ids` (operate on these objects;
default: the selection) failed with "nothing selected" when objects
were given but none was selected: execute checked their `enabled`
predicate against the selection, with an exception for edit.clear only.
Of the 220 commands mentioning ids, 48 were refused so; their run
already used the ids. execute now evaluates `enabled` with the given
ids selected and puts the selection back, so each predicate keeps its
meaning (Make with Top Object still needs two objects).

Fixes #1575
2026-10-13 10:52:38 +07:00
Gert Pellin a169693817 Warp › Arc: bend in the object's real proportions
warp_point worked on box coordinates normalised to -1..1 on both axes:
Arc's radius was in x units but its edges were offset in y units, so a
box that isn't square bent into no circular arc (skewed sides from
Bend 10 %), and at 100 % the radius (2/π) was smaller than the
half-height, folding the inner edge over. warp_point now takes the
box's aspect along the warp; Arc keeps the centre line's length on a
circular arc in real proportions, with concentric edges, and an inner
edge that would pass the centre stops there. Other styles are as they
were; a square is bent as before.

Fixes #1578
2026-10-13 10:52:37 +07:00
bflatastic db85d465c8 SVG import: flowed text with a region too large to place is left out with a warning 2026-10-13 10:52:37 +07:00
Gert Pellin de8d5c96ab Pathfinder: Trim, Merge and Crop remove strokes
The public Pathfinder documentation says Trim and Crop remove any
strokes, and Merge removes them and merges same-coloured fills. The
pieces kept every stroke, and Merge, grouping by the whole style, left
same-filled objects with different strokes apart. The three now drop
stroke layers before styles are compared.

Fixes #1570
2026-10-13 10:52:37 +07:00
Gert Pellin e794c1ac63 SVG import: Inkscape flowed text (flowRoot) becomes area type
<flowRoot> (SVG 1.2 flowed text, which Inkscape writes for text typed
into a frame) was dropped without a word: usvg doesn't draw it. The
text pass now reads it as area type: the first <rect> of its
<flowRegion> is the frame (with the rect's transform), each <flowPara>
a paragraph (white space collapsed), styled through the same cascade
as <text>. Another kind of region leaves the text out with a warning.

Fixes #1568
2026-10-13 10:52:37 +07:00
BFlat 0573d3cdfb Merge pull request #1596 from storytold/fix/1561-symbol-percentages
SVG import: percentage lengths in a symbol with a viewBox are of the viewBox
2026-10-13 10:26:19 +07:00
bflatastic 9ca74ac011 Image Trace: Shapes, and corners whose pixel is mostly outside the shape
Shapes (params `shapes`, a checkbox in the panel; off unless asked for): a filled
outline that is a circle, an ellipse, a rectangle or a regular polygon comes out
as that live shape. A shape is only made when every point of the traced outline
lies within 0.5 px (0.2% of the shape's size, if more) of it; an ellipse whose
radii differ by less than twice that is a circle of its area.

The outline before fitting: a corner's own pixel no longer pulls the two edges
that meet there, and a one-pixel step between two long edges is the corner where
they meet. Sharp corners kept: Default 69% -> 76%, the colour presets 86% -> 91%.

The harness gains plain-shape inputs and "shapes %".
2026-10-13 10:25:05 +07:00
bflatastic 5fe916a425 SVG import: percentage lengths in a symbol with a viewBox are of the viewBox (#1561) 2026-10-13 09:58:14 +07:00
bflatastic 8e7b9ba436 Image Trace: stroked centre lines lie in the middle of their ink and are as wide as it
Create: Strokes (the Outline preset, or the option) traced lines along the pixel skeleton: right to a pixel,
with a width counted in pixels, and fitted by a separate, older fitter.

- Each skeleton point is moved to the middle of the line's ink, read from the image across the line: every
  pixel counts as much ink as its colour is the line's rather than the paper's, the ink's centroid is the
  middle and its sum the width (an anti-aliased or blurred line says both to a fraction of a pixel).
- The stroke width is the median of those widths, no longer a whole number of pixels.
- Each end goes where the ink ends, less the half width a round cap adds.
- Centre lines are fitted as outlines are: a straight line is two anchors, corners stay corners, Snap Curves To
  Lines levels them. The old Douglas-Peucker and simplify fitter, and the crate's pathops dependency, go.

Outline on the thin-line inputs, all sizes and variants (docs/image-trace-quality.md), before -> after:
- painted outline 0.310 -> 0.055 px off on average; 81 -> 14 anchors for five lines
- centre line 0.295 -> 0.052 px from the true centre line; stroke width 0.13 -> 0.02 px off; 18.4 -> 2.7
  anchors a stroke (blurred, noisy and scanned lines: 2.0 each; aliased: 4.6)
- largest distance 1.84 -> 2.94 px: the square ends of a 10 px line under a round cap
Presets without Create: Strokes are unchanged.
2026-10-13 09:36:47 +07:00
BFlat cbcee3bd54 Merge pull request #1593 from storytold/u/trace-upright
Image Trace: Snap Curves To Lines levels lines a degree or two askew
2026-10-13 09:36:14 +07:00
BFlat 0810a15f6e Merge pull request #1566 from marcoarnulfo/fix/new-symbol-options-dialog
Symbols panel: New Symbol asks for Symbol Options before making the symbol
2026-10-13 09:31:54 +07:00