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