1 | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | |
5 | use std::collections::HashMap; |
6 | use std::num::NonZeroU32; |
7 | use std::str::FromStr; |
8 | |
9 | #[rustfmt::skip] mod names; |
10 | mod parse; |
11 | mod text; |
12 | |
13 | use tiny_skia_path::Transform; |
14 | |
15 | use crate::{ |
16 | BlendMode, ImageRendering, Opacity, ShapeRendering, SpreadMethod, TextRendering, Units, |
17 | Visibility, |
18 | }; |
19 | pub use names::{AId, EId}; |
20 | |
21 | /// An SVG tree container. |
22 | /// |
23 | /// Contains only element and text nodes. |
24 | /// Text nodes are present only inside the `text` element. |
25 | pub struct Document<'input> { |
26 | nodes: Vec<NodeData>, |
27 | attrs: Vec<Attribute<'input>>, |
28 | links: HashMap<String, NodeId>, |
29 | } |
30 | |
31 | impl<'input> Document<'input> { |
32 | /// Returns the root node. |
33 | #[inline ] |
34 | pub fn root<'a>(&'a self) -> SvgNode<'a, 'input> { |
35 | SvgNode { |
36 | id: NodeId::new(0), |
37 | d: &self.nodes[0], |
38 | doc: self, |
39 | } |
40 | } |
41 | |
42 | /// Returns the root element. |
43 | #[inline ] |
44 | pub fn root_element<'a>(&'a self) -> SvgNode<'a, 'input> { |
45 | // `unwrap` is safe, because `Document` is guarantee to have at least one element. |
46 | self.root().first_element_child().unwrap() |
47 | } |
48 | |
49 | /// Returns an iterator over document's descendant nodes. |
50 | /// |
51 | /// Shorthand for `doc.root().descendants()`. |
52 | #[inline ] |
53 | pub fn descendants<'a>(&'a self) -> Descendants<'a, 'input> { |
54 | self.root().descendants() |
55 | } |
56 | |
57 | /// Returns an element by ID. |
58 | /// |
59 | /// Unlike the [`Descendants`] iterator, this is just a HashMap lookup. |
60 | /// Meaning it's way faster. |
61 | #[inline ] |
62 | pub fn element_by_id<'a>(&'a self, id: &str) -> Option<SvgNode<'a, 'input>> { |
63 | let node_id = self.links.get(id)?; |
64 | Some(self.get(*node_id)) |
65 | } |
66 | |
67 | #[inline ] |
68 | fn get<'a>(&'a self, id: NodeId) -> SvgNode<'a, 'input> { |
69 | SvgNode { |
70 | id, |
71 | d: &self.nodes[id.get_usize()], |
72 | doc: self, |
73 | } |
74 | } |
75 | } |
76 | |
77 | impl std::fmt::Debug for Document<'_> { |
78 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { |
79 | if !self.root().has_children() { |
80 | return write!(f, "Document []" ); |
81 | } |
82 | |
83 | macro_rules! writeln_indented { |
84 | ($depth:expr, $f:expr, $fmt:expr) => { |
85 | for _ in 0..$depth { write!($f, " " )?; } |
86 | writeln!($f, $fmt)?; |
87 | }; |
88 | ($depth:expr, $f:expr, $fmt:expr, $($arg:tt)*) => { |
89 | for _ in 0..$depth { write!($f, " " )?; } |
90 | writeln!($f, $fmt, $($arg)*)?; |
91 | }; |
92 | } |
93 | |
94 | fn print_children( |
95 | parent: SvgNode, |
96 | depth: usize, |
97 | f: &mut std::fmt::Formatter, |
98 | ) -> Result<(), std::fmt::Error> { |
99 | for child in parent.children() { |
100 | if child.is_element() { |
101 | writeln_indented!(depth, f, "Element {{" ); |
102 | writeln_indented!(depth, f, " tag_name: {:?}" , child.tag_name()); |
103 | |
104 | if !child.attributes().is_empty() { |
105 | writeln_indented!(depth + 1, f, "attributes: [" ); |
106 | for attr in child.attributes() { |
107 | writeln_indented!(depth + 2, f, " {:?}" , attr); |
108 | } |
109 | writeln_indented!(depth + 1, f, "]" ); |
110 | } |
111 | |
112 | if child.has_children() { |
113 | writeln_indented!(depth, f, " children: [" ); |
114 | print_children(child, depth + 2, f)?; |
115 | writeln_indented!(depth, f, " ]" ); |
116 | } |
117 | |
118 | writeln_indented!(depth, f, " }}" ); |
119 | } else { |
120 | writeln_indented!(depth, f, " {:?}" , child); |
121 | } |
122 | } |
123 | |
124 | Ok(()) |
125 | } |
126 | |
127 | writeln!(f, "Document [" )?; |
128 | print_children(self.root(), 1, f)?; |
129 | writeln!(f, "]" )?; |
130 | |
131 | Ok(()) |
132 | } |
133 | } |
134 | |
135 | #[derive (Clone, Copy, Debug)] |
136 | pub(crate) struct ShortRange { |
137 | start: u32, |
138 | end: u32, |
139 | } |
140 | |
141 | impl ShortRange { |
142 | #[inline ] |
143 | fn new(start: u32, end: u32) -> Self { |
144 | ShortRange { start, end } |
145 | } |
146 | |
147 | #[inline ] |
148 | fn to_urange(self) -> std::ops::Range<usize> { |
149 | self.start as usize..self.end as usize |
150 | } |
151 | } |
152 | |
153 | #[derive (Clone, Copy, PartialEq, Debug)] |
154 | pub(crate) struct NodeId(NonZeroU32); |
155 | |
156 | impl NodeId { |
157 | #[inline ] |
158 | fn new(id: u32) -> Self { |
159 | debug_assert!(id < core::u32::MAX); |
160 | |
161 | // We are using `NonZeroU32` to reduce overhead of `Option<NodeId>`. |
162 | NodeId(NonZeroU32::new(id + 1).unwrap()) |
163 | } |
164 | |
165 | #[inline ] |
166 | fn get(self) -> u32 { |
167 | self.0.get() - 1 |
168 | } |
169 | |
170 | #[inline ] |
171 | fn get_usize(self) -> usize { |
172 | self.get() as usize |
173 | } |
174 | } |
175 | |
176 | impl From<usize> for NodeId { |
177 | #[inline ] |
178 | fn from(id: usize) -> Self { |
179 | // We already checked that `id` is limited by u32::MAX. |
180 | debug_assert!(id <= core::u32::MAX as usize); |
181 | NodeId::new(id as u32) |
182 | } |
183 | } |
184 | |
185 | pub(crate) enum NodeKind { |
186 | Root, |
187 | Element { |
188 | tag_name: EId, |
189 | attributes: ShortRange, |
190 | }, |
191 | Text(String), |
192 | } |
193 | |
194 | struct NodeData { |
195 | parent: Option<NodeId>, |
196 | next_sibling: Option<NodeId>, |
197 | children: Option<(NodeId, NodeId)>, |
198 | kind: NodeKind, |
199 | } |
200 | |
201 | /// An attribute. |
202 | #[derive (Clone)] |
203 | pub struct Attribute<'input> { |
204 | /// Attribute's name. |
205 | pub name: AId, |
206 | /// Attribute's value. |
207 | pub value: roxmltree::StringStorage<'input>, |
208 | } |
209 | |
210 | impl std::fmt::Debug for Attribute<'_> { |
211 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { |
212 | write!( |
213 | f, |
214 | "Attribute {{ name: {:?}, value: {} }}" , |
215 | self.name, self.value |
216 | ) |
217 | } |
218 | } |
219 | |
220 | /// An SVG node. |
221 | #[derive (Clone, Copy)] |
222 | pub struct SvgNode<'a, 'input: 'a> { |
223 | id: NodeId, |
224 | doc: &'a Document<'input>, |
225 | d: &'a NodeData, |
226 | } |
227 | |
228 | impl Eq for SvgNode<'_, '_> {} |
229 | |
230 | impl PartialEq for SvgNode<'_, '_> { |
231 | #[inline ] |
232 | fn eq(&self, other: &Self) -> bool { |
233 | self.id == other.id && std::ptr::eq(self.doc, b:other.doc) && std::ptr::eq(self.d, b:other.d) |
234 | } |
235 | } |
236 | |
237 | impl<'a, 'input: 'a> SvgNode<'a, 'input> { |
238 | #[inline ] |
239 | fn id(&self) -> NodeId { |
240 | self.id |
241 | } |
242 | |
243 | /// Checks if the current node is an element. |
244 | #[inline ] |
245 | pub fn is_element(&self) -> bool { |
246 | matches!(self.d.kind, NodeKind::Element { .. }) |
247 | } |
248 | |
249 | /// Checks if the current node is a text. |
250 | #[inline ] |
251 | pub fn is_text(&self) -> bool { |
252 | matches!(self.d.kind, NodeKind::Text(_)) |
253 | } |
254 | |
255 | /// Returns node's document. |
256 | #[inline ] |
257 | pub fn document(&self) -> &'a Document<'input> { |
258 | self.doc |
259 | } |
260 | |
261 | /// Returns element's tag name, unless the current node is text. |
262 | #[inline ] |
263 | pub fn tag_name(&self) -> Option<EId> { |
264 | match self.d.kind { |
265 | NodeKind::Element { tag_name, .. } => Some(tag_name), |
266 | _ => None, |
267 | } |
268 | } |
269 | /// Returns element's `id` attribute value. |
270 | /// |
271 | /// Returns an empty string otherwise. |
272 | #[inline ] |
273 | pub fn element_id(&self) -> &'a str { |
274 | self.attribute(AId::Id).unwrap_or("" ) |
275 | } |
276 | |
277 | /// Returns an attribute value. |
278 | pub fn attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> { |
279 | let value = self |
280 | .attributes() |
281 | .iter() |
282 | .find(|a| a.name == aid) |
283 | .map(|a| a.value.as_str())?; |
284 | match T::parse(*self, aid, value) { |
285 | Some(v) => Some(v), |
286 | None => { |
287 | // TODO: show position in XML |
288 | log::warn!("Failed to parse {} value: ' {}'." , aid, value); |
289 | None |
290 | } |
291 | } |
292 | } |
293 | |
294 | /// Returns an attribute value. |
295 | /// |
296 | /// Same as `SvgNode::attribute`, but doesn't show a warning. |
297 | pub fn try_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> { |
298 | let value = self |
299 | .attributes() |
300 | .iter() |
301 | .find(|a| a.name == aid) |
302 | .map(|a| a.value.as_str())?; |
303 | T::parse(*self, aid, value) |
304 | } |
305 | |
306 | #[inline ] |
307 | fn node_attribute(&self, aid: AId) -> Option<SvgNode<'a, 'input>> { |
308 | let value = self.attribute(aid)?; |
309 | let id = if aid == AId::Href { |
310 | svgtypes::IRI::from_str(value).ok().map(|v| v.0) |
311 | } else { |
312 | svgtypes::FuncIRI::from_str(value).ok().map(|v| v.0) |
313 | }?; |
314 | |
315 | self.document().element_by_id(id) |
316 | } |
317 | |
318 | /// Checks if an attribute is present. |
319 | #[inline ] |
320 | pub fn has_attribute(&self, aid: AId) -> bool { |
321 | self.attributes().iter().any(|a| a.name == aid) |
322 | } |
323 | |
324 | /// Returns a list of all element's attributes. |
325 | #[inline ] |
326 | pub fn attributes(&self) -> &'a [Attribute<'input>] { |
327 | match self.d.kind { |
328 | NodeKind::Element { ref attributes, .. } => &self.doc.attrs[attributes.to_urange()], |
329 | _ => &[], |
330 | } |
331 | } |
332 | |
333 | #[inline ] |
334 | fn attribute_id(&self, aid: AId) -> Option<usize> { |
335 | match self.d.kind { |
336 | NodeKind::Element { ref attributes, .. } => { |
337 | let idx = self.attributes().iter().position(|attr| attr.name == aid)?; |
338 | Some(attributes.start as usize + idx) |
339 | } |
340 | _ => None, |
341 | } |
342 | } |
343 | |
344 | /// Finds a [`Node`] that contains the required attribute. |
345 | /// |
346 | /// For inheritable attributes walks over ancestors until a node with |
347 | /// the specified attribute is found. |
348 | /// |
349 | /// For non-inheritable attributes checks only the current node and the parent one. |
350 | /// As per SVG spec. |
351 | pub fn find_attribute<T: FromValue<'a, 'input>>(&self, aid: AId) -> Option<T> { |
352 | self.find_attribute_impl(aid)?.attribute(aid) |
353 | } |
354 | |
355 | fn find_attribute_impl(&self, aid: AId) -> Option<SvgNode<'a, 'input>> { |
356 | if aid.is_inheritable() { |
357 | for n in self.ancestors() { |
358 | if n.has_attribute(aid) { |
359 | return Some(n); |
360 | } |
361 | } |
362 | |
363 | None |
364 | } else { |
365 | if self.has_attribute(aid) { |
366 | Some(*self) |
367 | } else { |
368 | // Non-inheritable attributes can inherit a value only from a direct parent. |
369 | let n = self.parent_element()?; |
370 | if n.has_attribute(aid) { |
371 | Some(n) |
372 | } else { |
373 | None |
374 | } |
375 | } |
376 | } |
377 | } |
378 | |
379 | /// Returns node's text data. |
380 | /// |
381 | /// For text nodes returns its content. For elements returns the first child node text. |
382 | #[inline ] |
383 | pub fn text(&self) -> &'a str { |
384 | match self.d.kind { |
385 | NodeKind::Element { .. } => match self.first_child() { |
386 | Some(child) if child.is_text() => match self.doc.nodes[child.id.get_usize()].kind { |
387 | NodeKind::Text(ref text) => text, |
388 | _ => "" , |
389 | }, |
390 | _ => "" , |
391 | }, |
392 | NodeKind::Text(ref text) => text, |
393 | _ => "" , |
394 | } |
395 | } |
396 | |
397 | /// Returns a parent node. |
398 | #[inline ] |
399 | pub fn parent(&self) -> Option<Self> { |
400 | self.d.parent.map(|id| self.doc.get(id)) |
401 | } |
402 | |
403 | /// Returns the parent element. |
404 | #[inline ] |
405 | pub fn parent_element(&self) -> Option<Self> { |
406 | self.ancestors().skip(1).find(|n| n.is_element()) |
407 | } |
408 | |
409 | /// Returns the next sibling. |
410 | #[inline ] |
411 | pub fn next_sibling(&self) -> Option<Self> { |
412 | self.d.next_sibling.map(|id| self.doc.get(id)) |
413 | } |
414 | |
415 | /// Returns the first child. |
416 | #[inline ] |
417 | pub fn first_child(&self) -> Option<Self> { |
418 | self.d.children.map(|(id, _)| self.doc.get(id)) |
419 | } |
420 | |
421 | /// Returns the first child element. |
422 | #[inline ] |
423 | pub fn first_element_child(&self) -> Option<Self> { |
424 | self.children().find(|n| n.is_element()) |
425 | } |
426 | |
427 | /// Returns the last child. |
428 | #[inline ] |
429 | pub fn last_child(&self) -> Option<Self> { |
430 | self.d.children.map(|(_, id)| self.doc.get(id)) |
431 | } |
432 | |
433 | /// Checks if the node has child nodes. |
434 | #[inline ] |
435 | pub fn has_children(&self) -> bool { |
436 | self.d.children.is_some() |
437 | } |
438 | |
439 | /// Returns an iterator over ancestor nodes starting at this node. |
440 | #[inline ] |
441 | pub fn ancestors(&self) -> Ancestors<'a, 'input> { |
442 | Ancestors(Some(*self)) |
443 | } |
444 | |
445 | /// Returns an iterator over children nodes. |
446 | #[inline ] |
447 | pub fn children(&self) -> Children<'a, 'input> { |
448 | Children { |
449 | front: self.first_child(), |
450 | back: self.last_child(), |
451 | } |
452 | } |
453 | |
454 | /// Returns an iterator which traverses the subtree starting at this node. |
455 | #[inline ] |
456 | fn traverse(&self) -> Traverse<'a, 'input> { |
457 | Traverse { |
458 | root: *self, |
459 | edge: None, |
460 | } |
461 | } |
462 | |
463 | /// Returns an iterator over this node and its descendants. |
464 | #[inline ] |
465 | pub fn descendants(&self) -> Descendants<'a, 'input> { |
466 | Descendants(self.traverse()) |
467 | } |
468 | |
469 | /// Returns an iterator over elements linked via `xlink:href`. |
470 | #[inline ] |
471 | pub fn href_iter(&self) -> HrefIter<'a, 'input> { |
472 | HrefIter { |
473 | doc: self.document(), |
474 | origin: self.id(), |
475 | curr: self.id(), |
476 | is_first: true, |
477 | is_finished: false, |
478 | } |
479 | } |
480 | } |
481 | |
482 | impl std::fmt::Debug for SvgNode<'_, '_> { |
483 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { |
484 | match self.d.kind { |
485 | NodeKind::Root => write!(f, "Root" ), |
486 | NodeKind::Element { .. } => { |
487 | write!( |
488 | f, |
489 | "Element {{ tag_name: {:?}, attributes: {:?} }}" , |
490 | self.tag_name(), |
491 | self.attributes() |
492 | ) |
493 | } |
494 | NodeKind::Text(ref text: &String) => write!(f, "Text( {:?})" , text), |
495 | } |
496 | } |
497 | } |
498 | |
499 | /// An iterator over ancestor nodes. |
500 | #[derive (Clone, Debug)] |
501 | pub struct Ancestors<'a, 'input: 'a>(Option<SvgNode<'a, 'input>>); |
502 | |
503 | impl<'a, 'input: 'a> Iterator for Ancestors<'a, 'input> { |
504 | type Item = SvgNode<'a, 'input>; |
505 | |
506 | #[inline ] |
507 | fn next(&mut self) -> Option<Self::Item> { |
508 | let node: Option> = self.0.take(); |
509 | self.0 = node.as_ref().and_then(SvgNode::parent); |
510 | node |
511 | } |
512 | } |
513 | |
514 | /// An iterator over children nodes. |
515 | #[derive (Clone, Debug)] |
516 | pub struct Children<'a, 'input: 'a> { |
517 | front: Option<SvgNode<'a, 'input>>, |
518 | back: Option<SvgNode<'a, 'input>>, |
519 | } |
520 | |
521 | impl<'a, 'input: 'a> Iterator for Children<'a, 'input> { |
522 | type Item = SvgNode<'a, 'input>; |
523 | |
524 | fn next(&mut self) -> Option<Self::Item> { |
525 | let node: Option> = self.front.take(); |
526 | if self.front == self.back { |
527 | self.back = None; |
528 | } else { |
529 | self.front = node.as_ref().and_then(SvgNode::next_sibling); |
530 | } |
531 | node |
532 | } |
533 | } |
534 | |
535 | #[derive (Clone, Copy, PartialEq, Debug)] |
536 | enum Edge<'a, 'input: 'a> { |
537 | Open(SvgNode<'a, 'input>), |
538 | Close(SvgNode<'a, 'input>), |
539 | } |
540 | |
541 | #[derive (Clone, Debug)] |
542 | struct Traverse<'a, 'input: 'a> { |
543 | root: SvgNode<'a, 'input>, |
544 | edge: Option<Edge<'a, 'input>>, |
545 | } |
546 | |
547 | impl<'a, 'input: 'a> Iterator for Traverse<'a, 'input> { |
548 | type Item = Edge<'a, 'input>; |
549 | |
550 | fn next(&mut self) -> Option<Self::Item> { |
551 | match self.edge { |
552 | Some(Edge::Open(node)) => { |
553 | self.edge = Some(match node.first_child() { |
554 | Some(first_child) => Edge::Open(first_child), |
555 | None => Edge::Close(node), |
556 | }); |
557 | } |
558 | Some(Edge::Close(node)) => { |
559 | if node == self.root { |
560 | self.edge = None; |
561 | } else if let Some(next_sibling) = node.next_sibling() { |
562 | self.edge = Some(Edge::Open(next_sibling)); |
563 | } else { |
564 | self.edge = node.parent().map(Edge::Close); |
565 | } |
566 | } |
567 | None => { |
568 | self.edge = Some(Edge::Open(self.root)); |
569 | } |
570 | } |
571 | |
572 | self.edge |
573 | } |
574 | } |
575 | |
576 | /// A descendants iterator. |
577 | #[derive (Clone, Debug)] |
578 | pub struct Descendants<'a, 'input: 'a>(Traverse<'a, 'input>); |
579 | |
580 | impl<'a, 'input: 'a> Iterator for Descendants<'a, 'input> { |
581 | type Item = SvgNode<'a, 'input>; |
582 | |
583 | #[inline ] |
584 | fn next(&mut self) -> Option<Self::Item> { |
585 | for edge: Edge<'_, '_> in &mut self.0 { |
586 | if let Edge::Open(node: SvgNode<'_, '_>) = edge { |
587 | return Some(node); |
588 | } |
589 | } |
590 | |
591 | None |
592 | } |
593 | } |
594 | |
595 | /// An iterator over `xlink:href` references. |
596 | #[derive (Clone, Debug)] |
597 | pub struct HrefIter<'a, 'input: 'a> { |
598 | doc: &'a Document<'input>, |
599 | origin: NodeId, |
600 | curr: NodeId, |
601 | is_first: bool, |
602 | is_finished: bool, |
603 | } |
604 | |
605 | impl<'a, 'input: 'a> Iterator for HrefIter<'a, 'input> { |
606 | type Item = SvgNode<'a, 'input>; |
607 | |
608 | fn next(&mut self) -> Option<Self::Item> { |
609 | if self.is_finished { |
610 | return None; |
611 | } |
612 | |
613 | if self.is_first { |
614 | self.is_first = false; |
615 | return Some(self.doc.get(self.curr)); |
616 | } |
617 | |
618 | if let Some(link) = self.doc.get(self.curr).node_attribute(AId::Href) { |
619 | if link.id() == self.curr || link.id() == self.origin { |
620 | log::warn!( |
621 | "Element '# {}' cannot reference itself via 'xlink:href'." , |
622 | self.doc.get(self.origin).element_id() |
623 | ); |
624 | self.is_finished = true; |
625 | return None; |
626 | } |
627 | |
628 | self.curr = link.id(); |
629 | Some(self.doc.get(self.curr)) |
630 | } else { |
631 | None |
632 | } |
633 | } |
634 | } |
635 | |
636 | impl EId { |
637 | /// Checks if this is a |
638 | /// [graphics element](https://www.w3.org/TR/SVG11/intro.html#TermGraphicsElement). |
639 | pub fn is_graphic(&self) -> bool { |
640 | matches!( |
641 | self, |
642 | EId::Circle |
643 | | EId::Ellipse |
644 | | EId::Image |
645 | | EId::Line |
646 | | EId::Path |
647 | | EId::Polygon |
648 | | EId::Polyline |
649 | | EId::Rect |
650 | | EId::Text |
651 | | EId::Use |
652 | ) |
653 | } |
654 | |
655 | /// Checks if this is a |
656 | /// [gradient element](https://www.w3.org/TR/SVG11/intro.html#TermGradientElement). |
657 | pub fn is_gradient(&self) -> bool { |
658 | matches!(self, EId::LinearGradient | EId::RadialGradient) |
659 | } |
660 | |
661 | /// Checks if this is a |
662 | /// [paint server element](https://www.w3.org/TR/SVG11/intro.html#TermPaint). |
663 | pub fn is_paint_server(&self) -> bool { |
664 | matches!( |
665 | self, |
666 | EId::LinearGradient | EId::RadialGradient | EId::Pattern |
667 | ) |
668 | } |
669 | } |
670 | |
671 | impl AId { |
672 | fn is_presentation(&self) -> bool { |
673 | matches!( |
674 | self, |
675 | AId::AlignmentBaseline |
676 | | AId::BaselineShift |
677 | | AId::ClipPath |
678 | | AId::ClipRule |
679 | | AId::Color |
680 | | AId::ColorInterpolation |
681 | | AId::ColorInterpolationFilters |
682 | | AId::ColorRendering |
683 | | AId::Direction |
684 | | AId::Display |
685 | | AId::DominantBaseline |
686 | | AId::Fill |
687 | | AId::FillOpacity |
688 | | AId::FillRule |
689 | | AId::Filter |
690 | | AId::FloodColor |
691 | | AId::FloodOpacity |
692 | | AId::FontFamily |
693 | | AId::FontKerning // technically not presentation |
694 | | AId::FontSize |
695 | | AId::FontSizeAdjust |
696 | | AId::FontStretch |
697 | | AId::FontStyle |
698 | | AId::FontVariant |
699 | | AId::FontWeight |
700 | | AId::GlyphOrientationHorizontal |
701 | | AId::GlyphOrientationVertical |
702 | | AId::ImageRendering |
703 | | AId::Isolation // technically not presentation |
704 | | AId::LetterSpacing |
705 | | AId::LightingColor |
706 | | AId::MarkerEnd |
707 | | AId::MarkerMid |
708 | | AId::MarkerStart |
709 | | AId::Mask |
710 | | AId::MaskType |
711 | | AId::MixBlendMode // technically not presentation |
712 | | AId::Opacity |
713 | | AId::Overflow |
714 | | AId::PaintOrder |
715 | | AId::ShapeRendering |
716 | | AId::StopColor |
717 | | AId::StopOpacity |
718 | | AId::Stroke |
719 | | AId::StrokeDasharray |
720 | | AId::StrokeDashoffset |
721 | | AId::StrokeLinecap |
722 | | AId::StrokeLinejoin |
723 | | AId::StrokeMiterlimit |
724 | | AId::StrokeOpacity |
725 | | AId::StrokeWidth |
726 | | AId::TextAnchor |
727 | | AId::TextDecoration |
728 | | AId::TextOverflow |
729 | | AId::TextRendering |
730 | | AId::Transform |
731 | | AId::TransformOrigin |
732 | | AId::UnicodeBidi |
733 | | AId::VectorEffect |
734 | | AId::Visibility |
735 | | AId::WhiteSpace |
736 | | AId::WordSpacing |
737 | | AId::WritingMode |
738 | ) |
739 | } |
740 | |
741 | /// Checks if the current attribute is inheritable. |
742 | fn is_inheritable(&self) -> bool { |
743 | if self.is_presentation() { |
744 | !is_non_inheritable(*self) |
745 | } else { |
746 | false |
747 | } |
748 | } |
749 | |
750 | fn allows_inherit_value(&self) -> bool { |
751 | matches!( |
752 | self, |
753 | AId::AlignmentBaseline |
754 | | AId::BaselineShift |
755 | | AId::ClipPath |
756 | | AId::ClipRule |
757 | | AId::Color |
758 | | AId::ColorInterpolationFilters |
759 | | AId::Direction |
760 | | AId::Display |
761 | | AId::DominantBaseline |
762 | | AId::Fill |
763 | | AId::FillOpacity |
764 | | AId::FillRule |
765 | | AId::Filter |
766 | | AId::FloodColor |
767 | | AId::FloodOpacity |
768 | | AId::FontFamily |
769 | | AId::FontKerning |
770 | | AId::FontSize |
771 | | AId::FontStretch |
772 | | AId::FontStyle |
773 | | AId::FontVariant |
774 | | AId::FontWeight |
775 | | AId::ImageRendering |
776 | | AId::Kerning |
777 | | AId::LetterSpacing |
778 | | AId::MarkerEnd |
779 | | AId::MarkerMid |
780 | | AId::MarkerStart |
781 | | AId::Mask |
782 | | AId::Opacity |
783 | | AId::Overflow |
784 | | AId::ShapeRendering |
785 | | AId::StopColor |
786 | | AId::StopOpacity |
787 | | AId::Stroke |
788 | | AId::StrokeDasharray |
789 | | AId::StrokeDashoffset |
790 | | AId::StrokeLinecap |
791 | | AId::StrokeLinejoin |
792 | | AId::StrokeMiterlimit |
793 | | AId::StrokeOpacity |
794 | | AId::StrokeWidth |
795 | | AId::TextAnchor |
796 | | AId::TextDecoration |
797 | | AId::TextRendering |
798 | | AId::Visibility |
799 | | AId::WordSpacing |
800 | | AId::WritingMode |
801 | ) |
802 | } |
803 | } |
804 | |
805 | fn is_non_inheritable(id: AId) -> bool { |
806 | matches!( |
807 | id, |
808 | AId::AlignmentBaseline |
809 | | AId::BaselineShift |
810 | | AId::ClipPath |
811 | | AId::Display |
812 | | AId::DominantBaseline |
813 | | AId::Filter |
814 | | AId::FloodColor |
815 | | AId::FloodOpacity |
816 | | AId::Mask |
817 | | AId::Opacity |
818 | | AId::Overflow |
819 | | AId::LightingColor |
820 | | AId::StopColor |
821 | | AId::StopOpacity |
822 | | AId::TextDecoration |
823 | | AId::Transform |
824 | | AId::TransformOrigin |
825 | ) |
826 | } |
827 | |
828 | // TODO: is there a way yo make it less ugly? Too many lifetimes. |
829 | /// A trait for parsing attribute values. |
830 | pub trait FromValue<'a, 'input: 'a>: Sized { |
831 | /// Parses an attribute value. |
832 | /// |
833 | /// When `None` is returned, the attribute value will be logged as a parsing failure. |
834 | fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &'a str) -> Option<Self>; |
835 | } |
836 | |
837 | impl<'a, 'input: 'a> FromValue<'a, 'input> for &'a str { |
838 | fn parse(_: SvgNode<'a, 'input>, _: AId, value: &'a str) -> Option<Self> { |
839 | Some(value) |
840 | } |
841 | } |
842 | |
843 | impl<'a, 'input: 'a> FromValue<'a, 'input> for f32 { |
844 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
845 | svgtypes::Number::from_str(value).ok().map(|v: Number| v.0 as f32) |
846 | } |
847 | } |
848 | |
849 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Length { |
850 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
851 | svgtypes::Length::from_str(value).ok() |
852 | } |
853 | } |
854 | |
855 | // TODO: to svgtypes? |
856 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Opacity { |
857 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
858 | let length: Length = svgtypes::Length::from_str(value).ok()?; |
859 | if length.unit == svgtypes::LengthUnit::Percent { |
860 | Some(Opacity::new_clamped(length.number as f32 / 100.0)) |
861 | } else if length.unit == svgtypes::LengthUnit::None { |
862 | Some(Opacity::new_clamped(length.number as f32)) |
863 | } else { |
864 | None |
865 | } |
866 | } |
867 | } |
868 | |
869 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Transform { |
870 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
871 | let ts: Transform = match svgtypes::Transform::from_str(value) { |
872 | Ok(v: Transform) => v, |
873 | Err(_) => return None, |
874 | }; |
875 | |
876 | let ts: Transform = Transform::from_row( |
877 | sx:ts.a as f32, |
878 | ky:ts.b as f32, |
879 | kx:ts.c as f32, |
880 | sy:ts.d as f32, |
881 | tx:ts.e as f32, |
882 | ty:ts.f as f32, |
883 | ); |
884 | |
885 | if ts.is_valid() { |
886 | Some(ts) |
887 | } else { |
888 | Some(Transform::default()) |
889 | } |
890 | } |
891 | } |
892 | |
893 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::TransformOrigin { |
894 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
895 | Self::from_str(value).ok() |
896 | } |
897 | } |
898 | |
899 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::ViewBox { |
900 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
901 | Self::from_str(value).ok() |
902 | } |
903 | } |
904 | |
905 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Units { |
906 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
907 | match value { |
908 | "userSpaceOnUse" => Some(Units::UserSpaceOnUse), |
909 | "objectBoundingBox" => Some(Units::ObjectBoundingBox), |
910 | _ => None, |
911 | } |
912 | } |
913 | } |
914 | |
915 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::AspectRatio { |
916 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
917 | Self::from_str(value).ok() |
918 | } |
919 | } |
920 | |
921 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::PaintOrder { |
922 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
923 | Self::from_str(value).ok() |
924 | } |
925 | } |
926 | |
927 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Color { |
928 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
929 | Self::from_str(value).ok() |
930 | } |
931 | } |
932 | |
933 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Angle { |
934 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
935 | Self::from_str(value).ok() |
936 | } |
937 | } |
938 | |
939 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::EnableBackground { |
940 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
941 | Self::from_str(value).ok() |
942 | } |
943 | } |
944 | |
945 | impl<'a, 'input: 'a> FromValue<'a, 'input> for svgtypes::Paint<'a> { |
946 | fn parse(_: SvgNode, _: AId, value: &'a str) -> Option<Self> { |
947 | Self::from_str(text:value).ok() |
948 | } |
949 | } |
950 | |
951 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<f32> { |
952 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
953 | let mut list: Vec = Vec::new(); |
954 | for n: Result in svgtypes::NumberListParser::from(value) { |
955 | list.push(n.ok()? as f32); |
956 | } |
957 | |
958 | Some(list) |
959 | } |
960 | } |
961 | |
962 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Vec<svgtypes::Length> { |
963 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
964 | let mut list: Vec = Vec::new(); |
965 | for n: Result in svgtypes::LengthListParser::from(value) { |
966 | list.push(n.ok()?); |
967 | } |
968 | |
969 | Some(list) |
970 | } |
971 | } |
972 | |
973 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Visibility { |
974 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
975 | match value { |
976 | "visible" => Some(Visibility::Visible), |
977 | "hidden" => Some(Visibility::Hidden), |
978 | "collapse" => Some(Visibility::Collapse), |
979 | _ => None, |
980 | } |
981 | } |
982 | } |
983 | |
984 | impl<'a, 'input: 'a> FromValue<'a, 'input> for SpreadMethod { |
985 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
986 | match value { |
987 | "pad" => Some(SpreadMethod::Pad), |
988 | "reflect" => Some(SpreadMethod::Reflect), |
989 | "repeat" => Some(SpreadMethod::Repeat), |
990 | _ => None, |
991 | } |
992 | } |
993 | } |
994 | |
995 | impl<'a, 'input: 'a> FromValue<'a, 'input> for ShapeRendering { |
996 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
997 | match value { |
998 | "optimizeSpeed" => Some(ShapeRendering::OptimizeSpeed), |
999 | "crispEdges" => Some(ShapeRendering::CrispEdges), |
1000 | "auto" | "geometricPrecision" => Some(ShapeRendering::GeometricPrecision), |
1001 | _ => None, |
1002 | } |
1003 | } |
1004 | } |
1005 | |
1006 | impl<'a, 'input: 'a> FromValue<'a, 'input> for TextRendering { |
1007 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
1008 | match value { |
1009 | "optimizeSpeed" => Some(TextRendering::OptimizeSpeed), |
1010 | "auto" | "optimizeLegibility" => Some(TextRendering::OptimizeLegibility), |
1011 | "geometricPrecision" => Some(TextRendering::GeometricPrecision), |
1012 | _ => None, |
1013 | } |
1014 | } |
1015 | } |
1016 | |
1017 | impl<'a, 'input: 'a> FromValue<'a, 'input> for ImageRendering { |
1018 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
1019 | match value { |
1020 | "auto" | "optimizeQuality" => Some(ImageRendering::OptimizeQuality), |
1021 | "optimizeSpeed" => Some(ImageRendering::OptimizeSpeed), |
1022 | _ => None, |
1023 | } |
1024 | } |
1025 | } |
1026 | |
1027 | impl<'a, 'input: 'a> FromValue<'a, 'input> for BlendMode { |
1028 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
1029 | match value { |
1030 | "normal" => Some(BlendMode::Normal), |
1031 | "multiply" => Some(BlendMode::Multiply), |
1032 | "screen" => Some(BlendMode::Screen), |
1033 | "overlay" => Some(BlendMode::Overlay), |
1034 | "darken" => Some(BlendMode::Darken), |
1035 | "lighten" => Some(BlendMode::Lighten), |
1036 | "color-dodge" => Some(BlendMode::ColorDodge), |
1037 | "color-burn" => Some(BlendMode::ColorBurn), |
1038 | "hard-light" => Some(BlendMode::HardLight), |
1039 | "soft-light" => Some(BlendMode::SoftLight), |
1040 | "difference" => Some(BlendMode::Difference), |
1041 | "exclusion" => Some(BlendMode::Exclusion), |
1042 | "hue" => Some(BlendMode::Hue), |
1043 | "saturation" => Some(BlendMode::Saturation), |
1044 | "color" => Some(BlendMode::Color), |
1045 | "luminosity" => Some(BlendMode::Luminosity), |
1046 | _ => None, |
1047 | } |
1048 | } |
1049 | } |
1050 | |
1051 | impl<'a, 'input: 'a> FromValue<'a, 'input> for SvgNode<'a, 'input> { |
1052 | fn parse(node: SvgNode<'a, 'input>, aid: AId, value: &str) -> Option<Self> { |
1053 | let id: &str = if aid == AId::Href { |
1054 | svgtypes::IRI::from_str(text:value).ok().map(|v: IRI<'_>| v.0) |
1055 | } else { |
1056 | svgtypes::FuncIRI::from_str(text:value).ok().map(|v: FuncIRI<'_>| v.0) |
1057 | }?; |
1058 | |
1059 | node.document().element_by_id(id) |
1060 | } |
1061 | } |
1062 | |