figma_schema/
node.rs

1use super::{
2    Color, Component, EasingType, Effect, File, LayoutConstraint, Paint, Rectangle, Styles,
3    TypeStyle,
4};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
8#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
9#[typeshare::typeshare]
10pub enum StrokeAlign {
11    /// stroke drawn inside the shape boundary
12    Inside,
13    /// stroke drawn outside the shape boundary
14    Outside,
15    /// stroke drawn centered along the shape boundary
16    Center,
17}
18
19#[derive(Debug, Deserialize, Serialize)]
20#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
21#[typeshare::typeshare]
22pub enum LayoutMode {
23    None,
24    Horizontal,
25    Vertical,
26}
27
28#[derive(Debug, Deserialize, Serialize, PartialEq)]
29#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
30#[typeshare::typeshare]
31pub enum AxisSizingMode {
32    Fixed,
33    Auto,
34}
35
36#[derive(Debug, Deserialize, Serialize)]
37#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
38#[typeshare::typeshare]
39pub enum PrimaryAxisAlignItems {
40    Min,
41    Center,
42    Max,
43    SpaceBetween,
44}
45
46#[derive(Debug, Deserialize, Serialize)]
47#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
48#[typeshare::typeshare]
49pub enum CounterAxisAlignItems {
50    Min,
51    Center,
52    Max,
53    Baseline,
54}
55
56/// Individual stroke weights
57#[derive(Debug, Deserialize, Serialize)]
58#[typeshare::typeshare]
59pub struct StrokeWeights {
60    /// The top stroke weight
61    pub top: f64,
62    /// The right stroke weight
63    pub right: f64,
64    /// The bottom stroke weight
65    pub bottom: f64,
66    /// The left stroke weight
67    pub left: f64,
68}
69
70#[derive(Debug, Deserialize, Serialize)]
71#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
72#[typeshare::typeshare]
73pub enum LayoutAlign {
74    Inherit,
75    Stretch,
76    Min,
77    Center,
78    Max,
79}
80
81#[derive(Debug, Deserialize, Serialize)]
82#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
83#[typeshare::typeshare]
84pub enum LayoutPositioning {
85    Absolute,
86}
87
88/// [Figma documentation](https://www.figma.com/developers/api#node-types)
89#[derive(Debug, Deserialize, Serialize)]
90#[serde(rename_all = "camelCase")]
91#[typeshare::typeshare]
92pub struct Node {
93    /// A string uniquely identifying this node within the document.
94    pub id: String,
95    /// The name given to the node by the user in the tool.
96    pub name: String,
97    /// Whether or not the node is visible on the canvas.
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub visible: Option<bool>,
100    /// The type of the node
101    pub r#type: NodeType,
102    /// An array of nodes that are direct children of this node
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub children: Option<Vec<Node>>,
105    /// Background color of the canvas
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub background_color: Option<Color>,
108    /// An array of fill paints applied to the node
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub fills: Option<Vec<Paint>>,
111    /// An array of stroke paints applied to the node
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub strokes: Option<Vec<Paint>>,
114    /// The weight of strokes on the node
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub stroke_weight: Option<f64>,
117    /// An object including the top, bottom, left, and right stroke weights. Only returned if individual stroke weights are used.
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub individual_stroke_weights: Option<StrokeWeights>,
120    /// Position of stroke relative to vector outline
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub stroke_align: Option<StrokeAlign>,
123    /// An array of floating point numbers describing the pattern of dash length and gap lengths that the vector path follows. For example a value of [1, 2] indicates that the path has a dash of length 1 followed by a gap of length 2, repeated.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub stroke_dashes: Option<Vec<f64>>,
126    /// Radius of each corner of the node if a single radius is set for all corners
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub corner_radius: Option<f64>,
129    /// Array of length 4 of the radius of each corner of the node, starting in the top left and proceeding clockwise
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub rectangle_corner_radii: Option<[f64; 4]>,
132    /// The duration of the prototyping transition on this node (in milliseconds)
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub transition_duration: Option<f64>,
135    /// The easing curve used in the prototyping transition on this node
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub transition_easing: Option<EasingType>,
138    /// Opacity of the node
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub opacity: Option<f64>,
141    /// Bounding box of the node in absolute space coordinates
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub absolute_bounding_box: Option<Rectangle>,
144    /// The bounds of the rendered node in the file in absolute space coordinates
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub absolute_render_bounds: Option<Rectangle>,
147    /// Whether the primary axis has a fixed length (determined by the user) or an automatic length (determined by the layout engine). This property is only applicable for auto-layout frames.
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub primary_axis_sizing_mode: Option<AxisSizingMode>,
150    /// Whether the counter axis has a fixed length (determined by the user) or an automatic length (determined by the layout engine). This property is only applicable for auto-layout frames.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub counter_axis_sizing_mode: Option<AxisSizingMode>,
153    /// Determines how the auto-layout frame’s children should be aligned in the primary axis direction. This property is only applicable for auto-layout frames.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub primary_axis_align_items: Option<PrimaryAxisAlignItems>,
156    /// Determines how the auto-layout frame’s children should be aligned in the counter axis direction. This property is only applicable for auto-layout frames.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub counter_axis_align_items: Option<CounterAxisAlignItems>,
159    /// The distance between children of the frame. Can be negative. This property is only applicable for auto-layout frames.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub item_spacing: Option<f64>,
162    /// Determines whether a layer's size and position should be determined by auto-layout settings or manually adjustable.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub layout_positioning: Option<LayoutPositioning>,
165    /// Whether this layer uses auto-layout to position its children.
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub layout_mode: Option<LayoutMode>,
168    /// The padding between the left border of the frame and its children. This property is only applicable for auto-layout frames.
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub padding_left: Option<f64>,
171    /// The padding between the right border of the frame and its children. This property is only applicable for auto-layout frames.
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub padding_right: Option<f64>,
174    /// The padding between the top border of the frame and its children. This property is only applicable for auto-layout frames.
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub padding_top: Option<f64>,
177    /// The padding between the bottom border of the frame and its children. This property is only applicable for auto-layout frames.
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub padding_bottom: Option<f64>,
180    /// An array of effects attached to this node
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub effects: Option<Vec<Effect>>,
183    /// A mapping of a StyleType to style ID of styles present on this node. The style ID can be used to look up more information about the style in the top-level styles field.
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub styles: Option<Styles>,
186    /// Text contained within a text box
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub characters: Option<String>,
189    /// Style of text including font family and weight
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub style: Option<TypeStyle>,
192    /// Horizontal and vertical layout contraints for node
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub constraints: Option<LayoutConstraint>,
195    /// Determines if the layer should stretch along the parent’s counter axis. This property is only provided for direct children of auto-layout frames.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub layout_align: Option<LayoutAlign>,
198    /// This property is applicable only for direct children of auto-layout frames, ignored otherwise. Determines whether a layer should stretch along the parent’s primary axis. A 0 corresponds to a fixed size and 1 corresponds to stretch
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub layout_grow: Option<f64>,
201}
202
203impl Node {
204    pub fn visible(&self) -> bool {
205        self.visible.unwrap_or(true)
206    }
207
208    pub fn background_color(&self) -> Option<&Color> {
209        self.background_color.as_ref()
210    }
211
212    pub fn absolute_bounding_box(&self) -> Option<&Rectangle> {
213        self.absolute_bounding_box.as_ref()
214    }
215
216    pub fn corner_radius(&self) -> Option<f64> {
217        self.corner_radius
218    }
219
220    pub fn rectangle_corner_radii(&self) -> Option<[f64; 4]> {
221        self.rectangle_corner_radii
222            .or_else(|| self.corner_radius.map(|r| [r, r, r, r]))
223    }
224
225    pub fn transition_duration(&self) -> Option<f64> {
226        self.transition_duration
227    }
228
229    pub fn transition_easing(&self) -> Option<&EasingType> {
230        self.transition_easing.as_ref()
231    }
232
233    pub fn opacity(&self) -> f64 {
234        self.opacity.unwrap_or(1.0)
235    }
236
237    pub fn padding_left(&self) -> f64 {
238        self.padding_left.unwrap_or(0.0)
239    }
240
241    pub fn padding_right(&self) -> f64 {
242        self.padding_right.unwrap_or(0.0)
243    }
244
245    pub fn padding_top(&self) -> f64 {
246        self.padding_top.unwrap_or(0.0)
247    }
248
249    pub fn padding_bottom(&self) -> f64 {
250        self.padding_bottom.unwrap_or(0.0)
251    }
252
253    pub fn children(&self) -> &[Node] {
254        self.children.as_deref().unwrap_or_default()
255    }
256
257    pub fn enabled_children(&self) -> impl Iterator<Item = &Node> {
258        self.children().iter().filter(|c| c.visible())
259    }
260
261    pub fn fills(&self) -> &[Paint] {
262        self.fills.as_deref().unwrap_or_default()
263    }
264
265    pub fn strokes(&self) -> &[Paint] {
266        self.strokes.as_deref().unwrap_or_default()
267    }
268
269    pub fn depth_first_stack_iter(&self) -> NodeDepthFirstStackIterator {
270        NodeDepthFirstStackIterator {
271            stack: vec![self],
272            iter_stack: vec![self.children().iter()],
273        }
274    }
275
276    pub fn component<'a>(&self, file: &'a File) -> Option<&'a Component> {
277        file.components.get(&self.id)
278    }
279
280    pub fn stroke_weight(&self) -> Option<f64> {
281        self.stroke_weight
282    }
283
284    pub fn stroke_align(&self) -> Option<&StrokeAlign> {
285        self.stroke_align.as_ref()
286    }
287}
288
289/// Node type indicates what kind of node you are working with: for example, a FRAME node versus a RECTANGLE node. A node can have additional properties associated with it depending on its node type.
290#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone)]
291#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
292#[typeshare::typeshare]
293pub enum NodeType {
294    Document,
295    Canvas,
296    Frame,
297    Group,
298    Vector,
299    BooleanOperation,
300    Star,
301    Line,
302    Ellipse,
303    RegularPolygon,
304    Rectangle,
305    Text,
306    Slice,
307    Component,
308    ComponentSet,
309    Instance,
310    Sticky,
311    ShapeWithText,
312    Connector,
313    Section,
314}
315
316pub struct NodeDepthFirstStackIterator<'a> {
317    iter_stack: Vec<std::slice::Iter<'a, Node>>,
318    stack: Vec<&'a Node>,
319}
320
321impl<'a> Iterator for NodeDepthFirstStackIterator<'a> {
322    type Item = (&'a Node, Vec<&'a Node>);
323
324    fn next(&mut self) -> Option<Self::Item> {
325        loop {
326            let mut bottom_of_iter_stack = self.iter_stack.pop()?;
327            let bottom_of_stack = self.stack.pop()?;
328            if let Some(current) = bottom_of_iter_stack.next() {
329                self.iter_stack.push(bottom_of_iter_stack);
330                self.iter_stack.push(current.children().iter());
331                self.stack.push(bottom_of_stack);
332                self.stack.push(current);
333                return Some((current, self.stack.clone()));
334            }
335        }
336    }
337}