1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4#![allow(unused)]
5
6use float_cmp::ApproxEq;
7
8use std::collections::HashMap;
9
10use derive_more::*;
11use serde::Deserialize;
12use smart_default::SmartDefault;
13
14#[derive(Debug, Deserialize)]
15pub struct File {
16 pub name: String,
17 pub lastModified: Option<String>,
18 pub thumbnailURL: Option<String>,
19 pub version: String,
20 pub document: Node,
21 pub components: HashMap<String, Component>,
22 //schemaVersion: 0,
23 styles: HashMap<String, Style>,
24}
25
26#[derive(Debug, Deserialize)]
27pub struct Component {
28 pub key: String,
29 pub file_key: Option<String>,
30 pub node_id: Option<String>,
31 pub thumbnail_url: Option<String>,
32 pub name: String,
33 pub description: String,
34 pub created_at: Option<String>,
35 pub updated_at: Option<String>,
36 //pub user : User,
37 //pub containing_frame : Option<FrameInfo>,
38 //pub containing_page Option<PageInfo>,
39}
40
41#[derive(Debug, Deserialize, Default)]
42pub struct NodeCommon {
43 pub id: String,
44 pub name: String,
45 #[serde(default = "return_true")]
46 pub visible: bool,
47 #[serde(default)]
48 pub children: Vec<Node>,
49}
50
51#[derive(Debug, Deserialize, Default)]
52pub struct LayoutConstraint {
53 pub vertical: String,
54 pub horizontal: String,
55}
56
57fn return_true() -> bool {
58 true
59}
60fn return_one() -> f32 {
61 1.
62}
63
64#[derive(Debug, Deserialize, Default, Clone, Copy)]
65pub struct Color {
66 pub a: f32,
67 pub r: f32,
68 pub g: f32,
69 pub b: f32,
70}
71
72impl Color {
73 pub fn is_transparent(&self) -> bool {
74 self.a.approx_eq(other:0., (f32::EPSILON * 3.0, 2))
75 }
76}
77
78// Sometimes figma is having null for coordinate for some reason, just ignore that and consider it is tempty
79fn deserialize_or_default<'de, T: Default + Deserialize<'de>, D: serde::Deserializer<'de>>(
80 de: D,
81) -> Result<T, D::Error> {
82 Ok(T::deserialize(deserializer:de).unwrap_or_default())
83}
84
85#[derive(Debug, Deserialize, Default, Clone, Copy)]
86#[serde(default)]
87pub struct Rectangle {
88 pub x: f32,
89 pub y: f32,
90 pub width: f32,
91 pub height: f32,
92}
93impl Rectangle {
94 pub fn origin(self) -> Vector {
95 Vector { x: self.x, y: self.y }
96 }
97 pub fn size(self) -> Vector {
98 Vector { x: self.width, y: self.height }
99 }
100}
101
102#[derive(Debug, Deserialize, Default, Clone, Copy, Add, AddAssign, Neg, Sub, SubAssign)]
103pub struct Vector {
104 pub x: f32,
105 pub y: f32,
106}
107
108#[derive(Debug, Deserialize)]
109pub struct Paint {
110 pub r#type: String,
111 #[serde(default = "return_true")]
112 pub visible: bool,
113 #[serde(default = "return_one")]
114 pub opacity: f32,
115 pub color: Option<Color>,
116 pub blendMode: BlendMode,
117 #[serde(default)]
118 pub gradientHandlePositions: Vec<Vector>,
119 #[serde(default)]
120 pub gradientStops: Vec<ColorStop>,
121 pub scaleMode: Option<String>,
122 pub imageTransform: Option<Transform>,
123 pub imageRef: Option<String>,
124}
125
126#[derive(Debug, Deserialize)]
127pub struct ColorStop {
128 pub position: f32,
129 pub color: Color,
130}
131
132#[derive(Debug, Deserialize)]
133pub struct LayoutGrid {
134 pub pattern: String,
135 pub sectionSize: f32,
136 pub visible: bool,
137 pub color: Color,
138 pub alignment: String,
139 pub gutterSize: f32,
140 pub offset: f32,
141 pub count: f32,
142}
143
144#[derive(Debug, Deserialize)]
145pub struct Effect {
146 pub r#type: String,
147 pub visible: bool,
148 pub radius: f32,
149 pub color: Option<Color>,
150 pub blendMode: Option<BlendMode>,
151 pub offset: Option<Vector>,
152}
153
154#[derive(Debug, Deserialize, SmartDefault)]
155#[serde(default)]
156pub struct TypeStyle {
157 pub fontFamily: String,
158 pub fontPostScriptName: Option<String>,
159 pub paragraphSpacing: f32,
160 pub paragraphIndentNumber: f32,
161 pub italic: bool,
162 pub fontWeight: f32,
163 pub fontSize: f32,
164 #[default("ORIGINAL")]
165 pub textCase: String,
166 #[default("NONE")]
167 pub textDecoration: String,
168 pub textAlignHorizontal: String,
169 pub textAlignVertical: String,
170 pub letterSpacing: f32,
171 pub fills: Vec<Paint>,
172 pub lineHeightPx: f32,
173 #[default(100.)]
174 pub lineHeightPercent: f32,
175 pub lineHeightPercentFontSize: f32,
176 pub lineHeightUnit: String,
177}
178
179#[derive(Debug, Deserialize, Default)]
180#[serde(default)]
181pub struct Path {
182 pub path: String,
183 pub windingRule: String,
184}
185
186type Transform = [[f32; 3]; 2];
187
188type BlendMode = String;
189type EasingType = String;
190
191#[derive(Debug, Deserialize, Default)]
192pub struct Frame {
193 #[serde(flatten)]
194 pub node: NodeCommon,
195 #[serde(default)]
196 pub locked: bool,
197 pub background: Vec<Paint>,
198 pub backgroundColor: Color,
199 #[serde(default)]
200 pub exportSettings: Vec<ExportSetting>,
201 pub blendMode: BlendMode,
202 #[serde(default)]
203 pub preserveRatio: bool,
204 pub constraints: LayoutConstraint,
205 pub transitionNodeID: Option<String>,
206 pub transitionDuration: Option<f32>,
207 pub transitionEasing: Option<EasingType>,
208 #[serde(default = "return_one")]
209 pub opacity: f32,
210 #[serde(deserialize_with = "deserialize_or_default")]
211 pub absoluteBoundingBox: Rectangle,
212 #[serde(deserialize_with = "deserialize_or_default")]
213 pub size: Option<Vector>,
214 #[serde(deserialize_with = "deserialize_or_default")]
215 pub relativeTransform: Option<Transform>,
216 pub clipsContent: bool,
217 #[serde(default)]
218 pub layoutGrids: Vec<LayoutGrid>,
219 #[serde(default)]
220 pub effects: Vec<Effect>,
221 #[serde(default)]
222 pub isMask: bool,
223 #[serde(default)]
224 pub isMaskOutline: bool,
225}
226
227#[derive(Debug, Deserialize)]
228pub struct ExportSetting {
229 pub suffix: String,
230 pub format: String,
231 pub constraint: Constraint,
232}
233
234#[derive(Debug, Deserialize, Default)]
235pub struct Constraint {
236 pub r#type: String,
237 pub value: f32,
238}
239
240#[derive(Debug, Deserialize, SmartDefault)]
241#[serde(default)]
242pub struct VectorNode {
243 #[serde(flatten)]
244 pub node: NodeCommon,
245 pub locked: bool,
246 pub exportSettings: Vec<ExportSetting>,
247 pub blendMode: BlendMode,
248 pub preserveRatio: bool,
249 pub constraints: LayoutConstraint,
250 pub transitionNodeID: Option<String>,
251 pub transitionDuration: Option<f32>,
252 pub transitionEasing: Option<EasingType>,
253 #[default(1.)]
254 pub opacity: f32,
255 #[serde(deserialize_with = "deserialize_or_default")]
256 pub absoluteBoundingBox: Rectangle,
257 pub effects: Vec<Effect>,
258 #[serde(deserialize_with = "deserialize_or_default")]
259 pub size: Option<Vector>,
260 #[serde(deserialize_with = "deserialize_or_default")]
261 pub relativeTransform: Option<Transform>,
262 pub isMask: bool,
263 pub fills: Vec<Paint>,
264 pub fillGeometry: Vec<Path>,
265 pub strokes: Vec<Paint>,
266 pub strokeWeight: f32,
267 #[default("NONE")]
268 pub strokeCap: String,
269 #[default("MITER")]
270 pub strokeJoin: String,
271 pub strokeDashes: Vec<f32>,
272 #[default(28.96)]
273 pub strokeMiterAngle: f32,
274 pub strokeGeometry: Vec<Path>,
275 pub strokeAlign: String,
276 pub styles: HashMap<String, String>,
277}
278
279#[derive(Debug, Deserialize)]
280#[serde(tag = "type")]
281pub enum Node {
282 DOCUMENT(NodeCommon),
283 CANVAS {
284 #[serde(flatten)]
285 node: NodeCommon,
286 backgroundColor: Color,
287 prototypeStartNodeID: Option<String>,
288 #[serde(default)]
289 exportSettings: Vec<ExportSetting>,
290 },
291 FRAME(Frame),
292 GROUP(Frame),
293 VECTOR(VectorNode),
294 BOOLEAN_OPERATION {
295 #[serde(flatten)]
296 vector: VectorNode,
297 booleanOperation: String,
298 },
299 STAR(VectorNode),
300 LINE(VectorNode),
301 ELLIPSE(VectorNode),
302 REGULAR_POLYGON(VectorNode),
303 RECTANGLE {
304 #[serde(flatten)]
305 vector: VectorNode,
306 cornerRadius: Option<f32>,
307 #[serde(default)]
308 rectangleCornerRadii: Vec<f32>,
309 },
310 TEXT {
311 #[serde(flatten)]
312 vector: VectorNode,
313 characters: String,
314 style: TypeStyle,
315 characterStyleOverrides: Vec<f32>,
316 },
317 SLICE {
318 #[serde(flatten)]
319 node: NodeCommon,
320 #[serde(default)]
321 exportSettings: Vec<ExportSetting>,
322 #[serde(deserialize_with = "deserialize_or_default")]
323 absoluteBoundingBox: Rectangle,
324 #[serde(deserialize_with = "deserialize_or_default")]
325 size: Option<Vector>,
326 #[serde(deserialize_with = "deserialize_or_default")]
327 relativeTransform: Option<Transform>,
328 },
329 COMPONENT(Frame),
330 INSTANCE {
331 #[serde(flatten)]
332 frame: Frame,
333 componentId: String,
334 },
335}
336
337impl Node {
338 pub fn common(&self) -> &NodeCommon {
339 match self {
340 Node::DOCUMENT(node: &NodeCommon) => node,
341 Node::CANVAS { node: &NodeCommon, .. } => node,
342 Node::FRAME(Frame { node: &NodeCommon, .. }) => node,
343 Node::GROUP(Frame { node: &NodeCommon, .. }) => node,
344 Node::VECTOR(VectorNode { node: &NodeCommon, .. }) => node,
345 Node::BOOLEAN_OPERATION { vector: VectorNode { node: &NodeCommon, .. }, .. } => node,
346 Node::STAR(VectorNode { node: &NodeCommon, .. }) => node,
347 Node::LINE(VectorNode { node: &NodeCommon, .. }) => node,
348 Node::ELLIPSE(VectorNode { node: &NodeCommon, .. }) => node,
349 Node::REGULAR_POLYGON(VectorNode { node: &NodeCommon, .. }) => node,
350 Node::RECTANGLE { vector: VectorNode { node: &NodeCommon, .. }, .. } => node,
351 Node::TEXT { vector: VectorNode { node: &NodeCommon, .. }, .. } => node,
352 Node::SLICE { node: &NodeCommon, .. } => node,
353 Node::COMPONENT(Frame { node: &NodeCommon, .. }) => node,
354 Node::INSTANCE { frame: Frame { node: &NodeCommon, .. }, .. } => node,
355 }
356 }
357}
358
359#[derive(Debug, Deserialize)]
360pub struct Style {
361 pub key: String,
362 pub name: String,
363 pub description: String,
364 pub styleType: String,
365}
366