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