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