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 | |
5 | use std::fmt::Display; |
6 | use std::io::Write; |
7 | |
8 | use svgtypes::{parse_font_families, FontFamily}; |
9 | use xmlwriter::XmlWriter; |
10 | |
11 | use crate::parser::{AId, EId}; |
12 | use crate::*; |
13 | |
14 | impl 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. |
22 | trait IsDefault: Default { |
23 | /// Checks that type has a default value. |
24 | fn is_default(&self) -> bool; |
25 | } |
26 | |
27 | impl<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)] |
36 | pub 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 | |
128 | impl 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 | |
142 | pub(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 | |
167 | fn 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 | |
529 | fn 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 | |
617 | fn 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 | |
645 | fn 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 | |
651 | fn 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 | |
800 | fn 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 | |
903 | trait 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 | |
922 | impl 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, &litude); |
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 | |
1166 | fn 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 | |
1221 | fn 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 | |
1243 | fn 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 | |
1336 | fn 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 | |
1358 | fn 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 | |
1402 | fn 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 | |
1417 | fn 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 | |
1448 | static 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 | |
1464 | fn 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. |
1484 | fn 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('&' , "&" )); |
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 | |