mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
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)
This commit is contained in:
4 files changed
+61
-4
No files matched your search
@@ -78,8 +78,13 @@ impl<'a> Mapper<'a> {
|
||||
let (w, h) = n.get("size").map_or((0.0, 0.0), |s| (num(s, "x").unwrap_or(0.0), num(s, "y").unwrap_or(0.0)));
|
||||
let at = e.get("relativePosition").and_then(|r| Some((num(r, "x")?, num(r, "y")?)));
|
||||
let t = n.get("transform").and_then(matrix).unwrap_or_default();
|
||||
// An automatic magnet (`AUTO`: the side is picked when the connector is drawn;
|
||||
// `CENTER`: the end aims at the layer's middle) attaches to the side facing the
|
||||
// other end, as a board connector drawn here does; the file's `relativePosition`
|
||||
// is not where such an end sits (#1909).
|
||||
let automatic = matches!(e.get("magnet").and_then(Kv::str), Some("AUTO" | "CENTER"));
|
||||
match routing {
|
||||
Routing::Straight => {
|
||||
Routing::Straight if !automatic => {
|
||||
let (rx, ry) = at.unwrap_or((0.5, 0.5));
|
||||
RouteEnd::Point(t * Point::new(w * rx, h * ry))
|
||||
}
|
||||
@@ -453,7 +458,8 @@ fn saved_runs(v: &View<'_>, from: RouteEnd, to: RouteEnd) -> Option<ConnectorPat
|
||||
fits.then_some(ConnectorPath::Runs { across, at })
|
||||
}
|
||||
|
||||
/// The side of its layer a connector end leaves from: the `magnet` the file names, else the edge
|
||||
/// The side of its layer a connector end leaves from: the `magnet` the file names (`AUTO` and
|
||||
/// `CENTER`: the side facing the other end, whatever `relativePosition` says; #1909), else the edge
|
||||
/// its point on the layer (`relativePosition`, 0–1) lies on, else the side facing the other end.
|
||||
fn side(e: &Kv<'_>, at: Option<(f64, f64)>) -> Side {
|
||||
match e.get("magnet").and_then(Kv::str) {
|
||||
@@ -461,6 +467,7 @@ fn side(e: &Kv<'_>, at: Option<(f64, f64)>) -> Side {
|
||||
Some("RIGHT") => Side::Right,
|
||||
Some("BOTTOM") => Side::Bottom,
|
||||
Some("LEFT") => Side::Left,
|
||||
Some("AUTO" | "CENTER") => Side::Auto,
|
||||
_ => match at {
|
||||
Some((x, _)) if x <= 0.0 => Side::Left,
|
||||
Some((x, _)) if x >= 1.0 => Side::Right,
|
||||
|
||||
@@ -666,6 +666,54 @@ fn connectors_without_a_saved_path_follow_their_line_style() {
|
||||
assert!(warnings.iter().any(|w| w.contains("FigCraft's own routing")), "{warnings:?}");
|
||||
}
|
||||
|
||||
/// #1909: a connector end whose magnet is `AUTO` (FigJam picks the side when it draws) or `CENTER`
|
||||
/// (the end aims at the layer's middle) leaves the side facing the other end, as a board connector
|
||||
/// drawn here does, whatever `relativePosition` the file kept for it; a named magnet still wins.
|
||||
#[test]
|
||||
fn connectors_with_an_automatic_side_leave_the_side_facing_the_target() {
|
||||
let mut message = tail_message();
|
||||
let Kv::Array(nodes) = field_mut(&mut message, "nodeChanges").unwrap() else { panic!() };
|
||||
let box_at = |id, name: &str, x| node(id, Some((1, "*")), "RECTANGLE", name, vec![("size", xy(100.0, 100.0)), ("transform", at(x, 600.0))]);
|
||||
// The ends keep a stale point on the left of A and the right of B, as the reporter's file did.
|
||||
let end = |node, magnet: &'static str, x| o(vec![("endpointNodeID", g(node)), ("magnet", e(magnet)), ("relativePosition", xy(x, 0.5))]);
|
||||
let joined = |id, name: &str, style: &'static str, start: &'static str, finish: &'static str| {
|
||||
node(
|
||||
id,
|
||||
Some((1, "+")),
|
||||
"CONNECTOR",
|
||||
name,
|
||||
vec![
|
||||
("strokePaints", arr(vec![solid(0.0, 0.0, 0.0)])),
|
||||
("connectorStart", end(60, start, 0.0)),
|
||||
("connectorEnd", end(61, finish, 1.0)),
|
||||
("connectorLineStyle", e(style)),
|
||||
],
|
||||
)
|
||||
};
|
||||
nodes.extend([
|
||||
box_at(60, "A", 0.0),
|
||||
box_at(61, "B", 300.0),
|
||||
joined(62, "Elbow", "ELBOWED", "AUTO", "AUTO"),
|
||||
joined(63, "Line", "STRAIGHT", "CENTER", "CENTER"),
|
||||
joined(64, "Curve", "CURVED", "AUTO", "AUTO"),
|
||||
joined(65, "Named", "ELBOWED", "BOTTOM", "AUTO"),
|
||||
]);
|
||||
let (doc, _) = import(&canvas(&schema(), &message), "Sides").unwrap();
|
||||
let ends = |name: &str| -> ((f64, f64), (f64, f64)) {
|
||||
let world = doc.world_transform(named(&doc, name).id);
|
||||
let pts: Vec<(f64, f64)> =
|
||||
network_of(&doc, name).vertices.iter().map(|v| world * figcraft_doc::geom::Point::new(v.x, v.y)).map(|p| (p.x, p.y)).collect();
|
||||
(pts[0], pts[pts.len() - 1])
|
||||
};
|
||||
// From A's right side (facing B) to B's left side (facing A), not from the stale left point; each
|
||||
// end starts `END_GAP` (4) outside its side.
|
||||
for name in ["Elbow", "Line", "Curve"] {
|
||||
assert_eq!(ends(name), ((104.0, 650.0), (296.0, 650.0)), "{name}");
|
||||
}
|
||||
// A named magnet keeps its side: the bottom of A; the automatic end still faces A.
|
||||
assert_eq!(ends("Named"), ((50.0, 704.0), (296.0, 650.0)));
|
||||
}
|
||||
|
||||
/// A connector with a label but no saved snapshot: a frame holding its line and its label,
|
||||
/// centred on the line's middle, in black when the connector gives no paint.
|
||||
#[test]
|
||||
|
||||
@@ -29,6 +29,7 @@ enum StrokeJoin MITER BEVEL ROUND
|
||||
enum VectorMirror NONE ANGLE ANGLE_AND_LENGTH
|
||||
enum ShapeWithTextType SQUARE ELLIPSE
|
||||
enum ConnectorLineStyle ELBOWED STRAIGHT CURVED
|
||||
enum ConnectorMagnet NONE AUTO TOP LEFT BOTTOM RIGHT CENTER
|
||||
enum ExpressionFunction ADDITION SUBTRACTION RESOLVE_VARIANT MULTIPLY DIVIDE EQUALS
|
||||
enum NavigationType NAVIGATE OVERLAY
|
||||
enum TransitionType INSTANT_TRANSITION DISSOLVE SMART_ANIMATE
|
||||
@@ -50,7 +51,7 @@ message VariableTarget VariableID id
|
||||
message VariableSetID GUID guid, AssetRef assetRef
|
||||
message ConditionalActions Action[] actions, VariableData condition
|
||||
message VectorData uint vectorNetworkBlob, Vector normalizedSize, NodeChange[] styleOverrideTable
|
||||
message ConnectorEndpoint GUID endpointNodeID, Vector position, Vector relativePosition
|
||||
message ConnectorEndpoint GUID endpointNodeID, Vector position, Vector relativePosition, ConnectorMagnet magnet
|
||||
message ConnectorControlPoint Vector position, Vector axis
|
||||
message VariableData AnyValue value, DataType dataType
|
||||
message Paint PaintType type, Color color, float opacity, bool visible, Image image, Matrix transform, ColorStop[] stops, VariableData colorVar, ImageScaleMode imageScaleMode
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (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` carries the file kind (boards); `.fig` import: resized instances and variable modes by asset key; atomic saves; auto-save; SVG export clip of nested frames; SVG/PDF export depth refusal (#208); `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; `.fig` export: written files checked by opening them in other software; experimental `.fig` export; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; embedded image colour profiles; `.fig` import, best effort; animated GIF fills and TIFF images; last major: created, formats from the installed Figma's Info.plist and help center, ours from source; REST import keeps slot layers and instances' slot content (#792); REST import reads grid track sizes (#465); `.fig` import fidelity from the community batches, budgets for large files, the import report, a `.fig` copy from Save as, paste from another design app; instance layers' size and resizing and Hug on import (#731, #732)) · **Target:** Figma Design (desktop app 126.10.7)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-04 · **Change:** minor (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` carries the file kind (boards); `.fig` import: resized instances and variable modes by asset key; atomic saves; auto-save; SVG export clip of nested frames; SVG/PDF export depth refusal (#208); `.fig` export: instance sizes, variables with modes, shared styles, variable-width strokes; `.fig` export: written files checked by opening them in other software; experimental `.fig` export; export and inspect behaviour suite and fixes; .fig import: editable vector networks and more layer kinds; embedded image colour profiles; `.fig` import, best effort; animated GIF fills and TIFF images; last major: created, formats from the installed Figma's Info.plist and help center, ours from source; REST import keeps slot layers and instances' slot content (#792); REST import reads grid track sizes (#465); `.fig` import fidelity from the community batches, budgets for large files, the import report, a `.fig` copy from Save as, paste from another design app; instance layers' size and resizing and HLine 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
|
||||
@@ -187,6 +187,7 @@ source paint coordinates.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | patch | M6, #1909: `.jam` import: a connector end whose magnet is `AUTO` (FigJam picks the side when it draws) or `CENTER` (the end aims at the layer's middle) left the side its stale `relativePosition` named, the left one in the reporter's file, so the connectors looped behind their shape or crossed it. It now leaves the side facing the other end, as a board connector drawn here does (`Side::Auto`), for straight, elbowed and curved connectors alike; a named magnet (`TOP`, `RIGHT`, `BOTTOM`, `LEFT`) is unchanged, and an end without a magnet still takes the edge its point lies on. The Plugin API lists the magnets (`ConnectorEndpoint.magnet`: "Which side of the node it attaches to"; NONE, AUTO, TOP, LEFT, BOTTOM, RIGHT, CENTER; "Straight connectors may use only CENTER or NONE magnets"). Test: `connectors_with_an_automatic_side_leave_the_side_facing_the_target`. Numbers unchanged. |
|
||||
| 2026-11-04 | minor | JPG export: `quality` (1 to 100, default 90) on `file.export`, `export.add` / `export.update` and MCP `export`, kept in the export setting; an sRGB file's JPGs embed the sRGB ICC profile, as Display P3 files' JPGs already embedded theirs, since an untagged JPG is only assumed to be sRGB (#2004). |
|
||||
| 2026-11-04 | minor | `.fig` and `.jam` import, #1910: an elbowed connector saved with its path keeps it. FigJam stores `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 (`figcraft_doc::route_along`), so a connector drawn around a layer in FigJam stays around it instead of being routed through it. Points that don't make such a path (off both axes, two runs in a row the same way, a first or last run sideways to its side or going back into its object, more runs than a hand could make) leave the connector to the router as before, and only connectors routed here count in the "follow FigCraft's own routing" note. A curved connector's `connectorBezierControlPoints` are still not read. Tests: `tail_tests::an_elbow_connector_saved_with_its_path_keeps_it`, `tail_tests::an_elbow_whose_saved_path_doesnt_fit_its_sides_is_routed_here`. |
|
||||
| 2026-11-01 | patch | `.fig` and `.jam` import: a connector without a saved snapshot (elbowed and curved ones are routed here) took no stroke, so it was invisible (#1807); its line's paint, weight, cap and dashes now come from the file's generated line record, as a saved snapshot's do, and the connector's own values, when it has any, win. Test: `fig::connector_label_tests::a_routed_connector_takes_its_line_style_from_the_generated_record`. Numbers unchanged |
|
||||
|
||||
Reference in new issue
Block a user