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}
655