Commit Graph
72 Commits
Author SHA1 Message Date
BFlat ed90dc103b Merge pull request #1773 from TrevorBrown-dev/fix/css-color-none-and-units
M8: CSS colours take none and every hue unit, refuse nan and inf, and to_css keeps the 8-bit alpha
2026-11-07 10:36:36 +08:00
BFlat e9fc975aa6 Merge pull request #1756 from dotCooCoo/feat/vector-network-corner-smoothing
M4: closed vectors take corner smoothing
2026-11-07 10:27:00 +08:00
BFlat b186c7a686 Merge pull request #1789 from TrevorBrown-dev/fix/svg-import-stretch-and-dash-offset
M6: SVG import: a dash offset on a closed outline is kept
2026-11-07 09:56:01 +08:00
Dylan M. Taylor 6d1b5da863 fix: create test and benchmark temp directories fresh, never adopt an existing one (#1977) 2026-11-03 23:48:46 -05:00
Dylan M. Taylor 16a79570f1 perf(svg): hash an embedded image's bytes once per import (#1945)
Every shape painted with a raster pattern hashed the pattern's whole
encoded image again to find its document key, so one embedded image reused
by many shapes multiplied the hashing work without adding any image data.
The key is kept per buffer for the import.
2026-11-03 22:57:28 -05:00
Dylan M. Taylor 6da4c30cae fix(svg): refuse a document compressed twice and charge a refused embedded one (#1924)
One gzip layer is unpacked within the import budget, but usvg gunzips what
it is handed again with no limit, so a doubly compressed document or
embedded image expanded outside the budget. A compressed embedded image
that failed its budget also left the shared allowance untouched, so every
sibling image could repeat the work; a refusal now spends what was left.
2026-11-03 22:26:21 -05:00
Trevor Brown dfac369df0 Merge remote-tracking branch 'upstream/main' into fix/css-color-none-and-units 2026-11-03 20:17:35 -05:00
Trevor BrownandClaude Fable 5.1 8fb53baa64 SVG import: a filled and stroked path with an open subpath is tested through its split
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 02:02:35 -05:00
Trevor BrownandClaude Fable 5.1 0c18e481eb Merge upstream main into fix/svg-import-stretch-and-dash-offset
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 01:23:24 -05:00
김삼현andClaude Opus 5.5 702b0d7894 M8: a whole ellipse drawn backwards keeps its simple stroke in SVG (#1848)
* M8: a whole ellipse drawn backwards keeps its simple stroke in SVG

An ellipse with a sweep of -2π goes all the way round, as the renderer
and the .fig export already read it. The SVG export's simple stroke
checked `sweep >= 2π` only, so such an ellipse's inside or outside
stroke was written as a filled even-odd ring instead of a stroke.

All three places now ask `figcraft_geom::full_sweep`, as the geometry
and the bounds do.

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

* M8: a backwards whole ellipse's stroke ring in the renderer and .fig export, tested

Review follow-up (#1848): the renderer's stroke ring and the .fig
export's stroke geometry read a sweep of -2π as whole too, now through
`full_sweep`. A test for each checks a backwards ellipse strokes the
same ring as a forward one, and fails if either goes back to checking
positive sweeps only.

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

* M8: the .fig backwards-ellipse test compares the stroke ring itself

Review follow-up (#1848): the test checked only that one stroke path was
written. It now compares the path's commands and winding rule for the
forward and backwards ellipse, and that the ring is even-odd.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 23:49:38 -05:00
김삼현andClaude Opus 5.5 6b2e464361 M8: a scaled node's spread shadow keeps its room in SVG (#1803)
The SVG export blurs a spread drop shadow in a filter whose region is
the silhouette's page bounds grown by the spread's mitre and the blur.
The spread grows the outline in the node's own units, though, and the
transform then scales it: on a star scaled three times, a spread of 10
mitred out to some 97 units while the region only made room for 48, so
SVG viewers cut the shadow's points off. The spread now grows the region
as far across and down as the transform takes a local step of that
length, as the paint bounds already do.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:12:23 -05:00
Trevor BrownandClaude Fable 5.1 83c28cf3ae M6: SVG import: a stretched image stays stretched (#1811)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 14:12:18 -05:00
Trevor BrownandClaude Fable 5.1 8bc405edbc SVG import: the dash offset fix alone, with open-path, mixed-subpath and text tests
The stretched-image fix moved to its own PR.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 12:16:33 -05:00
Trevor BrownandClaude Fable 5.1 d72f096186 M8: color-mix(): a none in color() is a missing component too
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 11:54:35 -05:00
Trevor BrownandClaude Fable 5.1 8b6c92796e SVG color-mix(): every missing component is carried over, through nested mixes too
Review follow-up on #1773: the carry-forward now covers red, green and blue in an sRGB mix (converted first in another space, so zero there, per CSS Color 5 section 4.1 on analogous components) as well as the alpha in every mix, and a component missing in both colours stays missing in the result, so an enclosing mix carries it from its other colour.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 00:39:38 -04:00
Trevor BrownandClaude Fable 5.1 16d97cf8e9 M6: SVG import: a stretched image stays stretched, and a dash offset on a closed outline is kept
Found while testing crates/svg import (no existing issue), each with a test that fails before and passes after. An <image preserveAspectRatio="none"> took ImageScaleMode::Fill, which keeps the picture aspect and crops; a box of a different aspect now takes a whole-picture Crop, which the renderer draws as a stretch (SVG 2 section 8.6). A stroke-dashoffset equal to half the first dash was skipped on every shape because an open line starts half a dash in by itself; a closed outline runs its pattern from its start, so the exemption now applies to open outlines only (SVG 2 section 13.5.6).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 00:33:58 -04:00
Trevor BrownandClaude Fable 5.1 4676077626 SVG color-mix(): a missing alpha is taken from the other colour before the mix
Review follow-up on #1773: with a none alpha now zero when a colour is rendered on its own (CSS Color 4 section 4.4), color-mix() needs the carry-forward rule of CSS Color 5 section 4.1, else color-mix(in srgb, rgb(255 0 0 / none), blue) came out half transparent instead of opaque purple. The operand reports a missing alpha and mix() fills it from the other colour; both missing stays zero.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 00:16:56 -04:00
dotCooCoo 000d39b042 M4: closed vectors take corner smoothing
A vector's corner smoothing was stored, imported and exported, but its
rounded points were drawn as plain circular arcs, and the Design panel
hid the Corner smoothing control for vectors.

A vector with a closed region now draws its rounded points with the
layer's corner smoothing, on the canvas, in hit testing, in PNG, SVG and
PDF exports, and in the fill and stroke geometry of .fig exports. Each
point keeps its own radius; a point without one stays sharp. The corner
uses the profile of smoothed polygon corners, and smoothing is reduced
before the radius when a corner does not fit in half of each segment.

FigCraft's own rules for cases the help page does not describe: only a
point between two straight segments that both bound a closed region is
smoothed; points on open branches and points next to a curved segment
keep the plain arc; concave corners are smoothed like convex ones.

The Design panel shows Corner smoothing and its iOS (60%) button for
vectors with a closed region, and not for open paths.
2026-10-31 02:33:38 -07:00
BFlat 6b28ef57dc M4: Dash cap setting for Dash and Custom strokes (#1728) 2026-10-31 09:41:39 +07:00
BFlat bdb6a05976 M8: regression tests for one-sided strokes on rounded shapes and XML-forbidden characters in SVG (#1351, #1124) (#1719)
Both reports were checked by running them and are already fixed on main. Pins a rounded rectangle or frame stroking only the side asked for in every alignment, and SVG font families and ids leaving out characters XML forbids.
2026-10-31 07:41:40 +07:00
BFlat 87e42a14f2 M4: a dashed line starts and ends with a half-length dash (#1718) 2026-10-31 05:28:15 +07:00
BFlat 7fdbda9f09 M8: One depth cap for a whole render, and render threads with room for it (#1605)
* M8: one depth cap for a whole render; render threads get 8 MiB stacks (#1082)

A pattern fill drew its source as a fresh render from depth 0, so patterns in patterns could stack three renders of 256 levels. The source now goes on from the depth of the layer it fills. A render level takes about 6.5 KB of stack (measured), about 1.7 MB at the cap: the canvas render, preview and save threads and a band pool of their own now get 8 MiB stacks instead of the default 2 MiB.

* M8: SVG and PDF exports carry the depth into pattern sources and embedded renders (#1082)
2026-10-30 10:43:49 +07:00
samhyunandClaude Opus 5.5 97f502cb42 M8: the SVG export chains layer blurs and casts shadows from the blurred layer
The canvas applies layer blurs one after another and casts drop shadows from the layer they leave, cut out by it. The SVG filter blurred SourceGraphic once per blur into the same result, so only the last was drawn, and its shadows came from the unblurred alpha: blurs of 12 and 16 with a shadow came back from an export 73 levels off. The blurs now chain, the shadows are cast from the last blur's alpha and cut out by it, and the import reads that back as one blur as strong as the stack with the same shadows. Coloured copies of a blurred layer are still reported, not read as black shadows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 10:20:30 +09:00
BFlat d890de92c4 M8: SVG import reads color() and color-mix() colours (#1550) (#1566)
The SVG parser reads neither, so fills came in black and strokes were dropped without a note. They are resolved before parsing: color(srgb|display-p3 ...) converted to the document's profile, color-mix(in srgb|oklab|oklch, ...) evaluated as CSS Color 5 defines it. Other spaces are approximated with a note; unreadable calls become a mid grey with a note. Bounded length, nesting and count.
2026-10-30 04:01:07 +07:00
BFlat 2e3d9b6f07 M8: SVG pattern tiles drawn once per scale, within the layer budget (#1080) (#1551) 2026-10-29 23:57:49 +07:00
BFlat 1df9190d0c M3: shadows of images with transparent pixels follow their alpha (#1289) (#1509)
* M3: shadows of images with transparent pixels follow their alpha (#1289)

A drop shadow on an image fill was hidden behind the layer's whole box, so a
transparent PNG's shadow only showed outside its bounding box. Fills that
paint their own alpha (patterns; images with transparent pixels or that leave
part of the box empty) now cast and hide drop shadows, and shade inner
shadows, by the layer's pixels, like groups. Opaque pictures keep their
outline. Each image is scanned for transparency once. Canvas, PNG, SVG and PDF.

* M3: the image shadow tests import their names explicitly (#1289)
2026-10-29 15:43:08 +07:00
BFlat c2a6b71020 Merge pull request #1459 from MortenHusted/fix/system-font-names
M7: CSS system font names draw in macOS's System Font
2026-10-29 13:41:53 +07:00
bflatastic 789fa33444 Merge main into #634 2026-10-29 12:25:06 +07:00
bflatastic f6df0b4513 Merge main into #1459 2026-10-29 12:22:30 +07:00
bflatastic 8c974023c4 Merge main into #1459 2026-10-29 11:23:08 +07:00
bflatastic 837cad5d25 Merge main into #1404 2026-10-29 10:29:09 +07:00
Dylan M. Taylor 763c3a8dc4 refactor(svg): split node rendering and text setup 2026-10-28 16:00:36 +00:00
Skyfuller1303 c785e96b1c Merge remote-tracking branch 'origin/main' into feat/fig-vector-region-fills
# Conflicts:
#	crates/format/src/fig/export.rs
#	crates/format/src/fig/map.rs
2026-10-28 10:12:00 +01:00
MortenHustedandClaude Opus 5.5 2ff69183d7 M7: CSS system font names draw in macOS's System Font
`system-ui`, `-apple-system`, `BlinkMacSystemFont` and
`.AppleSystemUIFont` join the names `apple_alias` resolves to the
installed System Font, as SF Pro and SF UI already do. SVG import keeps
a family the SVG lists when a font answers to its name, so on a Mac
`font-family="-apple-system, system-ui, sans-serif"` keeps
`-apple-system` and draws in System Font; elsewhere the generic
stand-ins apply as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:35:46 +01:00
NetterSilverandClaude Haiku 5.5 213cc75ff2 Fix looks_like_svg rejecting DOCTYPE with an internal subset
Found while testing crates/svg/src/import/mod.rs:723-740 (looks_like_svg, the `<!` arm using s.find('>'))

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 04:13:59 +01:00
aiongg a99248b296 refactor(svg): move crates/svg/src/import/mod.rs to crates/svg/src/import.rs 2026-10-26 20:17:47 +00:00
Skyfuller1303 a4076caaf4 Merge main and preserve vector region paints through path operations
Retain regional paint stacks and coordinate frames through simplify, offset, flatten, bucket normalization and Shape Builder. Preserve legacy fill semantics and synchronize importer warning catalogs. Verified with cargo xtask ci on Windows and Ubuntu/WSL, native UI screenshots, and CLI round-trip/export checks.
2026-10-26 19:50:00 +01:00
bflatastic e6e4e9ab8a Merge main into #1053 2026-10-26 22:57:25 +07:00
NetterSilverandClaude Haiku 5.5 283c6de2b1 Keep significant digits in SVG matrix scale coefficients
Found while testing crates/svg/src/lib.rs:97 (num, used by matrix at :129 for the image pattern's patternTransform at :243-254)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 06:07:08 +00:00
NetterSilverandClaude Opus 5.5 35f5faa2e6 Check SVG nesting depth after DTD entity expansion (#1177)
Fixes #1081

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 05:25:43 -04:00
NetterSilverandClaude Sonnet 5.5 8b94da604a fix: use apostrophe-aware title case on the canvas text path
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 05:52:05 +02:00
bflatastic d0db758684 Batch r2-1 review: SVG ids unique as written; a filled open curve closes for its fill 2026-10-24 10:31:26 +07:00
bflatastic a5e8d256e6 Merge #1052: Drop XML-forbidden control characters from live-text SVG export (@Silvertree2010) 2026-10-24 09:23:00 +07:00
bflatastic fed124fa77 Merge #1238: Fix duplicate SVG ids when a layer name matches a generated id (@Silvertree2010)
# Conflicts:
#	crates/svg/src/tests.rs
2026-10-24 09:20:00 +07:00
bflatastic f109945c47 Merge #1165: Make SVG unique_id linear for repeated layer names (@Silvertree2010) 2026-10-24 09:19:41 +07:00
bflatastic ab5a62387b Merge #1034: M6: SVG import fills open subpaths the way SVG does (@MortenHusted) 2026-10-24 09:13:28 +07:00
NetterSilverandClaude Haiku 5.5 6494743b6e Fix duplicate SVG ids when a layer name matches a generated id
Found while testing crates/svg/src/lib.rs:549 (unique_id) with :162 (id) and :387

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 22:55:35 +00:00
NetterSilverandClaude Haiku 5.5 b5e5c2100e Make SVG unique_id linear for repeated layer names
Fixes #1106

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 21:48:28 +02:00
NetterSilverandClaude Opus 5.5 6d748536b6 Drop XML-forbidden control characters from live-text SVG export
Found while testing crates/svg/src/lib.rs:153 (esc, used for tspan text at :598, :608 and :613)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 13:01:37 +00:00
MortenHusted 6db58d32cb M4: individual stroke weights follow the corner radius like CSS borders
stroke_area used rect_side_strokes only for square corners; a rounded
rect-like layer with individual weights fell through to a uniform ring at
stroke.weight (canvas, SVG, PDF, shadows, shape builder, outline stroke).
pathops::rounded_side_strokes now draws them as CSS does: the outer edge is
the rounded rect (the fill outline for inside strokes), the inner edge is
inset by each side's weight with elliptical corners max(0, R - weight), so
border-l-4 rounded-r-lg is a straight bar and a weight that differs across
a rounded corner tapers through it. Center and outside grow the outer box
and each non-zero radius by half or all of the weight (CSS has only
inside). The inner edge runs against the outer one, so the ring is a ring
under non-zero too; where it could leave the outer edge (radii shrunk to
fit a narrow inner box, smoothed outer corners) it is cut out instead.
2026-10-22 11:20:24 +02:00