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