1use std::collections::HashSet;
8use std::str::FromStr;
9use std::sync::Arc;
10
11use strict_num::PositiveF32;
12use svgtypes::{AspectRatio, Length, LengthUnit as Unit};
13
14use crate::{
15 filter::{self, *},
16 ApproxZeroUlps, Color, Group, Node, NonEmptyString, NonZeroF32, NonZeroRect, Opacity, Size,
17 Units,
18};
19
20use super::converter::{self, SvgColorExt};
21use super::paint_server::{convert_units, resolve_number};
22use super::svgtree::{AId, EId, FromValue, SvgNode};
23use super::OptionLog;
24
25impl<'a, 'input: 'a> FromValue<'a, 'input> for filter::ColorInterpolation {
26 fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
27 match value {
28 "sRGB" => Some(filter::ColorInterpolation::SRGB),
29 "linearRGB" => Some(filter::ColorInterpolation::LinearRGB),
30 _ => None,
31 }
32 }
33}
34
35pub(crate) fn convert(
36 node: SvgNode,
37 state: &converter::State,
38 object_bbox: Option<NonZeroRect>,
39 cache: &mut converter::Cache,
40) -> Result<Vec<Arc<Filter>>, ()> {
41 let value = match node.attribute::<&str>(AId::Filter) {
42 Some(v) => v,
43 None => return Ok(Vec::new()),
44 };
45
46 let mut has_invalid_urls = false;
47 let mut filters = Vec::new();
48
49 let create_base_filter_func =
50 |kind, filters: &mut Vec<Arc<Filter>>, cache: &mut converter::Cache| {
51 let mut rect = match kind {
56 Kind::DropShadow(_) | Kind::GaussianBlur(_) => {
57 NonZeroRect::from_xywh(-0.5, -0.5, 2.0, 2.0).unwrap()
58 }
59 _ => NonZeroRect::from_xywh(-0.1, -0.1, 1.2, 1.2).unwrap(),
60 };
61
62 let object_bbox = match object_bbox {
63 Some(v) => v,
64 None => {
65 log::warn!(
66 "Filter '{}' has an invalid region. Skipped.",
67 node.element_id()
68 );
69 return;
70 }
71 };
72
73 rect = rect.bbox_transform(object_bbox);
74
75 filters.push(Arc::new(Filter {
76 id: cache.gen_filter_id(),
77 rect,
78 primitives: vec![Primitive {
79 rect: rect,
80 color_interpolation: ColorInterpolation::SRGB,
82 result: "result".to_string(),
83 kind,
84 }],
85 }));
86 };
87
88 for func in svgtypes::FilterValueListParser::from(value) {
89 let func = match func {
90 Ok(v) => v,
91 Err(e) => {
92 log::warn!("Failed to parse a filter value cause {}. Skipping.", e);
94 return Ok(Vec::new());
95 }
96 };
97
98 match func {
99 svgtypes::FilterValue::Blur(std_dev) => create_base_filter_func(
100 convert_blur_function(node, std_dev, state),
101 &mut filters,
102 cache,
103 ),
104 svgtypes::FilterValue::DropShadow {
105 color,
106 dx,
107 dy,
108 std_dev,
109 } => create_base_filter_func(
110 convert_drop_shadow_function(node, color, dx, dy, std_dev, state),
111 &mut filters,
112 cache,
113 ),
114 svgtypes::FilterValue::Brightness(amount) => {
115 create_base_filter_func(convert_brightness_function(amount), &mut filters, cache)
116 }
117 svgtypes::FilterValue::Contrast(amount) => {
118 create_base_filter_func(convert_contrast_function(amount), &mut filters, cache)
119 }
120 svgtypes::FilterValue::Grayscale(amount) => {
121 create_base_filter_func(convert_grayscale_function(amount), &mut filters, cache)
122 }
123 svgtypes::FilterValue::HueRotate(angle) => {
124 create_base_filter_func(convert_hue_rotate_function(angle), &mut filters, cache)
125 }
126 svgtypes::FilterValue::Invert(amount) => {
127 create_base_filter_func(convert_invert_function(amount), &mut filters, cache)
128 }
129 svgtypes::FilterValue::Opacity(amount) => {
130 create_base_filter_func(convert_opacity_function(amount), &mut filters, cache)
131 }
132 svgtypes::FilterValue::Sepia(amount) => {
133 create_base_filter_func(convert_sepia_function(amount), &mut filters, cache)
134 }
135 svgtypes::FilterValue::Saturate(amount) => {
136 create_base_filter_func(convert_saturate_function(amount), &mut filters, cache)
137 }
138 svgtypes::FilterValue::Url(url) => {
139 if let Some(link) = node.document().element_by_id(url) {
140 if let Ok(res) = convert_url(link, state, object_bbox, cache) {
141 if let Some(f) = res {
142 filters.push(f);
143 }
144 } else {
145 has_invalid_urls = true;
146 }
147 } else {
148 has_invalid_urls = true;
149 }
150 }
151 }
152 }
153
154 if filters.is_empty() && has_invalid_urls {
159 return Err(());
160 }
161
162 Ok(filters)
163}
164
165fn convert_url(
166 node: SvgNode,
167 state: &converter::State,
168 object_bbox: Option<NonZeroRect>,
169 cache: &mut converter::Cache,
170) -> Result<Option<Arc<Filter>>, ()> {
171 let units = convert_units(node, AId::FilterUnits, Units::ObjectBoundingBox);
172 let primitive_units = convert_units(node, AId::PrimitiveUnits, Units::UserSpaceOnUse);
173
174 let cacheable = units == Units::UserSpaceOnUse && primitive_units == Units::UserSpaceOnUse;
180 if cacheable {
181 if let Some(filter) = cache.filters.get(node.element_id()) {
182 return Ok(Some(filter.clone()));
183 }
184 }
185
186 let rect = NonZeroRect::from_xywh(
187 resolve_number(
188 node,
189 AId::X,
190 units,
191 state,
192 Length::new(-10.0, Unit::Percent),
193 ),
194 resolve_number(
195 node,
196 AId::Y,
197 units,
198 state,
199 Length::new(-10.0, Unit::Percent),
200 ),
201 resolve_number(
202 node,
203 AId::Width,
204 units,
205 state,
206 Length::new(120.0, Unit::Percent),
207 ),
208 resolve_number(
209 node,
210 AId::Height,
211 units,
212 state,
213 Length::new(120.0, Unit::Percent),
214 ),
215 );
216
217 let mut rect = rect
218 .log_none(|| {
219 log::warn!(
220 "Filter '{}' has an invalid region. Skipped.",
221 node.element_id()
222 )
223 })
224 .ok_or(())?;
225
226 if units == Units::ObjectBoundingBox {
227 if let Some(object_bbox) = object_bbox {
228 rect = rect.bbox_transform(object_bbox);
229 } else {
230 log::warn!("Filters on zero-sized shapes are not allowed.");
231 return Err(());
232 }
233 }
234
235 let node_with_primitives = match find_filter_with_primitives(node) {
236 Some(v) => v,
237 None => return Err(()),
238 };
239 let primitives = collect_children(
240 &node_with_primitives,
241 primitive_units,
242 state,
243 object_bbox,
244 rect,
245 cache,
246 );
247 if primitives.is_empty() {
248 return Err(());
249 }
250
251 let mut id = NonEmptyString::new(node.element_id().to_string()).ok_or(())?;
252 if !cacheable && cache.filters.contains_key(id.get()) {
254 id = cache.gen_filter_id();
255 }
256 let id_copy = id.get().to_string();
257
258 let filter = Arc::new(Filter {
259 id,
260 rect,
261 primitives,
262 });
263
264 cache.filters.insert(id_copy, filter.clone());
265
266 Ok(Some(filter))
267}
268
269fn find_filter_with_primitives<'a>(node: SvgNode<'a, 'a>) -> Option<SvgNode<'a, 'a>> {
270 for link in node.href_iter() {
271 if link.tag_name() != Some(EId::Filter) {
272 log::warn!(
273 "Filter '{}' cannot reference '{}' via 'xlink:href'.",
274 node.element_id(),
275 link.tag_name().unwrap()
276 );
277 return None;
278 }
279
280 if link.has_children() {
281 return Some(link);
282 }
283 }
284
285 None
286}
287
288struct FilterResults {
289 names: HashSet<String>,
290 idx: usize,
291}
292
293fn collect_children(
294 filter: &SvgNode,
295 units: Units,
296 state: &converter::State,
297 object_bbox: Option<NonZeroRect>,
298 filter_region: NonZeroRect,
299 cache: &mut converter::Cache,
300) -> Vec<Primitive> {
301 let mut primitives = Vec::new();
302
303 let mut results = FilterResults {
304 names: HashSet::new(),
305 idx: 1,
306 };
307
308 let scale = if units == Units::ObjectBoundingBox {
309 if let Some(object_bbox) = object_bbox {
310 object_bbox.size()
311 } else {
312 return Vec::new();
314 }
315 } else {
316 Size::from_wh(1.0, 1.0).unwrap()
317 };
318
319 for child in filter.children() {
320 let tag_name = match child.tag_name() {
321 Some(v) => v,
322 None => continue,
323 };
324
325 let filter_subregion = match resolve_primitive_region(
326 child,
327 tag_name,
328 units,
329 state,
330 object_bbox,
331 filter_region,
332 ) {
333 Some(v) => v,
334 None => break,
335 };
336
337 let kind =
338 match tag_name {
339 EId::FeDropShadow => convert_drop_shadow(child, scale, &primitives),
340 EId::FeGaussianBlur => convert_gaussian_blur(child, scale, &primitives),
341 EId::FeOffset => convert_offset(child, scale, &primitives),
342 EId::FeBlend => convert_blend(child, &primitives),
343 EId::FeFlood => convert_flood(child),
344 EId::FeComposite => convert_composite(child, &primitives),
345 EId::FeMerge => convert_merge(child, &primitives),
346 EId::FeTile => convert_tile(child, &primitives),
347 EId::FeImage => convert_image(child, filter_subregion, state, cache),
348 EId::FeComponentTransfer => convert_component_transfer(child, &primitives),
349 EId::FeColorMatrix => convert_color_matrix(child, &primitives),
350 EId::FeConvolveMatrix => convert_convolve_matrix(child, &primitives)
351 .unwrap_or_else(create_dummy_primitive),
352 EId::FeMorphology => convert_morphology(child, scale, &primitives),
353 EId::FeDisplacementMap => convert_displacement_map(child, scale, &primitives),
354 EId::FeTurbulence => convert_turbulence(child),
355 EId::FeDiffuseLighting => convert_diffuse_lighting(child, &primitives)
356 .unwrap_or_else(create_dummy_primitive),
357 EId::FeSpecularLighting => convert_specular_lighting(child, &primitives)
358 .unwrap_or_else(create_dummy_primitive),
359 tag_name => {
360 log::warn!("'{}' is not a valid filter primitive. Skipped.", tag_name);
361 continue;
362 }
363 };
364
365 let color_interpolation = child
366 .find_attribute(AId::ColorInterpolationFilters)
367 .unwrap_or_default();
368
369 primitives.push(Primitive {
370 rect: filter_subregion,
371 color_interpolation,
372 result: gen_result(child, &mut results),
373 kind,
374 });
375 }
376
377 primitives
380}
381
382fn resolve_primitive_region(
384 fe: SvgNode,
385 kind: EId,
386 units: Units,
387 state: &converter::State,
388 bbox: Option<NonZeroRect>,
389 filter_region: NonZeroRect,
390) -> Option<NonZeroRect> {
391 let x = fe.try_convert_length(AId::X, units, state);
392 let y = fe.try_convert_length(AId::Y, units, state);
393 let width = fe.try_convert_length(AId::Width, units, state);
394 let height = fe.try_convert_length(AId::Height, units, state);
395
396 let region = match kind {
397 EId::FeFlood | EId::FeImage => {
398 if units == Units::ObjectBoundingBox {
400 let bbox = bbox?;
401
402 let r = NonZeroRect::from_xywh(
406 x.unwrap_or(0.0),
407 y.unwrap_or(0.0),
408 width.unwrap_or(1.0),
409 height.unwrap_or(1.0),
410 )?;
411
412 return Some(r.bbox_transform(bbox));
413 } else {
414 filter_region
415 }
416 }
417 _ => filter_region,
418 };
419
420 if units == Units::ObjectBoundingBox {
422 let subregion_bbox = NonZeroRect::from_xywh(
423 x.unwrap_or(0.0),
424 y.unwrap_or(0.0),
425 width.unwrap_or(1.0),
426 height.unwrap_or(1.0),
427 )?;
428
429 Some(region.bbox_transform(subregion_bbox))
430 } else {
431 NonZeroRect::from_xywh(
432 x.unwrap_or(region.x()),
433 y.unwrap_or(region.y()),
434 width.unwrap_or(region.width()),
435 height.unwrap_or(region.height()),
436 )
437 }
438}
439
440#[inline(never)]
444pub(crate) fn create_dummy_primitive() -> Kind {
445 Kind::Flood(Flood {
446 color: Color::black(),
447 opacity: Opacity::ZERO,
448 })
449}
450
451#[inline(never)]
452fn resolve_input(node: SvgNode, aid: AId, primitives: &[Primitive]) -> Input {
453 match node.attribute(aid) {
454 Some(s) => {
455 let input = parse_in(s);
456
457 if let Input::Reference(ref name) = input {
460 if !primitives.iter().any(|p| p.result == *name) {
461 return if let Some(prev) = primitives.last() {
462 Input::Reference(prev.result.clone())
463 } else {
464 Input::SourceGraphic
465 };
466 }
467 }
468
469 input
470 }
471 None => {
472 if let Some(prev) = primitives.last() {
473 Input::Reference(prev.result.clone())
476 } else {
477 Input::SourceGraphic
480 }
481 }
482 }
483}
484
485fn parse_in(s: &str) -> Input {
486 match s {
487 "SourceGraphic" => Input::SourceGraphic,
488 "SourceAlpha" => Input::SourceAlpha,
489 "BackgroundImage" | "BackgroundAlpha" | "FillPaint" | "StrokePaint" => {
490 log::warn!("{} filter input isn't supported and not planed.", s);
491 Input::SourceGraphic
492 }
493 _ => Input::Reference(s.to_string()),
494 }
495}
496
497fn gen_result(node: SvgNode, results: &mut FilterResults) -> String {
498 match node.attribute::<&str>(AId::Result) {
499 Some(s) => {
500 results.names.insert(s.to_string());
502 results.idx += 1;
503
504 s.to_string()
505 }
506 None => {
507 loop {
509 let name = format!("result{}", results.idx);
510 results.idx += 1;
511
512 if !results.names.contains(&name) {
513 return name;
514 }
515 }
516 }
517 }
518}
519
520fn convert_blend(fe: SvgNode, primitives: &[Primitive]) -> Kind {
521 let mode = fe.attribute(AId::Mode).unwrap_or_default();
522 let input1 = resolve_input(fe, AId::In, primitives);
523 let input2 = resolve_input(fe, AId::In2, primitives);
524 Kind::Blend(Blend {
525 mode,
526 input1,
527 input2,
528 })
529}
530
531fn convert_color_matrix(fe: SvgNode, primitives: &[Primitive]) -> Kind {
532 let kind = convert_color_matrix_kind(fe).unwrap_or_default();
533 Kind::ColorMatrix(ColorMatrix {
534 input: resolve_input(fe, AId::In, primitives),
535 kind,
536 })
537}
538
539fn convert_color_matrix_kind(fe: SvgNode) -> Option<ColorMatrixKind> {
540 match fe.attribute(AId::Type) {
541 Some("saturate") => {
542 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
543 if !list.is_empty() {
544 let n = crate::f32_bound(0.0, list[0], 1.0);
545 return Some(ColorMatrixKind::Saturate(PositiveF32::new(n).unwrap()));
546 } else {
547 return Some(ColorMatrixKind::Saturate(PositiveF32::new(1.0).unwrap()));
548 }
549 }
550 }
551 Some("hueRotate") => {
552 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
553 if !list.is_empty() {
554 return Some(ColorMatrixKind::HueRotate(list[0]));
555 } else {
556 return Some(ColorMatrixKind::HueRotate(0.0));
557 }
558 }
559 }
560 Some("luminanceToAlpha") => {
561 return Some(ColorMatrixKind::LuminanceToAlpha);
562 }
563 _ => {
564 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
566 if list.len() == 20 {
567 return Some(ColorMatrixKind::Matrix(list));
568 }
569 }
570 }
571 }
572
573 None
574}
575
576fn convert_component_transfer(fe: SvgNode, primitives: &[Primitive]) -> Kind {
577 let mut kind = ComponentTransfer {
578 input: resolve_input(fe, AId::In, primitives),
579 func_r: TransferFunction::Identity,
580 func_g: TransferFunction::Identity,
581 func_b: TransferFunction::Identity,
582 func_a: TransferFunction::Identity,
583 };
584
585 for child in fe.children().filter(|n| n.is_element()) {
586 if let Some(func) = convert_transfer_function(child) {
587 match child.tag_name().unwrap() {
588 EId::FeFuncR => kind.func_r = func,
589 EId::FeFuncG => kind.func_g = func,
590 EId::FeFuncB => kind.func_b = func,
591 EId::FeFuncA => kind.func_a = func,
592 _ => {}
593 }
594 }
595 }
596
597 Kind::ComponentTransfer(kind)
598}
599
600fn convert_transfer_function(node: SvgNode) -> Option<TransferFunction> {
601 match node.attribute(AId::Type)? {
602 "identity" => Some(TransferFunction::Identity),
603 "table" => match node.attribute::<Vec<f32>>(AId::TableValues) {
604 Some(values) => Some(TransferFunction::Table(values)),
605 None => Some(TransferFunction::Table(Vec::new())),
606 },
607 "discrete" => match node.attribute::<Vec<f32>>(AId::TableValues) {
608 Some(values) => Some(TransferFunction::Discrete(values)),
609 None => Some(TransferFunction::Discrete(Vec::new())),
610 },
611 "linear" => Some(TransferFunction::Linear {
612 slope: node.attribute(AId::Slope).unwrap_or(1.0),
613 intercept: node.attribute(AId::Intercept).unwrap_or(0.0),
614 }),
615 "gamma" => Some(TransferFunction::Gamma {
616 amplitude: node.attribute(AId::Amplitude).unwrap_or(1.0),
617 exponent: node.attribute(AId::Exponent).unwrap_or(1.0),
618 offset: node.attribute(AId::Offset).unwrap_or(0.0),
619 }),
620 _ => None,
621 }
622}
623
624fn convert_composite(fe: SvgNode, primitives: &[Primitive]) -> Kind {
625 let operator = match fe.attribute(AId::Operator).unwrap_or("over") {
626 "in" => CompositeOperator::In,
627 "out" => CompositeOperator::Out,
628 "atop" => CompositeOperator::Atop,
629 "xor" => CompositeOperator::Xor,
630 "arithmetic" => CompositeOperator::Arithmetic {
631 k1: fe.attribute(AId::K1).unwrap_or(0.0),
632 k2: fe.attribute(AId::K2).unwrap_or(0.0),
633 k3: fe.attribute(AId::K3).unwrap_or(0.0),
634 k4: fe.attribute(AId::K4).unwrap_or(0.0),
635 },
636 _ => CompositeOperator::Over,
637 };
638
639 let input1 = resolve_input(fe, AId::In, primitives);
640 let input2 = resolve_input(fe, AId::In2, primitives);
641
642 Kind::Composite(Composite {
643 operator,
644 input1,
645 input2,
646 })
647}
648
649fn convert_convolve_matrix(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
650 fn parse_target(target: Option<f32>, order: u32) -> Option<u32> {
651 let default_target = (order as f32 / 2.0).floor() as u32;
652 let target = target.unwrap_or(default_target as f32) as i32;
653 if target < 0 || target >= order as i32 {
654 None
655 } else {
656 Some(target as u32)
657 }
658 }
659
660 let mut order_x = 3;
661 let mut order_y = 3;
662 if let Some(value) = fe.attribute::<&str>(AId::Order) {
663 let mut s = svgtypes::NumberListParser::from(value);
664 let x = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(3);
665 let y = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(x);
666 if x > 0 && y > 0 {
667 order_x = x as u32;
668 order_y = y as u32;
669 }
670 }
671
672 let mut matrix = Vec::new();
673 if let Some(list) = fe.attribute::<Vec<f32>>(AId::KernelMatrix) {
674 if list.len() == (order_x * order_y) as usize {
675 matrix = list;
676 }
677 }
678
679 let mut kernel_sum: f32 = matrix.iter().sum();
680 kernel_sum = (kernel_sum * 1_000_000.0).round() / 1_000_000.0;
682 if kernel_sum.approx_zero_ulps(4) {
683 kernel_sum = 1.0;
684 }
685
686 let divisor = fe.attribute(AId::Divisor).unwrap_or(kernel_sum);
687 if divisor.approx_zero_ulps(4) {
688 return None;
689 }
690
691 let bias = fe.attribute(AId::Bias).unwrap_or(0.0);
692
693 let target_x = parse_target(fe.attribute(AId::TargetX), order_x)?;
694 let target_y = parse_target(fe.attribute(AId::TargetY), order_y)?;
695
696 let kernel_matrix = ConvolveMatrixData::new(target_x, target_y, order_x, order_y, matrix)?;
697
698 let edge_mode = match fe.attribute(AId::EdgeMode).unwrap_or("duplicate") {
699 "none" => EdgeMode::None,
700 "wrap" => EdgeMode::Wrap,
701 _ => EdgeMode::Duplicate,
702 };
703
704 let preserve_alpha = fe.attribute(AId::PreserveAlpha).unwrap_or("false") == "true";
705
706 Some(Kind::ConvolveMatrix(ConvolveMatrix {
707 input: resolve_input(fe, AId::In, primitives),
708 matrix: kernel_matrix,
709 divisor: NonZeroF32::new(divisor).unwrap(),
710 bias,
711 edge_mode,
712 preserve_alpha,
713 }))
714}
715
716fn convert_displacement_map(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
717 let parse_channel = |aid| match fe.attribute(aid).unwrap_or("A") {
718 "R" => ColorChannel::R,
719 "G" => ColorChannel::G,
720 "B" => ColorChannel::B,
721 _ => ColorChannel::A,
722 };
723
724 let scale = (scale.width() + scale.height()) / 2.0;
727
728 Kind::DisplacementMap(DisplacementMap {
729 input1: resolve_input(fe, AId::In, primitives),
730 input2: resolve_input(fe, AId::In2, primitives),
731 scale: fe.attribute(AId::Scale).unwrap_or(0.0) * scale,
732 x_channel_selector: parse_channel(AId::XChannelSelector),
733 y_channel_selector: parse_channel(AId::YChannelSelector),
734 })
735}
736
737fn convert_drop_shadow(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
738 let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "2 2");
739
740 let (color, opacity) = fe
741 .attribute(AId::FloodColor)
742 .unwrap_or_else(svgtypes::Color::black)
743 .split_alpha();
744
745 let flood_opacity = fe
746 .attribute::<Opacity>(AId::FloodOpacity)
747 .unwrap_or(Opacity::ONE);
748
749 Kind::DropShadow(DropShadow {
750 input: resolve_input(fe, AId::In, primitives),
751 dx: fe.attribute(AId::Dx).unwrap_or(2.0) * scale.width(),
752 dy: fe.attribute(AId::Dy).unwrap_or(2.0) * scale.height(),
753 std_dev_x,
754 std_dev_y,
755 color,
756 opacity: opacity * flood_opacity,
757 })
758}
759
760fn convert_flood(fe: SvgNode) -> Kind {
761 let (color, opacity) = fe
762 .attribute(AId::FloodColor)
763 .unwrap_or_else(svgtypes::Color::black)
764 .split_alpha();
765
766 let flood_opacity = fe
767 .attribute::<Opacity>(AId::FloodOpacity)
768 .unwrap_or(Opacity::ONE);
769
770 Kind::Flood(Flood {
771 color,
772 opacity: opacity * flood_opacity,
773 })
774}
775
776fn convert_gaussian_blur(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
777 let (std_dev_x, std_dev_y) = convert_std_dev_attr(fe, scale, "0 0");
778 Kind::GaussianBlur(GaussianBlur {
779 input: resolve_input(fe, AId::In, primitives),
780 std_dev_x,
781 std_dev_y,
782 })
783}
784
785fn convert_std_dev_attr(fe: SvgNode, scale: Size, default: &str) -> (PositiveF32, PositiveF32) {
786 let text = fe.attribute(AId::StdDeviation).unwrap_or(default);
787 let mut parser = svgtypes::NumberListParser::from(text);
788
789 let n1 = parser.next().and_then(|n| n.ok());
790 let n2 = parser.next().and_then(|n| n.ok());
791 let n3 = parser.next().and_then(|n| n.ok());
794
795 let (std_dev_x, std_dev_y) = match (n1, n2, n3) {
796 (Some(n1), Some(n2), None) => (n1, n2),
797 (Some(n1), None, None) => (n1, n1),
798 _ => (0.0, 0.0),
799 };
800
801 let std_dev_x = (std_dev_x as f32) * scale.width();
802 let std_dev_y = (std_dev_y as f32) * scale.height();
803
804 let std_dev_x = PositiveF32::new(std_dev_x as f32).unwrap_or(PositiveF32::ZERO);
805 let std_dev_y = PositiveF32::new(std_dev_y as f32).unwrap_or(PositiveF32::ZERO);
806
807 (std_dev_x, std_dev_y)
808}
809
810fn convert_image(
811 fe: SvgNode,
812 filter_subregion: NonZeroRect,
813 state: &converter::State,
814 cache: &mut converter::Cache,
815) -> Kind {
816 match convert_image_inner(fe, filter_subregion, state, cache) {
817 Some(kind) => kind,
818 None => create_dummy_primitive(),
819 }
820}
821
822fn convert_image_inner(
823 fe: SvgNode,
824 filter_subregion: NonZeroRect,
825 state: &converter::State,
826 cache: &mut converter::Cache,
827) -> Option<Kind> {
828 let rendering_mode = fe
829 .find_attribute(AId::ImageRendering)
830 .unwrap_or(state.opt.image_rendering);
831
832 if let Some(node) = fe.try_attribute::<SvgNode>(AId::Href) {
833 let mut state = state.clone();
834 state.fe_image_link = true;
835 let mut root = Group::empty();
836 super::converter::convert_element(node, &state, cache, &mut root);
837 return if root.has_children() {
838 root.calculate_bounding_boxes();
839 if let Some(Node::Group(ref mut g)) = root.children.first_mut() {
841 if let Some(child2) = g.children.first_mut() {
842 g.id = child2.id().to_string();
843 match child2 {
844 Node::Group(ref mut g2) => g2.id.clear(),
845 Node::Path(ref mut path) => path.id.clear(),
846 Node::Image(ref mut image) => image.id.clear(),
847 Node::Text(ref mut text) => text.id.clear(),
848 }
849 }
850 }
851
852 Some(Kind::Image(Image { root }))
853 } else {
854 None
855 };
856 }
857
858 let href = fe.try_attribute(AId::Href).log_none(|| {
859 log::warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped.")
860 })?;
861 let img_data = super::image::get_href_data(href, state)?;
862 let actual_size = img_data.actual_size()?;
863
864 let aspect: AspectRatio = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default();
865
866 let mut root = Group::empty();
867 super::image::convert_inner(
868 img_data,
869 cache.gen_image_id().take(),
870 true,
871 rendering_mode,
872 aspect,
873 actual_size,
874 filter_subregion.translate_to(0.0, 0.0)?,
875 cache,
876 &mut root,
877 );
878 root.calculate_bounding_boxes();
879
880 Some(Kind::Image(Image { root }))
881}
882
883fn convert_diffuse_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
884 let light_source = convert_light_source(fe)?;
885 Some(Kind::DiffuseLighting(DiffuseLighting {
886 input: resolve_input(fe, AId::In, primitives),
887 surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),
888 diffuse_constant: fe.attribute(AId::DiffuseConstant).unwrap_or(1.0),
889 lighting_color: convert_lighting_color(fe),
890 light_source,
891 }))
892}
893
894fn convert_specular_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
895 let light_source = convert_light_source(fe)?;
896
897 let specular_exponent = fe.attribute(AId::SpecularExponent).unwrap_or(1.0);
898 if !(1.0..=128.0).contains(&specular_exponent) {
899 return None;
901 }
902
903 let specular_exponent = crate::f32_bound(1.0, specular_exponent, 128.0);
904
905 Some(Kind::SpecularLighting(SpecularLighting {
906 input: resolve_input(fe, AId::In, primitives),
907 surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(1.0),
908 specular_constant: fe.attribute(AId::SpecularConstant).unwrap_or(1.0),
909 specular_exponent,
910 lighting_color: convert_lighting_color(fe),
911 light_source,
912 }))
913}
914
915#[inline(never)]
916fn convert_lighting_color(node: SvgNode) -> Color {
917 match node.attribute(AId::LightingColor) {
919 Some("currentColor") => {
920 node.find_attribute(AId::Color)
921 .unwrap_or(svgtypes::Color::black())
923 .split_alpha()
924 .0
925 }
926 Some(value) => {
927 if let Ok(c) = svgtypes::Color::from_str(value) {
928 c.split_alpha().0
929 } else {
930 log::warn!("Failed to parse lighting-color value: '{}'.", value);
931 Color::white()
932 }
933 }
934 _ => Color::white(),
935 }
936}
937
938#[inline(never)]
939fn convert_light_source(parent: SvgNode) -> Option<LightSource> {
940 let child = parent.children().find(|n| {
941 matches!(
942 n.tag_name(),
943 Some(EId::FeDistantLight) | Some(EId::FePointLight) | Some(EId::FeSpotLight)
944 )
945 })?;
946
947 match child.tag_name() {
948 Some(EId::FeDistantLight) => Some(LightSource::DistantLight(DistantLight {
949 azimuth: child.attribute(AId::Azimuth).unwrap_or(0.0),
950 elevation: child.attribute(AId::Elevation).unwrap_or(0.0),
951 })),
952 Some(EId::FePointLight) => Some(LightSource::PointLight(PointLight {
953 x: child.attribute(AId::X).unwrap_or(0.0),
954 y: child.attribute(AId::Y).unwrap_or(0.0),
955 z: child.attribute(AId::Z).unwrap_or(0.0),
956 })),
957 Some(EId::FeSpotLight) => {
958 let specular_exponent = child.attribute(AId::SpecularExponent).unwrap_or(1.0);
959 let specular_exponent = PositiveF32::new(specular_exponent)
960 .unwrap_or_else(|| PositiveF32::new(1.0).unwrap());
961
962 Some(LightSource::SpotLight(SpotLight {
963 x: child.attribute(AId::X).unwrap_or(0.0),
964 y: child.attribute(AId::Y).unwrap_or(0.0),
965 z: child.attribute(AId::Z).unwrap_or(0.0),
966 points_at_x: child.attribute(AId::PointsAtX).unwrap_or(0.0),
967 points_at_y: child.attribute(AId::PointsAtY).unwrap_or(0.0),
968 points_at_z: child.attribute(AId::PointsAtZ).unwrap_or(0.0),
969 specular_exponent,
970 limiting_cone_angle: child.attribute(AId::LimitingConeAngle),
971 }))
972 }
973 _ => None,
974 }
975}
976
977fn convert_merge(fe: SvgNode, primitives: &[Primitive]) -> Kind {
978 let mut inputs = Vec::new();
979 for child in fe.children() {
980 inputs.push(resolve_input(child, AId::In, primitives));
981 }
982
983 Kind::Merge(Merge { inputs })
984}
985
986fn convert_morphology(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
987 let operator = match fe.attribute(AId::Operator).unwrap_or("erode") {
988 "dilate" => MorphologyOperator::Dilate,
989 _ => MorphologyOperator::Erode,
990 };
991
992 let mut radius_x = PositiveF32::new(scale.width()).unwrap();
993 let mut radius_y = PositiveF32::new(scale.height()).unwrap();
994 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Radius) {
995 let mut rx = 0.0;
996 let mut ry = 0.0;
997 if list.len() == 2 {
998 rx = list[0];
999 ry = list[1];
1000 } else if list.len() == 1 {
1001 rx = list[0];
1002 ry = list[0]; }
1004
1005 if rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1006 rx = 1.0;
1007 ry = 1.0;
1008 }
1009
1010 if rx.approx_zero_ulps(4) && !ry.approx_zero_ulps(4) {
1013 rx = 1.0;
1014 }
1015 if !rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1016 ry = 1.0;
1017 }
1018
1019 if rx.is_sign_positive() && ry.is_sign_positive() {
1021 radius_x = PositiveF32::new(rx * scale.width()).unwrap();
1022 radius_y = PositiveF32::new(ry * scale.height()).unwrap();
1023 }
1024 }
1025
1026 Kind::Morphology(Morphology {
1027 input: resolve_input(fe, AId::In, primitives),
1028 operator,
1029 radius_x,
1030 radius_y,
1031 })
1032}
1033
1034fn convert_offset(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
1035 Kind::Offset(Offset {
1036 input: resolve_input(fe, AId::In, primitives),
1037 dx: fe.attribute(AId::Dx).unwrap_or(0.0) * scale.width(),
1038 dy: fe.attribute(AId::Dy).unwrap_or(0.0) * scale.height(),
1039 })
1040}
1041
1042fn convert_tile(fe: SvgNode, primitives: &[Primitive]) -> Kind {
1043 Kind::Tile(Tile {
1044 input: resolve_input(fe, AId::In, primitives),
1045 })
1046}
1047
1048fn convert_turbulence(fe: SvgNode) -> Kind {
1049 let mut base_frequency_x = PositiveF32::ZERO;
1050 let mut base_frequency_y = PositiveF32::ZERO;
1051 if let Some(list) = fe.attribute::<Vec<f32>>(AId::BaseFrequency) {
1052 let mut x = 0.0;
1053 let mut y = 0.0;
1054 if list.len() == 2 {
1055 x = list[0];
1056 y = list[1];
1057 } else if list.len() == 1 {
1058 x = list[0];
1059 y = list[0]; }
1061
1062 if x.is_sign_positive() && y.is_sign_positive() {
1063 base_frequency_x = PositiveF32::new(x).unwrap();
1064 base_frequency_y = PositiveF32::new(y).unwrap();
1065 }
1066 }
1067
1068 let mut num_octaves = fe.attribute(AId::NumOctaves).unwrap_or(1.0);
1069 if num_octaves.is_sign_negative() {
1070 num_octaves = 0.0;
1071 }
1072
1073 let kind = match fe.attribute(AId::Type).unwrap_or("turbulence") {
1074 "fractalNoise" => TurbulenceKind::FractalNoise,
1075 _ => TurbulenceKind::Turbulence,
1076 };
1077
1078 Kind::Turbulence(Turbulence {
1079 base_frequency_x,
1080 base_frequency_y,
1081 num_octaves: num_octaves.round() as u32,
1082 seed: fe.attribute::<f32>(AId::Seed).unwrap_or(0.0).trunc() as i32,
1083 stitch_tiles: fe.attribute(AId::StitchTiles) == Some("stitch"),
1084 kind,
1085 })
1086}
1087
1088#[inline(never)]
1089fn convert_grayscale_function(amount: f64) -> Kind {
1090 let amount = amount.min(1.0) as f32;
1091 Kind::ColorMatrix(ColorMatrix {
1092 input: Input::SourceGraphic,
1093 kind: ColorMatrixKind::Matrix(vec![
1094 (0.2126 + 0.7874 * (1.0 - amount)),
1095 (0.7152 - 0.7152 * (1.0 - amount)),
1096 (0.0722 - 0.0722 * (1.0 - amount)),
1097 0.0,
1098 0.0,
1099 (0.2126 - 0.2126 * (1.0 - amount)),
1100 (0.7152 + 0.2848 * (1.0 - amount)),
1101 (0.0722 - 0.0722 * (1.0 - amount)),
1102 0.0,
1103 0.0,
1104 (0.2126 - 0.2126 * (1.0 - amount)),
1105 (0.7152 - 0.7152 * (1.0 - amount)),
1106 (0.0722 + 0.9278 * (1.0 - amount)),
1107 0.0,
1108 0.0,
1109 0.0,
1110 0.0,
1111 0.0,
1112 1.0,
1113 0.0,
1114 ]),
1115 })
1116}
1117
1118#[inline(never)]
1119fn convert_sepia_function(amount: f64) -> Kind {
1120 let amount = amount.min(1.0) as f32;
1121 Kind::ColorMatrix(ColorMatrix {
1122 input: Input::SourceGraphic,
1123 kind: ColorMatrixKind::Matrix(vec![
1124 (0.393 + 0.607 * (1.0 - amount)),
1125 (0.769 - 0.769 * (1.0 - amount)),
1126 (0.189 - 0.189 * (1.0 - amount)),
1127 0.0,
1128 0.0,
1129 (0.349 - 0.349 * (1.0 - amount)),
1130 (0.686 + 0.314 * (1.0 - amount)),
1131 (0.168 - 0.168 * (1.0 - amount)),
1132 0.0,
1133 0.0,
1134 (0.272 - 0.272 * (1.0 - amount)),
1135 (0.534 - 0.534 * (1.0 - amount)),
1136 (0.131 + 0.869 * (1.0 - amount)),
1137 0.0,
1138 0.0,
1139 0.0,
1140 0.0,
1141 0.0,
1142 1.0,
1143 0.0,
1144 ]),
1145 })
1146}
1147
1148#[inline(never)]
1149fn convert_saturate_function(amount: f64) -> Kind {
1150 let amount = PositiveF32::new(amount as f32).unwrap_or(PositiveF32::ZERO);
1151 Kind::ColorMatrix(ColorMatrix {
1152 input: Input::SourceGraphic,
1153 kind: ColorMatrixKind::Saturate(amount),
1154 })
1155}
1156
1157#[inline(never)]
1158fn convert_hue_rotate_function(amount: svgtypes::Angle) -> Kind {
1159 Kind::ColorMatrix(ColorMatrix {
1160 input: Input::SourceGraphic,
1161 kind: ColorMatrixKind::HueRotate(amount.to_degrees() as f32),
1162 })
1163}
1164
1165#[inline(never)]
1166fn convert_invert_function(amount: f64) -> Kind {
1167 let amount = amount.min(1.0) as f32;
1168 Kind::ComponentTransfer(ComponentTransfer {
1169 input: Input::SourceGraphic,
1170 func_r: TransferFunction::Table(vec![amount, 1.0 - amount]),
1171 func_g: TransferFunction::Table(vec![amount, 1.0 - amount]),
1172 func_b: TransferFunction::Table(vec![amount, 1.0 - amount]),
1173 func_a: TransferFunction::Identity,
1174 })
1175}
1176
1177#[inline(never)]
1178fn convert_opacity_function(amount: f64) -> Kind {
1179 let amount = amount.min(1.0) as f32;
1180 Kind::ComponentTransfer(ComponentTransfer {
1181 input: Input::SourceGraphic,
1182 func_r: TransferFunction::Identity,
1183 func_g: TransferFunction::Identity,
1184 func_b: TransferFunction::Identity,
1185 func_a: TransferFunction::Table(vec![0.0, amount]),
1186 })
1187}
1188
1189#[inline(never)]
1190fn convert_brightness_function(amount: f64) -> Kind {
1191 let amount = amount as f32;
1192 Kind::ComponentTransfer(ComponentTransfer {
1193 input: Input::SourceGraphic,
1194 func_r: TransferFunction::Linear {
1195 slope: amount,
1196 intercept: 0.0,
1197 },
1198 func_g: TransferFunction::Linear {
1199 slope: amount,
1200 intercept: 0.0,
1201 },
1202 func_b: TransferFunction::Linear {
1203 slope: amount,
1204 intercept: 0.0,
1205 },
1206 func_a: TransferFunction::Identity,
1207 })
1208}
1209
1210#[inline(never)]
1211fn convert_contrast_function(amount: f64) -> Kind {
1212 let amount = amount as f32;
1213 Kind::ComponentTransfer(ComponentTransfer {
1214 input: Input::SourceGraphic,
1215 func_r: TransferFunction::Linear {
1216 slope: amount,
1217 intercept: -(0.5 * amount) + 0.5,
1218 },
1219 func_g: TransferFunction::Linear {
1220 slope: amount,
1221 intercept: -(0.5 * amount) + 0.5,
1222 },
1223 func_b: TransferFunction::Linear {
1224 slope: amount,
1225 intercept: -(0.5 * amount) + 0.5,
1226 },
1227 func_a: TransferFunction::Identity,
1228 })
1229}
1230
1231#[inline(never)]
1232fn convert_blur_function(node: SvgNode, std_dev: Length, state: &converter::State) -> Kind {
1233 let std_dev = PositiveF32::new(super::units::convert_user_length(
1234 std_dev,
1235 node,
1236 AId::Dx,
1237 state,
1238 ))
1239 .unwrap_or(PositiveF32::ZERO);
1240 Kind::GaussianBlur(GaussianBlur {
1241 input: Input::SourceGraphic,
1242 std_dev_x: std_dev,
1243 std_dev_y: std_dev,
1244 })
1245}
1246
1247#[inline(never)]
1248fn convert_drop_shadow_function(
1249 node: SvgNode,
1250 color: Option<svgtypes::Color>,
1251 dx: Length,
1252 dy: Length,
1253 std_dev: Length,
1254 state: &converter::State,
1255) -> Kind {
1256 let std_dev = PositiveF32::new(super::units::convert_user_length(
1257 std_dev,
1258 node,
1259 AId::Dx,
1260 state,
1261 ))
1262 .unwrap_or(PositiveF32::ZERO);
1263
1264 let (color, opacity) = color
1265 .unwrap_or_else(|| {
1266 node.find_attribute(AId::Color)
1267 .unwrap_or_else(svgtypes::Color::black)
1268 })
1269 .split_alpha();
1270
1271 Kind::DropShadow(DropShadow {
1272 input: Input::SourceGraphic,
1273 dx: super::units::convert_user_length(dx, node, AId::Dx, state),
1274 dy: super::units::convert_user_length(dy, node, AId::Dy, state),
1275 std_dev_x: std_dev,
1276 std_dev_y: std_dev,
1277 color,
1278 opacity,
1279 })
1280}