i_slint_compiler/passes/
compile_paths.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This pass converts the verbose markup used for paths, such as
5//!    Path {
6//!        LineTo { ... } ArcTo { ... }
7//!    }
8//! to a vector of path elements (PathData) that is assigned to the
9//! elements property of the Path element. That way the generators have to deal
10//! with path embedding only as part of the property assignment.
11
12use crate::diagnostics::BuildDiagnostics;
13use crate::expression_tree::*;
14use crate::langtype::ElementType;
15use crate::langtype::{Struct, Type};
16use crate::object_tree::*;
17use crate::EmbedResourcesKind;
18use smol_str::SmolStr;
19use std::cell::RefCell;
20use std::rc::Rc;
21
22pub fn compile_paths(
23    component: &Rc<Component>,
24    tr: &crate::typeregister::TypeRegister,
25    _embed_resources: EmbedResourcesKind,
26    diag: &mut BuildDiagnostics,
27) {
28    let path_type = tr.lookup_element("Path").unwrap();
29    let path_type = path_type.as_builtin();
30
31    recurse_elem(&component.root_element, &(), &mut |elem_, _| {
32        let accepted_type = match &elem_.borrow().base_type {
33            ElementType::Builtin(be)
34                if be.native_class.class_name == path_type.native_class.class_name =>
35            {
36                path_type
37            }
38            _ => return,
39        };
40
41        #[cfg(feature = "software-renderer")]
42        if _embed_resources == EmbedResourcesKind::EmbedTextures {
43            diag.push_warning(
44                "Path element is not supported with the software renderer".into(),
45                &*elem_.borrow(),
46            )
47        }
48
49        let element_types = &accepted_type.additional_accepted_child_types;
50
51        let commands_binding =
52            elem_.borrow_mut().bindings.remove("commands").map(RefCell::into_inner);
53
54        let path_data_binding = if let Some(commands_expr) = commands_binding {
55            if let Some(path_child) = elem_.borrow().children.iter().find(|child| {
56                element_types
57                    .contains_key(&child.borrow().base_type.as_builtin().native_class.class_name)
58            }) {
59                diag.push_error(
60                    "Path elements cannot be mixed with the use of the SVG commands property"
61                        .into(),
62                    &*path_child.borrow(),
63                );
64                return;
65            }
66
67            match &commands_expr.expression {
68                Expression::StringLiteral(commands) => {
69                    match compile_path_from_string_literal(commands) {
70                        Ok(binding) => binding,
71                        Err(e) => {
72                            diag.push_error(
73                                format!("Error parsing SVG commands ({e})"),
74                                &commands_expr,
75                            );
76                            return;
77                        }
78                    }
79                }
80                expr if expr.ty() == Type::String => Expression::PathData(
81                    crate::expression_tree::Path::Commands(Box::new(commands_expr.expression)),
82                )
83                .into(),
84                _ => {
85                    diag.push_error(
86                        "The commands property only accepts strings".into(),
87                        &*elem_.borrow(),
88                    );
89                    return;
90                }
91            }
92        } else {
93            let mut elem = elem_.borrow_mut();
94            let enclosing_component = elem.enclosing_component.upgrade().unwrap();
95            let new_children = Vec::with_capacity(elem.children.len());
96            let old_children = std::mem::replace(&mut elem.children, new_children);
97
98            let mut path_data = Vec::new();
99
100            for child in old_children {
101                let element_name =
102                    &child.borrow().base_type.as_builtin().native_class.class_name.clone();
103
104                if let Some(element_type) = element_types.get(element_name).cloned() {
105                    if child.borrow().repeated.is_some() {
106                        diag.push_error(
107                            "Path elements are not supported with `for`-`in` syntax, yet (https://github.com/slint-ui/slint/issues/754)".into(),
108                            &*child.borrow(),
109                        );
110                    } else {
111                        let mut bindings = std::collections::BTreeMap::new();
112                        {
113                            let mut child = child.borrow_mut();
114                            for k in element_type.properties.keys() {
115                                if let Some(binding) = child.bindings.remove(k) {
116                                    bindings.insert(k.clone(), binding);
117                                }
118                            }
119                        }
120                        path_data.push(PathElement { element_type, bindings });
121                        enclosing_component.optimized_elements.borrow_mut().push(child);
122                    }
123                } else {
124                    elem.children.push(child);
125                }
126            }
127            Expression::PathData(crate::expression_tree::Path::Elements(path_data)).into()
128        };
129
130        elem_.borrow_mut().bindings.insert("elements".into(), RefCell::new(path_data_binding));
131    });
132}
133
134fn compile_path_from_string_literal(
135    commands: &str,
136) -> Result<BindingExpression, lyon_extra::parser::ParseError> {
137    let mut builder = lyon_path::Path::builder();
138    let mut parser = lyon_extra::parser::PathParser::new();
139    parser.parse(
140        &lyon_extra::parser::ParserOptions::DEFAULT,
141        &mut lyon_extra::parser::Source::new(commands.chars()),
142        &mut builder,
143    )?;
144    let path = builder.build();
145
146    let event_enum = crate::typeregister::BUILTIN.with(|e| e.enums.PathEvent.clone());
147    let point_type = Rc::new(Struct {
148        fields: IntoIterator::into_iter([
149            (SmolStr::new_static("x"), Type::Float32),
150            (SmolStr::new_static("y"), Type::Float32),
151        ])
152        .collect(),
153        name: Some("slint::private_api::Point".into()),
154        node: None,
155        rust_attributes: None,
156    });
157
158    let mut points = Vec::new();
159    let events = path
160        .into_iter()
161        .map(|event| {
162            Expression::EnumerationValue(match event {
163                lyon_path::Event::Begin { at } => {
164                    points.push(at);
165                    event_enum.clone().try_value_from_string("begin").unwrap()
166                }
167                lyon_path::Event::Line { from, to } => {
168                    points.push(from);
169                    points.push(to);
170
171                    event_enum.clone().try_value_from_string("line").unwrap()
172                }
173                lyon_path::Event::Quadratic { from, ctrl, to } => {
174                    points.push(from);
175                    points.push(ctrl);
176                    points.push(to);
177
178                    event_enum.clone().try_value_from_string("quadratic").unwrap()
179                }
180                lyon_path::Event::Cubic { from, ctrl1, ctrl2, to } => {
181                    points.push(from);
182                    points.push(ctrl1);
183                    points.push(ctrl2);
184                    points.push(to);
185                    event_enum.clone().try_value_from_string("cubic").unwrap()
186                }
187                lyon_path::Event::End { first: _, last: _, close } => {
188                    if close {
189                        event_enum.clone().try_value_from_string("end-closed").unwrap()
190                    } else {
191                        event_enum.clone().try_value_from_string("end-open").unwrap()
192                    }
193                }
194            })
195        })
196        .collect();
197
198    let points = points
199        .into_iter()
200        .map(|point| Expression::Struct {
201            ty: point_type.clone(),
202            values: IntoIterator::into_iter([
203                (SmolStr::new_static("x"), Expression::NumberLiteral(point.x as _, Unit::None)),
204                (SmolStr::new_static("y"), Expression::NumberLiteral(point.y as _, Unit::None)),
205            ])
206            .collect(),
207        })
208        .collect();
209
210    Ok(Expression::PathData(Path::Events(events, points)).into())
211}