Commit Graph
341 Commits
Author SHA1 Message Date
bflatastic 12d1e6406d Merge main into #1801 2026-11-07 11:16:31 +07:00
BFlat b1078ae51c Merge pull request #1914 from TrevorBrown-dev/fix/jam-connector-auto-side
FigJam import: a connector end with an AUTO or CENTER magnet leaves the side facing the other end (#1909)
2026-11-07 12:14:59 +08:00
BFlat 7da396e1b3 Merge pull request #2067 from storytold/carry/1942
fix(geom): keep arc subdivision bounded whatever the radius
2026-11-07 12:14:07 +08:00
BFlat 00e7b3c9d9 Merge pull request #1800 from TheGalitube/fix/h7861-reject-overflowing-32-bit-varint
Reject overflowing u32 varints
2026-11-07 12:00:12 +08:00
bflatastic 4bb57d57ac Merge main into #1914 2026-11-07 10:57:26 +07:00
bflatastic f1a5360859 Merge main into #1942 2026-11-07 10:54:18 +07:00
bflatastic dbc9bf7236 Merge main into #1987 2026-11-07 10:34:16 +07:00
bflatastic 78ac626a8c Test: connector ends start 4 pt outside their sides since main's end gap 2026-11-07 10:31:29 +07:00
BFlat 7dbb439ed2 Merge pull request #1937 from storytold/fix/sec-format-charge-failed-reads
fix(format): charge an archive member's unpacking even when the read fails
2026-11-07 11:29:14 +08:00
Trevor BrownandClaude Fable 5.1 0257f53bb9 Test: a connector saved with its path starts 4 pt outside its sides, as main's connectors do
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 21:55:50 -05:00
Trevor Brown 4b351d9468 Merge remote-tracking branch 'upstream/main' into fix/jam-connector-control-points
# Conflicts:
#	crates/doc/src/lib.rs
#	docs/boards-parity.md
#	docs/file-format-parity.md
2026-11-06 21:55:49 -05:00
BFlat b20860829c Merge pull request #1983 from storytold/fix/sec-rest-variable-fill
fix(format): cap the bytes copied to fill a variable's missing modes
2026-11-07 10:43:14 +08:00
bflatastic 98494e4be8 Merge main into #1914 2026-11-07 09:39:45 +07:00
BFlat 2e7cafe662 Merge pull request #1876 from TrevorBrown-dev/feat/text-style-paragraph
M3: text styles carry paragraph spacing and indent, the list style and the OpenType features (#1829)
2026-11-07 10:29:27 +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 fa0698d6cb Merge pull request #1984 from storytold/fix/sec-sketch-run-work
fix(format): bound the characters Sketch text runs write in all
2026-11-07 10:23:58 +08:00
BFlat 9e83091a14 Merge pull request #1798 from TheGalitube/fix/h7855-duplicate-kiwi-field-ids-silentl
Reject duplicate Kiwi field IDs
2026-11-07 10:11:22 +08:00
bflatastic 3ebd14ad2d Merge main into #1876 2026-11-07 09:10:47 +07:00
BFlat 47b5979ef0 Merge pull request #2012 from TrevorBrown-dev/feat/text-direction-1830
M4: a paragraph's base direction can be forced: Text ▸ Text direction ▸ Left to right / Right to left (#1830)
2026-11-07 09:58:59 +08:00
bflatastic acbc988225 Schema test: its field ids no longer repeat, now that repeats are refused 2026-11-07 08:48:40 +07:00
bflatastic dfa6b858c8 Merge remote-tracking branch 'origin/main' into landb/1798 2026-11-07 08:45:50 +07:00
BFlat cbe6c414b8 Merge pull request #2064 from storytold/carry/1932
perf(format): index the lookups that made style-heavy files quadratic
2026-11-07 09:43:25 +08:00
Trevor BrownandClaude Fable 5.1 1ba0e1d62a Merge upstream/main into feat/text-style-paragraph
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 20:40:40 -05:00
Trevor BrownandClaude Fable 5.1 5a63c08a78 Merge upstream/main into feat/text-direction-1830
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 20:40:21 -05:00
BFlat 83054f771d Merge pull request #1971 from TrevorBrown-dev/fix/connector-end-gap
Boards: connectors attached to a side start 4 pt outside the border, as FigJam's do (#1916)
2026-11-07 09:37:24 +08:00
bflatastic 7eac937acc Style-heavy files: a repeated name keeps its first place, and one more lookup uses the id set 2026-11-07 08:24:02 +07:00
bflatastic 03aa619fba Merge main into #1932 2026-11-07 08:24:02 +07:00
bflatastic 638ae9bd29 Merge main into #1895 2026-11-07 08:20:40 +07:00
Trevor BrownandClaude Fable 5.1 2e331ecd70 Merge upstream/main into feat/text-style-paragraph
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 16:45:00 -05:00
Trevor BrownandClaude Fable 5.1 7c6bc49e9f Merge upstream/main into feat/text-direction-1830
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 16:41:34 -05:00
8da11395e9 M3: glass effects take variables, and their frost stays within 0–100 (#1886)
* M3: glass effects take variables, and their frost stays within 0–100 (#1885)

Glass's light angle, intensity, refraction, depth, dispersion, frost and
splay bind to number variables, on a layer (`variable.bind`,
`effects/<i>/<field>`, `radius` being the frost) and in an effect style
(`style.bind`), and the Design panel's glass fields take a variable like
the shadow and blur fields. A variable reads as the field shows it:
intensity, refraction, dispersion and splay in percent.

A glass's frost stays within 0–100, the reference tool's range: a larger
frost from an older file draws at 100, and the field, a bound variable and
scaling stop there.

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

* M3: a frost from an older file scales and shows as it draws (#1885)

Review follow-up: a frost saved past 100 draws at 100, so scaling a layer
starts from that (150 halved draws at 50, not 75) and the Design panel's
Frost field shows it. The revision row's binding path escapes its pipes, so
the table keeps three columns.

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

* M3: Dev Mode and .fig export show a frost from an older file as it draws (#1885)

A frost saved before the 0–100 range draws at 100, and the canvas, CSS and
scaling already read it that way, but Dev Mode's Glass row and the .fig
export still read the stored number, so they said 150 where the canvas
shows 100. Both now read the sanitized frost.

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

---------

Co-authored-by: Oleksii Sukholita <oleksii.sukholita@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 22:08:15 +01:00
Trevor BrownandClaude Fable 5.1 c16c3676c2 M4: text direction: CSS keeps Left beside a forced rtl, paragraph arrays read to the paragraph count, the empty last paragraph keeps its direction (#1830)
From review: `text-align: left` beside `direction: rtl` (Left is absolute on the canvas); the REST import reads the four parallel paragraph arrays up to the paragraph count, not the 256-entry list limit; `rtl_para` gets an entry for the position after the last char so an empty last paragraph keeps its forced direction; the localization status line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 03:46:28 -05:00
Trevor BrownandClaude Fable 5.1 cd5c9aeb09 M4: a paragraph's base direction can be forced: Text ▸ Text direction (#1830)
Direction came from the letters only; Add right-to-left text says controls override it per paragraph (Text > Text direction, Left to right or Right to left). `TextDirection` on `ParaList`, `Bidi::of` with a base level per paragraph, `text.direction` and the three menu commands (the edited paragraphs while editing), `node.set textDirection`, the Type settings control once a right-to-left script is in the text, inspect `textDirection` / `lineDirections`, the CSS `direction`, and the `.fig` lines' `sourceDirectionality` in and out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-05 03:23:07 -05:00
Trevor BrownandClaude Fable 5.1 31dcd0e5fe M6: a saved connector path going back into its object is routed instead (#1910)
Review: the first and last runs were checked for lying at right angles to their sides, not for
going out of the object, so a path whose first run went up through the start's box (or whose last
arrived from inside the end's) was kept and drawn through it. Both runs now have to go the way the
side faces; one along the side or back into the object leaves the connector to the router. Two more
cases in the routed-here test.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 03:46:49 -05:00
Trevor BrownandClaude Fable 5.1 bda7ca910d M6: a FigJam elbow connector saved with its path keeps it on import (#1910)
The `.jam` importer routed every elbowed connector itself, so one drawn around a layer by hand in
FigJam came in routed through it. FigJam saves the path as `connectorControlPoints`: one per run
between the first and the last, each a point on its run (in the connector's parent's coordinates)
with the run's axis; the first run leaves the start's side and the last arrives at the end's. They
become the `ConnectorPath::Runs` a board connector reshaped by hand has, walked by
`figcraft_doc::route_along`, so the path is kept exactly. Points that don't make such a path (off
both axes, two runs the same way in a row, a first or last run sideways to its side, more runs than
a hand could make) leave the connector to the router as before, and only connectors routed here
count in the "own routing" note. `figcraft_doc::attach_points` and `MAX_RUNS` are exported for it.
Docs: file-format-parity.md, boards-parity.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-04 01:52:02 -05:00
Dylan M. Taylor deee3d8acf fix(format): bound the characters Sketch text runs write in all 2026-11-04 04:50:11 +00:00
Dylan M. Taylor ddb8bfa665 fix(format): cap the bytes copied to fill a variable's missing modes 2026-11-04 04:44:24 +00:00
Dylan M. Taylor 519ffd4987 fix(format): import a Sketch page once however often the file lists it (#1973) 2026-11-03 23:01:19 -05:00
Dylan M. Taylor 88851996a0 fix(format): a saved file replaces its predecessor with the same permissions (#1957) 2026-11-03 22:22:03 -05:00
Trevor BrownandClaude Fable 5.1 2dc7bed2a0 M12: connectors attached to a side start 4 pt outside the border (#1916)
A connector end attached to a side was routed from the middle of that side, so the stroke's
centre line started on the border and half its width plus the round cap lay on the fill, and
several connectors leaving one side piled up into a knot sticking into the card. FigJam starts
the line a few pixels outside the border and connectors leaving one side run together along the
side's normal before they fan out (the issue's screenshots). `board_route::END_GAP` (4 pt): an end
attached to a side (`RouteEnd::Box`) starts that far outside it along the side's normal; an end
placed by hand on the object (`RouteEnd::On`) keeps its exact point; a free end is unchanged.
The pinned route coordinates in the engine, format and UI tests move with it; the overlay's
attach points stay on the border. Docs: the Connectors rows, status line and history of
boards-parity.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 21:08:19 -05:00
Dylan M. Taylor 411b16931b fix(geom): keep arc subdivision bounded whatever the radius
kurbo turns an arc into cubics by radius divided by tolerance, so the fixed
0.01 let an ellipse, polygon corner, rounded rectangle, per-side stroke
corner or SVG arc with a radius like 1e300 ask for more cubics than memory
holds before any size check saw the geometry. Arcs now use a tolerance
that scales with the radius, and SVG path data from documents, commands and
imports is refused before parsing when a number passes 1e9.
2026-11-03 15:45:48 +00:00
Trevor BrownandClaude Fable 5.1 fe18f6d96d M6: .jam import: a connector end with an AUTO or CENTER magnet faces the other end (#1909)
An end whose magnet is AUTO has no fixed point: FigJam picks the side when it draws, and the file
keeps whatever relativePosition the end last had, so the importer sent every automatic end out of
the stale side (the left one in the reporter's file). AUTO and CENTER ends now attach to the side
facing the other end, as a board connector drawn here does, for straight, elbowed and curved
connectors alike; named magnets and ends without one are unchanged.

Test: connectors_with_an_automatic_side_leave_the_side_facing_the_target (the fixture schema gains
the ConnectorMagnet enum). Docs: file-format-parity .jam row, status line and revision history.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-03 09:48:51 -05:00
Dylan M. Taylor 1206c0156e fix(format): charge an archive member's unpacking even when the read fails
The shared unpacking total (and Sketch's) was debited only when a member
read succeeded, so a snapshot or page member damaged near its end could be
named again and again by an index and unpacked each time without ever
spending the budget. A failed read is now charged its recorded size; one
refused for that size before any unpacking still costs nothing.
2026-11-03 14:41:56 +00:00
Dylan M. Taylor 37803cb1c8 perf(format): index the lookups that made style-heavy files quadratic
REST import searched every style imported so far for each layer's style
reference; .fig export compared each text span's style with every distinct
style before it; Penpot token-set ordering searched the whole order array
for every comparison of the sort. Each now looks up a set, a map or a
position table built once.
2026-11-03 14:30:05 +00:00
Dylan M. Taylor 35684eaa02 fix(format): index kiwi field lookups and bound schema and empty-array growth (#1896)
A .fig message looked up each tag's field with a linear scan of its
definition, so a wide schema and many tags cost their product. Schema
decoding capped fields per definition but not in total, and inflated the
schema chunk under the 512 MiB message cap. Empty non-byte arrays spent
nothing from the decoded-value budget while still growing the output.

Field ids (and enum values) are now found through a per-definition map
built on first use, keeping first-match semantics. A schema holds at most
200 000 fields together and its chunk may inflate to 16 MiB. Every array
field spends one value for itself before its elements.
2026-11-03 06:09:08 -05:00
c69eb55f31 M8: an ellipse whose start has gone round many turns keeps its sweep in .fig (#1891)
* M8: an ellipse whose start has gone round many turns keeps its sweep in .fig

The importer clamped the starting and ending angles to ±1000 rad each
before taking the sweep, so an arc starting near or past 1000 rad lost
its sweep: a whole ellipse starting at 999 came back sweeping 1 rad, one
starting at 1500 swept nothing. The sweep is now the difference of the
angles as written, and a start past a turn is brought into [0, 2π) the
way the renderer's sine and cosine see it. The .fig export writes the
start within a turn too, where a 32-bit angle still holds it and the
sweep.

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

* M8: an arc far from zero keeps its sweep rather than closing into a whole ellipse

Review follow-up on #1891. The whole-turn tolerance grows with the
angles, so once a start can lie far from zero it swallowed real arcs: an
arc of 6.1 rad written at 1e6 rad read as a whole ellipse, and at 3e7 rad
even a sweep of nothing did. The tolerance now only applies while it is
at most 1e-3 rad (about 0.06°), which holds up to a start of about 4,000
rad; further out the sweep is kept as written.

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

* M8: an arc with no end stays whole however far out it starts

Review follow-up on #1891. With no endingAngle the importer took the end
as start + 2π and the sweep as the difference, which far from zero
rounds away from a turn: from a start of 1e15 it came out 6.25, drawn as
an arc with its radius edges now that the snap to a whole turn is
bounded. No end now means a sweep of exactly 2π.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:13:17 -05:00
Dylan M. TaylorandBrandon Thomas 0eec582805 perf(format): drop dangling Penpot token aliases with a worklist (#1897)
drop_dangling_aliases rebuilt the id set and rescanned every token each
round, and an alias chain lost one link per round: quadratic in the
chain. Dependencies are now indexed once and invalidation propagates
through a queue, counting how many tokens make each variable id, so each
token is visited a bounded number of times with the same result.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:13:07 -05:00
Dylan M. Taylor 19afdd21f9 fix(format): import a .fig blob once however many hashes name it
Distinct image hashes pointing at one embedded blob shared its copy, but
the import resolved each hash separately: the blob was copied out and
hashed again for every alias. Aliases without an images/ member of their
own are now renamed to the first hash naming the blob before layers are
built, so the blob is copied and hashed once.

Image paints also spelled out their whole hash as hex for every layer,
whatever its length; hashes longer than any image can be named by (64
bytes) now resolve to nothing, as a missing hash does.
2026-11-02 20:44:37 -05:00
Trevor BrownandClaude Fable 5.1 695ca07528 M3: text styles carry paragraph spacing and indent, the list style and the OpenType features (#1829)
ParagraphStyle beside TextStyle in StyleKind::Text: style.create takes it from the layer or a value, style.apply sets it (the list on the paragraphs touched for a range), style.update moves it, editing it unlinks, .fig files keep it with the style; tests in styles_behaviour_tests and fig_export_assets_tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-02 06:43:02 -05:00
mortezanaeemi-ops 5c9bb79b1a Add files via upload 2026-11-02 10:34:09 +03:30