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 | |
5 | use std::sync::Arc; |
6 | |
7 | use strict_num::NonZeroPositiveF32; |
8 | pub use svgtypes::FontFamily; |
9 | |
10 | use 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)] |
17 | pub enum FontStretch { |
18 | UltraCondensed, |
19 | ExtraCondensed, |
20 | Condensed, |
21 | SemiCondensed, |
22 | Normal, |
23 | SemiExpanded, |
24 | Expanded, |
25 | ExtraExpanded, |
26 | UltraExpanded, |
27 | } |
28 | |
29 | impl 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)] |
38 | pub 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 | |
47 | impl 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)] |
56 | pub 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 | |
63 | impl 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)] |
90 | pub 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 | |
105 | impl 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)] |
114 | pub 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 | |
129 | impl 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)] |
138 | pub enum BaselineShift { |
139 | Baseline, |
140 | Subscript, |
141 | Superscript, |
142 | Number(f32), |
143 | } |
144 | |
145 | impl 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)] |
155 | pub enum LengthAdjust { |
156 | Spacing, |
157 | SpacingAndGlyphs, |
158 | } |
159 | |
160 | impl 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)] |
173 | pub struct TextDecorationStyle { |
174 | pub(crate) fill: Option<Fill>, |
175 | pub(crate) stroke: Option<Stroke>, |
176 | } |
177 | |
178 | impl 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)] |
192 | pub struct TextDecoration { |
193 | pub(crate) underline: Option<TextDecorationStyle>, |
194 | pub(crate) overline: Option<TextDecorationStyle>, |
195 | pub(crate) line_through: Option<TextDecorationStyle>, |
196 | } |
197 | |
198 | impl 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)] |
219 | pub 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 | |
240 | impl 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)] |
345 | pub enum TextAnchor { |
346 | Start, |
347 | Middle, |
348 | End, |
349 | } |
350 | |
351 | impl Default for TextAnchor { |
352 | fn default() -> Self { |
353 | Self::Start |
354 | } |
355 | } |
356 | |
357 | /// A path used by text-on-path. |
358 | #[derive (Debug)] |
359 | pub struct TextPath { |
360 | pub(crate) id: NonEmptyString, |
361 | pub(crate) start_offset: f32, |
362 | pub(crate) path: Arc<tiny_skia_path::Path>, |
363 | } |
364 | |
365 | impl 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)] |
388 | pub 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)] |
401 | pub 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 | |
410 | impl 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)] |
445 | pub enum WritingMode { |
446 | LeftToRight, |
447 | TopToBottom, |
448 | } |
449 | |
450 | /// A text element. |
451 | /// |
452 | /// `text` element in SVG. |
453 | #[derive (Clone, Debug)] |
454 | pub 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 | |
470 | impl 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 | |