| 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 | |