Skip to main content

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