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_state = State {
455 view_box: size.to_non_zero_rect(0.0, 0.0),
456 ..state.clone()
457 };
458 let svg_ts = svg.resolve_transform(AId::Transform, &root_state);
459 let root_ts = svg_ts.pre_concat(view_box.to_transform(tree.size()));
460 if root_ts.is_identity() && background_color.is_none() {
461 convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
462 } else {
463 let mut g = Group::empty();
464
465 if let Some(background_color) = background_color {
466 if let Some(path) = background_path(background_color, view_box.rect.to_rect()) {
467 g.children.push(Node::Path(Box::new(path)));
468 }
469 }
470
471 g.transform = root_ts;
472 g.abs_transform = root_ts;
473 convert_children(svg_doc.root(), &state, &mut cache, &mut g);
474 g.calculate_bounding_boxes();
475 tree.root.children.push(Node::Group(Box::new(g)));
476 }
477
478 cache.clip_paths.clear();
480 cache.masks.clear();
481 cache.filters.clear();
482 cache.paint.clear();
483
484 super::paint_server::update_paint_servers(
485 &mut tree.root,
486 Transform::default(),
487 None,
488 None,
489 &mut cache,
490 );
491 tree.collect_paint_servers();
492 tree.root.collect_clip_paths(&mut tree.clip_paths);
493 tree.root.collect_masks(&mut tree.masks);
494 tree.root.collect_filters(&mut tree.filters);
495 tree.root.calculate_bounding_boxes();
496
497 #[cfg(feature = "text")]
500 {
501 tree.fontdb = cache.fontdb;
502 }
503
504 if restore_viewbox {
505 calculate_svg_bbox(&mut tree);
506 }
507
508 Ok(tree)
509}
510
511fn background_path(background_color: svgtypes::Color, area: Rect) -> Option<Path> {
512 let path = PathBuilder::from_rect(area);
513
514 let fill = Fill {
515 paint: Paint::Color(Color::new_rgb(
516 background_color.red,
517 background_color.green,
518 background_color.blue,
519 )),
520 opacity: NormalizedF32::new(background_color.alpha as f32 / 255.0)?,
521 ..Default::default()
522 };
523
524 let mut path = Path::new_simple(Arc::new(path))?;
525 path.fill = Some(fill);
526
527 Some(path)
528}
529
530fn resolve_svg_size(svg: &SvgNode, opt: &Options) -> (Result<Size, Error>, bool) {
531 let mut state = State {
532 parent_clip_path: None,
533 context_element: None,
534 parent_markers: Vec::new(),
535 fe_image_link: false,
536 view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(),
537 use_size: (None, None),
538 opt,
539 };
540
541 let def = Length::new(100.0, Unit::Percent);
542 let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
543 let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
544
545 let view_box = svg.parse_viewbox();
546
547 let restore_viewbox =
548 if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
549 if width.unit == Unit::Percent {
551 width = Length::new(
552 (width.number / 100.0) * state.opt.default_size.width() as f64,
553 Unit::None,
554 );
555 }
556
557 if height.unit == Unit::Percent {
558 height = Length::new(
559 (height.number / 100.0) * state.opt.default_size.height() as f64,
560 Unit::None,
561 );
562 }
563
564 true
565 } else {
566 false
567 };
568
569 let size = if let Some(vbox) = view_box {
570 state.view_box = vbox;
571
572 let w = if width.unit == Unit::Percent {
573 vbox.width() * (width.number as f32 / 100.0)
574 } else {
575 svg.convert_user_length(AId::Width, &state, def)
576 };
577
578 let h = if height.unit == Unit::Percent {
579 vbox.height() * (height.number as f32 / 100.0)
580 } else {
581 svg.convert_user_length(AId::Height, &state, def)
582 };
583
584 match (
587 svg.attribute::<Length>(AId::Width),
588 svg.attribute::<Length>(AId::Height),
589 ) {
590 (Some(_), None) => Size::from_wh(w, vbox.height() * w / vbox.width()),
591 (None, Some(_)) => Size::from_wh(vbox.width() * h / vbox.height(), h),
592 (_, _) => Size::from_wh(w, h),
593 }
594 } else {
595 Size::from_wh(
596 svg.convert_user_length(AId::Width, &state, def),
597 svg.convert_user_length(AId::Height, &state, def),
598 )
599 };
600
601 (size.ok_or(Error::InvalidSize), restore_viewbox)
602}
603
604fn calculate_svg_bbox(tree: &mut Tree) {
608 let bbox = tree.root.abs_bounding_box();
609 if let Some(size) = Size::from_wh(bbox.right(), bbox.bottom()) {
610 tree.size = size;
611 }
612}
613
614#[inline(never)]
615pub(crate) fn convert_children(
616 parent_node: SvgNode,
617 state: &State,
618 cache: &mut Cache,
619 parent: &mut Group,
620) {
621 for node in parent_node.children() {
622 convert_element(node, state, cache, parent);
623 }
624}
625
626#[inline(never)]
627pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) {
628 let tag_name = match node.tag_name() {
629 Some(v) => v,
630 None => return,
631 };
632
633 if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
634 return;
635 }
636
637 if !node.is_visible_element(state.opt) {
638 return;
639 }
640
641 if tag_name == EId::Use {
642 super::use_node::convert(node, state, cache, parent);
643 return;
644 }
645
646 if tag_name == EId::Switch {
647 super::switch::convert(node, state, cache, parent);
648 return;
649 }
650
651 if tag_name == EId::Svg && node.parent_element().is_some() {
658 super::use_node::convert_svg(node, state, cache, parent);
659
660 return;
661 }
662
663 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
664 convert_element_impl(tag_name, node, state, cache, g);
665 }) {
666 parent.children.push(Node::Group(Box::new(g)));
667 }
668}
669
670#[inline(never)]
671fn convert_element_impl(
672 tag_name: EId,
673 node: SvgNode,
674 state: &State,
675 cache: &mut Cache,
676 parent: &mut Group,
677) {
678 match tag_name {
679 EId::Rect
680 | EId::Circle
681 | EId::Ellipse
682 | EId::Line
683 | EId::Polyline
684 | EId::Polygon
685 | EId::Path => {
686 if let Some(path) = super::shapes::convert(node, state) {
687 convert_path(node, path, state, cache, parent);
688 }
689 }
690 EId::Image => {
691 super::image::convert(node, state, cache, parent);
692 }
693 EId::Text => {
694 #[cfg(feature = "text")]
695 {
696 super::text::convert(node, state, cache, parent);
697 }
698 }
699 EId::Svg => {
700 convert_children(node, state, cache, parent);
704 }
705 EId::G => {
706 convert_children(node, state, cache, parent);
707 }
708 _ => {}
709 }
710}
711
712#[inline(never)]
717pub(crate) fn convert_clip_path_elements(
718 clip_node: SvgNode,
719 state: &State,
720 cache: &mut Cache,
721 parent: &mut Group,
722) {
723 for node in clip_node.children() {
724 let tag_name = match node.tag_name() {
725 Some(v) => v,
726 None => continue,
727 };
728
729 if !tag_name.is_graphic() {
730 continue;
731 }
732
733 if !node.is_visible_element(state.opt) {
734 continue;
735 }
736
737 if tag_name == EId::Use {
738 super::use_node::convert(node, state, cache, parent);
739 continue;
740 }
741
742 if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| {
743 convert_clip_path_elements_impl(tag_name, node, state, cache, g);
744 }) {
745 parent.children.push(Node::Group(Box::new(g)));
746 }
747 }
748}
749
750#[inline(never)]
751fn convert_clip_path_elements_impl(
752 tag_name: EId,
753 node: SvgNode,
754 state: &State,
755 cache: &mut Cache,
756 parent: &mut Group,
757) {
758 match tag_name {
759 EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
760 if let Some(path) = super::shapes::convert(node, state) {
761 convert_path(node, path, state, cache, parent);
762 }
763 }
764 EId::Text => {
765 #[cfg(feature = "text")]
766 {
767 super::text::convert(node, state, cache, parent);
768 }
769 }
770 _ => {
771 log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
772 }
773 }
774}
775
776#[derive(Clone, Copy, PartialEq, Debug)]
777enum Isolation {
778 Auto,
779 Isolate,
780}
781
782impl Default for Isolation {
783 fn default() -> Self {
784 Self::Auto
785 }
786}
787
788impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation {
789 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
790 match value {
791 "auto" => Some(Isolation::Auto),
792 "isolate" => Some(Isolation::Isolate),
793 _ => None,
794 }
795 }
796}
797
798pub(crate) fn convert_group(
800 node: SvgNode,
801 state: &State,
802 force: bool,
803 cache: &mut Cache,
804 parent: &mut Group,
805 collect_children: &dyn Fn(&mut Cache, &mut Group),
806) -> Option<Group> {
807 let opacity = if state.parent_clip_path.is_none() {
809 node.attribute::<Opacity>(AId::Opacity)
810 .unwrap_or(Opacity::ONE)
811 } else {
812 Opacity::ONE
813 };
814
815 let is_root_svg = node.tag_name() == Some(EId::Svg) && node.parent_element().is_none();
816 let transform = if is_root_svg {
817 Transform::default()
819 } else {
820 node.resolve_transform(AId::Transform, state)
821 };
822 let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
823 let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
824 let isolate = isolation == Isolation::Isolate;
825
826 let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use));
828 let id = if is_g_or_use && state.parent_markers.is_empty() {
829 node.element_id().to_string()
830 } else {
831 String::new()
832 };
833
834 let abs_transform = parent.abs_transform.pre_concat(transform);
835 let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
836 let mut g = Group {
837 id,
838 transform,
839 abs_transform,
840 opacity,
841 blend_mode,
842 isolate,
843 clip_path: None,
844 mask: None,
845 filters: Vec::new(),
846 is_context_element: false,
847 bounding_box: dummy,
848 abs_bounding_box: dummy,
849 stroke_bounding_box: dummy,
850 abs_stroke_bounding_box: dummy,
851 layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
852 abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
853 children: Vec::new(),
854 };
855 collect_children(cache, &mut g);
856
857 let object_bbox = g.calculate_object_bbox();
860
861 let mut clip_path = None;
865 if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) {
866 clip_path = super::clippath::convert(link, state, object_bbox, cache);
867 if clip_path.is_none() {
868 return None;
869 }
870 }
871
872 let mut mask = None;
873 if state.parent_clip_path.is_none() {
874 if let Some(link) = node.attribute::<SvgNode>(AId::Mask) {
875 mask = super::mask::convert(link, state, object_bbox, cache);
876 if mask.is_none() {
877 return None;
878 }
879 }
880 }
881
882 let filters = {
883 let mut filters = Vec::new();
884 if state.parent_clip_path.is_none() {
885 if node.attribute(AId::Filter) == Some("none") {
886 } else if node.has_attribute(AId::Filter) {
888 if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) {
889 filters = f;
890 } else {
891 return None;
904 }
905 }
906 }
907
908 filters
909 };
910
911 let required = opacity.get().approx_ne_ulps(&1.0, 4)
912 || clip_path.is_some()
913 || mask.is_some()
914 || !filters.is_empty()
915 || !transform.is_identity()
916 || blend_mode != BlendMode::Normal
917 || isolate
918 || is_g_or_use
919 || force;
920
921 if !required {
922 parent.children.append(&mut g.children);
923 return None;
924 }
925
926 g.clip_path = clip_path;
927 g.mask = mask;
928 g.filters = filters;
929
930 g.calculate_bounding_boxes();
932
933 Some(g)
934}
935
936fn convert_path(
937 node: SvgNode,
938 tiny_skia_path: Arc<tiny_skia_path::Path>,
939 state: &State,
940 cache: &mut Cache,
941 parent: &mut Group,
942) {
943 debug_assert!(tiny_skia_path.len() >= 2);
944 if tiny_skia_path.len() < 2 {
945 return;
946 }
947
948 let has_bbox = tiny_skia_path.bounds().width() > 0.0 && tiny_skia_path.bounds().height() > 0.0;
949 let mut fill = super::style::resolve_fill(node, has_bbox, state, cache);
950 let mut stroke = super::style::resolve_stroke(node, has_bbox, state, cache);
951 let visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
952 let mut visible = visibility == Visibility::Visible;
953 let rendering_mode: ShapeRendering = node
954 .find_attribute(AId::ShapeRendering)
955 .unwrap_or(state.opt.shape_rendering);
956
957 let raw_paint_order: svgtypes::PaintOrder =
959 node.find_attribute(AId::PaintOrder).unwrap_or_default();
960 let paint_order = svg_paint_order_to_usvg(raw_paint_order);
961 let path_transform = parent.abs_transform;
962
963 if fill.is_none() && stroke.is_none() {
966 visible = false;
967 }
968
969 if let Some(fill) = fill.as_mut() {
970 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
971 fill.context_element
972 {
973 process_paint(
974 &mut fill.paint,
975 true,
976 context_transform,
977 context_bbox.map(|r| r.to_rect()),
978 path_transform,
979 tiny_skia_path.bounds(),
980 cache,
981 );
982 fill.context_element = None;
983 }
984 }
985
986 if let Some(stroke) = stroke.as_mut() {
987 if let Some(ContextElement::PathNode(context_transform, context_bbox)) =
988 stroke.context_element
989 {
990 process_paint(
991 &mut stroke.paint,
992 true,
993 context_transform,
994 context_bbox.map(|r| r.to_rect()),
995 path_transform,
996 tiny_skia_path.bounds(),
997 cache,
998 );
999 stroke.context_element = None;
1000 }
1001 }
1002
1003 let mut marker = None;
1004 if marker::is_valid(node) && visibility == Visibility::Visible {
1005 let mut marker_group = Group {
1006 abs_transform: parent.abs_transform,
1007 ..Group::empty()
1008 };
1009
1010 let mut marker_state = state.clone();
1011
1012 let bbox = tiny_skia_path
1013 .compute_tight_bounds()
1014 .and_then(|r| r.to_non_zero_rect());
1015
1016 let fill = fill.clone().map(|mut f| {
1017 f.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1018 f
1019 });
1020
1021 let stroke = stroke.clone().map(|mut s| {
1022 s.context_element = Some(ContextElement::PathNode(path_transform, bbox));
1023 s
1024 });
1025
1026 marker_state.context_element = Some((fill, stroke));
1027
1028 marker::convert(
1029 node,
1030 &tiny_skia_path,
1031 &marker_state,
1032 cache,
1033 &mut marker_group,
1034 );
1035 marker_group.calculate_bounding_boxes();
1036 marker = Some(marker_group);
1037 }
1038
1039 let id = if state.parent_markers.is_empty() {
1041 node.element_id().to_string()
1042 } else {
1043 String::new()
1044 };
1045
1046 let path = Path::new(
1047 id,
1048 visible,
1049 fill,
1050 stroke,
1051 paint_order,
1052 rendering_mode,
1053 tiny_skia_path,
1054 path_transform,
1055 );
1056
1057 let path = match path {
1058 Some(v) => v,
1059 None => return,
1060 };
1061
1062 match (raw_paint_order.order, marker) {
1063 ([PaintOrderKind::Markers, _, _], Some(markers_node)) => {
1064 parent.children.push(Node::Group(Box::new(markers_node)));
1065 parent.children.push(Node::Path(Box::new(path.clone())));
1066 }
1067 ([first, PaintOrderKind::Markers, last], Some(markers_node)) => {
1068 append_single_paint_path(first, &path, parent);
1069 parent.children.push(Node::Group(Box::new(markers_node)));
1070 append_single_paint_path(last, &path, parent);
1071 }
1072 ([_, _, PaintOrderKind::Markers], Some(markers_node)) => {
1073 parent.children.push(Node::Path(Box::new(path.clone())));
1074 parent.children.push(Node::Group(Box::new(markers_node)));
1075 }
1076 _ => parent.children.push(Node::Path(Box::new(path.clone()))),
1077 }
1078}
1079
1080fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) {
1081 match paint_order_kind {
1082 PaintOrderKind::Fill => {
1083 if path.fill.is_some() {
1084 let mut fill_path = path.clone();
1085 fill_path.stroke = None;
1086 fill_path.id = String::new();
1087 parent.children.push(Node::Path(Box::new(fill_path)));
1088 }
1089 }
1090 PaintOrderKind::Stroke => {
1091 if path.stroke.is_some() {
1092 let mut stroke_path = path.clone();
1093 stroke_path.fill = None;
1094 stroke_path.id = String::new();
1095 parent.children.push(Node::Path(Box::new(stroke_path)));
1096 }
1097 }
1098 _ => {}
1099 }
1100}
1101
1102pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder {
1103 match (order.order[0], order.order[1]) {
1104 (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
1105 (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => {
1106 PaintOrder::StrokeAndFill
1107 }
1108 _ => PaintOrder::FillAndStroke,
1109 }
1110}
1111
1112impl SvgNode<'_, '_> {
1113 pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform {
1114 let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default();
1115 let transform_origin = self
1116 .attribute::<TransformOrigin>(AId::TransformOrigin)
1117 .or_else(|| {
1118 if self.tag_name() == Some(EId::Svg) && self.parent_element().is_none() {
1120 Some(TransformOrigin::new(
1121 Length::new(50.0, Unit::Percent),
1122 Length::new(50.0, Unit::Percent),
1123 Length::zero(),
1124 ))
1125 } else {
1126 None
1127 }
1128 });
1129
1130 if let Some(transform_origin) = transform_origin {
1131 let dx = convert_length(
1132 transform_origin.x_offset,
1133 *self,
1134 AId::Width,
1135 Units::UserSpaceOnUse,
1136 state,
1137 );
1138 let dy = convert_length(
1139 transform_origin.y_offset,
1140 *self,
1141 AId::Height,
1142 Units::UserSpaceOnUse,
1143 state,
1144 );
1145 transform = Transform::default()
1146 .pre_translate(dx, dy)
1147 .pre_concat(transform)
1148 .pre_translate(-dx, -dy);
1149 }
1150
1151 transform
1152 }
1153}