usvg/tree/
text.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use std::sync::Arc;
5
6use strict_num::NonZeroPositiveF32;
7pub use svgtypes::FontFamily;
8
9#[cfg(feature = "text")]
10use crate::layout::Span;
11use 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)]
16pub enum FontStretch {
17    UltraCondensed,
18    ExtraCondensed,
19    Condensed,
20    SemiCondensed,
21    Normal,
22    SemiExpanded,
23    Expanded,
24    ExtraExpanded,
25    UltraExpanded,
26}
27
28impl Default for FontStretch {
29    #[inline]
30    fn default() -> Self {
31        Self::Normal
32    }
33}
34
35#[cfg(feature = "text")]
36impl 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")]
53impl 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)]
71pub 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
80impl Default for FontStyle {
81    #[inline]
82    fn default() -> FontStyle {
83        Self::Normal
84    }
85}
86
87#[cfg(feature = "text")]
88impl 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")]
99impl 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)]
111pub 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
118impl 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)]
145pub 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
160impl 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)]
169pub 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
184impl 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)]
193pub enum BaselineShift {
194    Baseline,
195    Subscript,
196    Superscript,
197    Number(f32),
198}
199
200impl 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)]
210pub enum LengthAdjust {
211    Spacing,
212    SpacingAndGlyphs,
213}
214
215impl 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)]
228pub struct TextDecorationStyle {
229    pub(crate) fill: Option<Fill>,
230    pub(crate) stroke: Option<Stroke>,
231}
232
233impl 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)]
247pub struct TextDecoration {
248    pub(crate) underline: Option<TextDecorationStyle>,
249    pub(crate) overline: Option<TextDecorationStyle>,
250    pub(crate) line_through: Option<TextDecorationStyle>,
251}
252
253impl 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)]
274pub 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
295impl 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)]
400pub enum TextAnchor {
401    Start,
402    Middle,
403    End,
404}
405
406impl Default for TextAnchor {
407    fn default() -> Self {
408        Self::Start
409    }
410}
411
412/// A path used by text-on-path.
413#[derive(Debug)]
414pub struct TextPath {
415    pub(crate) id: NonEmptyString,
416    pub(crate) start_offset: f32,
417    pub(crate) path: Arc<tiny_skia_path::Path>,
418}
419
420impl 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)]
443pub 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)]
456pub 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
465impl 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)]
500pub enum WritingMode {
501    LeftToRight,
502    TopToBottom,
503}
504
505/// A text element.
506///
507/// `text` element in SVG.
508#[derive(Clone, Debug)]
509pub 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
527impl 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}