1 | // Copyright 2018 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use std::sync::Arc; |
5 | |
6 | use strict_num::NonZeroPositiveF32; |
7 | pub use svgtypes::FontFamily; |
8 | |
9 | #[cfg (feature = "text" )] |
10 | use crate::layout::Span; |
11 | use crate::{Fill, Group, NonEmptyString, PaintOrder, Rect, Stroke, TextRendering, Transform}; |
12 | |
13 | /// A font stretch property. |
14 | #[allow (missing_docs)] |
15 | #[derive (Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)] |
16 | pub enum FontStretch { |
17 | UltraCondensed, |
18 | ExtraCondensed, |
19 | Condensed, |
20 | SemiCondensed, |
21 | Normal, |
22 | SemiExpanded, |
23 | Expanded, |
24 | ExtraExpanded, |
25 | UltraExpanded, |
26 | } |
27 | |
28 | impl Default for FontStretch { |
29 | #[inline ] |
30 | fn default() -> Self { |
31 | Self::Normal |
32 | } |
33 | } |
34 | |
35 | #[cfg (feature = "text" )] |
36 | impl From<fontdb::Stretch> for FontStretch { |
37 | fn from(stretch: fontdb::Stretch) -> Self { |
38 | match stretch { |
39 | fontdb::Stretch::UltraCondensed => FontStretch::UltraCondensed, |
40 | fontdb::Stretch::ExtraCondensed => FontStretch::ExtraCondensed, |
41 | fontdb::Stretch::Condensed => FontStretch::Condensed, |
42 | fontdb::Stretch::SemiCondensed => FontStretch::SemiCondensed, |
43 | fontdb::Stretch::Normal => FontStretch::Normal, |
44 | fontdb::Stretch::SemiExpanded => FontStretch::SemiExpanded, |
45 | fontdb::Stretch::Expanded => FontStretch::Expanded, |
46 | fontdb::Stretch::ExtraExpanded => FontStretch::ExtraExpanded, |
47 | fontdb::Stretch::UltraExpanded => FontStretch::UltraExpanded, |
48 | } |
49 | } |
50 | } |
51 | |
52 | #[cfg (feature = "text" )] |
53 | impl From<FontStretch> for fontdb::Stretch { |
54 | fn from(stretch: FontStretch) -> Self { |
55 | match stretch { |
56 | FontStretch::UltraCondensed => fontdb::Stretch::UltraCondensed, |
57 | FontStretch::ExtraCondensed => fontdb::Stretch::ExtraCondensed, |
58 | FontStretch::Condensed => fontdb::Stretch::Condensed, |
59 | FontStretch::SemiCondensed => fontdb::Stretch::SemiCondensed, |
60 | FontStretch::Normal => fontdb::Stretch::Normal, |
61 | FontStretch::SemiExpanded => fontdb::Stretch::SemiExpanded, |
62 | FontStretch::Expanded => fontdb::Stretch::Expanded, |
63 | FontStretch::ExtraExpanded => fontdb::Stretch::ExtraExpanded, |
64 | FontStretch::UltraExpanded => fontdb::Stretch::UltraExpanded, |
65 | } |
66 | } |
67 | } |
68 | |
69 | /// A font style property. |
70 | #[derive (Clone, Copy, PartialEq, Eq, Debug, Hash)] |
71 | pub enum FontStyle { |
72 | /// A face that is neither italic not obliqued. |
73 | Normal, |
74 | /// A form that is generally cursive in nature. |
75 | Italic, |
76 | /// A typically-sloped version of the regular face. |
77 | Oblique, |
78 | } |
79 | |
80 | impl Default for FontStyle { |
81 | #[inline ] |
82 | fn default() -> FontStyle { |
83 | Self::Normal |
84 | } |
85 | } |
86 | |
87 | #[cfg (feature = "text" )] |
88 | impl From<fontdb::Style> for FontStyle { |
89 | fn from(style: fontdb::Style) -> Self { |
90 | match style { |
91 | fontdb::Style::Normal => FontStyle::Normal, |
92 | fontdb::Style::Italic => FontStyle::Italic, |
93 | fontdb::Style::Oblique => FontStyle::Oblique, |
94 | } |
95 | } |
96 | } |
97 | |
98 | #[cfg (feature = "text" )] |
99 | impl From<FontStyle> for fontdb::Style { |
100 | fn from(style: FontStyle) -> Self { |
101 | match style { |
102 | FontStyle::Normal => fontdb::Style::Normal, |
103 | FontStyle::Italic => fontdb::Style::Italic, |
104 | FontStyle::Oblique => fontdb::Style::Oblique, |
105 | } |
106 | } |
107 | } |
108 | |
109 | /// Text font properties. |
110 | #[derive (Clone, Eq, PartialEq, Hash, Debug)] |
111 | pub struct Font { |
112 | pub(crate) families: Vec<FontFamily>, |
113 | pub(crate) style: FontStyle, |
114 | pub(crate) stretch: FontStretch, |
115 | pub(crate) weight: u16, |
116 | } |
117 | |
118 | impl Font { |
119 | /// A list of family names. |
120 | /// |
121 | /// Never empty. Uses `usvg::Options::font_family` as fallback. |
122 | pub fn families(&self) -> &[FontFamily] { |
123 | &self.families |
124 | } |
125 | |
126 | /// A font style. |
127 | pub fn style(&self) -> FontStyle { |
128 | self.style |
129 | } |
130 | |
131 | /// A font stretch. |
132 | pub fn stretch(&self) -> FontStretch { |
133 | self.stretch |
134 | } |
135 | |
136 | /// A font width. |
137 | pub fn weight(&self) -> u16 { |
138 | self.weight |
139 | } |
140 | } |
141 | |
142 | /// A dominant baseline property. |
143 | #[allow (missing_docs)] |
144 | #[derive (Clone, Copy, PartialEq, Debug)] |
145 | pub enum DominantBaseline { |
146 | Auto, |
147 | UseScript, |
148 | NoChange, |
149 | ResetSize, |
150 | Ideographic, |
151 | Alphabetic, |
152 | Hanging, |
153 | Mathematical, |
154 | Central, |
155 | Middle, |
156 | TextAfterEdge, |
157 | TextBeforeEdge, |
158 | } |
159 | |
160 | impl Default for DominantBaseline { |
161 | fn default() -> Self { |
162 | Self::Auto |
163 | } |
164 | } |
165 | |
166 | /// An alignment baseline property. |
167 | #[allow (missing_docs)] |
168 | #[derive (Clone, Copy, PartialEq, Debug)] |
169 | pub enum AlignmentBaseline { |
170 | Auto, |
171 | Baseline, |
172 | BeforeEdge, |
173 | TextBeforeEdge, |
174 | Middle, |
175 | Central, |
176 | AfterEdge, |
177 | TextAfterEdge, |
178 | Ideographic, |
179 | Alphabetic, |
180 | Hanging, |
181 | Mathematical, |
182 | } |
183 | |
184 | impl Default for AlignmentBaseline { |
185 | fn default() -> Self { |
186 | Self::Auto |
187 | } |
188 | } |
189 | |
190 | /// A baseline shift property. |
191 | #[allow (missing_docs)] |
192 | #[derive (Clone, Copy, PartialEq, Debug)] |
193 | pub enum BaselineShift { |
194 | Baseline, |
195 | Subscript, |
196 | Superscript, |
197 | Number(f32), |
198 | } |
199 | |
200 | impl Default for BaselineShift { |
201 | #[inline ] |
202 | fn default() -> BaselineShift { |
203 | BaselineShift::Baseline |
204 | } |
205 | } |
206 | |
207 | /// A length adjust property. |
208 | #[allow (missing_docs)] |
209 | #[derive (Clone, Copy, PartialEq, Debug)] |
210 | pub enum LengthAdjust { |
211 | Spacing, |
212 | SpacingAndGlyphs, |
213 | } |
214 | |
215 | impl Default for LengthAdjust { |
216 | fn default() -> Self { |
217 | Self::Spacing |
218 | } |
219 | } |
220 | |
221 | /// A text span decoration style. |
222 | /// |
223 | /// In SVG, text decoration and text it's applied to can have different styles. |
224 | /// So you can have black text and green underline. |
225 | /// |
226 | /// Also, in SVG you can specify text decoration stroking. |
227 | #[derive (Clone, Debug)] |
228 | pub struct TextDecorationStyle { |
229 | pub(crate) fill: Option<Fill>, |
230 | pub(crate) stroke: Option<Stroke>, |
231 | } |
232 | |
233 | impl TextDecorationStyle { |
234 | /// A fill style. |
235 | pub fn fill(&self) -> Option<&Fill> { |
236 | self.fill.as_ref() |
237 | } |
238 | |
239 | /// A stroke style. |
240 | pub fn stroke(&self) -> Option<&Stroke> { |
241 | self.stroke.as_ref() |
242 | } |
243 | } |
244 | |
245 | /// A text span decoration. |
246 | #[derive (Clone, Debug)] |
247 | pub struct TextDecoration { |
248 | pub(crate) underline: Option<TextDecorationStyle>, |
249 | pub(crate) overline: Option<TextDecorationStyle>, |
250 | pub(crate) line_through: Option<TextDecorationStyle>, |
251 | } |
252 | |
253 | impl TextDecoration { |
254 | /// An optional underline and its style. |
255 | pub fn underline(&self) -> Option<&TextDecorationStyle> { |
256 | self.underline.as_ref() |
257 | } |
258 | |
259 | /// An optional overline and its style. |
260 | pub fn overline(&self) -> Option<&TextDecorationStyle> { |
261 | self.overline.as_ref() |
262 | } |
263 | |
264 | /// An optional line-through and its style. |
265 | pub fn line_through(&self) -> Option<&TextDecorationStyle> { |
266 | self.line_through.as_ref() |
267 | } |
268 | } |
269 | |
270 | /// A text style span. |
271 | /// |
272 | /// Spans do not overlap inside a text chunk. |
273 | #[derive (Clone, Debug)] |
274 | pub struct TextSpan { |
275 | pub(crate) start: usize, |
276 | pub(crate) end: usize, |
277 | pub(crate) fill: Option<Fill>, |
278 | pub(crate) stroke: Option<Stroke>, |
279 | pub(crate) paint_order: PaintOrder, |
280 | pub(crate) font: Font, |
281 | pub(crate) font_size: NonZeroPositiveF32, |
282 | pub(crate) small_caps: bool, |
283 | pub(crate) apply_kerning: bool, |
284 | pub(crate) decoration: TextDecoration, |
285 | pub(crate) dominant_baseline: DominantBaseline, |
286 | pub(crate) alignment_baseline: AlignmentBaseline, |
287 | pub(crate) baseline_shift: Vec<BaselineShift>, |
288 | pub(crate) visible: bool, |
289 | pub(crate) letter_spacing: f32, |
290 | pub(crate) word_spacing: f32, |
291 | pub(crate) text_length: Option<f32>, |
292 | pub(crate) length_adjust: LengthAdjust, |
293 | } |
294 | |
295 | impl TextSpan { |
296 | /// A span start in bytes. |
297 | /// |
298 | /// Offset is relative to the parent text chunk and not the parent text element. |
299 | pub fn start(&self) -> usize { |
300 | self.start |
301 | } |
302 | |
303 | /// A span end in bytes. |
304 | /// |
305 | /// Offset is relative to the parent text chunk and not the parent text element. |
306 | pub fn end(&self) -> usize { |
307 | self.end |
308 | } |
309 | |
310 | /// A fill style. |
311 | pub fn fill(&self) -> Option<&Fill> { |
312 | self.fill.as_ref() |
313 | } |
314 | |
315 | /// A stroke style. |
316 | pub fn stroke(&self) -> Option<&Stroke> { |
317 | self.stroke.as_ref() |
318 | } |
319 | |
320 | /// A paint order style. |
321 | pub fn paint_order(&self) -> PaintOrder { |
322 | self.paint_order |
323 | } |
324 | |
325 | /// A font. |
326 | pub fn font(&self) -> &Font { |
327 | &self.font |
328 | } |
329 | |
330 | /// A font size. |
331 | pub fn font_size(&self) -> NonZeroPositiveF32 { |
332 | self.font_size |
333 | } |
334 | |
335 | /// Indicates that small caps should be used. |
336 | /// |
337 | /// Set by `font-variant="small-caps"` |
338 | pub fn small_caps(&self) -> bool { |
339 | self.small_caps |
340 | } |
341 | |
342 | /// Indicates that a kerning should be applied. |
343 | /// |
344 | /// Supports both `kerning` and `font-kerning` properties. |
345 | pub fn apply_kerning(&self) -> bool { |
346 | self.apply_kerning |
347 | } |
348 | |
349 | /// A span decorations. |
350 | pub fn decoration(&self) -> &TextDecoration { |
351 | &self.decoration |
352 | } |
353 | |
354 | /// A span dominant baseline. |
355 | pub fn dominant_baseline(&self) -> DominantBaseline { |
356 | self.dominant_baseline |
357 | } |
358 | |
359 | /// A span alignment baseline. |
360 | pub fn alignment_baseline(&self) -> AlignmentBaseline { |
361 | self.alignment_baseline |
362 | } |
363 | |
364 | /// A list of all baseline shift that should be applied to this span. |
365 | /// |
366 | /// Ordered from `text` element down to the actual `span` element. |
367 | pub fn baseline_shift(&self) -> &[BaselineShift] { |
368 | &self.baseline_shift |
369 | } |
370 | |
371 | /// A visibility property. |
372 | pub fn is_visible(&self) -> bool { |
373 | self.visible |
374 | } |
375 | |
376 | /// A letter spacing property. |
377 | pub fn letter_spacing(&self) -> f32 { |
378 | self.letter_spacing |
379 | } |
380 | |
381 | /// A word spacing property. |
382 | pub fn word_spacing(&self) -> f32 { |
383 | self.word_spacing |
384 | } |
385 | |
386 | /// A text length property. |
387 | pub fn text_length(&self) -> Option<f32> { |
388 | self.text_length |
389 | } |
390 | |
391 | /// A length adjust property. |
392 | pub fn length_adjust(&self) -> LengthAdjust { |
393 | self.length_adjust |
394 | } |
395 | } |
396 | |
397 | /// A text chunk anchor property. |
398 | #[allow (missing_docs)] |
399 | #[derive (Clone, Copy, PartialEq, Debug)] |
400 | pub enum TextAnchor { |
401 | Start, |
402 | Middle, |
403 | End, |
404 | } |
405 | |
406 | impl Default for TextAnchor { |
407 | fn default() -> Self { |
408 | Self::Start |
409 | } |
410 | } |
411 | |
412 | /// A path used by text-on-path. |
413 | #[derive (Debug)] |
414 | pub struct TextPath { |
415 | pub(crate) id: NonEmptyString, |
416 | pub(crate) start_offset: f32, |
417 | pub(crate) path: Arc<tiny_skia_path::Path>, |
418 | } |
419 | |
420 | impl TextPath { |
421 | /// Element's ID. |
422 | /// |
423 | /// Taken from the SVG itself. |
424 | pub fn id(&self) -> &str { |
425 | self.id.get() |
426 | } |
427 | |
428 | /// A text offset in SVG coordinates. |
429 | /// |
430 | /// Percentage values already resolved. |
431 | pub fn start_offset(&self) -> f32 { |
432 | self.start_offset |
433 | } |
434 | |
435 | /// A path. |
436 | pub fn path(&self) -> &tiny_skia_path::Path { |
437 | &self.path |
438 | } |
439 | } |
440 | |
441 | /// A text chunk flow property. |
442 | #[derive (Clone, Debug)] |
443 | pub enum TextFlow { |
444 | /// A linear layout. |
445 | /// |
446 | /// Includes left-to-right, right-to-left and top-to-bottom. |
447 | Linear, |
448 | /// A text-on-path layout. |
449 | Path(Arc<TextPath>), |
450 | } |
451 | |
452 | /// A text chunk. |
453 | /// |
454 | /// Text alignment and BIDI reordering can only be done inside a text chunk. |
455 | #[derive (Clone, Debug)] |
456 | pub struct TextChunk { |
457 | pub(crate) x: Option<f32>, |
458 | pub(crate) y: Option<f32>, |
459 | pub(crate) anchor: TextAnchor, |
460 | pub(crate) spans: Vec<TextSpan>, |
461 | pub(crate) text_flow: TextFlow, |
462 | pub(crate) text: String, |
463 | } |
464 | |
465 | impl TextChunk { |
466 | /// An absolute X axis offset. |
467 | pub fn x(&self) -> Option<f32> { |
468 | self.x |
469 | } |
470 | |
471 | /// An absolute Y axis offset. |
472 | pub fn y(&self) -> Option<f32> { |
473 | self.y |
474 | } |
475 | |
476 | /// A text anchor. |
477 | pub fn anchor(&self) -> TextAnchor { |
478 | self.anchor |
479 | } |
480 | |
481 | /// A list of text chunk style spans. |
482 | pub fn spans(&self) -> &[TextSpan] { |
483 | &self.spans |
484 | } |
485 | |
486 | /// A text chunk flow. |
487 | pub fn text_flow(&self) -> TextFlow { |
488 | self.text_flow.clone() |
489 | } |
490 | |
491 | /// A text chunk actual text. |
492 | pub fn text(&self) -> &str { |
493 | &self.text |
494 | } |
495 | } |
496 | |
497 | /// A writing mode. |
498 | #[allow (missing_docs)] |
499 | #[derive (Clone, Copy, PartialEq, Debug)] |
500 | pub enum WritingMode { |
501 | LeftToRight, |
502 | TopToBottom, |
503 | } |
504 | |
505 | /// A text element. |
506 | /// |
507 | /// `text` element in SVG. |
508 | #[derive (Clone, Debug)] |
509 | pub struct Text { |
510 | pub(crate) id: String, |
511 | pub(crate) rendering_mode: TextRendering, |
512 | pub(crate) dx: Vec<f32>, |
513 | pub(crate) dy: Vec<f32>, |
514 | pub(crate) rotate: Vec<f32>, |
515 | pub(crate) writing_mode: WritingMode, |
516 | pub(crate) chunks: Vec<TextChunk>, |
517 | pub(crate) abs_transform: Transform, |
518 | pub(crate) bounding_box: Rect, |
519 | pub(crate) abs_bounding_box: Rect, |
520 | pub(crate) stroke_bounding_box: Rect, |
521 | pub(crate) abs_stroke_bounding_box: Rect, |
522 | pub(crate) flattened: Box<Group>, |
523 | #[cfg (feature = "text" )] |
524 | pub(crate) layouted: Vec<Span>, |
525 | } |
526 | |
527 | impl Text { |
528 | /// Element's ID. |
529 | /// |
530 | /// Taken from the SVG itself. |
531 | /// Isn't automatically generated. |
532 | /// Can be empty. |
533 | pub fn id(&self) -> &str { |
534 | &self.id |
535 | } |
536 | |
537 | /// Rendering mode. |
538 | /// |
539 | /// `text-rendering` in SVG. |
540 | pub fn rendering_mode(&self) -> TextRendering { |
541 | self.rendering_mode |
542 | } |
543 | |
544 | /// A relative X axis offsets. |
545 | /// |
546 | /// One offset for each Unicode codepoint. Aka `char` in Rust. |
547 | pub fn dx(&self) -> &[f32] { |
548 | &self.dx |
549 | } |
550 | |
551 | /// A relative Y axis offsets. |
552 | /// |
553 | /// One offset for each Unicode codepoint. Aka `char` in Rust. |
554 | pub fn dy(&self) -> &[f32] { |
555 | &self.dy |
556 | } |
557 | |
558 | /// A list of rotation angles. |
559 | /// |
560 | /// One angle for each Unicode codepoint. Aka `char` in Rust. |
561 | pub fn rotate(&self) -> &[f32] { |
562 | &self.rotate |
563 | } |
564 | |
565 | /// A writing mode. |
566 | pub fn writing_mode(&self) -> WritingMode { |
567 | self.writing_mode |
568 | } |
569 | |
570 | /// A list of text chunks. |
571 | pub fn chunks(&self) -> &[TextChunk] { |
572 | &self.chunks |
573 | } |
574 | |
575 | /// Element's absolute transform. |
576 | /// |
577 | /// Contains all ancestors transforms including elements's transform. |
578 | /// |
579 | /// Note that this is not the relative transform present in SVG. |
580 | /// The SVG one would be set only on groups. |
581 | pub fn abs_transform(&self) -> Transform { |
582 | self.abs_transform |
583 | } |
584 | |
585 | /// Element's text bounding box. |
586 | /// |
587 | /// Text bounding box is special in SVG and doesn't represent |
588 | /// tight bounds of the element's content. |
589 | /// You can find more about it |
590 | /// [here](https://razrfalcon.github.io/notes-on-svg-parsing/text/bbox.html). |
591 | /// |
592 | /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms. |
593 | /// |
594 | /// Returns `None` when the `text` build feature was disabled. |
595 | /// This is because we have to perform a text layout before calculating a bounding box. |
596 | pub fn bounding_box(&self) -> Rect { |
597 | self.bounding_box |
598 | } |
599 | |
600 | /// Element's text bounding box in canvas coordinates. |
601 | /// |
602 | /// `userSpaceOnUse` in SVG terms. |
603 | pub fn abs_bounding_box(&self) -> Rect { |
604 | self.abs_bounding_box |
605 | } |
606 | |
607 | /// Element's object bounding box including stroke. |
608 | /// |
609 | /// Similar to `bounding_box`, but includes stroke. |
610 | /// |
611 | /// Will have the same value as `bounding_box` when path has no stroke. |
612 | pub fn stroke_bounding_box(&self) -> Rect { |
613 | self.stroke_bounding_box |
614 | } |
615 | |
616 | /// Element's bounding box including stroke in canvas coordinates. |
617 | pub fn abs_stroke_bounding_box(&self) -> Rect { |
618 | self.abs_stroke_bounding_box |
619 | } |
620 | |
621 | /// Text converted into paths, ready to render. |
622 | /// |
623 | /// Note that this is only a |
624 | /// "best-effort" attempt: The text will be converted into group/paths/image |
625 | /// primitives, so that they can be rendered with the existing infrastructure. |
626 | /// This process is in general lossless and should lead to correct output, with |
627 | /// two notable exceptions: |
628 | /// 1. For glyphs based on the `SVG` table, only glyphs that are pure SVG 1.1/2.0 |
629 | /// are supported. Glyphs that make use of features in the OpenType specification |
630 | /// that are not part of the original SVG specification are not supported. |
631 | /// 2. For glyphs based on the `COLR` table, there are a certain number of features |
632 | /// that are not (correctly) supported, such as conical |
633 | /// gradients, certain gradient transforms and some blend modes. But this shouldn't |
634 | /// cause any issues in 95% of the cases, as most of those are edge cases. |
635 | /// If the two above are not acceptable, then you will need to implement your own |
636 | /// glyph rendering logic based on the layouted glyphs (see the `layouted` method). |
637 | pub fn flattened(&self) -> &Group { |
638 | &self.flattened |
639 | } |
640 | |
641 | /// The positioned glyphs and decoration spans of the text. |
642 | /// |
643 | /// This should only be used if you need more low-level access |
644 | /// to the glyphs that make up the text. If you just need the |
645 | /// outlines of the text, you should use `flattened` instead. |
646 | #[cfg (feature = "text" )] |
647 | pub fn layouted(&self) -> &[Span] { |
648 | &self.layouted |
649 | } |
650 | |
651 | pub(crate) fn subroots(&self, f: &mut dyn FnMut(&Group)) { |
652 | f(&self.flattened); |
653 | } |
654 | } |
655 | |