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