1 | // This Source Code Form is subject to the terms of the Mozilla Public |
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this |
3 | // file, You can obtain one at http://mozilla.org/MPL/2.0/. |
4 | |
5 | use std::collections::{HashMap, HashSet}; |
6 | use std::hash::{Hash, Hasher}; |
7 | use std::str::FromStr; |
8 | use std::sync::Arc; |
9 | |
10 | use svgtypes::{Length, LengthUnit as Unit, PaintOrderKind, TransformOrigin}; |
11 | |
12 | use super::svgtree::{self, AId, EId, FromValue, SvgNode}; |
13 | use super::units::{self, convert_length}; |
14 | use super::{Error, Options}; |
15 | use crate::*; |
16 | |
17 | #[derive (Clone)] |
18 | pub struct State<'a> { |
19 | pub(crate) parent_clip_path: Option<SvgNode<'a, 'a>>, |
20 | pub(crate) parent_markers: Vec<SvgNode<'a, 'a>>, |
21 | pub(crate) fe_image_link: bool, |
22 | /// A viewBox of the parent SVG element. |
23 | pub(crate) view_box: NonZeroRect, |
24 | /// A size of the parent `use` element. |
25 | /// Used only during nested `svg` size resolving. |
26 | /// Width and height can be set independently. |
27 | pub(crate) use_size: (Option<f32>, Option<f32>), |
28 | pub(crate) opt: &'a Options, |
29 | #[cfg (feature = "text" )] |
30 | pub(crate) fontdb: &'a fontdb::Database, |
31 | } |
32 | |
33 | #[derive (Clone, Default)] |
34 | pub struct Cache { |
35 | pub clip_paths: HashMap<String, Arc<ClipPath>>, |
36 | pub masks: HashMap<String, Arc<Mask>>, |
37 | pub filters: HashMap<String, Arc<filter::Filter>>, |
38 | pub paint: HashMap<String, Paint>, |
39 | |
40 | // used for ID generation |
41 | all_ids: HashSet<u64>, |
42 | linear_gradient_index: usize, |
43 | radial_gradient_index: usize, |
44 | pattern_index: usize, |
45 | clip_path_index: usize, |
46 | mask_index: usize, |
47 | filter_index: usize, |
48 | } |
49 | |
50 | impl Cache { |
51 | // TODO: macros? |
52 | pub(crate) fn gen_linear_gradient_id(&mut self) -> NonEmptyString { |
53 | loop { |
54 | self.linear_gradient_index += 1; |
55 | let new_id = format!("linearGradient {}" , self.linear_gradient_index); |
56 | let new_hash = string_hash(&new_id); |
57 | if !self.all_ids.contains(&new_hash) { |
58 | return NonEmptyString::new(new_id).unwrap(); |
59 | } |
60 | } |
61 | } |
62 | |
63 | pub(crate) fn gen_radial_gradient_id(&mut self) -> NonEmptyString { |
64 | loop { |
65 | self.radial_gradient_index += 1; |
66 | let new_id = format!("radialGradient {}" , self.radial_gradient_index); |
67 | let new_hash = string_hash(&new_id); |
68 | if !self.all_ids.contains(&new_hash) { |
69 | return NonEmptyString::new(new_id).unwrap(); |
70 | } |
71 | } |
72 | } |
73 | |
74 | pub(crate) fn gen_pattern_id(&mut self) -> NonEmptyString { |
75 | loop { |
76 | self.pattern_index += 1; |
77 | let new_id = format!("pattern {}" , self.pattern_index); |
78 | let new_hash = string_hash(&new_id); |
79 | if !self.all_ids.contains(&new_hash) { |
80 | return NonEmptyString::new(new_id).unwrap(); |
81 | } |
82 | } |
83 | } |
84 | |
85 | pub(crate) fn gen_clip_path_id(&mut self) -> NonEmptyString { |
86 | loop { |
87 | self.clip_path_index += 1; |
88 | let new_id = format!("clipPath {}" , self.clip_path_index); |
89 | let new_hash = string_hash(&new_id); |
90 | if !self.all_ids.contains(&new_hash) { |
91 | return NonEmptyString::new(new_id).unwrap(); |
92 | } |
93 | } |
94 | } |
95 | |
96 | pub(crate) fn gen_mask_id(&mut self) -> NonEmptyString { |
97 | loop { |
98 | self.mask_index += 1; |
99 | let new_id = format!("mask {}" , self.mask_index); |
100 | let new_hash = string_hash(&new_id); |
101 | if !self.all_ids.contains(&new_hash) { |
102 | return NonEmptyString::new(new_id).unwrap(); |
103 | } |
104 | } |
105 | } |
106 | |
107 | pub(crate) fn gen_filter_id(&mut self) -> NonEmptyString { |
108 | loop { |
109 | self.filter_index += 1; |
110 | let new_id = format!("filter {}" , self.filter_index); |
111 | let new_hash = string_hash(&new_id); |
112 | if !self.all_ids.contains(&new_hash) { |
113 | return NonEmptyString::new(new_id).unwrap(); |
114 | } |
115 | } |
116 | } |
117 | } |
118 | |
119 | // TODO: is there a simpler way? |
120 | fn string_hash(s: &str) -> u64 { |
121 | let mut h: DefaultHasher = std::collections::hash_map::DefaultHasher::new(); |
122 | s.hash(&mut h); |
123 | h.finish() |
124 | } |
125 | |
126 | impl<'a, 'input: 'a> SvgNode<'a, 'input> { |
127 | pub(crate) fn convert_length( |
128 | &self, |
129 | aid: AId, |
130 | object_units: Units, |
131 | state: &State, |
132 | def: Length, |
133 | ) -> f32 { |
134 | units::convert_length( |
135 | self.attribute(aid).unwrap_or(def), |
136 | *self, |
137 | aid, |
138 | object_units, |
139 | state, |
140 | ) |
141 | } |
142 | |
143 | pub fn convert_user_length(&self, aid: AId, state: &State, def: Length) -> f32 { |
144 | self.convert_length(aid, Units::UserSpaceOnUse, state, def) |
145 | } |
146 | |
147 | pub fn parse_viewbox(&self) -> Option<NonZeroRect> { |
148 | let vb: svgtypes::ViewBox = self.attribute(AId::ViewBox)?; |
149 | NonZeroRect::from_xywh(vb.x as f32, vb.y as f32, vb.w as f32, vb.h as f32) |
150 | } |
151 | |
152 | pub fn resolve_length(&self, aid: AId, state: &State, def: f32) -> f32 { |
153 | debug_assert!( |
154 | !matches!(aid, AId::BaselineShift | AId::FontSize), |
155 | " {} cannot be resolved via this function" , |
156 | aid |
157 | ); |
158 | |
159 | if let Some(n) = self.ancestors().find(|n| n.has_attribute(aid)) { |
160 | if let Some(length) = n.attribute(aid) { |
161 | return units::convert_user_length(length, n, aid, state); |
162 | } |
163 | } |
164 | |
165 | def |
166 | } |
167 | |
168 | pub fn resolve_valid_length( |
169 | &self, |
170 | aid: AId, |
171 | state: &State, |
172 | def: f32, |
173 | ) -> Option<NonZeroPositiveF32> { |
174 | let n = self.resolve_length(aid, state, def); |
175 | NonZeroPositiveF32::new(n) |
176 | } |
177 | |
178 | pub(crate) fn try_convert_length( |
179 | &self, |
180 | aid: AId, |
181 | object_units: Units, |
182 | state: &State, |
183 | ) -> Option<f32> { |
184 | Some(units::convert_length( |
185 | self.attribute(aid)?, |
186 | *self, |
187 | aid, |
188 | object_units, |
189 | state, |
190 | )) |
191 | } |
192 | |
193 | pub fn has_valid_transform(&self, aid: AId) -> bool { |
194 | // Do not use Node::attribute::<Transform>, because it will always |
195 | // return a valid transform. |
196 | |
197 | let attr = match self.attribute(aid) { |
198 | Some(attr) => attr, |
199 | None => return true, |
200 | }; |
201 | |
202 | let ts = match svgtypes::Transform::from_str(attr) { |
203 | Ok(v) => v, |
204 | Err(_) => return true, |
205 | }; |
206 | |
207 | let ts = Transform::from_row( |
208 | ts.a as f32, |
209 | ts.b as f32, |
210 | ts.c as f32, |
211 | ts.d as f32, |
212 | ts.e as f32, |
213 | ts.f as f32, |
214 | ); |
215 | ts.is_valid() |
216 | } |
217 | |
218 | pub fn is_visible_element(&self, opt: &crate::Options) -> bool { |
219 | self.attribute(AId::Display) != Some("none" ) |
220 | && self.has_valid_transform(AId::Transform) |
221 | && super::switch::is_condition_passed(*self, opt) |
222 | } |
223 | } |
224 | |
225 | pub trait SvgColorExt { |
226 | fn split_alpha(self) -> (Color, Opacity); |
227 | } |
228 | |
229 | impl SvgColorExt for svgtypes::Color { |
230 | fn split_alpha(self) -> (Color, Opacity) { |
231 | ( |
232 | Color::new_rgb(self.red, self.green, self.blue), |
233 | Opacity::new_u8(self.alpha), |
234 | ) |
235 | } |
236 | } |
237 | |
238 | /// Converts an input `Document` into a `Tree`. |
239 | /// |
240 | /// # Errors |
241 | /// |
242 | /// - If `Document` doesn't have an SVG node - returns an empty tree. |
243 | /// - If `Document` doesn't have a valid size - returns `Error::InvalidSize`. |
244 | pub(crate) fn convert_doc( |
245 | svg_doc: &svgtree::Document, |
246 | opt: &Options, |
247 | #[cfg (feature = "text" )] fontdb: &fontdb::Database, |
248 | ) -> Result<Tree, Error> { |
249 | let svg = svg_doc.root_element(); |
250 | let (size, restore_viewbox) = resolve_svg_size( |
251 | &svg, |
252 | opt, |
253 | #[cfg (feature = "text" )] |
254 | fontdb, |
255 | ); |
256 | let size = size?; |
257 | let view_box = ViewBox { |
258 | rect: svg |
259 | .parse_viewbox() |
260 | .unwrap_or_else(|| size.to_non_zero_rect(0.0, 0.0)), |
261 | aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(), |
262 | }; |
263 | |
264 | let mut tree = Tree { |
265 | size, |
266 | view_box, |
267 | root: Group::empty(), |
268 | linear_gradients: Vec::new(), |
269 | radial_gradients: Vec::new(), |
270 | patterns: Vec::new(), |
271 | clip_paths: Vec::new(), |
272 | masks: Vec::new(), |
273 | filters: Vec::new(), |
274 | }; |
275 | |
276 | if !svg.is_visible_element(opt) { |
277 | return Ok(tree); |
278 | } |
279 | |
280 | let state = State { |
281 | parent_clip_path: None, |
282 | parent_markers: Vec::new(), |
283 | fe_image_link: false, |
284 | view_box: view_box.rect, |
285 | use_size: (None, None), |
286 | opt, |
287 | #[cfg (feature = "text" )] |
288 | fontdb, |
289 | }; |
290 | |
291 | let mut cache = Cache::default(); |
292 | |
293 | for node in svg_doc.descendants() { |
294 | if let Some(tag) = node.tag_name() { |
295 | if matches!( |
296 | tag, |
297 | EId::ClipPath |
298 | | EId::Filter |
299 | | EId::LinearGradient |
300 | | EId::Mask |
301 | | EId::Pattern |
302 | | EId::RadialGradient |
303 | ) { |
304 | if !node.element_id().is_empty() { |
305 | cache.all_ids.insert(string_hash(node.element_id())); |
306 | } |
307 | } |
308 | } |
309 | } |
310 | |
311 | convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root); |
312 | |
313 | // Clear cache to make sure that all `Arc<T>` objects have a single strong reference. |
314 | cache.clip_paths.clear(); |
315 | cache.masks.clear(); |
316 | cache.filters.clear(); |
317 | cache.paint.clear(); |
318 | |
319 | super::paint_server::to_user_coordinates(&mut tree.root, None, &mut cache); |
320 | tree.collect_paint_servers(); |
321 | tree.root.collect_clip_paths(&mut tree.clip_paths); |
322 | tree.root.collect_masks(&mut tree.masks); |
323 | tree.root.collect_filters(&mut tree.filters); |
324 | tree.root.calculate_bounding_boxes(); |
325 | |
326 | if restore_viewbox { |
327 | calculate_svg_bbox(&mut tree); |
328 | } |
329 | |
330 | Ok(tree) |
331 | } |
332 | |
333 | fn resolve_svg_size( |
334 | svg: &SvgNode, |
335 | opt: &Options, |
336 | #[cfg (feature = "text" )] fontdb: &fontdb::Database, |
337 | ) -> (Result<Size, Error>, bool) { |
338 | let mut state = State { |
339 | parent_clip_path: None, |
340 | parent_markers: Vec::new(), |
341 | fe_image_link: false, |
342 | view_box: NonZeroRect::from_xywh(0.0, 0.0, 100.0, 100.0).unwrap(), |
343 | use_size: (None, None), |
344 | opt, |
345 | #[cfg (feature = "text" )] |
346 | fontdb, |
347 | }; |
348 | |
349 | let def = Length::new(100.0, Unit::Percent); |
350 | let mut width: Length = svg.attribute(AId::Width).unwrap_or(def); |
351 | let mut height: Length = svg.attribute(AId::Height).unwrap_or(def); |
352 | |
353 | let view_box = svg.parse_viewbox(); |
354 | |
355 | let restore_viewbox = |
356 | if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() { |
357 | // Apply the percentages to the fallback size. |
358 | if width.unit == Unit::Percent { |
359 | width = Length::new( |
360 | (width.number / 100.0) * state.opt.default_size.width() as f64, |
361 | Unit::None, |
362 | ); |
363 | } |
364 | |
365 | if height.unit == Unit::Percent { |
366 | height = Length::new( |
367 | (height.number / 100.0) * state.opt.default_size.height() as f64, |
368 | Unit::None, |
369 | ); |
370 | } |
371 | |
372 | true |
373 | } else { |
374 | false |
375 | }; |
376 | |
377 | let size = if let Some(vbox) = view_box { |
378 | state.view_box = vbox; |
379 | |
380 | let w = if width.unit == Unit::Percent { |
381 | vbox.width() * (width.number as f32 / 100.0) |
382 | } else { |
383 | svg.convert_user_length(AId::Width, &state, def) |
384 | }; |
385 | |
386 | let h = if height.unit == Unit::Percent { |
387 | vbox.height() * (height.number as f32 / 100.0) |
388 | } else { |
389 | svg.convert_user_length(AId::Height, &state, def) |
390 | }; |
391 | |
392 | Size::from_wh(w, h) |
393 | } else { |
394 | Size::from_wh( |
395 | svg.convert_user_length(AId::Width, &state, def), |
396 | svg.convert_user_length(AId::Height, &state, def), |
397 | ) |
398 | }; |
399 | |
400 | (size.ok_or(Error::InvalidSize), restore_viewbox) |
401 | } |
402 | |
403 | /// Calculates SVG's size and viewBox in case there were not set. |
404 | /// |
405 | /// Simply iterates over all nodes and calculates a bounding box. |
406 | fn calculate_svg_bbox(tree: &mut Tree) { |
407 | let bbox: Rect = tree.root.abs_bounding_box(); |
408 | |
409 | if let Some(rect: NonZeroRect) = NonZeroRect::from_xywh(x:0.0, y:0.0, w:bbox.right(), h:bbox.bottom()) { |
410 | tree.view_box.rect = rect; |
411 | } |
412 | |
413 | if let Some(size: Size) = Size::from_wh(width:bbox.right(), height:bbox.bottom()) { |
414 | tree.size = size; |
415 | } |
416 | } |
417 | |
418 | #[inline (never)] |
419 | pub(crate) fn convert_children( |
420 | parent_node: SvgNode, |
421 | state: &State, |
422 | cache: &mut Cache, |
423 | parent: &mut Group, |
424 | ) { |
425 | for node: SvgNode<'_, '_> in parent_node.children() { |
426 | convert_element(node, state, cache, parent); |
427 | } |
428 | } |
429 | |
430 | #[inline (never)] |
431 | pub(crate) fn convert_element(node: SvgNode, state: &State, cache: &mut Cache, parent: &mut Group) { |
432 | let tag_name = match node.tag_name() { |
433 | Some(v) => v, |
434 | None => return, |
435 | }; |
436 | |
437 | if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) { |
438 | return; |
439 | } |
440 | |
441 | if !node.is_visible_element(state.opt) { |
442 | return; |
443 | } |
444 | |
445 | if tag_name == EId::Use { |
446 | super::use_node::convert(node, state, cache, parent); |
447 | return; |
448 | } |
449 | |
450 | if tag_name == EId::Switch { |
451 | super::switch::convert(node, state, cache, parent); |
452 | return; |
453 | } |
454 | |
455 | if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { |
456 | convert_element_impl(tag_name, node, state, cache, g); |
457 | }) { |
458 | parent.children.push(Node::Group(Box::new(g))); |
459 | } |
460 | } |
461 | |
462 | #[inline (never)] |
463 | fn convert_element_impl( |
464 | tag_name: EId, |
465 | node: SvgNode, |
466 | state: &State, |
467 | cache: &mut Cache, |
468 | parent: &mut Group, |
469 | ) { |
470 | match tag_name { |
471 | EId::Rect |
472 | | EId::Circle |
473 | | EId::Ellipse |
474 | | EId::Line |
475 | | EId::Polyline |
476 | | EId::Polygon |
477 | | EId::Path => { |
478 | if let Some(path) = super::shapes::convert(node, state) { |
479 | convert_path(node, path, state, cache, parent); |
480 | } |
481 | } |
482 | EId::Image => { |
483 | super::image::convert(node, state, parent); |
484 | } |
485 | EId::Text => { |
486 | #[cfg (feature = "text" )] |
487 | { |
488 | super::text::convert(node, state, cache, parent); |
489 | } |
490 | } |
491 | EId::Svg => { |
492 | if node.parent_element().is_some() { |
493 | super::use_node::convert_svg(node, state, cache, parent); |
494 | } else { |
495 | // Skip root `svg`. |
496 | convert_children(node, state, cache, parent); |
497 | } |
498 | } |
499 | EId::G => { |
500 | convert_children(node, state, cache, parent); |
501 | } |
502 | _ => {} |
503 | } |
504 | } |
505 | |
506 | // `clipPath` can have only shape and `text` children. |
507 | // |
508 | // `line` doesn't impact rendering because stroke is always disabled |
509 | // for `clipPath` children. |
510 | #[inline (never)] |
511 | pub(crate) fn convert_clip_path_elements( |
512 | clip_node: SvgNode, |
513 | state: &State, |
514 | cache: &mut Cache, |
515 | parent: &mut Group, |
516 | ) { |
517 | for node in clip_node.children() { |
518 | let tag_name = match node.tag_name() { |
519 | Some(v) => v, |
520 | None => continue, |
521 | }; |
522 | |
523 | if !tag_name.is_graphic() { |
524 | continue; |
525 | } |
526 | |
527 | if !node.is_visible_element(state.opt) { |
528 | continue; |
529 | } |
530 | |
531 | if tag_name == EId::Use { |
532 | super::use_node::convert(node, state, cache, parent); |
533 | continue; |
534 | } |
535 | |
536 | if let Some(g) = convert_group(node, state, false, cache, parent, &|cache, g| { |
537 | convert_clip_path_elements_impl(tag_name, node, state, cache, g); |
538 | }) { |
539 | parent.children.push(Node::Group(Box::new(g))); |
540 | } |
541 | } |
542 | } |
543 | |
544 | #[inline (never)] |
545 | fn convert_clip_path_elements_impl( |
546 | tag_name: EId, |
547 | node: SvgNode, |
548 | state: &State, |
549 | cache: &mut Cache, |
550 | parent: &mut Group, |
551 | ) { |
552 | match tag_name { |
553 | EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => { |
554 | if let Some(path: Arc) = super::shapes::convert(node, state) { |
555 | convert_path(node, path, state, cache, parent); |
556 | } |
557 | } |
558 | EId::Text => { |
559 | #[cfg (feature = "text" )] |
560 | { |
561 | super::text::convert(text_node:node, state, cache, parent); |
562 | } |
563 | } |
564 | _ => { |
565 | log::warn!("' {}' is no a valid 'clip-path' child." , tag_name); |
566 | } |
567 | } |
568 | } |
569 | |
570 | #[derive (Clone, Copy, PartialEq, Debug)] |
571 | enum Isolation { |
572 | Auto, |
573 | Isolate, |
574 | } |
575 | |
576 | impl Default for Isolation { |
577 | fn default() -> Self { |
578 | Self::Auto |
579 | } |
580 | } |
581 | |
582 | impl<'a, 'input: 'a> FromValue<'a, 'input> for Isolation { |
583 | fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> { |
584 | match value { |
585 | "auto" => Some(Isolation::Auto), |
586 | "isolate" => Some(Isolation::Isolate), |
587 | _ => None, |
588 | } |
589 | } |
590 | } |
591 | |
592 | // TODO: explain |
593 | pub(crate) fn convert_group( |
594 | node: SvgNode, |
595 | state: &State, |
596 | force: bool, |
597 | cache: &mut Cache, |
598 | parent: &mut Group, |
599 | collect_children: &dyn Fn(&mut Cache, &mut Group), |
600 | ) -> Option<Group> { |
601 | // A `clipPath` child cannot have an opacity. |
602 | let opacity = if state.parent_clip_path.is_none() { |
603 | node.attribute::<Opacity>(AId::Opacity) |
604 | .unwrap_or(Opacity::ONE) |
605 | } else { |
606 | Opacity::ONE |
607 | }; |
608 | |
609 | let transform = node.resolve_transform(AId::Transform, state); |
610 | let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default(); |
611 | let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default(); |
612 | let isolate = isolation == Isolation::Isolate; |
613 | |
614 | // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. |
615 | let is_g_or_use = matches!(node.tag_name(), Some(EId::G) | Some(EId::Use)); |
616 | let id = if is_g_or_use && state.parent_markers.is_empty() { |
617 | node.element_id().to_string() |
618 | } else { |
619 | String::new() |
620 | }; |
621 | |
622 | let abs_transform = parent.abs_transform.pre_concat(transform); |
623 | let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap(); |
624 | let mut g = Group { |
625 | id, |
626 | transform, |
627 | abs_transform, |
628 | opacity, |
629 | blend_mode, |
630 | isolate, |
631 | clip_path: None, |
632 | mask: None, |
633 | filters: Vec::new(), |
634 | bounding_box: dummy, |
635 | abs_bounding_box: dummy, |
636 | stroke_bounding_box: dummy, |
637 | abs_stroke_bounding_box: dummy, |
638 | layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), |
639 | abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(), |
640 | children: Vec::new(), |
641 | }; |
642 | collect_children(cache, &mut g); |
643 | |
644 | // We need to know group's bounding box before converting |
645 | // clipPaths, masks and filters. |
646 | let object_bbox = g.calculate_object_bbox(); |
647 | |
648 | // `mask` and `filter` cannot be set on `clipPath` children. |
649 | // But `clip-path` can. |
650 | |
651 | let mut clip_path = None; |
652 | if let Some(link) = node.attribute::<SvgNode>(AId::ClipPath) { |
653 | clip_path = super::clippath::convert(link, state, object_bbox, cache); |
654 | if clip_path.is_none() { |
655 | return None; |
656 | } |
657 | } |
658 | |
659 | let mut mask = None; |
660 | if state.parent_clip_path.is_none() { |
661 | if let Some(link) = node.attribute::<SvgNode>(AId::Mask) { |
662 | mask = super::mask::convert(link, state, object_bbox, cache); |
663 | if mask.is_none() { |
664 | return None; |
665 | } |
666 | } |
667 | } |
668 | |
669 | let filters = { |
670 | let mut filters = Vec::new(); |
671 | if state.parent_clip_path.is_none() { |
672 | if node.attribute(AId::Filter) == Some("none" ) { |
673 | // Do nothing. |
674 | } else if node.has_attribute(AId::Filter) { |
675 | if let Ok(f) = super::filter::convert(node, state, object_bbox, cache) { |
676 | filters = f; |
677 | } else { |
678 | // A filter that not a link or a filter with a link to a non existing element. |
679 | // |
680 | // Unlike `clip-path` and `mask`, when a `filter` link is invalid |
681 | // then the whole element should be ignored. |
682 | // |
683 | // This is kinda an undefined behaviour. |
684 | // In most cases, Chrome, Firefox and rsvg will ignore such elements, |
685 | // but in some cases Chrome allows it. Not sure why. |
686 | // Inkscape (0.92) simply ignores such attributes, rendering element as is. |
687 | // Batik (1.12) crashes. |
688 | // |
689 | // Test file: e-filter-051.svg |
690 | return None; |
691 | } |
692 | } |
693 | } |
694 | |
695 | filters |
696 | }; |
697 | |
698 | let required = opacity.get().approx_ne_ulps(&1.0, 4) |
699 | || clip_path.is_some() |
700 | || mask.is_some() |
701 | || !filters.is_empty() |
702 | || !transform.is_identity() |
703 | || blend_mode != BlendMode::Normal |
704 | || isolate |
705 | || is_g_or_use |
706 | || force; |
707 | |
708 | if !required { |
709 | parent.children.append(&mut g.children); |
710 | return None; |
711 | } |
712 | |
713 | g.clip_path = clip_path; |
714 | g.mask = mask; |
715 | g.filters = filters; |
716 | |
717 | // Must be called after we set Group::filters |
718 | g.calculate_bounding_boxes(); |
719 | |
720 | Some(g) |
721 | } |
722 | |
723 | fn convert_path( |
724 | node: SvgNode, |
725 | path: Arc<tiny_skia_path::Path>, |
726 | state: &State, |
727 | cache: &mut Cache, |
728 | parent: &mut Group, |
729 | ) { |
730 | debug_assert!(path.len() >= 2); |
731 | if path.len() < 2 { |
732 | return; |
733 | } |
734 | |
735 | let has_bbox = path.bounds().width() > 0.0 && path.bounds().height() > 0.0; |
736 | let fill = super::style::resolve_fill(node, has_bbox, state, cache); |
737 | let stroke = super::style::resolve_stroke(node, has_bbox, state, cache); |
738 | let mut visibility: Visibility = node.find_attribute(AId::Visibility).unwrap_or_default(); |
739 | let rendering_mode: ShapeRendering = node |
740 | .find_attribute(AId::ShapeRendering) |
741 | .unwrap_or(state.opt.shape_rendering); |
742 | |
743 | // TODO: handle `markers` before `stroke` |
744 | let raw_paint_order: svgtypes::PaintOrder = |
745 | node.find_attribute(AId::PaintOrder).unwrap_or_default(); |
746 | let paint_order = svg_paint_order_to_usvg(raw_paint_order); |
747 | |
748 | // If a path doesn't have a fill or a stroke then it's invisible. |
749 | // By setting `visibility` to `hidden` we are disabling rendering of this path. |
750 | if fill.is_none() && stroke.is_none() { |
751 | visibility = Visibility::Hidden; |
752 | } |
753 | |
754 | let mut markers_node = None; |
755 | if super::marker::is_valid(node) && visibility == Visibility::Visible { |
756 | let mut marker = Group::empty(); |
757 | super::marker::convert(node, &path, state, cache, &mut marker); |
758 | marker.calculate_bounding_boxes(); |
759 | markers_node = Some(marker); |
760 | } |
761 | |
762 | // Nodes generated by markers must not have an ID. Otherwise we would have duplicates. |
763 | let id = if state.parent_markers.is_empty() { |
764 | node.element_id().to_string() |
765 | } else { |
766 | String::new() |
767 | }; |
768 | |
769 | let path = Path::new( |
770 | id, |
771 | visibility, |
772 | fill, |
773 | stroke, |
774 | paint_order, |
775 | rendering_mode, |
776 | path, |
777 | parent.abs_transform, |
778 | ); |
779 | |
780 | let path = match path { |
781 | Some(v) => v, |
782 | None => return, |
783 | }; |
784 | |
785 | match raw_paint_order.order { |
786 | [PaintOrderKind::Markers, _, _] => { |
787 | if let Some(markers_node) = markers_node { |
788 | parent.children.push(Node::Group(Box::new(markers_node))); |
789 | } |
790 | |
791 | parent.children.push(Node::Path(Box::new(path.clone()))); |
792 | } |
793 | [first, PaintOrderKind::Markers, last] => { |
794 | append_single_paint_path(first, &path, parent); |
795 | |
796 | if let Some(markers_node) = markers_node { |
797 | parent.children.push(Node::Group(Box::new(markers_node))); |
798 | } |
799 | |
800 | append_single_paint_path(last, &path, parent); |
801 | } |
802 | [_, _, PaintOrderKind::Markers] => { |
803 | parent.children.push(Node::Path(Box::new(path.clone()))); |
804 | |
805 | if let Some(markers_node) = markers_node { |
806 | parent.children.push(Node::Group(Box::new(markers_node))); |
807 | } |
808 | } |
809 | _ => parent.children.push(Node::Path(Box::new(path.clone()))), |
810 | } |
811 | } |
812 | |
813 | fn append_single_paint_path(paint_order_kind: PaintOrderKind, path: &Path, parent: &mut Group) { |
814 | match paint_order_kind { |
815 | PaintOrderKind::Fill => { |
816 | if path.fill.is_some() { |
817 | let mut fill_path: Path = path.clone(); |
818 | fill_path.stroke = None; |
819 | fill_path.id = String::new(); |
820 | parent.children.push(Node::Path(Box::new(fill_path))); |
821 | } |
822 | } |
823 | PaintOrderKind::Stroke => { |
824 | if path.stroke.is_some() { |
825 | let mut stroke_path: Path = path.clone(); |
826 | stroke_path.fill = None; |
827 | stroke_path.id = String::new(); |
828 | parent.children.push(Node::Path(Box::new(stroke_path))); |
829 | } |
830 | } |
831 | _ => {} |
832 | } |
833 | } |
834 | |
835 | pub fn svg_paint_order_to_usvg(order: svgtypes::PaintOrder) -> PaintOrder { |
836 | match (order.order[0], order.order[1]) { |
837 | (svgtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill, |
838 | (svgtypes::PaintOrderKind::Markers, svgtypes::PaintOrderKind::Stroke) => { |
839 | PaintOrder::StrokeAndFill |
840 | } |
841 | _ => PaintOrder::FillAndStroke, |
842 | } |
843 | } |
844 | |
845 | impl SvgNode<'_, '_> { |
846 | pub(crate) fn resolve_transform(&self, transform_aid: AId, state: &State) -> Transform { |
847 | let mut transform: Transform = self.attribute(transform_aid).unwrap_or_default(); |
848 | let transform_origin: Option<TransformOrigin> = self.attribute(AId::TransformOrigin); |
849 | |
850 | if let Some(transform_origin) = transform_origin { |
851 | let dx = convert_length( |
852 | transform_origin.x_offset, |
853 | *self, |
854 | AId::Width, |
855 | Units::UserSpaceOnUse, |
856 | state, |
857 | ); |
858 | let dy = convert_length( |
859 | transform_origin.y_offset, |
860 | *self, |
861 | AId::Height, |
862 | Units::UserSpaceOnUse, |
863 | state, |
864 | ); |
865 | transform = Transform::default() |
866 | .pre_translate(dx, dy) |
867 | .pre_concat(transform) |
868 | .pre_translate(-dx, -dy); |
869 | } |
870 | |
871 | transform |
872 | } |
873 | } |
874 | |