usvg/
writer.rs

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