usvg/tree/
text.rs

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