1use std::collections::{HashMap, HashSet};
5use std::hash::{Hash, Hasher};
6use std::str::FromStr;
7use std::sync::Arc;
8
9#[cfg(feature = "text")]
10use crate::{FontVariation, GlyphId};
11#[cfg(feature = "text")]
12use fontdb::Database;
13#[cfg(feature = "text")]
14use fontdb::ID;
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, Vec<FontVariation>), Option<tiny_skia_path::Path>>,
54 #[cfg(feature = "text")]
55 cache_colr: HashMap<(ID, GlyphId, Vec<FontVariation>), 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_svg, cache_svg, svg, Node);
208 font_lookup!(fontdb_raster, cache_raster, raster, BitmapImage);
209
210 #[cfg(feature = "text")]
211 pub(crate) fn fontdb_outline(
212 &mut self,
213 font: ID,
214 glyph: GlyphId,
215 variations: &[FontVariation],
216 ) -> Option<tiny_skia_path::Path> {
217 let key = (font, glyph, variations.to_vec());
218 match self.cache_outline.get(&key) {
219 Some(cache_hit) => cache_hit.clone(),
220 None => {
221 let lookup = self.fontdb.outline(font, glyph, variations);
222 self.cache_outline.insert(key, lookup.clone());
223 lookup
224 }
225 }
226 }
227
228 #[cfg(feature = "text")]
229 pub(crate) fn fontdb_colr(
230 &mut self,
231 font: ID,
232 glyph: GlyphId,
233 variations: &[FontVariation],
234 ) -> Option<Tree> {
235 let key = (font, glyph, variations.to_vec());
236 match self.cache_colr.get(&key) {
237 Some(cache_hit) => cache_hit.clone(),
238 None => {
239 let lookup = self.fontdb.colr(font, glyph, variations);
240 self.cache_colr.insert(key, lookup.clone());
241 lookup
242 }
243 }
244 }
245
246 #[cfg(feature = "text")]
247 pub(crate) fn has_opsz_axis(&mut self, font: ID) -> bool {
248 if let Some(&cached) = self.cache_has_opsz.get(&font) {
249 return cached;
250 }
251 let has_opsz = self.fontdb.has_opsz_axis(font);
252 self.cache_has_opsz.insert(font, has_opsz);
253 has_opsz
254 }
255}
256
257fn string_hash(s: &str) -> u64 {
259 let mut h = std::collections::hash_map::DefaultHasher::new();
260 s.hash(&mut h);
261 h.finish()
262}
263
264impl<'a, 'input: 'a> SvgNode<'a, 'input> {
265 pub(crate) fn convert_length(
266 &self,
267 aid: AId,
268 object_units: Units,
269 state: &State,
270 def: Length,
271 ) -> f32 {
272 units::convert_length(
273 self.attribute(aid).unwrap_or(def),
274 *self,
275 aid,
276 object_units,
277 state,
278 )
279 }
280
281 pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 {
282 self.convert_length(aid, Units::UserSpaceOnUse, state, def)
283 }
284
285 pub fn parse_viewbox(&self) -> Option<NonZeroRect> {
286 let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?;
287 NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32)
288 }
289
290 pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 {
291 debug_assert!(
292 !matches!(aid, AId::BaselineShift | AId::FontSize),
293 "{} cannot be resolved via this function",
294 aid
295 );
296
297 if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) {
298 if let Some(length) = n.attribute(aid) {
299 return units::convert_user_length(length, n, aid, state);
300 }
301 }
302
303 def
304 }
305
306 pub fn resolve_valid_length(
307 &self,
308 aid: AId,
309 state: &State,
310 def: f32,
311 ) -> Option<NonZeroPositiveF32> {
312 let n = self.resolve_length(aid, state, def);
313 NonZeroPositiveF32::new(n)
314 }
315
316 pub(crate) fn try_convert_length(
317 &self,
318 aid: AId,
319 object_units: Units,
320 state: &State,
321 ) -> Option<f32> {
322 Some(units::convert_length(
323 self.attribute(aid)?,
324 *self,
325 aid,
326 object_units,
327 state,
328 ))
329 }
330
331 pub fn has_valid_transform(&self, aid: AId) -> bool {
332 let attr = match self.attribute(aid) {
336 Some(attr) => attr,
337 None => return true,
338 };
339
340 let ts = match svgtypes::Transform::from_str(attr) {
341 Ok(v) => v,
342 Err(_) => return true,
343 };
344
345 let ts = Transform::from_row(
346 ts.a as f32,
347 ts.b as f32,
348 ts.c as f32,
349 ts.d as f32,
350 ts.e as f32,
351 ts.f as f32,
352 );
353 ts.is_valid()
354 }
355
356 pub fn is_visible_element(&self, opt: &crate::Options) -> bool {
357 self.attribute(AId::Display) != Some("none")
358 && self.has_valid_transform(AId::Transform)
359 && super::switch::is_condition_passed(*self, opt)
360 }
361}
362
363pub trait SvgColorExt {
364 fn split_alpha(self) -> (Color, Opacity);
365}
366
367impl SvgColorExt for svgtypes::Color {
368 fn split_alpha(self) -> (Color, Opacity) {
369 (
370 Color::new_rgb(self.red, self.green, self.blue),
371 Opacity::new_u8(self.alpha),
372 )
373 }
374}
375
376pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> {
383 let svg = svg_doc.root_element();
384 let (size, restore_viewbox) = resolve_svg_size(&svg, opt);
385 let size = size?;
386 let view_box = ViewBox {
387 rect: svg
388 .parse_viewbox()
389 .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)),
390 aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
391 };
392
393 let background_color = svg
394 .attribute::<&str>(AId::BackgroundColor)
395 .and_then(|s| svgtypes::Paint::from_str(s).ok())
396 .and_then(|paint| match paint {
397 svgtypes::Paint::Color(c) => Some(c),
398 _ => None,
399 });
400
401 let mut tree = Tree {
402 size,
403 root: Group::empty(),
404 linear_gradients: Vec::new(),
405 radial_gradients: Vec::new(),
406 patterns: Vec::new(),
407 clip_paths: Vec::new(),
408 masks: Vec::new(),
409 filters: Vec::new(),
410 #[cfg(feature = "text")]
411 fontdb: opt.fontdb.clone(),
412 };
413
414 if !svg.is_visible_element(opt) {
415 return Ok(tree);
416 }
417
418 let state = State {
419 parent_clip_path: None,
420 context_element: None,
421 parent_markers: Vec::new(),
422 fe_image_link: false,
423 view_box: view_box.rect,
424 use_size: (None, None),
425 opt,
426 };
427
428 let mut cache = Cache::new(
429 #[cfg(feature = "text")]
430 opt.fontdb.clone(),
431 );
432
433 for node in svg_doc.descendants() {
434 if let Some(tag) = node.tag_name() {
435 if matches!(
436 tag,
437 EId::ClipPath
438 | EId::Filter
439 | EId::LinearGradient
440 | EId::Mask
441 | EId::Pattern
442 | EId::RadialGradient
443 | EId::Image
444 ) {
445 if !node.element_id().is_empty() {
446 cache.all_ids.insert(string_hash(node.element_id()));
447 }
448 }
449 }
450 }
451
452 let root_ts = view_box.to_transform(tree.size());
453 if root_ts.is_identity() && background_color.is_none() {
454 convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
455 } else {
456 let mut g = Group::empty();
457
458 if let Some(background_color) = background_color {
459 if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {
460 g.children.push(Node::Path(Box::new(path)));
461 }
462 }
463
464 g.transform = root_ts;
465 g.abs_transform = root_ts;
466 convert_children(svg_doc.root(), &state, &mut cache, &mut g);
467 g.calculate_bounding_boxes();
468 tree.root.children.push(Node::Group(Box::new(g)));
469 }
470
471 cache.clip_paths.clear();
473 cache.masks.clear();
474 cache.filters.clear();
475 cache.paint.clear();
476
477 super::paint_server::update_paint_servers(
478 &mut tree.root,
479 Transform::default(),
480 None,
481 None,
482 &mut cache,
483 );
484 tree.collect_paint_servers();
485 tree.root.collect_clip_paths(&mut tree.clip_paths);
486 tree.root.collect_masks(&mut tree.masks);
487 tree.root.collect_filters(&mut tree.filters);
488 tree.root.calculate_bounding_boxes();
489
490 #[cfg(feature = "text")]
493 {
494 tree.fontdb = cache.fontdb;
495 }
496
497 if restore_viewbox {
498 calculate_svg_bbox(&mut tree);
499 }
500
501 Ok(tree)
502}
503
504fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
505 let path = PathBuilder::from_rect(area);
506
507 let fill = Fill {
508 paint: Paint::Color(Color::new_rgb(
509 background_color.red,
510 background_color.green,
511 background_color.blue,
512 )),
513 opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,
514 ..Default::default()
515 };
516
517 let mut path = Path::new_simple(Arc::new(path))?;
518 path.fill = Some(fill);
519
520 Some(path)
521}
522
523fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
524 let mut state = State {
525 parent_clip_path: None,
526 context_element: None,
527 parent_markers: Vec::new(),
528 fe_image_link: false,
529 view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
530 use_size: (None, None),
531 opt,
532 };
533
534 let def = Length::new(100.0, Unit::Percent);
535 let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
536 let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
537
538 let view_box = svg.parse_viewbox();
539
540 let restore_viewbox =
541 if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
542 if width.unit == Unit::Percent {
544 width = Length::new(
545 (width.number / 100.0) * state.opt.default_size.width() as f64,
546 Unit::None,
547 );
548 }
549
550 if height.unit == Unit::Percent {
551 height = Length::new(
552 (height.number / 100.0) * state.opt.default_size.height() as f64,
553 Unit::None,
554 );
555 }
556
557 true
558 } else {
559 false
560 };
561
562 let size = if let Some(vbox) = view_box {
563 state.view_box = vbox;
564
565 let w = if width.unit == Unit::Percent {
566 vbox.width() * (width.number as f32 / 100.0)
567 } else {
568 svg.convert_user_length(AId::Width, &state, def)
569 };
570
571 let h = if height.unit == Unit::Percent {
572 vbox.height() * (height.number as f32 / 100.0)
573 } else {
574 svg.convert_user_length(AId::Height, &state, def)
575 };
576
577 match (
580 svg.attribute::<Length>(AId::Width),
581 svg.attribute::<Length>(AId::Height),
582 ) {
583 (Some(_), None) => Size::from_wh(w, vbox.height() * w / vbox.width()),
584 (None, Some(_)) => Size::from_wh(vbox.width() * h / vbox.height(), h),
585 (_, _) => Size::from_wh(w, h),
586 }
587 } else {
588 Size::from_wh(
589 svg.convert_user_length(AId::Width, &state, def),
590 svg.convert_user_length(AId::Height, &state, def),
591 )
592 };
593
594 (size.ok_or(Error::InvalidSize), restore_viewbox)
595}
596
597fn calculate_svg_bbox(tree: &mut Tree) {
601 let bbox = tree.root.abs_bounding_box();
602 if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
603 tree.size = size;
604 }
605}
606
607#[inline(never)]
608pub(crate) fn convert_children(
609 parent_node: SvgNode,
610 state: &State,
611 cache: &mut Cache,
612 parent: &mut Group,
613) {
614 for node in parent_node.children() {
615 convert_element(node, state, cache, parent);
616 }
617}
618
619#[inline(never)]
620pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
621 let tag_name = match node.tag_name() {
622 Some(v) => v,
623 None => return,
624 };
625
626 if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
627 return;
628 }
629
630 if !node.is_visible_element(state.opt) {
631 return;
632 }
633
634 if tag_name == EId::Use {
635 super::use_node::convert(node, state, cache, parent);
636 return;
637 }
638
639 if tag_name == EId::Switch {
640 super::switch::convert(node, state, cache, parent);
641 return;
642 }
643
644 if tag_name == EId::Svg && node.parent_element().is_some() {
651 super::use_node::convert_svg(node, state, cache, parent);
652
653 return;
654 }
655
656 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
657 convert_element_impl(tag_name, node, state, cache, g);
658 }) {
659 parent.children.push(Node::Group(Box::new(g)));
660 }
661}
662
663#[inline(never)]
664fn convert_element_impl(
665 tag_name: EId,
666 node: SvgNode,
667 state: &State,
668 cache: &mut Cache,
669 parent: &mut Group,
670) {
671 match tag_name {
672 EId::Rect
673 | EId::Circle
674 | EId::Ellipse
675 | EId::Line
676 | EId::Polyline
677 | EId::Polygon
678 | EId::Path => {
679 if let Some(path) = super::shapes::convert(node, state) {
680 convert_path(node, path, state, cache, parent);
681 }
682 }
683 EId::Image => {
684 super::image::convert(node, state, cache, parent);
685 }
686 EId::Text => {
687 #[cfg(feature = "text")]
688 {
689 super::text::convert(node, state, cache, parent);
690 }
691 }
692 EId::Svg => {
693 convert_children(node, state, cache, parent);
697 }
698 EId::G => {
699 convert_children(node, state, cache, parent);
700 }
701 _ => {}
702 }
703}
704
705#[inline(never)]
710pub(crate) fn convert_clip_path_elements(
711 clip_node: SvgNode,
712 state: &State,
713 cache: &mut Cache,
714 parent: &mut Group,
715) {
716 for node in clip_node.children() {
717 let tag_name = match node.tag_name() {
718 Some(v) => v,
719 None => continue,
720 };
721
722 if !tag_name.is_graphic() {
723 continue;
724 }
725
726 if !node.is_visible_element(state.opt) {
727 continue;
728 }
729
730 if tag_name == EId::Use {
731 super::use_node::convert(node, state, cache, parent);
732 continue;
733 }
734
735 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
736 convert_clip_path_elements_impl(tag_name, node, state, cache, g);
737 }) {
738 parent.children.push(Node::Group(Box::new(g)));
739 }
740 }
741}
742
743#[inline(never)]
744fn convert_clip_path_elements_impl(
745 tag_name: EId,
746 node: SvgNode,
747 state: &State,
748 cache: &mut Cache,
749 parent: &mut Group,
750) {
751 match tag_name {
752 EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
753 if let Some(path) = super::shapes::convert(node, state) {
754 convert_path(node, path, state, cache, parent);
755 }
756 }
757 EId::Text => {
758 #[cfg(feature = "text")]
759 {
760 super::text::convert(node, state, cache, parent);
761 }
762 }
763 _ => {
764 log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
765 }
766 }
767}
768
769#[derive(Clone, Copy, PartialEq, Debug)]
770enum Isolation {
771 Auto,
772 Isolate,
773}
774
775impl Default for Isolation {
776 fn default() -> Self {
777 Self::Auto
778 }
779}
780
781impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
782 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
783 match value {
784 "auto" => Some(Isolation::Auto),
785 "isolate" => Some(Isolation::Isolate),
786 _ => None,
787 }
788 }
789}
790
791pub(crate) fn convert_group(
793 node: SvgNode,
794 state: &State,
795 force: bool,
796 cache: &mut Cache,
797 parent: &mut Group,
798 collect_children: &dyn Fn(&mut Cache, &mut Group),
799) -> Option<Group> {
800 let opacity = if state.parent_clip_path.is_none() {
802 node.attribute::<Opacity>(AId::Opacity)
803 .unwrap_or(Opacity::ONE)
804 } else {
805 Opacity::ONE
806 };
807
808 let transform = node.resolve_transform(AId::Transform, state);
809 let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
810 let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
811 let isolate = isolation == Isolation::Isolate;
812
813 let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
815 let id = if is_g_or_use && state.parent_markers.is_empty() {
816 node.element_id().to_string()
817 } else {
818 String::new()
819 };
820
821 let abs_transform = parent.abs_transform.pre_concat(transform);
822 let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
823 let mut g = Group {
824 id,
825 transform,
826 abs_transform,
827 opacity,
828 blend_mode,
829 isolate,
830 clip_path: None,
831 mask: None,
832 filters: Vec::new(),
833 is_context_element: false,
834 bounding_box: dummy,
835 abs_bounding_box: dummy,
836 stroke_bounding_box: dummy,
837 abs_stroke_bounding_box: dummy,
838 layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
839 abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
840 children: Vec::new(),
841 };
842 collect_children(cache, &mut g);
843
844 let object_bbox = g.calculate_object_bbox();
847
848 let mut clip_path = None;
852 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
853 clip_path = super::clippath::convert(link, state, object_bbox, cache);
854 if clip_path.is_none() {
855 return None;
856 }
857 }
858
859 let mut mask = None;
860 if state.parent_clip_path.is_none() {
861 if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
862 mask = super::mask::convert(link, state, object_bbox, cache);
863 if mask.is_none() {
864 return None;
865 }
866 }
867 }
868
869 let filters = {
870 let mut filters = Vec::new();
871 if state.parent_clip_path.is_none() {
872 if node.attribute(AId::Filter) == Some("none") {
873 } else if node.has_attribute(AId::Filter) {
875 if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
876 filters = f;
877 } else {
878 return None;
891 }
892 }
893 }
894
895 filters
896 };
897
898 let required = opacity.get().approx_ne_ulps(&1.0, 4)
899 || clip_path.is_some()
900 || mask.is_some()
901 || !filters.is_empty()
902 || !transform.is_identity()
903 || blend_mode != BlendMode::Normal
904 || isolate
905 || is_g_or_use
906 || force;
907
908 if !required {
909 parent.children.append(&mut g.children);
910 return None;
911 }
912
913 g.clip_path = clip_path;
914 g.mask = mask;
915 g.filters = filters;
916
917 g.calculate_bounding_boxes();
919
920 Some(g)
921}
922
923fn convert_path(
924 node: SvgNode,
925 tiny_skia_path: Arc<tiny_skia_path::Path>,
926 state: &State,
927 cache: &mut Cache,
928 parent: &mut Group,
929) {
930 debug_assert!(tiny_skia_path.len() >= 2);
931 if tiny_skia_path.len() < 2 {
932 return;
933 }
934
935 let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
936 let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
937 let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
938 let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
939 let mut visible = visibility == Visibility::Visible;
940 let rendering_mode: ShapeRendering = node
941 .find_attribute(AId::ShapeRendering)
942 .unwrap_or(state.opt.shape_rendering);
943
944 let raw_paint_order: svgtypes::PaintOrder =
946 node.find_attribute(AId::PaintOrder).unwrap_or_default();
947 let paint_order = svg_paint_order_to_usvg(raw_paint_order);
948 let path_transform = parent.abs_transform;
949
950 if fill.is_none() && stroke.is_none() {
953 visible = false;
954 }
955
956 if let Some(fill) = fill.as_mut() {
957 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
958 fill.context_element
959 {
960 process_paint(
961 &mut fill.paint,
962 true,
963 context_transform,
964 context_bbox.map(|r| r.to_rect()),
965 path_transform,
966 tiny_skia_path.bounds(),
967 cache,
968 );
969 fill.context_element = None;
970 }
971 }
972
973 if let Some(stroke) = stroke.as_mut() {
974 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
975 stroke.context_element
976 {
977 process_paint(
978 &mut stroke.paint,
979 true,
980 context_transform,
981 context_bbox.map(|r| r.to_rect()),
982 path_transform,
983 tiny_skia_path.bounds(),
984 cache,
985 );
986 stroke.context_element = None;
987 }
988 }
989
990 let mut marker = None;
991 if marker::is_valid(node) && visibility == Visibility::Visible {
992 let mut marker_group = Group {
993 abs_transform: parent.abs_transform,
994 ..Group::empty()
995 };
996
997 let mut marker_state = state.clone();
998
999 let bbox = tiny_skia_path
1000 .compute_tight_bounds()
1001 .and_then(|r| r.to_non_zero_rect());
1002
1003 let fill = fill.clone().map(|mut f| {
1004 f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1005 f
1006 });
1007
1008 let stroke = stroke.clone().map(|mut s| {
1009 s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1010 s
1011 });
1012
1013 marker_state.context_element = Some((fill, stroke));
1014
1015 marker::convert(
1016 node,
1017 &tiny_skia_path,
1018 &marker_state,
1019 cache,
1020 &mut marker_group,
1021 );
1022 marker_group.calculate_bounding_boxes();
1023 marker = Some(marker_group);
1024 }
1025
1026 let id = if state.parent_markers.is_empty() {
1028 node.element_id().to_string()
1029 } else {
1030 String::new()
1031 };
1032
1033 let path = Path::new(
1034 id,
1035 visible,
1036 fill,
1037 stroke,
1038 paint_order,
1039 rendering_mode,
1040 tiny_skia_path,
1041 path_transform,
1042 );
1043
1044 let path = match path {
1045 Some(v) => v,
1046 None => return,
1047 };
1048
1049 match (raw_paint_order.order, marker) {
1050 ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {
1051 parent.children.push(Node::Group(Box::new(markers_node)));
1052 parent.children.push(Node::Path(Box::new(path.clone())));
1053 }
1054 ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {
1055 append_single_paint_path(first, &path, parent);
1056 parent.children.push(Node::Group(Box::new(markers_node)));
1057 append_single_paint_path(last, &path, parent);
1058 }
1059 ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {
1060 parent.children.push(Node::Path(Box::new(path.clone())));
1061 parent.children.push(Node::Group(Box::new(markers_node)));
1062 }
1063 _ => parent.children.push(Node::Path(Box::new(path.clone()))),
1064 }
1065}
1066
1067fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
1068 match paint_order_kind {
1069 PaintOrderKind::Fill => {
1070 if path.fill.is_some() {
1071 let mut fill_path = path.clone();
1072 fill_path.stroke = None;
1073 fill_path.id = String::new();
1074 parent.children.push(Node::Path(Box::new(fill_path)));
1075 }
1076 }
1077 PaintOrderKind::Stroke => {
1078 if path.stroke.is_some() {
1079 let mut stroke_path = path.clone();
1080 stroke_path.fill = None;
1081 stroke_path.id = String::new();
1082 parent.children.push(Node::Path(Box::new(stroke_path)));
1083 }
1084 }
1085 _ => {}
1086 }
1087}
1088
1089pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
1090 match (order.order[0], order.order[1]) {
1091 (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
1092 (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
1093 PaintOrder::StrokeAndFill
1094 }
1095 _ => PaintOrder::FillAndStroke,
1096 }
1097}
1098
1099impl SvgNode<'_, '_> {
1100 pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
1101 let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
1102 let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin);
1103
1104 if let Some(transform_origin) = transform_origin {
1105 let dx = convert_length(
1106 transform_origin.x_offset,
1107 *self,
1108 AId::Width,
1109 Units::UserSpaceOnUse,
1110 state,
1111 );
1112 let dy = convert_length(
1113 transform_origin.y_offset,
1114 *self,
1115 AId::Height,
1116 Units::UserSpaceOnUse,
1117 state,
1118 );
1119 transform = Transform::default()
1120 .pre_translate(dx, dy)
1121 .pre_concat(transform)
1122 .pre_translate(-dx, -dy);
1123 }
1124
1125 transform
1126 }
1127}