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