Commit Graph
26 Commits
Author SHA1 Message Date
BFlat e3e39bbc4d M6: Dev Mode measurements kept on the canvas, a read-only layers panel (#1248)
* M6: Dev Mode measurements kept on the canvas, a read-only layers panel

Measurements placed between two layers, along a side or to the layer
around one (devMode.addMeasurement / removeMeasurement / measurements),
stored on the layer they start from (saved, synced, undoable; old files
open without them), drawn in Dev Mode as labelled redlines that follow
the layers and listed, placed and removed in the Inspect tab. In Dev Mode
the layers panel only selects and expands.

* M6: Dev Mode measurements: explicit imports, a measurement row of its own (Sonar)
2026-10-24 13:33:26 +07:00
bflatastic 8dcba0684b Batch r2-1 review: .fig export grid sizing in helpers; instance layers clear inherited track sizes
auto_layout's grid branch moves to grid_layout and grid_sizing (SonarCloud cognitive complexity).
unset() clears gridColumnsSizing and gridRowsSizing, so an instance layer with equal tracks no
longer reads back its main layer's track sizes.
2026-10-24 10:31:08 +07:00
xiaou61 beb87d8696 M1: describe the fallback the export actually takes (#465)
The warning still said fixed and content-sized tracks were written as equal
tracks, which the export no longer does. It now reports only the tracks whose
size the format can't hold, and only for the tracks an exported grid has.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:14:02 +08:00
xiaou61 414aa1b63c M1: write grid track sizes in .fig export (#465)
The import has read `gridColumnsSizing` / `gridRowsSizing` since #630, but the
export wrote only equal tracks and warned about it, so a grid with a fixed
column beside two weighted ones came back equal after a save and reload.

Write the sizing map next to the order map, keyed by the same track ids: fixed
px, fractional fill and content-sized tracks keep their sizes. A track the
format has no name for (negative or out-of-range size, or a document track the
frame doesn't list) keeps its id and reads back equal, with the existing
warning.

Co-authored-by: xiaou61 <113765024+xiaou61@users.noreply.github.com>
2026-10-20 11:04:13 +08:00
skylar taylor-barrick 3f5f673ebc Merge pull request #777 from skylartaylor/document-link-schemes
Open only web links and local libraries from a document
2026-10-17 14:34:58 -06:00
skylar taylor-barrick c7acd1a81d chore: clear the rustc 1.96 clippy lints that fail xtask ci
Unrelated to the security changes in this branch; `main` already failed
`clippy --workspace --all-targets -- -D warnings` without them. Eight
clippy::nonminimal_bool sites across seven files, each taking clippy's own
suggested rewrite.

Every rewrite is pure boolean algebra with the negation kept outside, so the
float comparisons and their NaN behaviour are untouched: !A && !B becomes
!(A || B), and !opt.is_some_and(f) becomes opt.is_none_or(!f). Two sites needed
explicit parentheses the suggestion omitted, where the rewritten term is the left
operand of an && and || binds looser.

designcraft cleared the same lint in bc840130; this is figcraft's equivalent.
2026-10-17 13:35:44 -06:00
bflatastic 9a69f79861 #552: write and read space evenly as SPACE_EVENLY_CSS
The format's SPACE_EVENLY is its older name for equal gaps without edge
space, so files keep reading it as space between; true space evenly is
SPACE_EVENLY_CSS, which the export now writes.
2026-10-17 22:39:10 +07:00
bflatastic 6cdfe2ab8e M3: layers inside instances keep their resizing overrides (#731)
A layer inside an instance can now override its resizing (hug, fill or
fixed, with min and max sizes; override group `resizing`) and its size.
They were refused, and imported ones were dropped at the first relayout.
Kept through rebuilds, undo, save, .fig and REST import, .fig export and
detach; reset per group and with Reset all changes. Position, constraints
and absolute or grid placement stay the main's.
2026-10-17 19:27:35 +07:00
bflatastic 546c09b3ba Merge fix/all-f2 into batch 10 2026-10-17 15:26:45 +07:00
bflatastic 3881cce291 Merge fix/632 2026-10-17 14:05:08 +07:00
Skyfuller1303 d9e79d604b M1: distinguish unresolved vector segment style metadata in .fig import
The vector network decoder now keeps each segment's style id. Import reports a segment style that names a row of the layer's style table as metadata that was not interpreted, and one that names no row as a reference to a missing style entry, each at most once per file. The earlier note claimed that stroke styles were left out, which overstated what is known about the metadata. Segment geometry, paint and width are unchanged, and the style ids are not kept in the native vector model.
2026-10-17 13:45:55 +07:00
bflatastic ee3e3f0bd4 #637: skip ink only counts under an underline
The public docs don't give a default for skip ink; they say the value is unset when the text
isn't underlined, and the exporter always wrote it on. So the export keeps writing on for text
without an underline and the real value under one (that's what the canvas draws), and the import
leaves a value on text without an underline out of the decoration look, so plain text keeps the
default look and round-trips unchanged. The native default stays off, so existing documents
draw as before.
2026-10-17 13:44:34 +07:00
RealtaNuaandClaude Opus 5.5 d27f45e063 fix(format): write a list's start on every line; report it in get_node (#686)
The .fig export now writes a numbered list's start on each of its lines,
so a reader that takes it per line or per list counts the same. Bullets
no longer keep a start. The field is named start_offset (it holds the
offset: 1 shows "2."). get_node reports listStartOffsets. The parity doc
lists what's still not read from .fig lists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 00:58:20 +07:00
RealtaNuaandClaude Opus 5.5 60fa813eb2 fix(format): numbered lists from .fig files keep their start number (#686)
Figma stores where a numbered list starts counting on each text line
(listStartOffset; a list shown as "2." has offset 1). The import dropped
it, so every list counted from 1.

A paragraph's list settings now carry a start number (written only when
set, so older files open and save unchanged). Numbering counts on from
it where a list starts; items that go on counting ignore it. The .fig
import reads it, the .fig export writes it on a list's first line, and
plain paragraphs drop it. Toggling a list off ignores differing starts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 00:27:34 +07:00
Skyfuller1303 3723fcffbd M1: keep text decoration skip ink from .fig files
Text layers, styled runs and text styles in .fig files carry a skip-ink boolean for their decoration. The importer now reads it into the native decoration look: an omitted run value inherits the layer's, a local value overrides a linked shared text style without changing the style itself, and instance overrides keep an explicit false over a true main. A malformed value counts as unset. The exporter writes the actual value of the layer and of each styled run instead of a constant true, and a decoration look that differs from the default only in skip ink no longer triggers the unsupported-decoration note, so default native text exports its real false value.
2026-10-16 05:03:34 +02:00
bflatastic fd6a7e0ba3 Merge main into #549: keep #539's whole URL and add the empty-URL arm 2026-10-16 02:33:05 +07:00
NetterSilverandClaude Haiku 5.5 a57976e88f Keep frame min/max size in .fig export without an auto layout parent
Fixes #457

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:00:16 +02:00
NetterSilverandClaude Haiku 5.5 3c28f605d1 Keep cleared range hyperlinks through FIG export and import
Fixes #458

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:55:48 +00:00
bflatastic a399ff8934 M3: variables inside styles 2026-10-13 13:36:54 +07:00
bflatastic 5548284c65 M5: prototyping: delays on mouse triggers, manual overlay offsets 2026-10-13 13:36:54 +07:00
bflatastic a2a0f80252 M2: auto layout: wrapped-line alignment 2026-10-13 13:36:54 +07:00
bflatastic c910060f36 M7: .fig export: one-sided stroke widths as outlines, bound layer fields name their field; docs
A variable width is written only as the same reach on both sides of the path (a
point with unequal sides drew nothing elsewhere). A stroke wider on one side, on a
shape that shows nothing else, is written as a vector filled with the area it
paints, with the stroke itself kept as plugin data that the import puts back.
Bound layer and text fields also name the number of the field they set. Docs: text,
effect and grid styles and symmetric variable widths are checked; non-colour
bindings come across as values.
2026-10-13 10:05:39 +07:00
bflatastic 3efff16df2 M7: .fig export: stroke reach as a share of the weight; docs
Variable-width points say each side's reach as a share of the stroke weight (in
pixels they drew far too wide elsewhere). Progress docs: instance sizes, variables
with modes and shared styles in the .fig export, and what was checked by opening
written files in other software.
2026-10-13 10:05:39 +07:00
bflatastic 03b173fd98 M7: .fig export: instance sizes, variables and modes, shared styles, variable-width strokes
Instances write what they change from their main as an override addressed by the
main's id (size, name, paints), what follows from that for their layers as derived
data (size, place, outline, colours their variables give), and a scale factor when
they were scaled as a whole; layers of a nested instance swapped for another
component keep their overrides. Variable collections, modes and variables are nodes
on a hidden page; paints, gradient stops, effects, layout grids and layer fields
name the variables bound to them, and frames and pages the modes they set. Shared
styles are nodes there too, linked from the layers that use them. Variable-width
strokes say their reach on each side of the path. The import reads all of it back
(style definitions and unused styles, variable order and scopes, bindings on paint
lists, stops, effects and grids, page modes, text bindings, width profiles).
2026-10-13 10:05:39 +07:00
bflatastic 0e6dc6871b M7: .fig export that opens and draws in other software
- The document and its pages now say they are visible and opaque, and a page carries its background opacity and enabled flag. Left out, a reader took them as hidden and drew nothing: every frame was blank and the page background transparent.
- The schema written into the file uses the format's own names, kinds (struct or message), field ids and enum values for everything the export writes. The schema text parser takes `NAME=n` and `type field=n`.
- Rectangles are written as rounded rectangles with their four radii. Frames, components, instances and sections carry their fill outline; plain strokes carry their stroke outline (a ring around boxes and whole ellipses, the widened path of other centred strokes).
- Text carries its layout settings and one line entry per paragraph. Font styles are written under their standard names ("SemiBold" becomes "Semi Bold", "Regular Italic" becomes "Italic"); the import reads both spellings as the same style.
- A line height that is a share of the font size is written as a raw multiplier: percent units count from the font's own line height, so 140 % was laid out taller than in FigCraft.
- Masks keep their type (alpha, outline, luminance), written and read back.
- The import reads per-side stroke weights whose zero sides are left out (a bottom-only border no longer comes back on all four sides).
- `meta.json` gives the page colour and the thumbnail size; archive members are in the usual order. Texture effects are written under the format's grain type; WebP export settings are written as PNG with a warning (the format has none).
- Docs: `docs/file-format-parity.md`, `docs/gaps.md` (gap 1), `ROADMAP.md`, `docs/target-app-parity.md` (wording only; no number moves).
2026-10-12 17:54:18 +07:00
bflatastic c420721597 M6: experimental .fig export
An experimental, best-effort `.fig` export, so work done in FigCraft can be handed back as a `.fig` file. It is written from the same public format write-ups the importer uses and is the inverse of the importer's mapping. Only round trips through FigCraft's own importer are verified; other apps may not open every file.

- **Encoder** (`crates/format/src/fig/kiwi.rs`): the kiwi encoder that test fixtures used is now shipped code. It never panics and checks every value against the schema: unknown fields or enum names, missing struct fields, wrong types, out-of-range numbers and non-finite floats are errors, and NULs inside strings are dropped. Varints and floats are written exactly the way the decoder reads them. `Schema::parse` reads the compact text schema form, and the importer's tests use it too.
- **Writer** (`crates/format/src/fig/export.rs`): writes the zip (`canvas.fig`, `meta.json`, `thumbnail.png`, `images/<hash>`). `canvas.fig` holds the fig-kiwi header, then a raw-deflate binary schema and a raw-deflate message (flate2's pure-Rust backend, already in the tree; no new dependencies). The schema covers only the fields we write. The canvas, images, blobs, node count and nesting depth are capped, and floats are made finite before they are written.
- **Mapping:** pages; frames, groups, sections, rectangles, ellipses (arcs), polygons, stars, lines and vectors (vector network blobs with per-point radius, mirroring and caps, plus fill outlines); text with styled runs (UTF-16 indexed) and list types; slices; boolean groups; solid, gradient and image paints (image bytes included); strokes; effects; corners; constraints; masks; auto layout, including grids with tracks and cells; child layout; layout grids; export settings; components, component sets and their properties. Instances are written as their main plus the fields their layers change, by id path. This covers nested instances and swaps, and fields the main sets but the instance clears. Prototype interactions (triggers, navigation and overlay actions, transitions, easing), scrolling and flow starts are included. Everything skipped is reported in a warnings list: style links, variables, comments, Dev Mode data, plugin data, guides, slots, brush and width-profile strokes, pattern fills, image adjustments, variable and conditional actions, and more.
- **Import fixes found by the round trips** (`map.rs`): ellipse arcs, boolean operation types and component set descriptions are now read. Durations are read as whole milliseconds, because seconds stored as 32-bit floats used to truncate by 1 ms.
- **File writes:** a failed write no longer leaves its `.tmp-figcraft` file behind. The junk-parameter sweep found this.
- **Commands and UI:**
  - `file.export {format: "fig"}`, or any path ending in `.fig`, writes the whole file and returns `warnings`. Without a path it returns base64 `data`.
  - File > Export... has a new "The whole file" section with a ".fig (experimental)" row. A note under it says other apps may not open every exported file. After export, a toast reports how many kinds of content were left out.
  - `figcraft-cli export --format fig` (or `-o x.fig`) and `convert` to `.fig` print the warnings as `note:` lines.
  - MCP's `export` accepts `format: "fig"` and passes the warnings through.
  - New UI strings have English and Spanish entries.
- **Docs:** file-format-parity.md has a new export section covering what is written, what is skipped, and that only round trips through our own importer are verified. Also updated: mcp.md, cli.md, gaps.md (gap 1), target-app-parity.md and ROADMAP.md. Grading is kept low. Import/export goes to 86%/58% and file formats to 60%. The file-exchange discount stays at x0.85, because nothing shows that another app opens these files.
2026-10-12 07:19:57 +07:00