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