Merge main into #2324

This commit is contained in:
bflatastic committed 2026-11-13 23:01:23 +07:00
commit b0b23d8c63
7 files changed
+147 -12

No files matched your search

+38
View File
@@ -383,3 +383,41 @@ fn the_caps_refuse_what_is_too_far_and_cut_what_is_too_long() {
assert_eq!(text_data(&s, id(&out, "id")).characters.chars().count(), 500);
assert_eq!(text_data(&s, id(&out["children"][0], "id")).characters.chars().count(), 500);
}
/// Where a node's own origin (the place `x`/`y` name) is on the page.
fn origin(s: &Session, n: NodeId) -> Point {
s.doc().unwrap().world_transform(n) * Point::new(0.0, 0.0)
}
#[test]
fn a_mind_map_in_a_section_without_a_place_is_centred_on_the_view() {
let mut s = board();
let (vx, vy) = {
let v = &s.ds().unwrap().view;
(v.cx, v.cy)
};
let sec = id(&s.execute("node.create", &json!({"type": "section", "x": 1000, "y": 500, "width": 4000, "height": 4000})).unwrap(), "id");
let out = s.execute("board.mindMap", &json!({"parent": sec})).unwrap();
let c = origin(&s, id(&out, "id"));
assert!(same(c.x, vx) && same(c.y, vy), "the root's place {c:?} is the view's centre ({vx}, {vy})");
}
#[test]
fn a_mind_map_in_a_turned_frame_without_a_place_is_centred_on_the_view_and_a_given_place_is_the_frames() {
let mut s = board();
let (vx, vy) = {
let v = &s.ds().unwrap().view;
(v.cx, v.cy)
};
let fr = id(&s.execute("node.create", &json!({"type": "frame", "x": 300, "y": 200, "width": 2000, "height": 2000})).unwrap(), "id");
s.execute("node.set", &json!({"id": fr, "props": {"rotation": 30}})).unwrap();
let out = s.execute("board.mindMap", &json!({"parent": fr})).unwrap();
let c = origin(&s, id(&out, "id"));
assert!(same(c.x, vx) && same(c.y, vy), "the root's place {c:?} is the view's centre ({vx}, {vy})");
// A given place is the frame's own, as on the page: (10, 20) of the frame, carried through it.
let given = id(&s.execute("board.mindMap", &json!({"parent": fr, "x": 10, "y": 20})).unwrap(), "id");
let want = s.doc().unwrap().world_transform(fr) * Point::new(10.0, 20.0);
let got = origin(&s, given);
assert!(same(got.x, want.x) && same(got.y, want.y), "{got:?} against {want:?}");
}
+6 -2
View File
@@ -168,9 +168,13 @@ pub(super) fn mind_map_cmd(s: &mut Session, p: &Value) -> Result<Value> {
let cmd = "board.mindMap";
require(s, cmd)?;
let parent = parent_of(cmd, s, p)?;
// The view's centre is on the page, and `x`/`y` are in the parent: a map in a section or frame
// takes the centre in that parent's coordinates (a parent that can't be inverted gives a
// non-finite centre, which `coord` refuses).
let (vx, vy) = {
let v = &s.ds()?.view;
(v.cx, v.cy)
let ds = s.ds()?;
let c = ds.doc.world_transform(parent).inverse() * figcraft_geom::Point::new(ds.view.cx, ds.view.cy);
(c.x, c.y)
};
let (x, y) = (coord(cmd, p, "x", vx)?, coord(cmd, p, "y", vy)?);
let texts = starters(cmd, p)?;
+2 -1
View File
@@ -142,7 +142,8 @@ message StyleId GUID guid=1
message VariableID GUID guid=1
message VariableSetID GUID guid=1
message ColorStopVar Color color=1, VariableData colorVar=2, float position=3
message Paint PaintType type=1, Color color=2, float opacity=3, bool visible=4, BlendMode blendMode=5, ColorStop[] stops=6, Matrix transform=7, Image image=8, ImageScaleMode imageScaleMode=10, float rotation=11, float scale=12, VariableData colorVar=21, ColorStopVar[] stopsVar=23
message PaintFilterMessage float tint=1, float shadows=2, float highlights=3, float exposure=5, float temperature=7, float vibrance=8, float contrast=9
message Paint PaintType type=1, Color color=2, float opacity=3, bool visible=4, BlendMode blendMode=5, ColorStop[] stops=6, Matrix transform=7, Image image=8, ImageScaleMode imageScaleMode=10, float rotation=11, float scale=12, PaintFilterMessage paintFilter=14, VariableData colorVar=21, ColorStopVar[] stopsVar=23
message Effect EffectType type=1, Color color=2, Vector offset=3, float radius=4, bool visible=5, BlendMode blendMode=6, float spread=7, bool showShadowBehindNode=8, BlurOpType blurOpType=19, float startRadius=21, Vector startOffset=20, Vector endOffset=28, NoiseType noiseType=26, Vector noiseSize=22, float density=25, float opacity=27, Color secondaryColor=24, bool clipToShape=23, VariableData radiusVar=9, VariableData colorVar=10, VariableData spreadVar=11, VariableData xVar=12, VariableData yVar=13, VariableData startRadiusVar=45
struct FontName string family, string style, string postscript
struct Number float value, NumberUnits units
+15 -2
View File
@@ -76,8 +76,21 @@ impl<'d> Exporter<'d> {
if let (ImageScaleMode::Crop, Some(c)) = (img.scale_mode, img.crop) {
f.push(("transform", matrix(Affine::new(c))));
}
if img.filters != Default::default() {
self.warn("image adjustments (exposure, contrast, …) were left out");
let x = img.filters.sanitized();
if !x.is_identity() {
// The file's tint is positive towards magenta, ours towards green; `0.0 -` keeps zero positive.
f.push((
"paintFilter",
o(vec![
("exposure", fl(f64::from(x.exposure))),
("contrast", fl(f64::from(x.contrast))),
("vibrance", fl(f64::from(x.saturation))),
("temperature", fl(f64::from(x.temperature))),
("tint", fl(0.0 - f64::from(x.tint))),
("highlights", fl(f64::from(x.highlights))),
("shadows", fl(f64::from(x.shadows))),
]),
));
}
}
PaintKind::Pattern(_) => {
+81 -3
View File
@@ -16,8 +16,8 @@ fn image_filter_schema() -> Schema {
Schema::parse(&text).unwrap()
}
#[test]
fn image_filters_import_from_current_container_and_skip_ambiguous_data() {
/// A file with one image rectangle per filter case (current container, legacy, both, neutral, unknown fields).
fn image_filter_fixture() -> Vec<u8> {
let filter = |fields: Vec<(&'static str, Kv<'static>)>| o(fields);
let image_paint = |id: u64, name: &'static str, mut extra: Vec<(&'static str, Kv<'static>)>| {
let mut paint = vec![("type", e("IMAGE")), ("image", o(vec![("hash", Kv::Bytes(HASH.to_vec()))]))];
@@ -54,7 +54,12 @@ fn image_filters_import_from_current_container_and_skip_ambiguous_data() {
vec![("paintFilter", filter(vec![("brightness", f(0.4)), ("detail", f(-0.2)), ("vignette", f(0.3)), ("mystery", f(0.1))]))],
),
];
let bytes = zip_file(&canvas(&image_filter_schema(), &o(vec![("nodeChanges", arr(nodes))])));
zip_file(&canvas(&image_filter_schema(), &o(vec![("nodeChanges", arr(nodes))])))
}
#[test]
fn image_filters_import_from_current_container_and_skip_ambiguous_data() {
let bytes = image_filter_fixture();
let (doc, warnings) = import(&bytes, "ImageFilters.fig").unwrap();
let get = |name: &str| {
let node = doc.nodes().find(|node| node.name == name).unwrap();
@@ -102,3 +107,76 @@ fn warnings_naming_unknown_filter_fields_are_capped() {
let named = warnings.iter().filter(|w| w.contains("unsupported image filter")).count();
assert!((100..=crate::rest::MAX_WARNINGS).contains(&named), "{named} of {} warnings", warnings.len());
}
#[test]
fn image_adjustments_survive_a_fig_export() {
let (mut doc, _) = import(&image_filter_fixture(), "ImageFilters.fig").unwrap();
let filters_of = |doc: &figcraft_doc::Document, name: &str| {
let node = doc.nodes().find(|node| node.name == name).unwrap();
let figcraft_doc::color::PaintKind::Image(image) = &node.fills[0].kind else { panic!("{name} is not an image fill") };
image.filters
};
// Values a document can hold and the file can't: out of range, and not a number.
let ids: Vec<figcraft_doc::NodeId> = doc.ids().collect();
for id in ids {
let Some(node) = doc.node_mut(id) else { continue };
let name = node.name.clone();
let Some(figcraft_doc::color::PaintKind::Image(image)) = node.fills.first_mut().map(|p| &mut p.kind) else { continue };
match name.as_str() {
"Legacy" => image.filters.exposure = 5.0,
"Explicit neutral" => image.filters.contrast = f32::NAN,
_ => {}
}
}
let (bytes, warnings) = export::export(&doc, None).unwrap();
assert!(!warnings.iter().any(|w| w.contains("image adjustments")), "{warnings:?}");
let (back, _) = import(&bytes, "Back.fig").unwrap();
for name in ["Current", "Negative tint", "Neutral"] {
let (a, b) = (filters_of(&doc, name), filters_of(&back, name));
for (x, y) in [
(a.exposure, b.exposure),
(a.contrast, b.contrast),
(a.saturation, b.saturation),
(a.temperature, b.temperature),
(a.tint, b.tint),
(a.highlights, b.highlights),
(a.shadows, b.shadows),
] {
assert!((x - y).abs() < 1e-6, "{name}: {a:?} came back as {b:?}");
}
}
assert!(filters_of(&back, "Neutral").is_identity());
assert!(!filters_of(&back, "Current").is_identity());
// What is written: the file's names and signs.
let archive = Archive::open(&bytes).unwrap();
let (schema, message) = decode_canvas(&archive.canvas).unwrap();
let msg = schema.decode_message(schema.find("Message").unwrap(), &message).unwrap();
let fill_of = |name: &str| {
let nc = msg.get("nodeChanges").unwrap().list().iter().find(|nc| nc.get("name").and_then(Kv::str) == Some(name)).unwrap();
nc.get("fillPaints").unwrap().list()[0].clone()
};
let current = fill_of("Current");
let written = |paint: &Kv, field: &str| paint.get("paintFilter").and_then(|f| f.get(field)).and_then(Kv::f64);
assert!((written(&current, "vibrance").unwrap() + 0.4).abs() < 1e-6);
assert!((written(&current, "tint").unwrap() - 1.0).abs() < 1e-6);
assert!((written(&current, "shadows").unwrap() - 0.7).abs() < 1e-6);
assert!(fill_of("Neutral").get("paintFilter").is_none());
assert!((written(&fill_of("Legacy"), "exposure").unwrap() - 1.0).abs() < 1e-6, "written within the file's range");
assert!(fill_of("Explicit neutral").get("paintFilter").is_none(), "not a number is no adjustment");
let partly = fill_of("Negative tint");
for field in ["exposure", "contrast", "vibrance", "temperature", "tint", "highlights", "shadows"] {
assert!(written(&partly, field).is_some(), "{field} is written");
}
assert!((written(&partly, "tint").unwrap() + 1.0).abs() < 1e-6);
// The numbers the format uses.
let s = Schema::parse(export::SCHEMA).unwrap();
let def = |n: &str| s.defs.iter().find(|d| d.name == n).unwrap();
let id = |d: &str, f: &str| def(d).fields.iter().find(|x| x.name == f).unwrap().value;
assert_eq!(id("Paint", "paintFilter"), 14);
assert_eq!(
["tint", "shadows", "highlights", "exposure", "temperature", "vibrance", "contrast"].map(|f| id("PaintFilterMessage", f)),
[1, 2, 3, 5, 7, 8, 9]
);
}
+4 -3
View File
@@ -1,6 +1,6 @@
# File format parity
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-13 · **Change:** minor (`.fig` export writes text on a path (#2323); `.fig` import: a numbered list keeps its start only where it starts counting (#2273); `.fig` export writes a page's prototype background (#2326); `.fig` export writes list spacing, hanging punctuation and lists, and vertical trim (#2305); `.fig` import and export read and write a boolean group's corner radius and smoothing (#2222); `.jam` import: stickies come in as native board stickies (#1808); `.fig` import scales per-point vector corner radii with the row that supplies their network and keeps different instance scales separate in the network cache (#2097); `.fig` export preserves scaled-instance lengths reset to their unscaled main values, including nested linked-style overrides (#2097); `.fig` import: the layers of an instance scaled with the Scale tool draw at its scale factor, not at their main's lengths (#2091); a color profile per PNG, JPG and WebP export setting (Same as file, sRGB or Display P3), the pixels converted; a `.jam` connector end with an `AUTO` or `CENTER` magnet leaves the side facing the other end, not the side its stale saved point named (#1909); a FigJam elbow connector saved with its path (`connectorControlPoints`) keeps it on import, as a board connector reshaped by hand does, instead of being routed again (#1910); JPG exports take a quality, 1 to 100 with 90 the default, and embed the file's colour profile when it is sRGB too (#2004); a FigJam connector without a saved snapshot took no stroke and was invisible; its line's paint, weight, cap and dashes now come from the file's generated line record (#1807); a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); imported and pasted text finds an installed family under the other names its fonts give it, and with other case and spacing (#1292); `.fig` export writes colour calculations as the colour they show in each mode and leaves out, with a warning, one that resolves to nothing (#1280); `.fig` import budgets four decoded values per compressed byte instead of one, still within 20M–128M, so dense files within that budget open instead of being refused as having too many values (#574); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; folders, devices and pipes are refused before anything is read (#1114); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); a FigJam `.jam` file opens as a board (#1342); FigJam `.jam` files open like `.fig` files: File ▸ Open, drag and drop, `file.import` and the command line (#1342); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); SVG import draws each pattern tile once per scale, within the layer budget (#1080); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); offline Penpot 2.x import/export (#1143); `.sketch` import checked against Sketch's renders: master backgrounds, symbol clipping, tints before Sketch 64, mask opacity, bitmap fills, image overrides on shapes, stretching stacks, blur radii, colour adjustments, OKLab gradients, text growth, shape group borders (#1389); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); image member names are checked on read, and REST image placeholders get archive-safe keys (#1086); Android Vector Drawable (XML) export; `.fig` export: files the incumbent imports, with component set properties, and grid track sizes at its field ids; Save and auto-save don't replace changes another app made to the open file (#1330); `.sketch` import, best effort, Sketch 48 and newer; the PNG row names ignore overlapping layers and where include bounding box is offered; the Fonts table's missing-font row describes the Missing fonts window; `.figcraft` carriLine truncated
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-13 · **Change:** minor (`.fig` export writes text on a path (#2323); `.fig` export writes image adjustments (#2297); `.fig` import: a numbered list keeps its start only where it starts counting (#2273); `.fig` export writes a page's prototype background (#2326); `.fig` export writes list spacing, hanging punctuation and lists, and vertical trim (#2305); `.fig` import and export read and write a boolean group's corner radius and smoothing (#2222); `.jam` import: stickies come in as native board stickies (#1808); `.fig` import scales per-point vector corner radii with the row that supplies their network and keeps different instance scales separate in the network cache (#2097); `.fig` export preserves scaled-instance lengths reset to their unscaled main values, including nested linked-style overrides (#2097); `.fig` import: the layers of an instance scaled with the Scale tool draw at its scale factor, not at their main's lengths (#2091); a color profile per PNG, JPG and WebP export setting (Same as file, sRGB or Display P3), the pixels converted; a `.jam` connector end with an `AUTO` or `CENTER` magnet leaves the side facing the other end, not the side its stale saved point named (#1909); a FigJam elbow connector saved with its path (`connectorControlPoints`) keeps it on import, as a board connector reshaped by hand does, instead of being routed again (#1910); JPG exports take a quality, 1 to 100 with 90 the default, and embed the file's colour profile when it is sRGB too (#2004); a FigJam connector without a saved snapshot took no stroke and was invisible; its line's paint, weight, cap and dashes now come from the file's generated line record (#1807); a Dash cap setting (none, round or square) on Dash and Custom strokes, drawn on both ends of every dash; pasted layers whose image the clipboard lacks are named and show a placeholder that reads as missing (#1646); paint-style colours on layers pasted from another design app keep their style instead of turning black (#1638); imported and pasted text finds an installed family under the other names its fonts give it, and with other case and spacing (#1292); `.fig` export writes colour calculations as the colour they show in each mode and leaves out, with a warning, one that resolves to nothing (#1280); `.fig` import budgets four decoded values per compressed byte instead of one, still within 20M–128M, so dense files within that budget open instead of being refused as having too many values (#574); `.fig` ZIP image reads share a total byte budget and count skipped images in the import warnings (#1076); every file a command reads whole by path is a regular file within a cap that fits it: documents 16 GiB, placed images 256 MiB, CLI scripts 1 GiB; folders, devices and pipes are refused before anything is read (#1114); files nested 256 levels deep import on the browser's 1 MB stack (#1101); SVG and PDF exports count pattern sources and the renders they embed from the layer's depth, so one cap bounds a whole export (#1082); opening a `.figcraft` file unpacks at most 6 GiB in all (2 GiB in the browser), every area together (#1079); `.fig` instance expansion no longer walks variable nodes for every instance or copies every outer override row into each nested instance (#1077); a FigJam `.jam` file opens as a board (#1342); FigJam `.jam` files open like `.fig` files: File ▸ Open, drag and drop, `file.import` and the command line (#1342); SVG import reads `color()` and `color-mix()` colours, with a note and a grey for those it can't (#1550); SVG import draws each pattern tile once per scale, within the layer budget (#1080); a file's JPEG thumbnail can't crash the home screen (#1491's progressive JPEG check, now shared); `.fig` import: library text styles linked by asset key, and override rows restating a nested level (#1234); `.fig` import keeps the file's saved line breaks while the text and its box are unedited (#1236); Download a missing free font from the Missing fonts window (`font.download`, #754); offline Penpot 2.x import/export (#1143); `.sketch` import checked against Sketch's renders: master backgrounds, symbol clipping, tints before Sketch 64, mask opacity, bitmap fills, image overrides on shapes, stretching stacks, blur radii, colour adjustments, OKLab gradients, text growth, shape group borders (#1389); FigJam outer shape outlines and sticky body fallback beside author text (#1341, #1397); image member names are checked on read, and REST image placeholders get archive-safe keys (#1086); Android Vector Drawable (XML) export; `.fig` export: files the incumbent imports, with component set properties, and grid track sizes at its field ids; Save and auto-save don't replace changes another app made to the open file (#1330); `.sketch` import, best effort, Sketch 48 and newer; the PNG row names ignore overlapping layers and where include bounding box is offered; the Fonts table's missing-font row descrLine truncated
Every format Figma reads or writes, with FigCraft's support. Sources: `Info.plist` of
`/Applications/Figma.app` (document types only; nothing inside the bundle's code was read), Figma's
@@ -98,7 +98,7 @@ scales and empty-style clears; they have not been checked in other software.
stroke outline of plain strokes (a ring around boxes and whole ellipses, the widened path of other
centred strokes);
solid, gradient (handles as the paint transform) and image paints (the image bytes, named by their SHA-1, scale mode,
rotation, tile scale, crop); strokes (weight, align, join, caps, dashes, miter limit, per-side
rotation, tile scale, crop, adjustments: exposure, contrast, saturation (the file's vibrance), temperature, tint, highlights and shadows); strokes (weight, align, join, caps, dashes, miter limit, per-side
weights); shadows, layer and background blurs (progressive too), noise and texture; corners and
smoothing, constraints, clip content, masks with their type; auto layout (direction, wrap, gaps, padding,
alignment, hug, strokes in layout, stacking order, grids with their tracks, their sizes (fixed, hug, fill) and cells) and how
@@ -139,7 +139,7 @@ scales and empty-style clears; they have not been checked in other software.
guides, a page's prototype device settings (device, rotation, colour, custom size, responsive), the opacity of a page's prototype background (written opaque), slot properties and slot content, brush
strokes (written plain), strokes wider on one side of their path (written as filled outlines, or
with the width on both sides when the shape has a fill too), pattern fills, WebP export settings (written as PNG),
image adjustments, glass settings other than frost, springs other than Gentle (written as ease
glass settings other than frost, springs other than Gentle (written as ease
out), set-variable, set-variable-mode and conditional actions, scroll-to offsets, text decoration
looks, variable font axes, text on a path with more than one line or on a path the format can't hold (written as plain text), the parts of a text's path after the first, text on a path in an instance but not in its main or the other way round (written as the main has it), an instance's text of more than one line on a main on a path (it stays on that path, as an override can't take it off), nested list levels, OpenType features, per-layer operations inside boolean groups, different stroke ends
on lines, the Dash cap (no public name for it). Not written at all: stroke outlines of dashed and per-side strokes and of inside or
@@ -196,6 +196,7 @@ scales and empty-style clears; they have not been checked in other software.
| Date | Change | Summary |
|---|---|---|
| 2026-11-12 | minor | M6, #2323: the `.fig` export writes text on a path as the format's own layer kind (`TEXT_PATH`): the first part of its path as a vector network of one open or closed chain with the segment the text starts on marked, the start as that segment's own parameter (`textPathStart.tValue`, found by inverse arc length) and the side as `forward`. Vertical alignment is written as top, which is how text on a path draws. The importer already read all of it; a closed path comes back starting at the marked segment, the same curve and start point. Text with more than one line and paths the format can't hold stay plain text, the parts of a path after the first are left out, and text on a path in an instance but not in its main component (or the other way round) is written as the main has it, each with its own warning. The field numbers come from the schema of a synthetic file saved from Figma's web app. Checked through the importer only, not in other software |
| 2026-11-12 | minor | M6, #2297: the `.fig` exporter writes an image paint's adjustments as `paintFilter` (seven fields: exposure, contrast, vibrance from saturation, temperature, tint, highlights, shadows; the tint's sign is flipped, as the importer reads it), and no longer warns about them; the importer already read them. Not checked in other software |
| 2026-11-12 | patch | M6, #2273: `.fig` import: a numbered list keeps its start only on the item where it starts counting. Figma writes the start on every line of the list (measured in Figma's web app, 2026-11-12, from a saved .fig: a list started at 5 has `listStartOffset` 4 on all its lines, and after Backspace takes a middle item out of the list, the item after it starts a new list and is written with 0), so reading every line's value restarted the items after a split at the old start. The export is unchanged and a re-import now equals what went in; REST `listStartOffsets` read the same way |
| 2026-11-13 | minor | M6, #2326: `.fig` export writes a page's prototype background (the file's `prototypeBackgroundColor`, field 141, a plain colour, written for every page, so Figma shows the same colour and not its own default for a page at FigCraft's default), measured on a synthetic file saved from Figma's web app. The colour is written opaque, as that file has it and as the importer reads it; a background that isn't opaque gets a warning for its opacity. The device warning now fires only for the page's device settings (device, rotation, colour, custom size, responsive), which stay left out. Checked through the importer only, not in other software. Numbers unchanged. |
| 2026-11-12 | minor | M6, #2305: the `.fig` export writes a text's list spacing (`listSpacing`), hanging punctuation (`hangingPunctuation`), hanging lists (`hangingList`) and vertical trim (`leadingTrim`: none or cap height), which the importer already read. A field is written only when it differs from its default (in a design file saved by Figma, a text at the defaults carries none of the four), and an instance override that goes back to the default clears it. The warning narrows to OpenType features, which the importer does not read from a `.fig`. Checked through the importer only, not in other software. |
+1 -1
View File
@@ -498,7 +498,7 @@ one undo step and returns the ids it made:
in the app does both).
- `board.mindMap {x?, y?, text?, children?, parent?, edit?}` → `{id, text, children: [{id, text, connector}]}`:
a mind map: a first node (`mindNode`, bold 24, no box; `text` defaults to `Topic`, `x`/`y` to the
middle of the view) with up to 20 `children` (texts; default `Idea 1`, `Idea 2`, `Idea 3`)
view's centre, in the parent's coordinates) with up to 20 `children` (texts; default `Idea 1`, `Idea 2`, `Idea 3`)
on its right, each joined to it by a curved branch (a connector with `branch: true`: stroke 4,
`#757575`, round caps). The map lays itself out: children stand in one column 88 units right of
the widest node of their parent's column, 48 apart, centred on their parent; a node is its text,