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::rc::Rc;
6
7use strict_num::NonZeroPositiveF32;
8
9use crate::{Fill, Group, Paint, PaintOrder, Stroke, TextRendering, Visibility};
10use tiny_skia_path::{NonZeroRect, Transform};
11
12/// A font stretch property.
13#[allow(missing_docs)]
14#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
15pub enum FontStretch {
16    UltraCondensed,
17    ExtraCondensed,
18    Condensed,
19    SemiCondensed,
20    Normal,
21    SemiExpanded,
22    Expanded,
23    ExtraExpanded,
24    UltraExpanded,
25}
26
27impl Default for FontStretch {
28    #[inline]
29    fn default() -> Self {
30        Self::Normal
31    }
32}
33
34/// A font style property.
35#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
36pub enum FontStyle {
37    /// A face that is neither italic not obliqued.
38    Normal,
39    /// A form that is generally cursive in nature.
40    Italic,
41    /// A typically-sloped version of the regular face.
42    Oblique,
43}
44
45impl Default for FontStyle {
46    #[inline]
47    fn default() -> FontStyle {
48        Self::Normal
49    }
50}
51
52/// Text font properties.
53#[derive(Clone, Eq, PartialEq, Hash, Debug)]
54pub struct Font {
55    /// A list of family names.
56    ///
57    /// Never empty. Uses `usvg_parser::Options::font_family` as fallback.
58    pub families: Vec<String>,
59    /// A font style.
60    pub style: FontStyle,
61    /// A font stretch.
62    pub stretch: FontStretch,
63    /// A font width.
64    pub weight: u16,
65}
66
67/// A dominant baseline property.
68#[allow(missing_docs)]
69#[derive(Clone, Copy, PartialEq, Debug)]
70pub enum DominantBaseline {
71    Auto,
72    UseScript,
73    NoChange,
74    ResetSize,
75    Ideographic,
76    Alphabetic,
77    Hanging,
78    Mathematical,
79    Central,
80    Middle,
81    TextAfterEdge,
82    TextBeforeEdge,
83}
84
85impl Default for DominantBaseline {
86    fn default() -> Self {
87        Self::Auto
88    }
89}
90
91/// An alignment baseline property.
92#[allow(missing_docs)]
93#[derive(Clone, Copy, PartialEq, Debug)]
94pub enum AlignmentBaseline {
95    Auto,
96    Baseline,
97    BeforeEdge,
98    TextBeforeEdge,
99    Middle,
100    Central,
101    AfterEdge,
102    TextAfterEdge,
103    Ideographic,
104    Alphabetic,
105    Hanging,
106    Mathematical,
107}
108
109impl Default for AlignmentBaseline {
110    fn default() -> Self {
111        Self::Auto
112    }
113}
114
115/// A baseline shift property.
116#[allow(missing_docs)]
117#[derive(Clone, Copy, PartialEq, Debug)]
118pub enum BaselineShift {
119    Baseline,
120    Subscript,
121    Superscript,
122    Number(f32),
123}
124
125impl Default for BaselineShift {
126    #[inline]
127    fn default() -> BaselineShift {
128        BaselineShift::Baseline
129    }
130}
131
132/// A length adjust property.
133#[allow(missing_docs)]
134#[derive(Clone, Copy, PartialEq, Debug)]
135pub enum LengthAdjust {
136    Spacing,
137    SpacingAndGlyphs,
138}
139
140impl Default for LengthAdjust {
141    fn default() -> Self {
142        Self::Spacing
143    }
144}
145
146/// A text span decoration style.
147///
148/// In SVG, text decoration and text it's applied to can have different styles.
149/// So you can have black text and green underline.
150///
151/// Also, in SVG you can specify text decoration stroking.
152#[derive(Clone, Debug)]
153pub struct TextDecorationStyle {
154    /// A fill style.
155    pub fill: Option<Fill>,
156    /// A stroke style.
157    pub stroke: Option<Stroke>,
158}
159
160/// A text span decoration.
161#[derive(Clone, Debug)]
162pub struct TextDecoration {
163    /// An optional underline and its style.
164    pub underline: Option<TextDecorationStyle>,
165    /// An optional overline and its style.
166    pub overline: Option<TextDecorationStyle>,
167    /// An optional line-through and its style.
168    pub line_through: Option<TextDecorationStyle>,
169}
170
171/// A text style span.
172///
173/// Spans do not overlap inside a text chunk.
174#[derive(Clone, Debug)]
175pub struct TextSpan {
176    /// A span start in bytes.
177    ///
178    /// Offset is relative to the parent text chunk and not the parent text element.
179    pub start: usize,
180    /// A span end in bytes.
181    ///
182    /// Offset is relative to the parent text chunk and not the parent text element.
183    pub end: usize,
184    /// A fill style.
185    pub fill: Option<Fill>,
186    /// A stroke style.
187    pub stroke: Option<Stroke>,
188    /// A paint order style.
189    pub paint_order: PaintOrder,
190    /// A font.
191    pub font: Font,
192    /// A font size.
193    pub font_size: NonZeroPositiveF32,
194    /// Indicates that small caps should be used.
195    ///
196    /// Set by `font-variant="small-caps"`
197    pub small_caps: bool,
198    /// Indicates that a kerning should be applied.
199    ///
200    /// Supports both `kerning` and `font-kerning` properties.
201    pub apply_kerning: bool,
202    /// A span decorations.
203    pub decoration: TextDecoration,
204    /// A span dominant baseline.
205    pub dominant_baseline: DominantBaseline,
206    /// A span alignment baseline.
207    pub alignment_baseline: AlignmentBaseline,
208    /// A list of all baseline shift that should be applied to this span.
209    ///
210    /// Ordered from `text` element down to the actual `span` element.
211    pub baseline_shift: Vec<BaselineShift>,
212    /// A visibility property.
213    pub visibility: Visibility,
214    /// A letter spacing property.
215    pub letter_spacing: f32,
216    /// A word spacing property.
217    pub word_spacing: f32,
218    /// A text length property.
219    pub text_length: Option<f32>,
220    /// A length adjust property.
221    pub length_adjust: LengthAdjust,
222}
223
224/// A text chunk anchor property.
225#[allow(missing_docs)]
226#[derive(Clone, Copy, PartialEq, Debug)]
227pub enum TextAnchor {
228    Start,
229    Middle,
230    End,
231}
232
233impl Default for TextAnchor {
234    fn default() -> Self {
235        Self::Start
236    }
237}
238
239/// A path used by text-on-path.
240#[derive(Clone, Debug)]
241pub struct TextPath {
242    /// Element's ID.
243    ///
244    /// Taken from the SVG itself.
245    pub id: String,
246
247    /// A text offset in SVG coordinates.
248    ///
249    /// Percentage values already resolved.
250    pub start_offset: f32,
251
252    /// A path.
253    pub path: Rc<tiny_skia_path::Path>,
254}
255
256/// A text chunk flow property.
257#[derive(Clone, Debug)]
258pub enum TextFlow {
259    /// A linear layout.
260    ///
261    /// Includes left-to-right, right-to-left and top-to-bottom.
262    Linear,
263    /// A text-on-path layout.
264    Path(Rc<TextPath>),
265}
266
267/// A text chunk.
268///
269/// Text alignment and BIDI reordering can only be done inside a text chunk.
270#[derive(Clone, Debug)]
271pub struct TextChunk {
272    /// An absolute X axis offset.
273    pub x: Option<f32>,
274    /// An absolute Y axis offset.
275    pub y: Option<f32>,
276    /// A text anchor.
277    pub anchor: TextAnchor,
278    /// A list of text chunk style spans.
279    pub spans: Vec<TextSpan>,
280    /// A text chunk flow.
281    pub text_flow: TextFlow,
282    /// A text chunk actual text.
283    pub text: String,
284}
285
286/// A writing mode.
287#[allow(missing_docs)]
288#[derive(Clone, Copy, PartialEq, Debug)]
289pub enum WritingMode {
290    LeftToRight,
291    TopToBottom,
292}
293
294/// A text element.
295///
296/// `text` element in SVG.
297#[derive(Clone, Debug)]
298pub struct Text {
299    /// Element's ID.
300    ///
301    /// Taken from the SVG itself.
302    /// Isn't automatically generated.
303    /// Can be empty.
304    pub id: String,
305
306    /// Rendering mode.
307    ///
308    /// `text-rendering` in SVG.
309    pub rendering_mode: TextRendering,
310
311    /// A relative X axis offsets.
312    ///
313    /// One offset for each Unicode codepoint. Aka `char` in Rust.
314    pub dx: Vec<f32>,
315
316    /// A relative Y axis offsets.
317    ///
318    /// One offset for each Unicode codepoint. Aka `char` in Rust.
319    pub dy: Vec<f32>,
320
321    /// A list of rotation angles.
322    ///
323    /// One angle for each Unicode codepoint. Aka `char` in Rust.
324    pub rotate: Vec<f32>,
325
326    /// A writing mode.
327    pub writing_mode: WritingMode,
328
329    /// A list of text chunks.
330    pub chunks: Vec<TextChunk>,
331
332    /// Element's absolute transform.
333    ///
334    /// Contains all ancestors transforms.
335    ///
336    /// Will be set after calling `usvg::Tree::postprocess`.
337    ///
338    /// Note that this is not the relative transform present in SVG.
339    /// The SVG one would be set only on groups.
340    pub abs_transform: Transform,
341
342    /// Contains a text bounding box.
343    ///
344    /// Text bounding box is special in SVG and doesn't represent
345    /// tight bounds of the element's content.
346    /// You can find more about it
347    /// [here](https://razrfalcon.github.io/notes-on-svg-parsing/text/bbox.html).
348    ///
349    /// `objectBoundingBox` in SVG terms. Meaning it doesn't affected by parent transforms.
350    ///
351    /// Will be set only after calling `usvg::Tree::postprocess` with
352    /// `usvg::PostProcessingSteps::convert_text_into_paths`.
353    /// Assuming the `text` build feature of `usvg` was enabled.
354    /// This is because we have to perform a text layout before calculating a bounding box.
355    pub bounding_box: Option<NonZeroRect>,
356
357    /// Element's object bounding box including stroke.
358    ///
359    /// Similar to `bounding_box`, but includes stroke.
360    ///
361    /// Will have the same value as `bounding_box` when path has no stroke.
362    pub stroke_bounding_box: Option<NonZeroRect>,
363
364    /// Text converted into paths, ready to render.
365    ///
366    /// Will be set only after calling `usvg::Tree::postprocess` with
367    /// `usvg::PostProcessingSteps::convert_text_into_paths`.
368    /// Assuming the `text` build feature of `usvg` was enabled.
369    pub flattened: Option<Box<Group>>,
370}
371
372impl Text {
373    pub(crate) fn subroots(&self, f: &mut dyn FnMut(&Group)) {
374        if let Some(ref flattened) = self.flattened {
375            f(flattened);
376            // Return now, since text chunks would have the same styles
377            // as the flattened text, which would lead to duplicates.
378            return;
379        }
380
381        let mut push_patt = |paint: Option<&Paint>| {
382            if let Some(Paint::Pattern(ref patt)) = paint {
383                f(&patt.borrow().root);
384            }
385        };
386
387        for chunk in &self.chunks {
388            for span in &chunk.spans {
389                push_patt(span.fill.as_ref().map(|f| &f.paint));
390                push_patt(span.stroke.as_ref().map(|f| &f.paint));
391
392                // Each text decoration can have paint.
393                if let Some(ref underline) = span.decoration.underline {
394                    push_patt(underline.fill.as_ref().map(|f| &f.paint));
395                    push_patt(underline.stroke.as_ref().map(|f| &f.paint));
396                }
397
398                if let Some(ref overline) = span.decoration.overline {
399                    push_patt(overline.fill.as_ref().map(|f| &f.paint));
400                    push_patt(overline.stroke.as_ref().map(|f| &f.paint));
401                }
402
403                if let Some(ref line_through) = span.decoration.line_through {
404                    push_patt(line_through.fill.as_ref().map(|f| &f.paint));
405                    push_patt(line_through.stroke.as_ref().map(|f| &f.paint));
406                }
407            }
408        }
409    }
410
411    pub(crate) fn subroots_mut(&mut self, f: &mut dyn FnMut(&mut Group)) {
412        if let Some(ref mut flattened) = self.flattened {
413            f(flattened);
414            // Return now, since text chunks would have the same styles
415            // as the flattened text, which would lead to duplicates.
416            return;
417        }
418
419        let mut push_patt = |paint: Option<&Paint>| {
420            if let Some(Paint::Pattern(ref patt)) = paint {
421                f(&mut patt.borrow_mut().root);
422            }
423        };
424
425        for chunk in &self.chunks {
426            for span in &chunk.spans {
427                push_patt(span.fill.as_ref().map(|f| &f.paint));
428                push_patt(span.stroke.as_ref().map(|f| &f.paint));
429
430                // Each text decoration can have paint.
431                if let Some(ref underline) = span.decoration.underline {
432                    push_patt(underline.fill.as_ref().map(|f| &f.paint));
433                    push_patt(underline.stroke.as_ref().map(|f| &f.paint));
434                }
435
436                if let Some(ref overline) = span.decoration.overline {
437                    push_patt(overline.fill.as_ref().map(|f| &f.paint));
438                    push_patt(overline.stroke.as_ref().map(|f| &f.paint));
439                }
440
441                if let Some(ref line_through) = span.decoration.line_through {
442                    push_patt(line_through.fill.as_ref().map(|f| &f.paint));
443                    push_patt(line_through.stroke.as_ref().map(|f| &f.paint));
444                }
445            }
446        }
447    }
448}