Skip to main content

usvg/tree/
text.rs

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