Skip to main content

ai_usvg/
writer.rs

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