1use crate::node::{Node, NodeData};
2use color::{AlphaColor, Srgb};
3use keyboard_types::Modifiers;
4use std::borrow::Cow;
5use style::color::AbsoluteColor;
6
7#[cfg(target_os = "macos")]
8pub(crate) const ACTION_MOD: Modifiers = Modifiers::META;
9#[cfg(not(target_os = "macos"))]
10pub(crate) const ACTION_MOD: Modifiers = Modifiers::CONTROL;
11
12pub type Color = AlphaColor<Srgb>;
13
14pub fn decode_font_bytes(bytes: &[u8]) -> Cow<'_, [u8]> {
18 if bytes.len() < 4 {
19 return Cow::Borrowed(bytes);
20 }
21 match &bytes[0..4] {
22 #[cfg(feature = "woff")]
23 b"wOFF" => wuff::decompress_woff1(bytes)
24 .map(Cow::Owned)
25 .unwrap_or_else(|_| {
26 #[cfg(feature = "tracing")]
27 tracing::warn!("Failed to decompress woff1 font");
28 Cow::Borrowed(bytes)
29 }),
30 #[cfg(feature = "woff")]
31 b"wOF2" => wuff::decompress_woff2(bytes)
32 .map(Cow::Owned)
33 .unwrap_or_else(|_| {
34 #[cfg(feature = "tracing")]
35 tracing::warn!("Failed to decompress woff2 font");
36 Cow::Borrowed(bytes)
37 }),
38 _ => Cow::Borrowed(bytes),
39 }
40}
41
42#[cfg(feature = "svg")]
43use std::sync::{Arc, LazyLock};
44#[cfg(feature = "svg")]
45use usvg::fontdb;
46#[cfg(feature = "svg")]
47pub(crate) static FONT_DB: LazyLock<Arc<fontdb::Database>> = LazyLock::new(|| {
48 let mut db = fontdb::Database::new();
49 db.load_system_fonts();
50 Arc::new(db)
51});
52
53#[derive(Clone, Copy, Debug)]
56pub enum ImageLayerKind {
57 Background,
58 Mask,
59}
60
61impl ImageLayerKind {
62 pub fn image_type(self, idx: usize) -> ImageType {
63 match self {
64 Self::Background => ImageType::Background(idx),
65 Self::Mask => ImageType::Mask(idx),
66 }
67 }
68}
69
70#[derive(Clone, Copy, Debug)]
71pub enum ImageType {
72 Image,
73 Background(usize),
74 Mask(usize),
75}
76
77#[derive(Clone, Debug, Copy, Eq, PartialEq)]
79pub struct Point<T> {
80 pub x: T,
82 pub y: T,
84}
85
86impl Point<f64> {
87 pub const ZERO: Self = Point { x: 0.0, y: 0.0 };
88}
89
90pub fn walk_tree(indent: usize, node: &Node) {
92 if let NodeData::Text(data) = &node.data {
94 if data.content.chars().all(|c| c.is_ascii_whitespace()) {
95 return;
96 }
97 }
98
99 print!("{}", " ".repeat(indent));
100 let id = node.id;
101 match &node.data {
102 NodeData::Document(_) => println!("#Document {id}"),
103
104 NodeData::Text(data) => {
105 if data.content.chars().all(|c| c.is_ascii_whitespace()) {
106 println!("{id} #text: <whitespace>");
107 } else {
108 let content = data.content.trim();
109 if content.len() > 10 {
110 println!(
111 "#text {id}: {}...",
112 content
113 .split_at(content.char_indices().take(10).last().unwrap().0)
114 .0
115 .escape_default()
116 )
117 } else {
118 println!("#text {id}: {}", data.content.trim().escape_default())
119 }
120 }
121 }
122
123 NodeData::Comment { .. } => println!("<!-- COMMENT {id} -->"),
124
125 NodeData::AnonymousBlock(_) => println!("{id} AnonymousBlock"),
126
127 NodeData::Element(data) => {
128 print!("<{} {id}", data.name.local);
129 for attr in data.attrs.iter() {
130 print!(" {}=\"{}\"", attr.name.local, attr.value);
131 }
132 if !node.children.is_empty() {
133 println!(">");
134 } else {
135 println!("/>");
136 }
137 } }
144
145 if !node.children.is_empty() {
146 for child_id in node.children.iter() {
147 walk_tree(indent + 2, node.with(*child_id));
148 }
149
150 if let NodeData::Element(data) = &node.data {
151 println!("{}</{}>", " ".repeat(indent), data.name.local);
152 }
153 }
154}
155
156#[cfg(feature = "svg")]
158pub(crate) fn parse_svg_image(source: &[u8]) -> Result<crate::node::SvgImageData, usvg::Error> {
159 let options = usvg::Options {
160 fontdb: Arc::clone(&*FONT_DB),
161 ..Default::default()
162 };
163 crate::node::SvgImageData::from_data(source, &options)
164}
165
166pub trait ToColorColor {
167 fn as_color_color(&self) -> Color;
169}
170impl ToColorColor for AbsoluteColor {
171 fn as_color_color(&self) -> Color {
172 Color::new(
173 *self
174 .to_color_space(style::color::ColorSpace::Srgb)
175 .raw_components(),
176 )
177 }
178}
179
180#[cfg(all(test, feature = "svg"))]
181mod svg_tests {
182 use super::parse_svg_image;
183
184 #[test]
185 fn svg_without_xmlns_parses() {
186 let src = br#"<svg viewBox="0 0 24 24"><path d="M0 0h24v24H0z"/></svg>"#;
187 let svg = parse_svg_image(src).unwrap();
188 assert!(!svg.tree.root().children().is_empty());
189 }
190
191 #[test]
192 fn missing_height_is_computed_from_width_and_viewbox_ratio() {
193 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="200"><rect width="100%" height="100%" fill="green"/></svg>"#;
194 let svg = parse_svg_image(src).unwrap();
195 assert_eq!(svg.intrinsic_width(), Some(200.0));
196 assert_eq!(svg.intrinsic_height(), None);
197 assert_eq!(svg.viewbox_aspect_ratio(), Some(1.0));
198 assert_eq!(svg.tree.size().width(), 200.0);
199 assert_eq!(svg.intrinsic_size(), (200.0, 200.0));
200 }
201
202 #[test]
203 fn viewbox_only_has_no_intrinsic_dimensions() {
204 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 485 58"></svg>"#;
205 let svg = parse_svg_image(src).unwrap();
206 assert_eq!(svg.intrinsic_width(), None);
207 assert_eq!(svg.intrinsic_height(), None);
208 assert!((svg.aspect_ratio() - (485.0 / 58.0)).abs() < 1e-3);
210 }
211
212 #[test]
213 fn absolute_dimensions_are_intrinsic() {
214 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="24" height="16" viewBox="0 0 48 32"></svg>"#;
215 let svg = parse_svg_image(src).unwrap();
216 assert_eq!(svg.intrinsic_width(), Some(24.0));
217 assert_eq!(svg.intrinsic_height(), Some(16.0));
218 }
219
220 #[test]
221 fn percentage_dimensions_are_not_intrinsic() {
222 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="50%" viewBox="0 0 200 100"></svg>"#;
223 let svg = parse_svg_image(src).unwrap();
224 assert_eq!(svg.intrinsic_width(), None);
225 assert_eq!(svg.intrinsic_height(), None);
226 }
227
228 #[test]
229 fn unit_lengths_are_intrinsic() {
230 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="24px" height="1.5em" viewBox="0 0 48 32"></svg>"#;
231 let svg = parse_svg_image(src).unwrap();
232 assert!(svg.intrinsic_width().is_some());
233 assert!(svg.intrinsic_height().is_some());
234 }
235
236 #[test]
237 fn non_numeric_dimensions_are_not_intrinsic() {
238 let src = br#"<svg xmlns="http://www.w3.org/2000/svg" width="auto" height="foo" viewBox="0 0 200 100"></svg>"#;
239 let svg = parse_svg_image(src).unwrap();
240 assert_eq!(svg.intrinsic_width(), None);
241 assert_eq!(svg.intrinsic_height(), None);
242 }
243}
244
245#[macro_export]
248macro_rules! qual_name {
249 ($local:tt $(, $ns:ident)?) => {
250 $crate::QualName {
251 prefix: None,
252 ns: $crate::ns!($($ns)?),
253 local: $crate::local_name!($local),
254 }
255 };
256}