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//! A collection of SVG filters.
6
7use std::collections::HashSet;
8use std::str::FromStr;
9use std::sync::Arc;
10
11use strict_num::PositiveF32;
12use svgtypes::{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 // Filter functions, unlike `filter` elements, do not have a filter region.
52 // We're currently do not support an unlimited region, so we simply use a fairly large one.
53 // This if far from ideal, but good for now.
54 // TODO: Should be fixed eventually.
55 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 // Unlike `filter` elements, filter functions use sRGB colors by default.
81 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 // Skip the whole attribute list on error.
93 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 a `filter` attribute had urls pointing to a missing elements
155 // and there are no valid filters at all - this is an error.
156 //
157 // Note that an invalid url is not an error in general.
158 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 // Check if this element was already converted.
175 //
176 // Only `userSpaceOnUse` clipPaths can be shared,
177 // because `objectBoundingBox` one will be converted into user one
178 // and will become node-specific.
179 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 // Generate ID only when we're parsing `objectBoundingBox` filter for the second time.
253 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: SvgNode<'_, '_> 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 // No need to warn. Already checked.
313 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 kind =
326 match tag_name {
327 EId::FeDropShadow => convert_drop_shadow(child, scale, &primitives),
328 EId::FeGaussianBlur => convert_gaussian_blur(child, scale, &primitives),
329 EId::FeOffset => convert_offset(child, scale, &primitives),
330 EId::FeBlend => convert_blend(child, &primitives),
331 EId::FeFlood => convert_flood(child),
332 EId::FeComposite => convert_composite(child, &primitives),
333 EId::FeMerge => convert_merge(child, &primitives),
334 EId::FeTile => convert_tile(child, &primitives),
335 EId::FeImage => convert_image(child, state, cache),
336 EId::FeComponentTransfer => convert_component_transfer(child, &primitives),
337 EId::FeColorMatrix => convert_color_matrix(child, &primitives),
338 EId::FeConvolveMatrix => convert_convolve_matrix(child, &primitives)
339 .unwrap_or_else(create_dummy_primitive),
340 EId::FeMorphology => convert_morphology(child, scale, &primitives),
341 EId::FeDisplacementMap => convert_displacement_map(child, scale, &primitives),
342 EId::FeTurbulence => convert_turbulence(child),
343 EId::FeDiffuseLighting => convert_diffuse_lighting(child, &primitives)
344 .unwrap_or_else(create_dummy_primitive),
345 EId::FeSpecularLighting => convert_specular_lighting(child, &primitives)
346 .unwrap_or_else(create_dummy_primitive),
347 tag_name => {
348 log::warn!("'{}' is not a valid filter primitive. Skipped.", tag_name);
349 continue;
350 }
351 };
352
353 if let Some(fe) = convert_primitive(
354 child,
355 kind,
356 units,
357 state,
358 object_bbox,
359 filter_region,
360 &mut results,
361 ) {
362 primitives.push(fe);
363 }
364 }
365
366 // TODO: remove primitives which results are not used
367
368 primitives
369}
370
371fn convert_primitive(
372 fe: SvgNode,
373 kind: Kind,
374 units: Units,
375 state: &converter::State,
376 bbox: Option<NonZeroRect>,
377 filter_region: NonZeroRect,
378 results: &mut FilterResults,
379) -> Option<Primitive> {
380 let rect: NonZeroRect = resolve_primitive_region(fe, &kind, units, state, bbox, filter_region)?;
381
382 let color_interpolation: ColorInterpolation = feOption
383 .find_attribute(AId::ColorInterpolationFilters)
384 .unwrap_or_default();
385
386 Some(Primitive {
387 rect,
388 color_interpolation,
389 result: gen_result(node:fe, results),
390 kind,
391 })
392}
393
394// TODO: rewrite/simplify/explain/whatever
395fn resolve_primitive_region(
396 fe: SvgNode,
397 kind: &Kind,
398 units: Units,
399 state: &converter::State,
400 bbox: Option<NonZeroRect>,
401 filter_region: NonZeroRect,
402) -> Option<NonZeroRect> {
403 let x = fe.try_convert_length(AId::X, units, state);
404 let y = fe.try_convert_length(AId::Y, units, state);
405 let width = fe.try_convert_length(AId::Width, units, state);
406 let height = fe.try_convert_length(AId::Height, units, state);
407
408 let region = match kind {
409 Kind::Flood(..) | Kind::Image(..) => {
410 // `feImage` uses the object bbox.
411 if units == Units::ObjectBoundingBox {
412 let bbox = bbox?;
413
414 // TODO: wrong
415 // let ts_bbox = tiny_skia::Rect::new(ts.e, ts.f, ts.a, ts.d).unwrap();
416
417 let r = NonZeroRect::from_xywh(
418 x.unwrap_or(0.0),
419 y.unwrap_or(0.0),
420 width.unwrap_or(1.0),
421 height.unwrap_or(1.0),
422 )?;
423
424 return Some(r.bbox_transform(bbox));
425 } else {
426 filter_region
427 }
428 }
429 _ => filter_region,
430 };
431
432 // TODO: Wrong! Does not account rotate and skew.
433 if units == Units::ObjectBoundingBox {
434 let subregion_bbox = NonZeroRect::from_xywh(
435 x.unwrap_or(0.0),
436 y.unwrap_or(0.0),
437 width.unwrap_or(1.0),
438 height.unwrap_or(1.0),
439 )?;
440
441 Some(region.bbox_transform(subregion_bbox))
442 } else {
443 NonZeroRect::from_xywh(
444 x.unwrap_or(region.x()),
445 y.unwrap_or(region.y()),
446 width.unwrap_or(region.width()),
447 height.unwrap_or(region.height()),
448 )
449 }
450}
451
452// A malformed filter primitive usually should produce a transparent image.
453// But since `FilterKind` structs are designed to always be valid,
454// we are using `FeFlood` as fallback.
455#[inline(never)]
456pub(crate) fn create_dummy_primitive() -> Kind {
457 Kind::Flood(Flood {
458 color: Color::black(),
459 opacity: Opacity::ZERO,
460 })
461}
462
463#[inline(never)]
464fn resolve_input(node: SvgNode, aid: AId, primitives: &[Primitive]) -> Input {
465 match node.attribute(aid) {
466 Some(s) => {
467 let input = parse_in(s);
468
469 // If `in` references an unknown `result` than fallback
470 // to previous result or `SourceGraphic`.
471 if let Input::Reference(ref name) = input {
472 if !primitives.iter().any(|p| p.result == *name) {
473 return if let Some(prev) = primitives.last() {
474 Input::Reference(prev.result.clone())
475 } else {
476 Input::SourceGraphic
477 };
478 }
479 }
480
481 input
482 }
483 None => {
484 if let Some(prev) = primitives.last() {
485 // If `in` is not set and this is not the first primitive
486 // than the input is a result of the previous primitive.
487 Input::Reference(prev.result.clone())
488 } else {
489 // If `in` is not set and this is the first primitive
490 // than the input is `SourceGraphic`.
491 Input::SourceGraphic
492 }
493 }
494 }
495}
496
497fn parse_in(s: &str) -> Input {
498 match s {
499 "SourceGraphic" => Input::SourceGraphic,
500 "SourceAlpha" => Input::SourceAlpha,
501 "BackgroundImage" | "BackgroundAlpha" | "FillPaint" | "StrokePaint" => {
502 log::warn!("{} filter input isn't supported and not planed.", s);
503 Input::SourceGraphic
504 }
505 _ => Input::Reference(s.to_string()),
506 }
507}
508
509fn gen_result(node: SvgNode, results: &mut FilterResults) -> String {
510 match node.attribute::<&str>(AId::Result) {
511 Some(s: &str) => {
512 // Remember predefined result.
513 results.names.insert(s.to_string());
514 results.idx += 1;
515
516 s.to_string()
517 }
518 None => {
519 // Generate an unique name for `result`.
520 loop {
521 let name: String = format!("result{}", results.idx);
522 results.idx += 1;
523
524 if !results.names.contains(&name) {
525 return name;
526 }
527 }
528 }
529 }
530}
531
532fn convert_blend(fe: SvgNode, primitives: &[Primitive]) -> Kind {
533 let mode: BlendMode = fe.attribute(AId::Mode).unwrap_or_default();
534 let input1: Input = resolve_input(node:fe, AId::In, primitives);
535 let input2: Input = resolve_input(node:fe, AId::In2, primitives);
536 Kind::Blend(Blend {
537 mode,
538 input1,
539 input2,
540 })
541}
542
543fn convert_color_matrix(fe: SvgNode, primitives: &[Primitive]) -> Kind {
544 let kind: ColorMatrixKind = convert_color_matrix_kind(fe).unwrap_or_default();
545 Kind::ColorMatrix(ColorMatrix {
546 input: resolve_input(node:fe, AId::In, primitives),
547 kind,
548 })
549}
550
551fn convert_color_matrix_kind(fe: SvgNode) -> Option<ColorMatrixKind> {
552 match fe.attribute(AId::Type) {
553 Some("saturate") => {
554 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
555 if !list.is_empty() {
556 let n = crate::f32_bound(0.0, list[0], 1.0);
557 return Some(ColorMatrixKind::Saturate(PositiveF32::new(n).unwrap()));
558 } else {
559 return Some(ColorMatrixKind::Saturate(PositiveF32::new(1.0).unwrap()));
560 }
561 }
562 }
563 Some("hueRotate") => {
564 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
565 if !list.is_empty() {
566 return Some(ColorMatrixKind::HueRotate(list[0]));
567 } else {
568 return Some(ColorMatrixKind::HueRotate(0.0));
569 }
570 }
571 }
572 Some("luminanceToAlpha") => {
573 return Some(ColorMatrixKind::LuminanceToAlpha);
574 }
575 _ => {
576 // Fallback to `matrix`.
577 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Values) {
578 if list.len() == 20 {
579 return Some(ColorMatrixKind::Matrix(list));
580 }
581 }
582 }
583 }
584
585 None
586}
587
588fn convert_component_transfer(fe: SvgNode, primitives: &[Primitive]) -> Kind {
589 let mut kind: ComponentTransfer = ComponentTransfer {
590 input: resolve_input(node:fe, AId::In, primitives),
591 func_r: TransferFunction::Identity,
592 func_g: TransferFunction::Identity,
593 func_b: TransferFunction::Identity,
594 func_a: TransferFunction::Identity,
595 };
596
597 for child: SvgNode<'_, '_> in fe.children().filter(|n: &SvgNode<'_, '_>| n.is_element()) {
598 if let Some(func: TransferFunction) = convert_transfer_function(node:child) {
599 match child.tag_name().unwrap() {
600 EId::FeFuncR => kind.func_r = func,
601 EId::FeFuncG => kind.func_g = func,
602 EId::FeFuncB => kind.func_b = func,
603 EId::FeFuncA => kind.func_a = func,
604 _ => {}
605 }
606 }
607 }
608
609 Kind::ComponentTransfer(kind)
610}
611
612fn convert_transfer_function(node: SvgNode) -> Option<TransferFunction> {
613 match node.attribute(AId::Type)? {
614 "identity" => Some(TransferFunction::Identity),
615 "table" => match node.attribute::<Vec<f32>>(AId::TableValues) {
616 Some(values: Vec) => Some(TransferFunction::Table(values)),
617 None => Some(TransferFunction::Table(Vec::new())),
618 },
619 "discrete" => match node.attribute::<Vec<f32>>(AId::TableValues) {
620 Some(values: Vec) => Some(TransferFunction::Discrete(values)),
621 None => Some(TransferFunction::Discrete(Vec::new())),
622 },
623 "linear" => Some(TransferFunction::Linear {
624 slope: node.attribute(AId::Slope).unwrap_or(default:1.0),
625 intercept: node.attribute(AId::Intercept).unwrap_or(default:0.0),
626 }),
627 "gamma" => Some(TransferFunction::Gamma {
628 amplitude: node.attribute(AId::Amplitude).unwrap_or(default:1.0),
629 exponent: node.attribute(AId::Exponent).unwrap_or(default:1.0),
630 offset: node.attribute(AId::Offset).unwrap_or(default:0.0),
631 }),
632 _ => None,
633 }
634}
635
636fn convert_composite(fe: SvgNode, primitives: &[Primitive]) -> Kind {
637 let operator: CompositeOperator = match fe.attribute(AId::Operator).unwrap_or(default:"over") {
638 "in" => CompositeOperator::In,
639 "out" => CompositeOperator::Out,
640 "atop" => CompositeOperator::Atop,
641 "xor" => CompositeOperator::Xor,
642 "arithmetic" => CompositeOperator::Arithmetic {
643 k1: fe.attribute(AId::K1).unwrap_or(default:0.0),
644 k2: fe.attribute(AId::K2).unwrap_or(default:0.0),
645 k3: fe.attribute(AId::K3).unwrap_or(default:0.0),
646 k4: fe.attribute(AId::K4).unwrap_or(default:0.0),
647 },
648 _ => CompositeOperator::Over,
649 };
650
651 let input1: Input = resolve_input(node:fe, AId::In, primitives);
652 let input2: Input = resolve_input(node:fe, AId::In2, primitives);
653
654 Kind::Composite(Composite {
655 operator,
656 input1,
657 input2,
658 })
659}
660
661fn convert_convolve_matrix(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
662 fn parse_target(target: Option<f32>, order: u32) -> Option<u32> {
663 let default_target = (order as f32 / 2.0).floor() as u32;
664 let target = target.unwrap_or(default_target as f32) as i32;
665 if target < 0 || target >= order as i32 {
666 None
667 } else {
668 Some(target as u32)
669 }
670 }
671
672 let mut order_x = 3;
673 let mut order_y = 3;
674 if let Some(value) = fe.attribute::<&str>(AId::Order) {
675 let mut s = svgtypes::NumberListParser::from(value);
676 let x = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(3);
677 let y = s.next().and_then(|a| a.ok()).map(|n| n as i32).unwrap_or(x);
678 if x > 0 && y > 0 {
679 order_x = x as u32;
680 order_y = y as u32;
681 }
682 }
683
684 let mut matrix = Vec::new();
685 if let Some(list) = fe.attribute::<Vec<f32>>(AId::KernelMatrix) {
686 if list.len() == (order_x * order_y) as usize {
687 matrix = list;
688 }
689 }
690
691 let mut kernel_sum: f32 = matrix.iter().sum();
692 // Round up to prevent float precision issues.
693 kernel_sum = (kernel_sum * 1_000_000.0).round() / 1_000_000.0;
694 if kernel_sum.approx_zero_ulps(4) {
695 kernel_sum = 1.0;
696 }
697
698 let divisor = fe.attribute(AId::Divisor).unwrap_or(kernel_sum);
699 if divisor.approx_zero_ulps(4) {
700 return None;
701 }
702
703 let bias = fe.attribute(AId::Bias).unwrap_or(0.0);
704
705 let target_x = parse_target(fe.attribute(AId::TargetX), order_x)?;
706 let target_y = parse_target(fe.attribute(AId::TargetY), order_y)?;
707
708 let kernel_matrix = ConvolveMatrixData::new(target_x, target_y, order_x, order_y, matrix)?;
709
710 let edge_mode = match fe.attribute(AId::EdgeMode).unwrap_or("duplicate") {
711 "none" => EdgeMode::None,
712 "wrap" => EdgeMode::Wrap,
713 _ => EdgeMode::Duplicate,
714 };
715
716 let preserve_alpha = fe.attribute(AId::PreserveAlpha).unwrap_or("false") == "true";
717
718 Some(Kind::ConvolveMatrix(ConvolveMatrix {
719 input: resolve_input(fe, AId::In, primitives),
720 matrix: kernel_matrix,
721 divisor: NonZeroF32::new(divisor).unwrap(),
722 bias,
723 edge_mode,
724 preserve_alpha,
725 }))
726}
727
728fn convert_displacement_map(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
729 let parse_channel: impl Fn(AId) -> ColorChannel = |aid: AId| match fe.attribute(aid).unwrap_or(default:"A") {
730 "R" => ColorChannel::R,
731 "G" => ColorChannel::G,
732 "B" => ColorChannel::B,
733 _ => ColorChannel::A,
734 };
735
736 // TODO: should probably split scale to scale_x and scale_y,
737 // but resvg doesn't support displacement map anyway...
738 let scale: f32 = (scale.width() + scale.height()) / 2.0;
739
740 Kind::DisplacementMap(DisplacementMap {
741 input1: resolve_input(node:fe, AId::In, primitives),
742 input2: resolve_input(node:fe, AId::In2, primitives),
743 scale: fe.attribute(AId::Scale).unwrap_or(default:0.0) * scale,
744 x_channel_selector: parse_channel(AId::XChannelSelector),
745 y_channel_selector: parse_channel(AId::YChannelSelector),
746 })
747}
748
749fn convert_drop_shadow(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
750 let (std_dev_x: PositiveF32, std_dev_y: PositiveF32) = convert_std_dev_attr(fe, scale, default:"2 2");
751
752 let (color: Color, opacity: NormalizedF32) = feColor
753 .attribute(AId::FloodColor)
754 .unwrap_or_else(svgtypes::Color::black)
755 .split_alpha();
756
757 let flood_opacity: NormalizedF32 = fe
758 .attribute::<Opacity>(AId::FloodOpacity)
759 .unwrap_or(default:Opacity::ONE);
760
761 Kind::DropShadow(DropShadow {
762 input: resolve_input(node:fe, AId::In, primitives),
763 dx: fe.attribute(AId::Dx).unwrap_or(default:2.0) * scale.width(),
764 dy: fe.attribute(AId::Dy).unwrap_or(default:2.0) * scale.height(),
765 std_dev_x,
766 std_dev_y,
767 color,
768 opacity: opacity * flood_opacity,
769 })
770}
771
772fn convert_flood(fe: SvgNode) -> Kind {
773 let (color: Color, opacity: NormalizedF32) = feColor
774 .attribute(AId::FloodColor)
775 .unwrap_or_else(svgtypes::Color::black)
776 .split_alpha();
777
778 let flood_opacity: NormalizedF32 = fe
779 .attribute::<Opacity>(AId::FloodOpacity)
780 .unwrap_or(default:Opacity::ONE);
781
782 Kind::Flood(Flood {
783 color,
784 opacity: opacity * flood_opacity,
785 })
786}
787
788fn convert_gaussian_blur(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
789 let (std_dev_x: PositiveF32, std_dev_y: PositiveF32) = convert_std_dev_attr(fe, scale, default:"0 0");
790 Kind::GaussianBlur(GaussianBlur {
791 input: resolve_input(node:fe, AId::In, primitives),
792 std_dev_x,
793 std_dev_y,
794 })
795}
796
797fn convert_std_dev_attr(fe: SvgNode, scale: Size, default: &str) -> (PositiveF32, PositiveF32) {
798 let text: &str = fe.attribute(AId::StdDeviation).unwrap_or(default);
799 let mut parser: NumberListParser<'_> = svgtypes::NumberListParser::from(text);
800
801 let n1: Option = parser.next().and_then(|n: Result| n.ok());
802 let n2: Option = parser.next().and_then(|n: Result| n.ok());
803 // `stdDeviation` must have no more than two values.
804 // Otherwise we should fallback to `0 0`.
805 let n3: Option = parser.next().and_then(|n: Result| n.ok());
806
807 let (std_dev_x: f64, std_dev_y: f64) = match (n1, n2, n3) {
808 (Some(n1: f64), Some(n2: f64), None) => (n1, n2),
809 (Some(n1: f64), None, None) => (n1, n1),
810 _ => (0.0, 0.0),
811 };
812
813 let std_dev_x: f32 = (std_dev_x as f32) * scale.width();
814 let std_dev_y: f32 = (std_dev_y as f32) * scale.height();
815
816 let std_dev_x: PositiveF32 = PositiveF32::new(std_dev_x as f32).unwrap_or(default:PositiveF32::ZERO);
817 let std_dev_y: PositiveF32 = PositiveF32::new(std_dev_y as f32).unwrap_or(default:PositiveF32::ZERO);
818
819 (std_dev_x, std_dev_y)
820}
821
822fn convert_image(fe: SvgNode, state: &converter::State, cache: &mut converter::Cache) -> Kind {
823 let aspect = fe.attribute(AId::PreserveAspectRatio).unwrap_or_default();
824 let rendering_mode = fe
825 .find_attribute(AId::ImageRendering)
826 .unwrap_or(state.opt.image_rendering);
827
828 if let Some(node) = fe.try_attribute::<SvgNode>(AId::Href) {
829 let mut state = state.clone();
830 state.fe_image_link = true;
831 let mut root = Group::empty();
832 super::converter::convert_element(node, &state, cache, &mut root);
833 return if root.has_children() {
834 root.calculate_bounding_boxes();
835 // Transfer node id from group's child to the group itself if needed.
836 if let Some(Node::Group(ref mut g)) = root.children.first_mut() {
837 if let Some(child2) = g.children.first_mut() {
838 g.id = child2.id().to_string();
839 match child2 {
840 Node::Group(ref mut g2) => g2.id.clear(),
841 Node::Path(ref mut path) => path.id.clear(),
842 Node::Image(ref mut image) => image.id.clear(),
843 Node::Text(ref mut text) => text.id.clear(),
844 }
845 }
846 }
847
848 Kind::Image(Image {
849 aspect,
850 rendering_mode,
851 data: ImageKind::Use(Box::new(root)),
852 })
853 } else {
854 create_dummy_primitive()
855 };
856 }
857
858 let href = match fe.try_attribute(AId::Href) {
859 Some(s) => s,
860 _ => {
861 log::warn!("The 'feImage' element lacks the 'xlink:href' attribute. Skipped.");
862 return create_dummy_primitive();
863 }
864 };
865
866 let href = super::image::get_href_data(href, state);
867 let img_data = match href {
868 Some(data) => data,
869 None => return create_dummy_primitive(),
870 };
871
872 Kind::Image(Image {
873 aspect,
874 rendering_mode,
875 data: ImageKind::Image(img_data),
876 })
877}
878
879fn convert_diffuse_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
880 let light_source: LightSource = convert_light_source(parent:fe)?;
881 Some(Kind::DiffuseLighting(DiffuseLighting {
882 input: resolve_input(node:fe, AId::In, primitives),
883 surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(default:1.0),
884 diffuse_constant: fe.attribute(AId::DiffuseConstant).unwrap_or(default:1.0),
885 lighting_color: convert_lighting_color(node:fe),
886 light_source,
887 }))
888}
889
890fn convert_specular_lighting(fe: SvgNode, primitives: &[Primitive]) -> Option<Kind> {
891 let light_source: LightSource = convert_light_source(parent:fe)?;
892
893 let specular_exponent: f32 = fe.attribute(AId::SpecularExponent).unwrap_or(default:1.0);
894 if !(1.0..=128.0).contains(&specular_exponent) {
895 // When exponent is out of range, the whole filter primitive should be ignored.
896 return None;
897 }
898
899 let specular_exponent: f32 = crate::f32_bound(min:1.0, val:specular_exponent, max:128.0);
900
901 Some(Kind::SpecularLighting(SpecularLighting {
902 input: resolve_input(node:fe, AId::In, primitives),
903 surface_scale: fe.attribute(AId::SurfaceScale).unwrap_or(default:1.0),
904 specular_constant: fe.attribute(AId::SpecularConstant).unwrap_or(default:1.0),
905 specular_exponent,
906 lighting_color: convert_lighting_color(node:fe),
907 light_source,
908 }))
909}
910
911#[inline(never)]
912fn convert_lighting_color(node: SvgNode) -> Color {
913 // Color's alpha doesn't affect lighting-color. Simply skip it.
914 match node.attribute(AId::LightingColor) {
915 Some("currentColor") => {
916 node(Color, NormalizedF32).find_attribute(AId::Color)
917 // Yes, a missing `currentColor` resolves to black and not white.
918 .unwrap_or(default:svgtypes::Color::black())
919 .split_alpha()
920 .0
921 }
922 Some(value: &str) => {
923 if let Ok(c: Color) = svgtypes::Color::from_str(value) {
924 c.split_alpha().0
925 } else {
926 log::warn!("Failed to parse lighting-color value: '{}'.", value);
927 Color::white()
928 }
929 }
930 _ => Color::white(),
931 }
932}
933
934#[inline(never)]
935fn convert_light_source(parent: SvgNode) -> Option<LightSource> {
936 let child = parent.children().find(|n| {
937 matches!(
938 n.tag_name(),
939 Some(EId::FeDistantLight) | Some(EId::FePointLight) | Some(EId::FeSpotLight)
940 )
941 })?;
942
943 match child.tag_name() {
944 Some(EId::FeDistantLight) => Some(LightSource::DistantLight(DistantLight {
945 azimuth: child.attribute(AId::Azimuth).unwrap_or(0.0),
946 elevation: child.attribute(AId::Elevation).unwrap_or(0.0),
947 })),
948 Some(EId::FePointLight) => Some(LightSource::PointLight(PointLight {
949 x: child.attribute(AId::X).unwrap_or(0.0),
950 y: child.attribute(AId::Y).unwrap_or(0.0),
951 z: child.attribute(AId::Z).unwrap_or(0.0),
952 })),
953 Some(EId::FeSpotLight) => {
954 let specular_exponent = child.attribute(AId::SpecularExponent).unwrap_or(1.0);
955 let specular_exponent = PositiveF32::new(specular_exponent)
956 .unwrap_or_else(|| PositiveF32::new(1.0).unwrap());
957
958 Some(LightSource::SpotLight(SpotLight {
959 x: child.attribute(AId::X).unwrap_or(0.0),
960 y: child.attribute(AId::Y).unwrap_or(0.0),
961 z: child.attribute(AId::Z).unwrap_or(0.0),
962 points_at_x: child.attribute(AId::PointsAtX).unwrap_or(0.0),
963 points_at_y: child.attribute(AId::PointsAtY).unwrap_or(0.0),
964 points_at_z: child.attribute(AId::PointsAtZ).unwrap_or(0.0),
965 specular_exponent,
966 limiting_cone_angle: child.attribute(AId::LimitingConeAngle),
967 }))
968 }
969 _ => None,
970 }
971}
972
973fn convert_merge(fe: SvgNode, primitives: &[Primitive]) -> Kind {
974 let mut inputs: Vec = Vec::new();
975 for child: SvgNode<'_, '_> in fe.children() {
976 inputs.push(resolve_input(node:child, AId::In, primitives));
977 }
978
979 Kind::Merge(Merge { inputs })
980}
981
982fn convert_morphology(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
983 let operator = match fe.attribute(AId::Operator).unwrap_or("erode") {
984 "dilate" => MorphologyOperator::Dilate,
985 _ => MorphologyOperator::Erode,
986 };
987
988 let mut radius_x = PositiveF32::new(scale.width()).unwrap();
989 let mut radius_y = PositiveF32::new(scale.height()).unwrap();
990 if let Some(list) = fe.attribute::<Vec<f32>>(AId::Radius) {
991 let mut rx = 0.0;
992 let mut ry = 0.0;
993 if list.len() == 2 {
994 rx = list[0];
995 ry = list[1];
996 } else if list.len() == 1 {
997 rx = list[0];
998 ry = list[0]; // The same as `rx`.
999 }
1000
1001 if rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1002 rx = 1.0;
1003 ry = 1.0;
1004 }
1005
1006 // If only one of the values is zero, reset it to 1.0
1007 // This is not specified in the spec, but this is how Chrome and Safari work.
1008 if rx.approx_zero_ulps(4) && !ry.approx_zero_ulps(4) {
1009 rx = 1.0;
1010 }
1011 if !rx.approx_zero_ulps(4) && ry.approx_zero_ulps(4) {
1012 ry = 1.0;
1013 }
1014
1015 // Both values must be positive.
1016 if rx.is_sign_positive() && ry.is_sign_positive() {
1017 radius_x = PositiveF32::new(rx * scale.width()).unwrap();
1018 radius_y = PositiveF32::new(ry * scale.height()).unwrap();
1019 }
1020 }
1021
1022 Kind::Morphology(Morphology {
1023 input: resolve_input(fe, AId::In, primitives),
1024 operator,
1025 radius_x,
1026 radius_y,
1027 })
1028}
1029
1030fn convert_offset(fe: SvgNode, scale: Size, primitives: &[Primitive]) -> Kind {
1031 Kind::Offset(Offset {
1032 input: resolve_input(node:fe, AId::In, primitives),
1033 dx: fe.attribute(AId::Dx).unwrap_or(default:0.0) * scale.width(),
1034 dy: fe.attribute(AId::Dy).unwrap_or(default:0.0) * scale.height(),
1035 })
1036}
1037
1038fn convert_tile(fe: SvgNode, primitives: &[Primitive]) -> Kind {
1039 Kind::Tile(Tile {
1040 input: resolve_input(node:fe, AId::In, primitives),
1041 })
1042}
1043
1044fn convert_turbulence(fe: SvgNode) -> Kind {
1045 let mut base_frequency_x = PositiveF32::ZERO;
1046 let mut base_frequency_y = PositiveF32::ZERO;
1047 if let Some(list) = fe.attribute::<Vec<f32>>(AId::BaseFrequency) {
1048 let mut x = 0.0;
1049 let mut y = 0.0;
1050 if list.len() == 2 {
1051 x = list[0];
1052 y = list[1];
1053 } else if list.len() == 1 {
1054 x = list[0];
1055 y = list[0]; // The same as `x`.
1056 }
1057
1058 if x.is_sign_positive() && y.is_sign_positive() {
1059 base_frequency_x = PositiveF32::new(x).unwrap();
1060 base_frequency_y = PositiveF32::new(y).unwrap();
1061 }
1062 }
1063
1064 let mut num_octaves = fe.attribute(AId::NumOctaves).unwrap_or(1.0);
1065 if num_octaves.is_sign_negative() {
1066 num_octaves = 0.0;
1067 }
1068
1069 let kind = match fe.attribute(AId::Type).unwrap_or("turbulence") {
1070 "fractalNoise" => TurbulenceKind::FractalNoise,
1071 _ => TurbulenceKind::Turbulence,
1072 };
1073
1074 Kind::Turbulence(Turbulence {
1075 base_frequency_x,
1076 base_frequency_y,
1077 num_octaves: num_octaves.round() as u32,
1078 seed: fe.attribute::<f32>(AId::Seed).unwrap_or(0.0).trunc() as i32,
1079 stitch_tiles: fe.attribute(AId::StitchTiles) == Some("stitch"),
1080 kind,
1081 })
1082}
1083
1084#[inline(never)]
1085fn convert_grayscale_function(amount: f64) -> Kind {
1086 let amount = amount.min(1.0) as f32;
1087 Kind::ColorMatrix(ColorMatrix {
1088 input: Input::SourceGraphic,
1089 kind: ColorMatrixKind::Matrix(vec![
1090 (0.2126 + 0.7874 * (1.0 - amount)),
1091 (0.7152 - 0.7152 * (1.0 - amount)),
1092 (0.0722 - 0.0722 * (1.0 - amount)),
1093 0.0,
1094 0.0,
1095 (0.2126 - 0.2126 * (1.0 - amount)),
1096 (0.7152 + 0.2848 * (1.0 - amount)),
1097 (0.0722 - 0.0722 * (1.0 - amount)),
1098 0.0,
1099 0.0,
1100 (0.2126 - 0.2126 * (1.0 - amount)),
1101 (0.7152 - 0.7152 * (1.0 - amount)),
1102 (0.0722 + 0.9278 * (1.0 - amount)),
1103 0.0,
1104 0.0,
1105 0.0,
1106 0.0,
1107 0.0,
1108 1.0,
1109 0.0,
1110 ]),
1111 })
1112}
1113
1114#[inline(never)]
1115fn convert_sepia_function(amount: f64) -> Kind {
1116 let amount = amount.min(1.0) as f32;
1117 Kind::ColorMatrix(ColorMatrix {
1118 input: Input::SourceGraphic,
1119 kind: ColorMatrixKind::Matrix(vec![
1120 (0.393 + 0.607 * (1.0 - amount)),
1121 (0.769 - 0.769 * (1.0 - amount)),
1122 (0.189 - 0.189 * (1.0 - amount)),
1123 0.0,
1124 0.0,
1125 (0.349 - 0.349 * (1.0 - amount)),
1126 (0.686 + 0.314 * (1.0 - amount)),
1127 (0.168 - 0.168 * (1.0 - amount)),
1128 0.0,
1129 0.0,
1130 (0.272 - 0.272 * (1.0 - amount)),
1131 (0.534 - 0.534 * (1.0 - amount)),
1132 (0.131 + 0.869 * (1.0 - amount)),
1133 0.0,
1134 0.0,
1135 0.0,
1136 0.0,
1137 0.0,
1138 1.0,
1139 0.0,
1140 ]),
1141 })
1142}
1143
1144#[inline(never)]
1145fn convert_saturate_function(amount: f64) -> Kind {
1146 let amount: PositiveF32 = PositiveF32::new(amount as f32).unwrap_or(default:PositiveF32::ZERO);
1147 Kind::ColorMatrix(ColorMatrix {
1148 input: Input::SourceGraphic,
1149 kind: ColorMatrixKind::Saturate(amount),
1150 })
1151}
1152
1153#[inline(never)]
1154fn convert_hue_rotate_function(amount: svgtypes::Angle) -> Kind {
1155 Kind::ColorMatrix(ColorMatrix {
1156 input: Input::SourceGraphic,
1157 kind: ColorMatrixKind::HueRotate(amount.to_degrees() as f32),
1158 })
1159}
1160
1161#[inline(never)]
1162fn convert_invert_function(amount: f64) -> Kind {
1163 let amount: f32 = amount.min(1.0) as f32;
1164 Kind::ComponentTransfer(ComponentTransfer {
1165 input: Input::SourceGraphic,
1166 func_r: TransferFunction::Table(vec![amount, 1.0 - amount]),
1167 func_g: TransferFunction::Table(vec![amount, 1.0 - amount]),
1168 func_b: TransferFunction::Table(vec![amount, 1.0 - amount]),
1169 func_a: TransferFunction::Identity,
1170 })
1171}
1172
1173#[inline(never)]
1174fn convert_opacity_function(amount: f64) -> Kind {
1175 let amount: f32 = amount.min(1.0) as f32;
1176 Kind::ComponentTransfer(ComponentTransfer {
1177 input: Input::SourceGraphic,
1178 func_r: TransferFunction::Identity,
1179 func_g: TransferFunction::Identity,
1180 func_b: TransferFunction::Identity,
1181 func_a: TransferFunction::Table(vec![0.0, amount]),
1182 })
1183}
1184
1185#[inline(never)]
1186fn convert_brightness_function(amount: f64) -> Kind {
1187 let amount: f32 = amount as f32;
1188 Kind::ComponentTransfer(ComponentTransfer {
1189 input: Input::SourceGraphic,
1190 func_r: TransferFunction::Linear {
1191 slope: amount,
1192 intercept: 0.0,
1193 },
1194 func_g: TransferFunction::Linear {
1195 slope: amount,
1196 intercept: 0.0,
1197 },
1198 func_b: TransferFunction::Linear {
1199 slope: amount,
1200 intercept: 0.0,
1201 },
1202 func_a: TransferFunction::Identity,
1203 })
1204}
1205
1206#[inline(never)]
1207fn convert_contrast_function(amount: f64) -> Kind {
1208 let amount: f32 = amount as f32;
1209 Kind::ComponentTransfer(ComponentTransfer {
1210 input: Input::SourceGraphic,
1211 func_r: TransferFunction::Linear {
1212 slope: amount,
1213 intercept: -(0.5 * amount) + 0.5,
1214 },
1215 func_g: TransferFunction::Linear {
1216 slope: amount,
1217 intercept: -(0.5 * amount) + 0.5,
1218 },
1219 func_b: TransferFunction::Linear {
1220 slope: amount,
1221 intercept: -(0.5 * amount) + 0.5,
1222 },
1223 func_a: TransferFunction::Identity,
1224 })
1225}
1226
1227#[inline(never)]
1228fn convert_blur_function(node: SvgNode, std_dev: Length, state: &converter::State) -> Kind {
1229 let std_dev: PositiveF32 = PositiveF32::new(super::units::convert_user_length(
1230 std_dev,
1231 node,
1232 AId::Dx,
1233 state,
1234 ))
1235 .unwrap_or(default:PositiveF32::ZERO);
1236 Kind::GaussianBlur(GaussianBlur {
1237 input: Input::SourceGraphic,
1238 std_dev_x: std_dev,
1239 std_dev_y: std_dev,
1240 })
1241}
1242
1243#[inline(never)]
1244fn convert_drop_shadow_function(
1245 node: SvgNode,
1246 color: Option<svgtypes::Color>,
1247 dx: Length,
1248 dy: Length,
1249 std_dev: Length,
1250 state: &converter::State,
1251) -> Kind {
1252 let std_dev = PositiveF32::new(super::units::convert_user_length(
1253 std_dev,
1254 node,
1255 AId::Dx,
1256 state,
1257 ))
1258 .unwrap_or(PositiveF32::ZERO);
1259
1260 let (color, opacity) = color
1261 .unwrap_or_else(|| {
1262 node.find_attribute(AId::Color)
1263 .unwrap_or_else(svgtypes::Color::black)
1264 })
1265 .split_alpha();
1266
1267 Kind::DropShadow(DropShadow {
1268 input: Input::SourceGraphic,
1269 dx: super::units::convert_user_length(dx, node, AId::Dx, state),
1270 dy: super::units::convert_user_length(dy, node, AId::Dy, state),
1271 std_dev_x: std_dev,
1272 std_dev_y: std_dev,
1273 color,
1274 opacity,
1275 })
1276}
1277