Skip to main content

usvg/
writer.rs

1// Copyright 2023 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::fmt::Display;
5use std::io::Write;
6
7use svgtypes::{FontFamily, parse_font_families};
8use xmlwriter::XmlWriter;
9
10use crate::parser::{AId, EId};
11use crate::*;
12
13impl Tree {
14    /// Writes `usvg::Tree` back to SVG.
15    pub fn to_string(&self, opt: &WriteOptions) -> String {
16        convert(self, opt)
17    }
18}
19
20/// Checks that type has a default value.
21trait IsDefault: Default {
22    /// Checks that type has a default value.
23    fn is_default(&self) -> bool;
24}
25
26impl<T: Default + PartialEq + Copy> IsDefault for T {
27    #[inline]
28    fn is_default(&self) -> bool {
29        *self == Self::default()
30    }
31}
32
33/// XML writing options.
34#[derive(Clone, Debug)]
35pub struct WriteOptions {
36    /// Used to add a custom prefix to each element ID during writing.
37    pub id_prefix: Option<String>,
38
39    /// Do not convert text into paths.
40    ///
41    /// Default: false
42    pub preserve_text: bool,
43
44    /// Set the coordinates numeric precision.
45    ///
46    /// Smaller precision can lead to a malformed output in some cases.
47    ///
48    /// Default: 8
49    pub coordinates_precision: u8,
50
51    /// Set the transform values numeric precision.
52    ///
53    /// Smaller precision can lead to a malformed output in some cases.
54    ///
55    /// Default: 8
56    pub transforms_precision: u8,
57
58    /// Use single quote marks instead of double quote.
59    ///
60    /// # Examples
61    ///
62    /// Before:
63    ///
64    /// ```text
65    /// <rect fill="red"/>
66    /// ```
67    ///
68    /// After:
69    ///
70    /// ```text
71    /// <rect fill='red'/>
72    /// ```
73    ///
74    /// Default: disabled
75    pub use_single_quote: bool,
76
77    /// Set XML nodes indention.
78    ///
79    /// # Examples
80    ///
81    /// `Indent::None`
82    /// Before:
83    ///
84    /// ```text
85    /// <svg>
86    ///     <rect fill="red"/>
87    /// </svg>
88    /// ```
89    ///
90    /// After:
91    ///
92    /// ```text
93    /// <svg><rect fill="red"/></svg>
94    /// ```
95    ///
96    /// Default: 4 spaces
97    pub indent: Indent,
98
99    /// Set XML attributes indention.
100    ///
101    /// # Examples
102    ///
103    /// `Indent::Spaces(2)`
104    ///
105    /// Before:
106    ///
107    /// ```text
108    /// <svg>
109    ///     <rect fill="red" stroke="black"/>
110    /// </svg>
111    /// ```
112    ///
113    /// After:
114    ///
115    /// ```text
116    /// <svg>
117    ///     <rect
118    ///       fill="red"
119    ///       stroke="black"/>
120    /// </svg>
121    /// ```
122    ///
123    /// Default: `None`
124    pub attributes_indent: Indent,
125}
126
127impl Default for WriteOptions {
128    fn default() -> Self {
129        Self {
130            id_prefix: Default::default(),
131            preserve_text: false,
132            coordinates_precision: 8,
133            transforms_precision: 8,
134            use_single_quote: false,
135            indent: Indent::Spaces(4),
136            attributes_indent: Indent::None,
137        }
138    }
139}
140
141pub(crate) fn convert(tree: &Tree, opt: &WriteOptions) -> String {
142    let mut xml = XmlWriter::new(xmlwriter::Options {
143        use_single_quote: opt.use_single_quote,
144        indent: opt.indent,
145        attributes_indent: opt.attributes_indent,
146    });
147
148    xml.start_svg_element(EId::Svg);
149    xml.write_svg_attribute(AId::Width, &tree.size.width());
150    xml.write_svg_attribute(AId::Height, &tree.size.height());
151    xml.write_attribute("xmlns", "http://www.w3.org/2000/svg");
152    if has_xlink(&tree.root) {
153        xml.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
154    }
155
156    let has_text_paths = has_text_paths(&tree.root);
157    if tree.has_defs_nodes() || has_text_paths {
158        write_defs(tree, opt, &mut xml, has_text_paths);
159    }
160
161    write_elements(&tree.root, false, opt, &mut xml);
162
163    xml.end_document()
164}
165
166fn write_filters(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter) {
167    let mut written_fe_image_nodes: Vec<String> = Vec::new();
168    for filter in tree.filters() {
169        for fe in &filter.primitives {
170            if let filter::Kind::Image(ref img) = fe.kind {
171                if let Some(child) = img.root().children.first() {
172                    if !written_fe_image_nodes.iter().any(|id| id == child.id()) {
173                        write_element(child, false, opt, xml);
174                        written_fe_image_nodes.push(child.id().to_string());
175                    }
176                }
177            }
178        }
179
180        xml.start_svg_element(EId::Filter);
181        xml.write_id_attribute(filter.id(), opt);
182        xml.write_rect_attrs(filter.rect);
183        xml.write_units(
184            AId::FilterUnits,
185            Units::UserSpaceOnUse,
186            Units::ObjectBoundingBox,
187        );
188
189        for fe in &filter.primitives {
190            match fe.kind {
191                filter::Kind::DropShadow(ref shadow) => {
192                    xml.start_svg_element(EId::FeDropShadow);
193                    xml.write_filter_primitive_attrs(filter.rect(), fe);
194                    xml.write_filter_input(AId::In, &shadow.input);
195                    xml.write_attribute_fmt(
196                        AId::StdDeviation.to_str(),
197                        format_args!("{} {}", shadow.std_dev_x.get(), shadow.std_dev_y.get()),
198                    );
199                    xml.write_svg_attribute(AId::Dx, &shadow.dx);
200                    xml.write_svg_attribute(AId::Dy, &shadow.dy);
201                    xml.write_color(AId::FloodColor, shadow.color);
202                    xml.write_svg_attribute(AId::FloodOpacity, &shadow.opacity.get());
203                    xml.write_svg_attribute(AId::Result, &fe.result);
204                    xml.end_element();
205                }
206                filter::Kind::GaussianBlur(ref blur) => {
207                    xml.start_svg_element(EId::FeGaussianBlur);
208                    xml.write_filter_primitive_attrs(filter.rect(), fe);
209                    xml.write_filter_input(AId::In, &blur.input);
210                    xml.write_attribute_fmt(
211                        AId::StdDeviation.to_str(),
212                        format_args!("{} {}", blur.std_dev_x.get(), blur.std_dev_y.get()),
213                    );
214                    xml.write_svg_attribute(AId::Result, &fe.result);
215                    xml.end_element();
216                }
217                filter::Kind::Offset(ref offset) => {
218                    xml.start_svg_element(EId::FeOffset);
219                    xml.write_filter_primitive_attrs(filter.rect(), fe);
220                    xml.write_filter_input(AId::In, &offset.input);
221                    xml.write_svg_attribute(AId::Dx, &offset.dx);
222                    xml.write_svg_attribute(AId::Dy, &offset.dy);
223                    xml.write_svg_attribute(AId::Result, &fe.result);
224                    xml.end_element();
225                }
226                filter::Kind::Blend(ref blend) => {
227                    xml.start_svg_element(EId::FeBlend);
228                    xml.write_filter_primitive_attrs(filter.rect(), fe);
229                    xml.write_filter_input(AId::In, &blend.input1);
230                    xml.write_filter_input(AId::In2, &blend.input2);
231                    xml.write_svg_attribute(AId::Mode, &blend.mode.to_string());
232                    xml.write_svg_attribute(AId::Result, &fe.result);
233                    xml.end_element();
234                }
235                filter::Kind::Flood(ref flood) => {
236                    xml.start_svg_element(EId::FeFlood);
237                    xml.write_filter_primitive_attrs(filter.rect(), fe);
238                    xml.write_color(AId::FloodColor, flood.color);
239                    xml.write_svg_attribute(AId::FloodOpacity, &flood.opacity.get());
240                    xml.write_svg_attribute(AId::Result, &fe.result);
241                    xml.end_element();
242                }
243                filter::Kind::Composite(ref composite) => {
244                    xml.start_svg_element(EId::FeComposite);
245                    xml.write_filter_primitive_attrs(filter.rect(), fe);
246                    xml.write_filter_input(AId::In, &composite.input1);
247                    xml.write_filter_input(AId::In2, &composite.input2);
248                    xml.write_svg_attribute(
249                        AId::Operator,
250                        match composite.operator {
251                            filter::CompositeOperator::Over => "over",
252                            filter::CompositeOperator::In => "in",
253                            filter::CompositeOperator::Out => "out",
254                            filter::CompositeOperator::Atop => "atop",
255                            filter::CompositeOperator::Xor => "xor",
256                            filter::CompositeOperator::Arithmetic { .. } => "arithmetic",
257                        },
258                    );
259
260                    if let filter::CompositeOperator::Arithmetic { k1, k2, k3, k4 } =
261                        composite.operator
262                    {
263                        xml.write_svg_attribute(AId::K1, &k1);
264                        xml.write_svg_attribute(AId::K2, &k2);
265                        xml.write_svg_attribute(AId::K3, &k3);
266                        xml.write_svg_attribute(AId::K4, &k4);
267                    }
268
269                    xml.write_svg_attribute(AId::Result, &fe.result);
270                    xml.end_element();
271                }
272                filter::Kind::Merge(ref merge) => {
273                    xml.start_svg_element(EId::FeMerge);
274                    xml.write_filter_primitive_attrs(filter.rect(), fe);
275                    xml.write_svg_attribute(AId::Result, &fe.result);
276                    for input in &merge.inputs {
277                        xml.start_svg_element(EId::FeMergeNode);
278                        xml.write_filter_input(AId::In, input);
279                        xml.end_element();
280                    }
281
282                    xml.end_element();
283                }
284                filter::Kind::Tile(ref tile) => {
285                    xml.start_svg_element(EId::FeTile);
286                    xml.write_filter_primitive_attrs(filter.rect(), fe);
287                    xml.write_filter_input(AId::In, &tile.input);
288                    xml.write_svg_attribute(AId::Result, &fe.result);
289                    xml.end_element();
290                }
291                filter::Kind::Image(ref img) => {
292                    xml.start_svg_element(EId::FeImage);
293                    xml.write_filter_primitive_attrs(filter.rect(), fe);
294                    if let Some(child) = img.root.children.first() {
295                        let prefix = opt.id_prefix.as_deref().unwrap_or_default();
296                        xml.write_attribute_fmt(
297                            "xlink:href",
298                            format_args!("#{}{}", prefix, child.id()),
299                        );
300                    }
301
302                    xml.write_svg_attribute(AId::Result, &fe.result);
303                    xml.end_element();
304                }
305                filter::Kind::ComponentTransfer(ref transfer) => {
306                    xml.start_svg_element(EId::FeComponentTransfer);
307                    xml.write_filter_primitive_attrs(filter.rect(), fe);
308                    xml.write_filter_input(AId::In, &transfer.input);
309                    xml.write_svg_attribute(AId::Result, &fe.result);
310
311                    xml.write_filter_transfer_function(EId::FeFuncR, &transfer.func_r);
312                    xml.write_filter_transfer_function(EId::FeFuncG, &transfer.func_g);
313                    xml.write_filter_transfer_function(EId::FeFuncB, &transfer.func_b);
314                    xml.write_filter_transfer_function(EId::FeFuncA, &transfer.func_a);
315
316                    xml.end_element();
317                }
318                filter::Kind::ColorMatrix(ref matrix) => {
319                    xml.start_svg_element(EId::FeColorMatrix);
320                    xml.write_filter_primitive_attrs(filter.rect(), fe);
321                    xml.write_filter_input(AId::In, &matrix.input);
322                    xml.write_svg_attribute(AId::Result, &fe.result);
323
324                    match matrix.kind {
325                        filter::ColorMatrixKind::Matrix(ref values) => {
326                            xml.write_svg_attribute(AId::Type, "matrix");
327                            xml.write_numbers(AId::Values, values);
328                        }
329                        filter::ColorMatrixKind::Saturate(value) => {
330                            xml.write_svg_attribute(AId::Type, "saturate");
331                            xml.write_svg_attribute(AId::Values, &value.get());
332                        }
333                        filter::ColorMatrixKind::HueRotate(angle) => {
334                            xml.write_svg_attribute(AId::Type, "hueRotate");
335                            xml.write_svg_attribute(AId::Values, &angle);
336                        }
337                        filter::ColorMatrixKind::LuminanceToAlpha => {
338                            xml.write_svg_attribute(AId::Type, "luminanceToAlpha");
339                        }
340                    }
341
342                    xml.end_element();
343                }
344                filter::Kind::ConvolveMatrix(ref matrix) => {
345                    xml.start_svg_element(EId::FeConvolveMatrix);
346                    xml.write_filter_primitive_attrs(filter.rect(), fe);
347                    xml.write_filter_input(AId::In, &matrix.input);
348                    xml.write_svg_attribute(AId::Result, &fe.result);
349
350                    xml.write_attribute_fmt(
351                        AId::Order.to_str(),
352                        format_args!("{} {}", matrix.matrix.columns, matrix.matrix.rows),
353                    );
354                    xml.write_numbers(AId::KernelMatrix, &matrix.matrix.data);
355                    xml.write_svg_attribute(AId::Divisor, &matrix.divisor.get());
356                    xml.write_svg_attribute(AId::Bias, &matrix.bias);
357                    xml.write_svg_attribute(AId::TargetX, &matrix.matrix.target_x);
358                    xml.write_svg_attribute(AId::TargetY, &matrix.matrix.target_y);
359                    xml.write_svg_attribute(
360                        AId::EdgeMode,
361                        match matrix.edge_mode {
362                            filter::EdgeMode::None => "none",
363                            filter::EdgeMode::Duplicate => "duplicate",
364                            filter::EdgeMode::Wrap => "wrap",
365                        },
366                    );
367                    xml.write_svg_attribute(
368                        AId::PreserveAlpha,
369                        if matrix.preserve_alpha {
370                            "true"
371                        } else {
372                            "false"
373                        },
374                    );
375
376                    xml.end_element();
377                }
378                filter::Kind::Morphology(ref morphology) => {
379                    xml.start_svg_element(EId::FeMorphology);
380                    xml.write_filter_primitive_attrs(filter.rect(), fe);
381                    xml.write_filter_input(AId::In, &morphology.input);
382                    xml.write_svg_attribute(AId::Result, &fe.result);
383
384                    xml.write_svg_attribute(
385                        AId::Operator,
386                        match morphology.operator {
387                            filter::MorphologyOperator::Erode => "erode",
388                            filter::MorphologyOperator::Dilate => "dilate",
389                        },
390                    );
391                    xml.write_attribute_fmt(
392                        AId::Radius.to_str(),
393                        format_args!(
394                            "{} {}",
395                            morphology.radius_x.get(),
396                            morphology.radius_y.get()
397                        ),
398                    );
399
400                    xml.end_element();
401                }
402                filter::Kind::DisplacementMap(ref map) => {
403                    xml.start_svg_element(EId::FeDisplacementMap);
404                    xml.write_filter_primitive_attrs(filter.rect(), fe);
405                    xml.write_filter_input(AId::In, &map.input1);
406                    xml.write_filter_input(AId::In2, &map.input2);
407                    xml.write_svg_attribute(AId::Result, &fe.result);
408
409                    xml.write_svg_attribute(AId::Scale, &map.scale);
410
411                    let mut write_channel = |c, aid| {
412                        xml.write_svg_attribute(
413                            aid,
414                            match c {
415                                filter::ColorChannel::R => "R",
416                                filter::ColorChannel::G => "G",
417                                filter::ColorChannel::B => "B",
418                                filter::ColorChannel::A => "A",
419                            },
420                        );
421                    };
422                    write_channel(map.x_channel_selector, AId::XChannelSelector);
423                    write_channel(map.y_channel_selector, AId::YChannelSelector);
424
425                    xml.end_element();
426                }
427                filter::Kind::Turbulence(ref turbulence) => {
428                    xml.start_svg_element(EId::FeTurbulence);
429                    xml.write_filter_primitive_attrs(filter.rect(), fe);
430                    xml.write_svg_attribute(AId::Result, &fe.result);
431
432                    xml.write_attribute_fmt(
433                        AId::BaseFrequency.to_str(),
434                        format_args!(
435                            "{} {}",
436                            turbulence.base_frequency_x.get(),
437                            turbulence.base_frequency_y.get()
438                        ),
439                    );
440                    xml.write_svg_attribute(AId::NumOctaves, &turbulence.num_octaves);
441                    xml.write_svg_attribute(AId::Seed, &turbulence.seed);
442                    xml.write_svg_attribute(
443                        AId::StitchTiles,
444                        match turbulence.stitch_tiles {
445                            true => "stitch",
446                            false => "noStitch",
447                        },
448                    );
449                    xml.write_svg_attribute(
450                        AId::Type,
451                        match turbulence.kind {
452                            filter::TurbulenceKind::FractalNoise => "fractalNoise",
453                            filter::TurbulenceKind::Turbulence => "turbulence",
454                        },
455                    );
456
457                    xml.end_element();
458                }
459                filter::Kind::DiffuseLighting(ref light) => {
460                    xml.start_svg_element(EId::FeDiffuseLighting);
461                    xml.write_filter_primitive_attrs(filter.rect(), fe);
462                    xml.write_svg_attribute(AId::Result, &fe.result);
463
464                    xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale);
465                    xml.write_svg_attribute(AId::DiffuseConstant, &light.diffuse_constant);
466                    xml.write_color(AId::LightingColor, light.lighting_color);
467                    write_light_source(&light.light_source, xml);
468
469                    xml.end_element();
470                }
471                filter::Kind::SpecularLighting(ref light) => {
472                    xml.start_svg_element(EId::FeSpecularLighting);
473                    xml.write_filter_primitive_attrs(filter.rect(), fe);
474                    xml.write_svg_attribute(AId::Result, &fe.result);
475
476                    xml.write_svg_attribute(AId::SurfaceScale, &light.surface_scale);
477                    xml.write_svg_attribute(AId::SpecularConstant, &light.specular_constant);
478                    xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent);
479                    xml.write_color(AId::LightingColor, light.lighting_color);
480                    write_light_source(&light.light_source, xml);
481
482                    xml.end_element();
483                }
484            };
485        }
486
487        xml.end_element();
488    }
489}
490
491fn write_defs(tree: &Tree, opt: &WriteOptions, xml: &mut XmlWriter, write_text_paths: bool) {
492    xml.start_svg_element(EId::Defs);
493    for lg in tree.linear_gradients() {
494        xml.start_svg_element(EId::LinearGradient);
495        xml.write_id_attribute(lg.id(), opt);
496        xml.write_svg_attribute(AId::X1, &lg.x1);
497        xml.write_svg_attribute(AId::Y1, &lg.y1);
498        xml.write_svg_attribute(AId::X2, &lg.x2);
499        xml.write_svg_attribute(AId::Y2, &lg.y2);
500        write_base_grad(&lg.base, opt, xml);
501        xml.end_element();
502    }
503
504    for rg in tree.radial_gradients() {
505        xml.start_svg_element(EId::RadialGradient);
506        xml.write_id_attribute(rg.id(), opt);
507        xml.write_svg_attribute(AId::Cx, &rg.cx);
508        xml.write_svg_attribute(AId::Cy, &rg.cy);
509        xml.write_svg_attribute(AId::R, &rg.r.get());
510        xml.write_svg_attribute(AId::Fx, &rg.fx);
511        xml.write_svg_attribute(AId::Fy, &rg.fy);
512        write_base_grad(&rg.base, opt, xml);
513        xml.end_element();
514    }
515
516    for pattern in tree.patterns() {
517        xml.start_svg_element(EId::Pattern);
518        xml.write_id_attribute(pattern.id(), opt);
519        xml.write_rect_attrs(pattern.rect);
520        xml.write_units(AId::PatternUnits, pattern.units, Units::ObjectBoundingBox);
521        xml.write_units(
522            AId::PatternContentUnits,
523            pattern.content_units,
524            Units::UserSpaceOnUse,
525        );
526        xml.write_transform(AId::PatternTransform, pattern.transform, opt);
527
528        write_elements(&pattern.root, false, opt, xml);
529
530        xml.end_element();
531    }
532
533    if write_text_paths {
534        write_text_path_paths(&tree.root, opt, xml);
535    }
536
537    write_filters(tree, opt, xml);
538
539    for clip in tree.clip_paths() {
540        xml.start_svg_element(EId::ClipPath);
541        xml.write_id_attribute(clip.id(), opt);
542        xml.write_transform(AId::Transform, clip.transform, opt);
543
544        if let Some(ref clip) = clip.clip_path {
545            xml.write_func_iri(AId::ClipPath, clip.id(), opt);
546        }
547
548        write_elements(&clip.root, true, opt, xml);
549
550        xml.end_element();
551    }
552
553    for mask in tree.masks() {
554        xml.start_svg_element(EId::Mask);
555        xml.write_id_attribute(mask.id(), opt);
556        if mask.kind == MaskType::Alpha {
557            xml.write_svg_attribute(AId::MaskType, "alpha");
558        }
559        xml.write_units(
560            AId::MaskUnits,
561            Units::UserSpaceOnUse,
562            Units::ObjectBoundingBox,
563        );
564        xml.write_rect_attrs(mask.rect);
565
566        if let Some(ref mask) = mask.mask {
567            xml.write_func_iri(AId::Mask, mask.id(), opt);
568        }
569
570        write_elements(&mask.root, false, opt, xml);
571
572        xml.end_element();
573    }
574    xml.end_element(); // end EId::Defs
575}
576
577fn has_text_paths(parent: &Group) -> bool {
578    for node in &parent.children {
579        if let Node::Group(group) = node {
580            if has_text_paths(group) {
581                return true;
582            }
583        } else if let Node::Text(text) = node {
584            for chunk in &text.chunks {
585                if let TextFlow::Path(text_path) = &chunk.text_flow {
586                    let path = Path::new(
587                        text_path.id().to_string(),
588                        true,
589                        None,
590                        None,
591                        PaintOrder::default(),
592                        ShapeRendering::default(),
593                        text_path.path.clone(),
594                        Transform::default(),
595                    );
596                    if path.is_some() {
597                        return true;
598                    }
599                }
600            }
601        }
602        let mut need_path = false;
603        node.subroots(|subroot| {
604            if !need_path && has_text_paths(subroot) {
605                need_path = true;
606            }
607        });
608        if need_path {
609            return true;
610        }
611    }
612    false
613}
614
615/// Write the `path` elements for text paths.
616fn write_text_path_paths(parent: &Group, opt: &WriteOptions, xml: &mut XmlWriter) {
617    for node in &parent.children {
618        if let Node::Group(group) = node {
619            write_text_path_paths(group, opt, xml);
620        } else if let Node::Text(text) = node {
621            for chunk in &text.chunks {
622                if let TextFlow::Path(text_path) = &chunk.text_flow {
623                    let path = Path::new(
624                        text_path.id().to_string(),
625                        true,
626                        None,
627                        None,
628                        PaintOrder::default(),
629                        ShapeRendering::default(),
630                        text_path.path.clone(),
631                        Transform::default(),
632                    );
633                    if let Some(path) = &path {
634                        write_path(path, false, Transform::default(), None, opt, xml);
635                    }
636                }
637            }
638        }
639
640        node.subroots(|subroot| write_text_path_paths(subroot, opt, xml));
641    }
642}
643
644fn write_elements(parent: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {
645    for n in &parent.children {
646        write_element(n, is_clip_path, opt, xml);
647    }
648}
649
650fn write_element(node: &Node, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {
651    match node {
652        Node::Path(p) => {
653            write_path(p, is_clip_path, Transform::default(), None, opt, xml);
654        }
655        Node::Image(img) => {
656            xml.start_svg_element(EId::Image);
657            if !img.id.is_empty() {
658                xml.write_id_attribute(&img.id, opt);
659            }
660
661            xml.write_svg_attribute(AId::Width, &img.size().width());
662            xml.write_svg_attribute(AId::Height, &img.size().height());
663
664            xml.write_visibility(img.visible);
665
666            match img.rendering_mode {
667                ImageRendering::OptimizeQuality => {}
668                ImageRendering::OptimizeSpeed => {
669                    xml.write_svg_attribute(AId::ImageRendering, "optimizeSpeed");
670                }
671                ImageRendering::Smooth => {
672                    xml.write_attribute(AId::Style.to_str(), "image-rendering:smooth");
673                }
674                ImageRendering::HighQuality => {
675                    xml.write_attribute(AId::Style.to_str(), "image-rendering:high-quality");
676                }
677                ImageRendering::CrispEdges => {
678                    xml.write_attribute(AId::Style.to_str(), "image-rendering:crisp-edges");
679                }
680                ImageRendering::Pixelated => {
681                    xml.write_attribute(AId::Style.to_str(), "image-rendering:pixelated");
682                }
683            }
684
685            xml.write_image_data(&img.kind);
686
687            xml.end_element();
688        }
689        Node::Group(g) => {
690            write_group_element(g, is_clip_path, opt, xml);
691        }
692        Node::Text(text) => {
693            if opt.preserve_text {
694                xml.start_svg_element(EId::Text);
695
696                if !text.id.is_empty() {
697                    xml.write_id_attribute(&text.id, opt);
698                }
699
700                xml.write_attribute("xml:space", "preserve");
701
702                match text.writing_mode {
703                    WritingMode::LeftToRight => {}
704                    WritingMode::TopToBottom => xml.write_svg_attribute(AId::WritingMode, "tb"),
705                }
706
707                match text.rendering_mode {
708                    TextRendering::OptimizeSpeed => {
709                        xml.write_svg_attribute(AId::TextRendering, "optimizeSpeed");
710                    }
711                    TextRendering::GeometricPrecision => {
712                        xml.write_svg_attribute(AId::TextRendering, "geometricPrecision");
713                    }
714                    TextRendering::OptimizeLegibility => {}
715                }
716
717                if text.rotate.iter().any(|r| *r != 0.0) {
718                    xml.write_numbers(AId::Rotate, &text.rotate);
719                }
720
721                if text.dx.iter().any(|dx| *dx != 0.0) {
722                    xml.write_numbers(AId::Dx, &text.dx);
723                }
724
725                if text.dy.iter().any(|dy| *dy != 0.0) {
726                    xml.write_numbers(AId::Dy, &text.dy);
727                }
728
729                xml.set_preserve_whitespaces(true);
730
731                for chunk in &text.chunks {
732                    if let TextFlow::Path(text_path) = &chunk.text_flow {
733                        xml.start_svg_element(EId::TextPath);
734
735                        let prefix = opt.id_prefix.as_deref().unwrap_or_default();
736                        xml.write_attribute_fmt(
737                            "xlink:href",
738                            format_args!("#{}{}", prefix, text_path.id()),
739                        );
740
741                        if text_path.start_offset != 0.0 {
742                            xml.write_svg_attribute(AId::StartOffset, &text_path.start_offset);
743                        }
744                    }
745
746                    xml.start_svg_element(EId::Tspan);
747
748                    if let Some(x) = chunk.x {
749                        xml.write_svg_attribute(AId::X, &x);
750                    }
751
752                    if let Some(y) = chunk.y {
753                        xml.write_svg_attribute(AId::Y, &y);
754                    }
755
756                    match chunk.anchor {
757                        TextAnchor::Start => {}
758                        TextAnchor::Middle => xml.write_svg_attribute(AId::TextAnchor, "middle"),
759                        TextAnchor::End => xml.write_svg_attribute(AId::TextAnchor, "end"),
760                    }
761
762                    for span in &chunk.spans {
763                        let decorations: Vec<_> = [
764                            ("underline", &span.decoration.underline),
765                            ("line-through", &span.decoration.line_through),
766                            ("overline", &span.decoration.overline),
767                        ]
768                        .iter()
769                        .filter_map(|&(key, option_value)| {
770                            option_value.as_ref().map(|value| (key, value))
771                        })
772                        .collect();
773
774                        // Decorations need to be dumped BEFORE we write the actual span data
775                        // (so that for example stroke color of span doesn't affect the text
776                        // itself while baseline shifts need to be written after (since they are
777                        // affected by the font size)
778                        for (deco_name, deco) in &decorations {
779                            xml.start_svg_element(EId::Tspan);
780                            xml.write_svg_attribute(AId::TextDecoration, deco_name);
781                            write_fill(&deco.fill, false, opt, xml);
782                            write_stroke(&deco.stroke, opt, xml);
783                        }
784
785                        write_span(is_clip_path, opt, xml, chunk, span);
786
787                        // End for each tspan we needed to create for decorations
788                        for _ in &decorations {
789                            xml.end_element();
790                        }
791                    }
792                    xml.end_element();
793
794                    // End textPath element
795                    if matches!(&chunk.text_flow, TextFlow::Path(_)) {
796                        xml.end_element();
797                    }
798                }
799
800                xml.end_element();
801                xml.set_preserve_whitespaces(false);
802            } else {
803                write_group_element(text.flattened(), is_clip_path, opt, xml);
804            }
805        }
806    }
807}
808
809fn write_group_element(g: &Group, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {
810    if is_clip_path {
811        // The `clipPath` element in SVG doesn't allow groups, only shapes and text.
812        // The problem is that in `usvg` we can set a `clip-path` only on groups.
813        // So in cases when a `clipPath` child has a `clip-path` as well,
814        // it would be inside a group. And we have to skip this group during writing.
815        //
816        // Basically, the following SVG:
817        //
818        // <clipPath id="clip">
819        //   <path clip-path="url(#clip-nested)"/>
820        // </clipPath>
821        //
822        // will be represented in usvg as:
823        //
824        // <clipPath id="clip">
825        //   <g clip-path="url(#clip-nested)">
826        //      <path/>
827        //   </g>
828        // </clipPath>
829        //
830        //
831        // Same with text. Text elements will be converted into groups,
832        // but only the group's children should be written.
833        for child in &g.children {
834            match child {
835                Node::Group(child_group) => {
836                    write_group_element(child_group, is_clip_path, opt, xml);
837                }
838                Node::Path(child_path) => {
839                    let clip_id = g.clip_path.as_ref().map(|cp| cp.id().to_string());
840                    write_path(
841                        child_path,
842                        is_clip_path,
843                        g.transform,
844                        clip_id.as_deref(),
845                        opt,
846                        xml,
847                    );
848                }
849                _ => {}
850            }
851        }
852        return;
853    }
854
855    xml.start_svg_element(EId::G);
856    if !g.id.is_empty() {
857        xml.write_id_attribute(&g.id, opt);
858    };
859
860    if let Some(ref clip) = g.clip_path {
861        xml.write_func_iri(AId::ClipPath, clip.id(), opt);
862    }
863
864    if let Some(ref mask) = g.mask {
865        xml.write_func_iri(AId::Mask, mask.id(), opt);
866    }
867
868    if !g.filters.is_empty() {
869        let prefix = opt.id_prefix.as_deref().unwrap_or_default();
870        let ids: Vec<_> = g
871            .filters
872            .iter()
873            .map(|filter| format!("url(#{}{})", prefix, filter.id()))
874            .collect();
875        xml.write_svg_attribute(AId::Filter, &ids.join(" "));
876    }
877
878    if g.opacity != Opacity::ONE {
879        xml.write_svg_attribute(AId::Opacity, &g.opacity.get());
880    }
881
882    xml.write_transform(AId::Transform, g.transform, opt);
883
884    if g.blend_mode != BlendMode::Normal || g.isolate {
885        // For reasons unknown, `mix-blend-mode` and `isolation` must be written
886        // as `style` attribute.
887        let isolation = if g.isolate { "isolate" } else { "auto" };
888        xml.write_attribute_fmt(
889            AId::Style.to_str(),
890            format_args!("mix-blend-mode:{};isolation:{}", g.blend_mode, isolation),
891        );
892    }
893
894    write_elements(g, false, opt, xml);
895
896    xml.end_element();
897}
898
899trait XmlWriterExt {
900    fn start_svg_element(&mut self, id: EId);
901    fn write_svg_attribute<V: Display + ?Sized>(&mut self, id: AId, value: &V);
902    fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions);
903    fn write_color(&mut self, id: AId, color: Color);
904    fn write_units(&mut self, id: AId, units: Units, def: Units);
905    fn write_transform(&mut self, id: AId, units: Transform, opt: &WriteOptions);
906    fn write_visibility(&mut self, value: bool);
907    fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions);
908    fn write_rect_attrs(&mut self, r: NonZeroRect);
909    fn write_numbers(&mut self, aid: AId, list: &[f32]);
910    fn write_image_data(&mut self, kind: &ImageKind);
911    fn write_filter_input(&mut self, id: AId, input: &filter::Input);
912    fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive);
913    fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction);
914}
915
916impl XmlWriterExt for XmlWriter {
917    #[inline(never)]
918    fn start_svg_element(&mut self, id: EId) {
919        self.start_element(id.to_str());
920    }
921
922    #[inline(never)]
923    fn write_svg_attribute<V: Display + ?Sized>(&mut self, id: AId, value: &V) {
924        self.write_attribute(id.to_str(), value);
925    }
926
927    #[inline(never)]
928    fn write_id_attribute(&mut self, id: &str, opt: &WriteOptions) {
929        debug_assert!(!id.is_empty());
930
931        if let Some(ref prefix) = opt.id_prefix {
932            let full_id = format!("{}{}", prefix, id);
933            self.write_attribute("id", &full_id);
934        } else {
935            self.write_attribute("id", id);
936        }
937    }
938
939    #[inline(never)]
940    fn write_color(&mut self, id: AId, c: Color) {
941        static CHARS: &[u8] = b"0123456789abcdef";
942
943        #[inline]
944        fn int2hex(n: u8) -> (u8, u8) {
945            (CHARS[(n >> 4) as usize], CHARS[(n & 0xf) as usize])
946        }
947
948        let (r1, r2) = int2hex(c.red);
949        let (g1, g2) = int2hex(c.green);
950        let (b1, b2) = int2hex(c.blue);
951
952        self.write_attribute_raw(id.to_str(), |buf| {
953            buf.extend_from_slice(&[b'#', r1, r2, g1, g2, b1, b2]);
954        });
955    }
956
957    // TODO: simplify
958    fn write_units(&mut self, id: AId, units: Units, def: Units) {
959        if units != def {
960            self.write_attribute(
961                id.to_str(),
962                match units {
963                    Units::UserSpaceOnUse => "userSpaceOnUse",
964                    Units::ObjectBoundingBox => "objectBoundingBox",
965                },
966            );
967        }
968    }
969
970    fn write_transform(&mut self, id: AId, ts: Transform, opt: &WriteOptions) {
971        if !ts.is_default() {
972            self.write_attribute_raw(id.to_str(), |buf| {
973                buf.extend_from_slice(b"matrix(");
974                write_num(ts.sx, buf, opt.transforms_precision);
975                buf.push(b' ');
976                write_num(ts.ky, buf, opt.transforms_precision);
977                buf.push(b' ');
978                write_num(ts.kx, buf, opt.transforms_precision);
979                buf.push(b' ');
980                write_num(ts.sy, buf, opt.transforms_precision);
981                buf.push(b' ');
982                write_num(ts.tx, buf, opt.transforms_precision);
983                buf.push(b' ');
984                write_num(ts.ty, buf, opt.transforms_precision);
985                buf.extend_from_slice(b")");
986            });
987        }
988    }
989
990    fn write_visibility(&mut self, value: bool) {
991        if !value {
992            self.write_attribute(AId::Visibility.to_str(), "hidden");
993        }
994    }
995
996    fn write_func_iri(&mut self, aid: AId, id: &str, opt: &WriteOptions) {
997        debug_assert!(!id.is_empty());
998        let prefix = opt.id_prefix.as_deref().unwrap_or_default();
999        self.write_attribute_fmt(aid.to_str(), format_args!("url(#{}{})", prefix, id));
1000    }
1001
1002    fn write_rect_attrs(&mut self, r: NonZeroRect) {
1003        self.write_svg_attribute(AId::X, &r.x());
1004        self.write_svg_attribute(AId::Y, &r.y());
1005        self.write_svg_attribute(AId::Width, &r.width());
1006        self.write_svg_attribute(AId::Height, &r.height());
1007    }
1008
1009    fn write_numbers(&mut self, aid: AId, list: &[f32]) {
1010        self.write_attribute_raw(aid.to_str(), |buf| {
1011            for n in list {
1012                buf.write_fmt(format_args!("{} ", n)).unwrap();
1013            }
1014
1015            if !list.is_empty() {
1016                buf.pop();
1017            }
1018        });
1019    }
1020
1021    fn write_filter_input(&mut self, id: AId, input: &filter::Input) {
1022        self.write_attribute(
1023            id.to_str(),
1024            match input {
1025                filter::Input::SourceGraphic => "SourceGraphic",
1026                filter::Input::SourceAlpha => "SourceAlpha",
1027                filter::Input::Reference(s) => s,
1028            },
1029        );
1030    }
1031
1032    fn write_filter_primitive_attrs(&mut self, parent_rect: NonZeroRect, fe: &filter::Primitive) {
1033        if parent_rect.x() != fe.rect().x() {
1034            self.write_svg_attribute(AId::X, &fe.rect().x());
1035        }
1036        if parent_rect.y() != fe.rect().y() {
1037            self.write_svg_attribute(AId::Y, &fe.rect().y());
1038        }
1039        if parent_rect.width() != fe.rect().width() {
1040            self.write_svg_attribute(AId::Width, &fe.rect().width());
1041        }
1042        if parent_rect.height() != fe.rect().height() {
1043            self.write_svg_attribute(AId::Height, &fe.rect().height());
1044        }
1045
1046        self.write_attribute(
1047            AId::ColorInterpolationFilters.to_str(),
1048            match fe.color_interpolation {
1049                filter::ColorInterpolation::SRGB => "sRGB",
1050                filter::ColorInterpolation::LinearRGB => "linearRGB",
1051            },
1052        );
1053    }
1054
1055    fn write_filter_transfer_function(&mut self, eid: EId, fe: &filter::TransferFunction) {
1056        self.start_svg_element(eid);
1057
1058        match fe {
1059            filter::TransferFunction::Identity => {
1060                self.write_svg_attribute(AId::Type, "identity");
1061            }
1062            filter::TransferFunction::Table(values) => {
1063                self.write_svg_attribute(AId::Type, "table");
1064                self.write_numbers(AId::TableValues, values);
1065            }
1066            filter::TransferFunction::Discrete(values) => {
1067                self.write_svg_attribute(AId::Type, "discrete");
1068                self.write_numbers(AId::TableValues, values);
1069            }
1070            filter::TransferFunction::Linear { slope, intercept } => {
1071                self.write_svg_attribute(AId::Type, "linear");
1072                self.write_svg_attribute(AId::Slope, &slope);
1073                self.write_svg_attribute(AId::Intercept, &intercept);
1074            }
1075            filter::TransferFunction::Gamma {
1076                amplitude,
1077                exponent,
1078                offset,
1079            } => {
1080                self.write_svg_attribute(AId::Type, "gamma");
1081                self.write_svg_attribute(AId::Amplitude, &amplitude);
1082                self.write_svg_attribute(AId::Exponent, &exponent);
1083                self.write_svg_attribute(AId::Offset, &offset);
1084            }
1085        }
1086
1087        self.end_element();
1088    }
1089
1090    fn write_image_data(&mut self, kind: &ImageKind) {
1091        let svg_string;
1092        let (mime, data) = match kind {
1093            ImageKind::JPEG(data) => ("jpeg", data.as_slice()),
1094            ImageKind::PNG(data) => ("png", data.as_slice()),
1095            ImageKind::GIF(data) => ("gif", data.as_slice()),
1096            ImageKind::WEBP(data) => ("webp", data.as_slice()),
1097            ImageKind::SVG(tree) => {
1098                svg_string = tree.to_string(&WriteOptions::default());
1099                ("svg+xml", svg_string.as_bytes())
1100            }
1101        };
1102
1103        self.write_attribute_raw("xlink:href", |buf| {
1104            buf.extend_from_slice(b"data:image/");
1105            buf.extend_from_slice(mime.as_bytes());
1106            buf.extend_from_slice(b";base64, ");
1107
1108            let mut enc =
1109                base64::write::EncoderWriter::new(buf, &base64::engine::general_purpose::STANDARD);
1110            enc.write_all(data).unwrap();
1111            enc.finish().unwrap();
1112        });
1113    }
1114}
1115
1116fn has_xlink(parent: &Group) -> bool {
1117    for node in &parent.children {
1118        match node {
1119            Node::Group(g) => {
1120                for filter in &g.filters {
1121                    if filter
1122                        .primitives
1123                        .iter()
1124                        .any(|p| matches!(p.kind, filter::Kind::Image(_)))
1125                    {
1126                        return true;
1127                    }
1128                }
1129
1130                if let Some(mask) = &g.mask {
1131                    if has_xlink(mask.root()) {
1132                        return true;
1133                    }
1134
1135                    if let Some(sub_mask) = &mask.mask {
1136                        if has_xlink(&sub_mask.root) {
1137                            return true;
1138                        }
1139                    }
1140                }
1141
1142                if has_xlink(g) {
1143                    return true;
1144                }
1145            }
1146            Node::Image(_) => {
1147                return true;
1148            }
1149            Node::Text(text) => {
1150                if text
1151                    .chunks
1152                    .iter()
1153                    .any(|t| matches!(t.text_flow, TextFlow::Path(_)))
1154                {
1155                    return true;
1156                }
1157            }
1158            _ => {}
1159        }
1160
1161        let mut present = false;
1162        node.subroots(|root| present |= has_xlink(root));
1163        if present {
1164            return true;
1165        }
1166    }
1167
1168    false
1169}
1170
1171fn write_base_grad(g: &BaseGradient, opt: &WriteOptions, xml: &mut XmlWriter) {
1172    xml.write_units(AId::GradientUnits, g.units, Units::ObjectBoundingBox);
1173    xml.write_transform(AId::GradientTransform, g.transform, opt);
1174
1175    match g.spread_method {
1176        SpreadMethod::Pad => {}
1177        SpreadMethod::Reflect => xml.write_svg_attribute(AId::SpreadMethod, "reflect"),
1178        SpreadMethod::Repeat => xml.write_svg_attribute(AId::SpreadMethod, "repeat"),
1179    }
1180
1181    for s in &g.stops {
1182        xml.start_svg_element(EId::Stop);
1183        xml.write_svg_attribute(AId::Offset, &s.offset.get());
1184        xml.write_color(AId::StopColor, s.color);
1185        if s.opacity != Opacity::ONE {
1186            xml.write_svg_attribute(AId::StopOpacity, &s.opacity.get());
1187        }
1188
1189        xml.end_element();
1190    }
1191}
1192
1193fn write_path(
1194    path: &Path,
1195    is_clip_path: bool,
1196    path_transform: Transform,
1197    clip_path: Option<&str>,
1198    opt: &WriteOptions,
1199    xml: &mut XmlWriter,
1200) {
1201    xml.start_svg_element(EId::Path);
1202    if !path.id.is_empty() {
1203        xml.write_id_attribute(&path.id, opt);
1204    }
1205
1206    write_fill(&path.fill, is_clip_path, opt, xml);
1207    write_stroke(&path.stroke, opt, xml);
1208
1209    xml.write_visibility(path.visible);
1210
1211    if path.paint_order == PaintOrder::StrokeAndFill {
1212        xml.write_svg_attribute(AId::PaintOrder, "stroke");
1213    }
1214
1215    match path.rendering_mode {
1216        ShapeRendering::OptimizeSpeed => {
1217            xml.write_svg_attribute(AId::ShapeRendering, "optimizeSpeed");
1218        }
1219        ShapeRendering::CrispEdges => xml.write_svg_attribute(AId::ShapeRendering, "crispEdges"),
1220        ShapeRendering::GeometricPrecision => {}
1221    }
1222
1223    if let Some(id) = clip_path {
1224        xml.write_func_iri(AId::ClipPath, id, opt);
1225    }
1226
1227    xml.write_transform(AId::Transform, path_transform, opt);
1228
1229    xml.write_attribute_raw("d", |buf| {
1230        use tiny_skia_path::PathSegment;
1231
1232        for seg in path.data.segments() {
1233            match seg {
1234                PathSegment::MoveTo(p) => {
1235                    buf.extend_from_slice(b"M ");
1236                    write_num(p.x, buf, opt.coordinates_precision);
1237                    buf.push(b' ');
1238                    write_num(p.y, buf, opt.coordinates_precision);
1239                    buf.push(b' ');
1240                }
1241                PathSegment::LineTo(p) => {
1242                    buf.extend_from_slice(b"L ");
1243                    write_num(p.x, buf, opt.coordinates_precision);
1244                    buf.push(b' ');
1245                    write_num(p.y, buf, opt.coordinates_precision);
1246                    buf.push(b' ');
1247                }
1248                PathSegment::QuadTo(p1, p) => {
1249                    buf.extend_from_slice(b"Q ");
1250                    write_num(p1.x, buf, opt.coordinates_precision);
1251                    buf.push(b' ');
1252                    write_num(p1.y, buf, opt.coordinates_precision);
1253                    buf.push(b' ');
1254                    write_num(p.x, buf, opt.coordinates_precision);
1255                    buf.push(b' ');
1256                    write_num(p.y, buf, opt.coordinates_precision);
1257                    buf.push(b' ');
1258                }
1259                PathSegment::CubicTo(p1, p2, p) => {
1260                    buf.extend_from_slice(b"C ");
1261                    write_num(p1.x, buf, opt.coordinates_precision);
1262                    buf.push(b' ');
1263                    write_num(p1.y, buf, opt.coordinates_precision);
1264                    buf.push(b' ');
1265                    write_num(p2.x, buf, opt.coordinates_precision);
1266                    buf.push(b' ');
1267                    write_num(p2.y, buf, opt.coordinates_precision);
1268                    buf.push(b' ');
1269                    write_num(p.x, buf, opt.coordinates_precision);
1270                    buf.push(b' ');
1271                    write_num(p.y, buf, opt.coordinates_precision);
1272                    buf.push(b' ');
1273                }
1274                PathSegment::Close => {
1275                    buf.extend_from_slice(b"Z ");
1276                }
1277            }
1278        }
1279
1280        buf.pop();
1281    });
1282
1283    xml.end_element();
1284}
1285
1286fn write_fill(fill: &Option<Fill>, is_clip_path: bool, opt: &WriteOptions, xml: &mut XmlWriter) {
1287    if let Some(fill) = fill {
1288        write_paint(AId::Fill, &fill.paint, opt, xml);
1289
1290        if fill.opacity != Opacity::ONE {
1291            xml.write_svg_attribute(AId::FillOpacity, &fill.opacity.get());
1292        }
1293
1294        if !fill.rule.is_default() {
1295            let name = if is_clip_path {
1296                AId::ClipRule
1297            } else {
1298                AId::FillRule
1299            };
1300
1301            xml.write_svg_attribute(name, "evenodd");
1302        }
1303    } else {
1304        xml.write_svg_attribute(AId::Fill, "none");
1305    }
1306}
1307
1308fn write_stroke(stroke: &Option<Stroke>, opt: &WriteOptions, xml: &mut XmlWriter) {
1309    if let Some(stroke) = stroke {
1310        write_paint(AId::Stroke, &stroke.paint, opt, xml);
1311
1312        if stroke.opacity != Opacity::ONE {
1313            xml.write_svg_attribute(AId::StrokeOpacity, &stroke.opacity.get());
1314        }
1315
1316        if !stroke.dashoffset.approx_zero_ulps(4) {
1317            xml.write_svg_attribute(AId::StrokeDashoffset, &stroke.dashoffset);
1318        }
1319
1320        if !stroke.miterlimit.is_default() {
1321            xml.write_svg_attribute(AId::StrokeMiterlimit, &stroke.miterlimit.get());
1322        }
1323
1324        if stroke.width.get() != 1.0 {
1325            xml.write_svg_attribute(AId::StrokeWidth, &stroke.width.get());
1326        }
1327
1328        match stroke.linecap {
1329            LineCap::Butt => {}
1330            LineCap::Round => xml.write_svg_attribute(AId::StrokeLinecap, "round"),
1331            LineCap::Square => xml.write_svg_attribute(AId::StrokeLinecap, "square"),
1332        }
1333
1334        match stroke.linejoin {
1335            LineJoin::Miter => {}
1336            LineJoin::MiterClip => xml.write_svg_attribute(AId::StrokeLinejoin, "miter-clip"),
1337            LineJoin::Round => xml.write_svg_attribute(AId::StrokeLinejoin, "round"),
1338            LineJoin::Bevel => xml.write_svg_attribute(AId::StrokeLinejoin, "bevel"),
1339        }
1340
1341        if let Some(ref array) = stroke.dasharray {
1342            xml.write_numbers(AId::StrokeDasharray, array);
1343        }
1344    } else {
1345        // Always set `stroke` to `none` to override the parent value.
1346        // In 99.9% of the cases it's redundant, but a group with `filter` with `StrokePaint`
1347        // will set `stroke`, which will interfere with children nodes.
1348        xml.write_svg_attribute(AId::Stroke, "none");
1349    }
1350}
1351
1352fn write_paint(aid: AId, paint: &Paint, opt: &WriteOptions, xml: &mut XmlWriter) {
1353    match paint {
1354        Paint::Color(c) => xml.write_color(aid, *c),
1355        Paint::LinearGradient(lg) => {
1356            xml.write_func_iri(aid, lg.id(), opt);
1357        }
1358        Paint::RadialGradient(rg) => {
1359            xml.write_func_iri(aid, rg.id(), opt);
1360        }
1361        Paint::Pattern(patt) => {
1362            xml.write_func_iri(aid, patt.id(), opt);
1363        }
1364    }
1365}
1366
1367fn write_light_source(light: &filter::LightSource, xml: &mut XmlWriter) {
1368    match light {
1369        filter::LightSource::DistantLight(light) => {
1370            xml.start_svg_element(EId::FeDistantLight);
1371            xml.write_svg_attribute(AId::Azimuth, &light.azimuth);
1372            xml.write_svg_attribute(AId::Elevation, &light.elevation);
1373        }
1374        filter::LightSource::PointLight(light) => {
1375            xml.start_svg_element(EId::FePointLight);
1376            xml.write_svg_attribute(AId::X, &light.x);
1377            xml.write_svg_attribute(AId::Y, &light.y);
1378            xml.write_svg_attribute(AId::Z, &light.z);
1379        }
1380        filter::LightSource::SpotLight(light) => {
1381            xml.start_svg_element(EId::FeSpotLight);
1382            xml.write_svg_attribute(AId::X, &light.x);
1383            xml.write_svg_attribute(AId::Y, &light.y);
1384            xml.write_svg_attribute(AId::Z, &light.z);
1385            xml.write_svg_attribute(AId::PointsAtX, &light.points_at_x);
1386            xml.write_svg_attribute(AId::PointsAtY, &light.points_at_y);
1387            xml.write_svg_attribute(AId::PointsAtZ, &light.points_at_z);
1388            xml.write_svg_attribute(AId::SpecularExponent, &light.specular_exponent);
1389            if let Some(n) = light.limiting_cone_angle {
1390                xml.write_svg_attribute(AId::LimitingConeAngle, &n);
1391            }
1392        }
1393    }
1394
1395    xml.end_element();
1396}
1397
1398static POW_VEC: &[f32] = &[
1399    1.0,
1400    10.0,
1401    100.0,
1402    1_000.0,
1403    10_000.0,
1404    100_000.0,
1405    1_000_000.0,
1406    10_000_000.0,
1407    100_000_000.0,
1408    1_000_000_000.0,
1409    10_000_000_000.0,
1410    100_000_000_000.0,
1411    1_000_000_000_000.0,
1412];
1413
1414fn write_num(num: f32, buf: &mut Vec<u8>, precision: u8) {
1415    // If number is an integer, it's faster to write it as i32.
1416    if num.fract().approx_zero_ulps(4) {
1417        write!(buf, "{}", num as i32).unwrap();
1418        return;
1419    }
1420
1421    // Round numbers up to the specified precision to prevent writing
1422    // ugly numbers like 29.999999999999996.
1423    // It's not 100% correct, but differences are insignificant.
1424    //
1425    // Note that at least in Rust 1.64 the number formatting in debug and release modes
1426    // can be slightly different. So having a lower precision makes
1427    // our output and tests reproducible.
1428    let v = (num * POW_VEC[precision as usize]).round() / POW_VEC[precision as usize];
1429
1430    write!(buf, "{}", v).unwrap();
1431}
1432
1433/// Write all of the tspan attributes except for decorations.
1434fn write_span(
1435    is_clip_path: bool,
1436    opt: &WriteOptions,
1437    xml: &mut XmlWriter,
1438    chunk: &TextChunk,
1439    span: &TextSpan,
1440) {
1441    xml.start_svg_element(EId::Tspan);
1442
1443    let font_family_to_str = |font_family: &FontFamily| match font_family {
1444        FontFamily::Monospace => "monospace".to_string(),
1445        FontFamily::Serif => "serif".to_string(),
1446        FontFamily::SansSerif => "sans-serif".to_string(),
1447        FontFamily::Cursive => "cursive".to_string(),
1448        FontFamily::Fantasy => "fantasy".to_string(),
1449        FontFamily::Named(s) => {
1450            // Only quote if absolutely necessary
1451            match parse_font_families(s) {
1452                Ok(_) => s.clone(),
1453                Err(_) => {
1454                    if opt.use_single_quote {
1455                        format!("\"{}\"", s)
1456                    } else {
1457                        format!("'{}'", s)
1458                    }
1459                }
1460            }
1461        }
1462    };
1463
1464    if !span.font.families.is_empty() {
1465        let families = span
1466            .font
1467            .families
1468            .iter()
1469            .map(font_family_to_str)
1470            .collect::<Vec<_>>()
1471            .join(", ");
1472        xml.write_svg_attribute(AId::FontFamily, &families);
1473    }
1474
1475    match span.font.style {
1476        FontStyle::Normal => {}
1477        FontStyle::Italic => xml.write_svg_attribute(AId::FontStyle, "italic"),
1478        FontStyle::Oblique => xml.write_svg_attribute(AId::FontStyle, "oblique"),
1479    }
1480
1481    if span.font.weight != 400 {
1482        xml.write_svg_attribute(AId::FontWeight, &span.font.weight);
1483    }
1484
1485    if span.font.stretch != FontStretch::Normal {
1486        let name = match span.font.stretch {
1487            FontStretch::Condensed => "condensed",
1488            FontStretch::ExtraCondensed => "extra-condensed",
1489            FontStretch::UltraCondensed => "ultra-condensed",
1490            FontStretch::SemiCondensed => "semi-condensed",
1491            FontStretch::Expanded => "expanded",
1492            FontStretch::SemiExpanded => "semi-expanded",
1493            FontStretch::ExtraExpanded => "extra-expanded",
1494            FontStretch::UltraExpanded => "ultra-expanded",
1495            FontStretch::Normal => unreachable!(),
1496        };
1497        xml.write_svg_attribute(AId::FontStretch, name);
1498    }
1499
1500    xml.write_svg_attribute(AId::FontSize, &span.font_size);
1501
1502    xml.write_visibility(span.visible);
1503
1504    if span.letter_spacing != 0.0 {
1505        xml.write_svg_attribute(AId::LetterSpacing, &span.letter_spacing);
1506    }
1507
1508    if span.word_spacing != 0.0 {
1509        xml.write_svg_attribute(AId::WordSpacing, &span.word_spacing);
1510    }
1511
1512    if let Some(text_length) = span.text_length {
1513        xml.write_svg_attribute(AId::TextLength, &text_length);
1514    }
1515
1516    if span.length_adjust == LengthAdjust::SpacingAndGlyphs {
1517        xml.write_svg_attribute(AId::LengthAdjust, "spacingAndGlyphs");
1518    }
1519
1520    if span.small_caps {
1521        xml.write_svg_attribute(AId::FontVariant, "small-caps");
1522    }
1523
1524    if span.paint_order == PaintOrder::StrokeAndFill {
1525        xml.write_svg_attribute(AId::PaintOrder, "stroke fill");
1526    }
1527
1528    if !span.apply_kerning {
1529        xml.write_attribute("style", "font-kerning:none");
1530    }
1531
1532    if span.dominant_baseline != DominantBaseline::Auto {
1533        let name = match span.dominant_baseline {
1534            DominantBaseline::UseScript => "use-script",
1535            DominantBaseline::NoChange => "no-change",
1536            DominantBaseline::ResetSize => "reset-size",
1537            DominantBaseline::TextBeforeEdge => "text-before-edge",
1538            DominantBaseline::Middle => "middle",
1539            DominantBaseline::Central => "central",
1540            DominantBaseline::TextAfterEdge => "text-after-edge",
1541            DominantBaseline::Ideographic => "ideographic",
1542            DominantBaseline::Alphabetic => "alphabetic",
1543            DominantBaseline::Hanging => "hanging",
1544            DominantBaseline::Mathematical => "mathematical",
1545            DominantBaseline::Auto => unreachable!(),
1546        };
1547        xml.write_svg_attribute(AId::DominantBaseline, name);
1548    }
1549
1550    if span.alignment_baseline != AlignmentBaseline::Auto {
1551        let name = match span.alignment_baseline {
1552            AlignmentBaseline::Baseline => "baseline",
1553            AlignmentBaseline::BeforeEdge => "before-edge",
1554            AlignmentBaseline::TextBeforeEdge => "text-before-edge",
1555            AlignmentBaseline::Middle => "middle",
1556            AlignmentBaseline::Central => "central",
1557            AlignmentBaseline::AfterEdge => "after-edge",
1558            AlignmentBaseline::TextAfterEdge => "text-after-edge",
1559            AlignmentBaseline::Ideographic => "ideographic",
1560            AlignmentBaseline::Alphabetic => "alphabetic",
1561            AlignmentBaseline::Hanging => "hanging",
1562            AlignmentBaseline::Mathematical => "mathematical",
1563            AlignmentBaseline::Auto => unreachable!(),
1564        };
1565        xml.write_svg_attribute(AId::AlignmentBaseline, name);
1566    }
1567
1568    write_fill(&span.fill, is_clip_path, opt, xml);
1569    write_stroke(&span.stroke, opt, xml);
1570
1571    for baseline_shift in &span.baseline_shift {
1572        xml.start_svg_element(EId::Tspan);
1573        match baseline_shift {
1574            BaselineShift::Baseline => {}
1575            BaselineShift::Number(num) => xml.write_svg_attribute(AId::BaselineShift, num),
1576            BaselineShift::Subscript => xml.write_svg_attribute(AId::BaselineShift, "sub"),
1577            BaselineShift::Superscript => xml.write_svg_attribute(AId::BaselineShift, "super"),
1578        }
1579    }
1580
1581    let cur_text = &chunk.text[span.start..span.end];
1582
1583    xml.write_text(&cur_text.replace('&', "&amp;"));
1584
1585    // End for each tspan we needed to create for baseline_shift
1586    for _ in &span.baseline_shift {
1587        xml.end_element();
1588    }
1589
1590    xml.end_element();
1591}