1use std::collections::{HashMap, HashSet};
5use std::hash::{Hash, Hasher};
6use std::str::FromStr;
7use std::sync::Arc;
8
9#[cfg(feature = "text")]
10use fontdb::Database;
11#[cfg(feature = "text")]
12use fontdb::ID;
13#[cfg(feature = "text")]
14use rustybuzz::ttf_parser::GlyphId;
15use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin};
16use tiny_skia_path::PathBuilder;
17
18use super::svgtree::{self, AId, EId, FromValue, SvgNode};
19use super::units::{self, convert_length};
20use super::{Error, Options, marker};
21#[cfg(feature = "text")]
22use crate::flatten::BitmapImage;
23use crate::parser::paint_server::process_paint;
24#[cfg(feature = "text")]
25use crate::text::flatten::DatabaseExt;
26use crate::*;
27
28#[derive(Clone)]
29pub struct State<'a> {
30 pub(crate) parent_clip_path: Option<SvgNode<'a, 'a>>,
31 pub(crate) parent_markers: Vec<SvgNode<'a, 'a>>,
32 pub(crate) context_element: Option<(Option<Fill>, Option<Stroke>)>,
35 pub(crate) fe_image_link: bool,
36 pub(crate) view_box: NonZeroRect,
38 pub(crate) use_size: (Option<f32>, Option<f32>),
42 pub(crate) opt: &'a Options<'a>,
43}
44
45#[derive(Clone)]
46pub struct Cache {
47 #[cfg(feature = "text")]
50 pub fontdb: Arc<Database>,
51
52 #[cfg(feature = "text")]
53 cache_outline: HashMap<(ID, GlyphId), Option<tiny_skia_path::Path>>,
54 #[cfg(feature = "text")]
55 cache_colr: HashMap<(ID, GlyphId), Option<Tree>>,
56 #[cfg(feature = "text")]
57 cache_svg: HashMap<(ID, GlyphId), Option<Node>>,
58 #[cfg(feature = "text")]
59 cache_raster: HashMap<(ID, GlyphId), Option<BitmapImage>>,
60 #[cfg(feature = "text")]
61 cache_has_opsz: HashMap<ID, bool>,
62
63 pub clip_paths: HashMap<String, Arc<ClipPath>>,
64 pub masks: HashMap<String, Arc<Mask>>,
65 pub filters: HashMap<String, Arc<filter::Filter>>,
66 pub paint: HashMap<String, Paint>,
67
68 all_ids: HashSet<u64>,
70 linear_gradient_index: usize,
71 radial_gradient_index: usize,
72 pattern_index: usize,
73 clip_path_index: usize,
74 mask_index: usize,
75 filter_index: usize,
76 image_index: usize,
77}
78
79macro_rules! font_lookup {
80 ($method_name:ident, $cache_map:ident, $font_variant:ident, $return_type:ty) => {
81 #[cfg(feature = "text")]
82 pub(crate) fn $method_name(&mut self, font: ID, glyph: GlyphId) -> Option<$return_type> {
83 let key = (font, glyph);
84 match self.$cache_map.get(&key) {
85 Some(cache_hit) => cache_hit.clone(),
86 None => {
87 let lookup = self.fontdb.$font_variant(font, glyph);
88 self.$cache_map.insert(key, lookup.clone());
89 lookup
90 }
91 }
92 }
93 };
94}
95
96impl Cache {
97 pub(crate) fn new(#[cfg(feature = "text")] fontdb: Arc<Database>) -> Self {
98 Self {
99 #[cfg(feature = "text")]
100 fontdb,
101
102 #[cfg(feature = "text")]
103 cache_outline: HashMap::new(),
104 #[cfg(feature = "text")]
105 cache_colr: HashMap::new(),
106 #[cfg(feature = "text")]
107 cache_svg: HashMap::new(),
108 #[cfg(feature = "text")]
109 cache_raster: HashMap::new(),
110 #[cfg(feature = "text")]
111 cache_has_opsz: HashMap::new(),
112
113 clip_paths: HashMap::new(),
114 masks: HashMap::new(),
115 filters: HashMap::new(),
116 paint: HashMap::new(),
117
118 all_ids: HashSet::new(),
119 linear_gradient_index: 0,
120 radial_gradient_index: 0,
121 pattern_index: 0,
122 clip_path_index: 0,
123 mask_index: 0,
124 filter_index: 0,
125 image_index: 0,
126 }
127 }
128
129 pub(crate) fn gen_linear_gradient_id(&mut self) -> NonEmptyString {
131 loop {
132 self.linear_gradient_index += 1;
133 let new_id = format!("linearGradient{}", self.linear_gradient_index);
134 let new_hash = string_hash(&new_id);
135 if !self.all_ids.contains(&new_hash) {
136 return NonEmptyString::new(new_id).unwrap();
137 }
138 }
139 }
140
141 pub(crate) fn gen_radial_gradient_id(&mut self) -> NonEmptyString {
142 loop {
143 self.radial_gradient_index += 1;
144 let new_id = format!("radialGradient{}", self.radial_gradient_index);
145 let new_hash = string_hash(&new_id);
146 if !self.all_ids.contains(&new_hash) {
147 return NonEmptyString::new(new_id).unwrap();
148 }
149 }
150 }
151
152 pub(crate) fn gen_pattern_id(&mut self) -> NonEmptyString {
153 loop {
154 self.pattern_index += 1;
155 let new_id = format!("pattern{}", self.pattern_index);
156 let new_hash = string_hash(&new_id);
157 if !self.all_ids.contains(&new_hash) {
158 return NonEmptyString::new(new_id).unwrap();
159 }
160 }
161 }
162
163 pub(crate) fn gen_clip_path_id(&mut self) -> NonEmptyString {
164 loop {
165 self.clip_path_index += 1;
166 let new_id = format!("clipPath{}", self.clip_path_index);
167 let new_hash = string_hash(&new_id);
168 if !self.all_ids.contains(&new_hash) {
169 return NonEmptyString::new(new_id).unwrap();
170 }
171 }
172 }
173
174 pub(crate) fn gen_mask_id(&mut self) -> NonEmptyString {
175 loop {
176 self.mask_index += 1;
177 let new_id = format!("mask{}", self.mask_index);
178 let new_hash = string_hash(&new_id);
179 if !self.all_ids.contains(&new_hash) {
180 return NonEmptyString::new(new_id).unwrap();
181 }
182 }
183 }
184
185 pub(crate) fn gen_filter_id(&mut self) -> NonEmptyString {
186 loop {
187 self.filter_index += 1;
188 let new_id = format!("filter{}", self.filter_index);
189 let new_hash = string_hash(&new_id);
190 if !self.all_ids.contains(&new_hash) {
191 return NonEmptyString::new(new_id).unwrap();
192 }
193 }
194 }
195
196 pub(crate) fn gen_image_id(&mut self) -> NonEmptyString {
197 loop {
198 self.image_index += 1;
199 let new_id = format!("image{}", self.image_index);
200 let new_hash = string_hash(&new_id);
201 if !self.all_ids.contains(&new_hash) {
202 return NonEmptyString::new(new_id).unwrap();
203 }
204 }
205 }
206
207 font_lookup!(fontdb_outline, cache_outline, outline, tiny_skia_path::Path);
208 font_lookup!(fontdb_colr, cache_colr, colr, Tree);
209 font_lookup!(fontdb_svg, cache_svg, svg, Node);
210 font_lookup!(fontdb_raster, cache_raster, raster, BitmapImage);
211
212 #[cfg(feature = "text")]
213 pub(crate) fn has_opsz_axis(&mut self, font: ID) -> bool {
214 if let Some(&cached) = self.cache_has_opsz.get(&font) {
215 return cached;
216 }
217 let has_opsz = self.fontdb.has_opsz_axis(font);
218 self.cache_has_opsz.insert(font, has_opsz);
219 has_opsz
220 }
221}
222
223fn string_hash(s: &str) -> u64 {
225 let mut h = std::collections::hash_map::DefaultHasher::new();
226 s.hash(&mut h);
227 h.finish()
228}
229
230impl<'a, 'input: 'a> SvgNode<'a, 'input> {
231 pub(crate) fn convert_length(
232 &self,
233 aid: AId,
234 object_units: Units,
235 state: &State,
236 def: Length,
237 ) -> f32 {
238 units::convert_length(
239 self.attribute(aid).unwrap_or(def),
240 *self,
241 aid,
242 object_units,
243 state,
244 )
245 }
246
247 pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 {
248 self.convert_length(aid, Units::UserSpaceOnUse, state, def)
249 }
250
251 pub fn parse_viewbox(&self) -> Option<NonZeroRect> {
252 let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?;
253 NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32)
254 }
255
256 pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 {
257 debug_assert!(
258 !matches!(aid, AId::BaselineShift | AId::FontSize),
259 "{} cannot be resolved via this function",
260 aid
261 );
262
263 if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) {
264 if let Some(length) = n.attribute(aid) {
265 return units::convert_user_length(length, n, aid, state);
266 }
267 }
268
269 def
270 }
271
272 pub fn resolve_valid_length(
273 &self,
274 aid: AId,
275 state: &State,
276 def: f32,
277 ) -> Option<NonZeroPositiveF32> {
278 let n = self.resolve_length(aid, state, def);
279 NonZeroPositiveF32::new(n)
280 }
281
282 pub(crate) fn try_convert_length(
283 &self,
284 aid: AId,
285 object_units: Units,
286 state: &State,
287 ) -> Option<f32> {
288 Some(units::convert_length(
289 self.attribute(aid)?,
290 *self,
291 aid,
292 object_units,
293 state,
294 ))
295 }
296
297 pub fn has_valid_transform(&self, aid: AId) -> bool {
298 let attr = match self.attribute(aid) {
302 Some(attr) => attr,
303 None => return true,
304 };
305
306 let ts = match svgtypes::Transform::from_str(attr) {
307 Ok(v) => v,
308 Err(_) => return true,
309 };
310
311 let ts = Transform::from_row(
312 ts.a as f32,
313 ts.b as f32,
314 ts.c as f32,
315 ts.d as f32,
316 ts.e as f32,
317 ts.f as f32,
318 );
319 ts.is_valid()
320 }
321
322 pub fn is_visible_element(&self, opt: &crate::Options) -> bool {
323 self.attribute(AId::Display) != Some("none")
324 && self.has_valid_transform(AId::Transform)
325 && super::switch::is_condition_passed(*self, opt)
326 }
327}
328
329pub trait SvgColorExt {
330 fn split_alpha(self) -> (Color, Opacity);
331}
332
333impl SvgColorExt for svgtypes::Color {
334 fn split_alpha(self) -> (Color, Opacity) {
335 (
336 Color::new_rgb(self.red, self.green, self.blue),
337 Opacity::new_u8(self.alpha),
338 )
339 }
340}
341
342pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> {
349 let svg = svg_doc.root_element();
350 let (size, restore_viewbox) = resolve_svg_size(&svg, opt);
351 let size = size?;
352 let view_box = ViewBox {
353 rect: svg
354 .parse_viewbox()
355 .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)),
356 aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
357 };
358
359 let background_color = svg
360 .attribute::<&str>(AId::BackgroundColor)
361 .and_then(|s| svgtypes::Paint::from_str(s).ok())
362 .and_then(|paint| match paint {
363 svgtypes::Paint::Color(c) => Some(c),
364 _ => None,
365 });
366
367 let mut tree = Tree {
368 size,
369 root: Group::empty(),
370 linear_gradients: Vec::new(),
371 radial_gradients: Vec::new(),
372 patterns: Vec::new(),
373 clip_paths: Vec::new(),
374 masks: Vec::new(),
375 filters: Vec::new(),
376 #[cfg(feature = "text")]
377 fontdb: opt.fontdb.clone(),
378 };
379
380 if !svg.is_visible_element(opt) {
381 return Ok(tree);
382 }
383
384 let state = State {
385 parent_clip_path: None,
386 context_element: None,
387 parent_markers: Vec::new(),
388 fe_image_link: false,
389 view_box: view_box.rect,
390 use_size: (None, None),
391 opt,
392 };
393
394 let mut cache = Cache::new(
395 #[cfg(feature = "text")]
396 opt.fontdb.clone(),
397 );
398
399 for node in svg_doc.descendants() {
400 if let Some(tag) = node.tag_name() {
401 if matches!(
402 tag,
403 EId::ClipPath
404 | EId::Filter
405 | EId::LinearGradient
406 | EId::Mask
407 | EId::Pattern
408 | EId::RadialGradient
409 | EId::Image
410 ) {
411 if !node.element_id().is_empty() {
412 cache.all_ids.insert(string_hash(node.element_id()));
413 }
414 }
415 }
416 }
417
418 let root_ts = view_box.to_transform(tree.size());
419 if root_ts.is_identity() && background_color.is_none() {
420 convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
421 } else {
422 let mut g = Group::empty();
423
424 if let Some(background_color) = background_color {
425 if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {
426 g.children.push(Node::Path(Box::new(path)));
427 }
428 }
429
430 g.transform = root_ts;
431 g.abs_transform = root_ts;
432 convert_children(svg_doc.root(), &state, &mut cache, &mut g);
433 g.calculate_bounding_boxes();
434 tree.root.children.push(Node::Group(Box::new(g)));
435 }
436
437 cache.clip_paths.clear();
439 cache.masks.clear();
440 cache.filters.clear();
441 cache.paint.clear();
442
443 super::paint_server::update_paint_servers(
444 &mut tree.root,
445 Transform::default(),
446 None,
447 None,
448 &mut cache,
449 );
450 tree.collect_paint_servers();
451 tree.root.collect_clip_paths(&mut tree.clip_paths);
452 tree.root.collect_masks(&mut tree.masks);
453 tree.root.collect_filters(&mut tree.filters);
454 tree.root.calculate_bounding_boxes();
455
456 #[cfg(feature = "text")]
459 {
460 tree.fontdb = cache.fontdb;
461 }
462
463 if restore_viewbox {
464 calculate_svg_bbox(&mut tree);
465 }
466
467 Ok(tree)
468}
469
470fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
471 let path = PathBuilder::from_rect(area);
472
473 let fill = Fill {
474 paint: Paint::Color(Color::new_rgb(
475 background_color.red,
476 background_color.green,
477 background_color.blue,
478 )),
479 opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,
480 ..Default::default()
481 };
482
483 let mut path = Path::new_simple(Arc::new(path))?;
484 path.fill = Some(fill);
485
486 Some(path)
487}
488
489fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
490 let mut state = State {
491 parent_clip_path: None,
492 context_element: None,
493 parent_markers: Vec::new(),
494 fe_image_link: false,
495 view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
496 use_size: (None, None),
497 opt,
498 };
499
500 let def = Length::new(100.0, Unit::Percent);
501 let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
502 let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
503
504 let view_box = svg.parse_viewbox();
505
506 let restore_viewbox =
507 if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
508 if width.unit == Unit::Percent {
510 width = Length::new(
511 (width.number / 100.0) * state.opt.default_size.width() as f64,
512 Unit::None,
513 );
514 }
515
516 if height.unit == Unit::Percent {
517 height = Length::new(
518 (height.number / 100.0) * state.opt.default_size.height() as f64,
519 Unit::None,
520 );
521 }
522
523 true
524 } else {
525 false
526 };
527
528 let size = if let Some(vbox) = view_box {
529 state.view_box = vbox;
530
531 let w = if width.unit == Unit::Percent {
532 vbox.width() * (width.number as f32 / 100.0)
533 } else {
534 svg.convert_user_length(AId::Width, &state, def)
535 };
536
537 let h = if height.unit == Unit::Percent {
538 vbox.height() * (height.number as f32 / 100.0)
539 } else {
540 svg.convert_user_length(AId::Height, &state, def)
541 };
542
543 Size::from_wh(w, h)
544 } else {
545 Size::from_wh(
546 svg.convert_user_length(AId::Width, &state, def),
547 svg.convert_user_length(AId::Height, &state, def),
548 )
549 };
550
551 (size.ok_or(Error::InvalidSize), restore_viewbox)
552}
553
554fn calculate_svg_bbox(tree: &mut Tree) {
558 let bbox = tree.root.abs_bounding_box();
559 if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
560 tree.size = size;
561 }
562}
563
564#[inline(never)]
565pub(crate) fn convert_children(
566 parent_node: SvgNode,
567 state: &State,
568 cache: &mut Cache,
569 parent: &mut Group,
570) {
571 for node in parent_node.children() {
572 convert_element(node, state, cache, parent);
573 }
574}
575
576#[inline(never)]
577pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
578 let tag_name = match node.tag_name() {
579 Some(v) => v,
580 None => return,
581 };
582
583 if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
584 return;
585 }
586
587 if !node.is_visible_element(state.opt) {
588 return;
589 }
590
591 if tag_name == EId::Use {
592 super::use_node::convert(node, state, cache, parent);
593 return;
594 }
595
596 if tag_name == EId::Switch {
597 super::switch::convert(node, state, cache, parent);
598 return;
599 }
600
601 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
602 convert_element_impl(tag_name, node, state, cache, g);
603 }) {
604 parent.children.push(Node::Group(Box::new(g)));
605 }
606}
607
608#[inline(never)]
609fn convert_element_impl(
610 tag_name: EId,
611 node: SvgNode,
612 state: &State,
613 cache: &mut Cache,
614 parent: &mut Group,
615) {
616 match tag_name {
617 EId::Rect
618 | EId::Circle
619 | EId::Ellipse
620 | EId::Line
621 | EId::Polyline
622 | EId::Polygon
623 | EId::Path => {
624 if let Some(path) = super::shapes::convert(node, state) {
625 convert_path(node, path, state, cache, parent);
626 }
627 }
628 EId::Image => {
629 super::image::convert(node, state, cache, parent);
630 }
631 EId::Text => {
632 #[cfg(feature = "text")]
633 {
634 super::text::convert(node, state, cache, parent);
635 }
636 }
637 EId::Svg => {
638 if node.parent_element().is_some() {
639 super::use_node::convert_svg(node, state, cache, parent);
640 } else {
641 convert_children(node, state, cache, parent);
643 }
644 }
645 EId::G => {
646 convert_children(node, state, cache, parent);
647 }
648 _ => {}
649 }
650}
651
652#[inline(never)]
657pub(crate) fn convert_clip_path_elements(
658 clip_node: SvgNode,
659 state: &State,
660 cache: &mut Cache,
661 parent: &mut Group,
662) {
663 for node in clip_node.children() {
664 let tag_name = match node.tag_name() {
665 Some(v) => v,
666 None => continue,
667 };
668
669 if !tag_name.is_graphic() {
670 continue;
671 }
672
673 if !node.is_visible_element(state.opt) {
674 continue;
675 }
676
677 if tag_name == EId::Use {
678 super::use_node::convert(node, state, cache, parent);
679 continue;
680 }
681
682 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
683 convert_clip_path_elements_impl(tag_name, node, state, cache, g);
684 }) {
685 parent.children.push(Node::Group(Box::new(g)));
686 }
687 }
688}
689
690#[inline(never)]
691fn convert_clip_path_elements_impl(
692 tag_name: EId,
693 node: SvgNode,
694 state: &State,
695 cache: &mut Cache,
696 parent: &mut Group,
697) {
698 match tag_name {
699 EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
700 if let Some(path) = super::shapes::convert(node, state) {
701 convert_path(node, path, state, cache, parent);
702 }
703 }
704 EId::Text => {
705 #[cfg(feature = "text")]
706 {
707 super::text::convert(node, state, cache, parent);
708 }
709 }
710 _ => {
711 log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
712 }
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Debug)]
717enum Isolation {
718 Auto,
719 Isolate,
720}
721
722impl Default for Isolation {
723 fn default() -> Self {
724 Self::Auto
725 }
726}
727
728impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
729 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
730 match value {
731 "auto" => Some(Isolation::Auto),
732 "isolate" => Some(Isolation::Isolate),
733 _ => None,
734 }
735 }
736}
737
738pub(crate) fn convert_group(
740 node: SvgNode,
741 state: &State,
742 force: bool,
743 cache: &mut Cache,
744 parent: &mut Group,
745 collect_children: &dyn Fn(&mut Cache, &mut Group),
746) -> Option<Group> {
747 let opacity = if state.parent_clip_path.is_none() {
749 node.attribute::<Opacity>(AId::Opacity)
750 .unwrap_or(Opacity::ONE)
751 } else {
752 Opacity::ONE
753 };
754
755 let transform = node.resolve_transform(AId::Transform, state);
756 let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
757 let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
758 let isolate = isolation == Isolation::Isolate;
759
760 let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
762 let id = if is_g_or_use && state.parent_markers.is_empty() {
763 node.element_id().to_string()
764 } else {
765 String::new()
766 };
767
768 let abs_transform = parent.abs_transform.pre_concat(transform);
769 let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
770 let mut g = Group {
771 id,
772 transform,
773 abs_transform,
774 opacity,
775 blend_mode,
776 isolate,
777 clip_path: None,
778 mask: None,
779 filters: Vec::new(),
780 is_context_element: false,
781 bounding_box: dummy,
782 abs_bounding_box: dummy,
783 stroke_bounding_box: dummy,
784 abs_stroke_bounding_box: dummy,
785 layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
786 abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
787 children: Vec::new(),
788 };
789 collect_children(cache, &mut g);
790
791 let object_bbox = g.calculate_object_bbox();
794
795 let mut clip_path = None;
799 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
800 clip_path = super::clippath::convert(link, state, object_bbox, cache);
801 if clip_path.is_none() {
802 return None;
803 }
804 }
805
806 let mut mask = None;
807 if state.parent_clip_path.is_none() {
808 if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
809 mask = super::mask::convert(link, state, object_bbox, cache);
810 if mask.is_none() {
811 return None;
812 }
813 }
814 }
815
816 let filters = {
817 let mut filters = Vec::new();
818 if state.parent_clip_path.is_none() {
819 if node.attribute(AId::Filter) == Some("none") {
820 } else if node.has_attribute(AId::Filter) {
822 if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
823 filters = f;
824 } else {
825 return None;
838 }
839 }
840 }
841
842 filters
843 };
844
845 let required = opacity.get().approx_ne_ulps(&1.0, 4)
846 || clip_path.is_some()
847 || mask.is_some()
848 || !filters.is_empty()
849 || !transform.is_identity()
850 || blend_mode != BlendMode::Normal
851 || isolate
852 || is_g_or_use
853 || force;
854
855 if !required {
856 parent.children.append(&mut g.children);
857 return None;
858 }
859
860 g.clip_path = clip_path;
861 g.mask = mask;
862 g.filters = filters;
863
864 g.calculate_bounding_boxes();
866
867 Some(g)
868}
869
870fn convert_path(
871 node: SvgNode,
872 tiny_skia_path: Arc<tiny_skia_path::Path>,
873 state: &State,
874 cache: &mut Cache,
875 parent: &mut Group,
876) {
877 debug_assert!(tiny_skia_path.len() >= 2);
878 if tiny_skia_path.len() < 2 {
879 return;
880 }
881
882 let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
883 let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
884 let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
885 let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
886 let mut visible = visibility == Visibility::Visible;
887 let rendering_mode: ShapeRendering = node
888 .find_attribute(AId::ShapeRendering)
889 .unwrap_or(state.opt.shape_rendering);
890
891 let raw_paint_order: svgtypes::PaintOrder =
893 node.find_attribute(AId::PaintOrder).unwrap_or_default();
894 let paint_order = svg_paint_order_to_usvg(raw_paint_order);
895 let path_transform = parent.abs_transform;
896
897 if fill.is_none() && stroke.is_none() {
900 visible = false;
901 }
902
903 if let Some(fill) = fill.as_mut() {
904 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
905 fill.context_element
906 {
907 process_paint(
908 &mut fill.paint,
909 true,
910 context_transform,
911 context_bbox.map(|r| r.to_rect()),
912 path_transform,
913 tiny_skia_path.bounds(),
914 cache,
915 );
916 fill.context_element = None;
917 }
918 }
919
920 if let Some(stroke) = stroke.as_mut() {
921 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
922 stroke.context_element
923 {
924 process_paint(
925 &mut stroke.paint,
926 true,
927 context_transform,
928 context_bbox.map(|r| r.to_rect()),
929 path_transform,
930 tiny_skia_path.bounds(),
931 cache,
932 );
933 stroke.context_element = None;
934 }
935 }
936
937 let mut marker = None;
938 if marker::is_valid(node) && visibility == Visibility::Visible {
939 let mut marker_group = Group {
940 abs_transform: parent.abs_transform,
941 ..Group::empty()
942 };
943
944 let mut marker_state = state.clone();
945
946 let bbox = tiny_skia_path
947 .compute_tight_bounds()
948 .and_then(|r| r.to_non_zero_rect());
949
950 let fill = fill.clone().map(|mut f| {
951 f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
952 f
953 });
954
955 let stroke = stroke.clone().map(|mut s| {
956 s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
957 s
958 });
959
960 marker_state.context_element = Some((fill, stroke));
961
962 marker::convert(
963 node,
964 &tiny_skia_path,
965 &marker_state,
966 cache,
967 &mut marker_group,
968 );
969 marker_group.calculate_bounding_boxes();
970 marker = Some(marker_group);
971 }
972
973 let id = if state.parent_markers.is_empty() {
975 node.element_id().to_string()
976 } else {
977 String::new()
978 };
979
980 let path = Path::new(
981 id,
982 visible,
983 fill,
984 stroke,
985 paint_order,
986 rendering_mode,
987 tiny_skia_path,
988 path_transform,
989 );
990
991 let path = match path {
992 Some(v) => v,
993 None => return,
994 };
995
996 match (raw_paint_order.order, marker) {
997 ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {
998 parent.children.push(Node::Group(Box::new(markers_node)));
999 parent.children.push(Node::Path(Box::new(path.clone())));
1000 }
1001 ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {
1002 append_single_paint_path(first, &path, parent);
1003 parent.children.push(Node::Group(Box::new(markers_node)));
1004 append_single_paint_path(last, &path, parent);
1005 }
1006 ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {
1007 parent.children.push(Node::Path(Box::new(path.clone())));
1008 parent.children.push(Node::Group(Box::new(markers_node)));
1009 }
1010 _ => parent.children.push(Node::Path(Box::new(path.clone()))),
1011 }
1012}
1013
1014fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
1015 match paint_order_kind {
1016 PaintOrderKind::Fill => {
1017 if path.fill.is_some() {
1018 let mut fill_path = path.clone();
1019 fill_path.stroke = None;
1020 fill_path.id = String::new();
1021 parent.children.push(Node::Path(Box::new(fill_path)));
1022 }
1023 }
1024 PaintOrderKind::Stroke => {
1025 if path.stroke.is_some() {
1026 let mut stroke_path = path.clone();
1027 stroke_path.fill = None;
1028 stroke_path.id = String::new();
1029 parent.children.push(Node::Path(Box::new(stroke_path)));
1030 }
1031 }
1032 _ => {}
1033 }
1034}
1035
1036pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
1037 match (order.order[0], order.order[1]) {
1038 (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
1039 (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
1040 PaintOrder::StrokeAndFill
1041 }
1042 _ => PaintOrder::FillAndStroke,
1043 }
1044}
1045
1046impl SvgNode<'_, '_> {
1047 pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
1048 let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
1049 let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);
1050
1051 if let Some(transform_origin) = transform_origin {
1052 let dx = convert_length(
1053 transform_origin.x_offset,
1054 *self,
1055 AId::Width,
1056 Units::UserSpaceOnUse,
1057 state,
1058 );
1059 let dy = convert_length(
1060 transform_origin.y_offset,
1061 *self,
1062 AId::Height,
1063 Units::UserSpaceOnUse,
1064 state,
1065 );
1066 transform = Transform::default()
1067 .pre_translate(dx, dy)
1068 .pre_concat(transform)
1069 .pre_translate(-dx, -dy);
1070 }
1071
1072 transform
1073 }
1074}