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