usvg/text/
flatten.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::mem;
6use std::sync::Arc;
7
8use fontdb::{Database, ID};
9use rustybuzz::ttf_parser;
10use rustybuzz::ttf_parser::{GlyphId, RasterImageFormat, RgbaColor};
11use tiny_skia_path::{NonZeroRect, Size, Transform};
12use xmlwriter::XmlWriter;
13
14use crate::text::colr::GlyphPainter;
15use crate::*;
16
17fn resolve_rendering_mode(text: &Text) -> ShapeRendering {
18    match text.rendering_mode {
19        TextRendering::OptimizeSpeed => ShapeRendering::CrispEdges,
20        TextRendering::OptimizeLegibility => ShapeRendering::GeometricPrecision,
21        TextRendering::GeometricPrecision => ShapeRendering::GeometricPrecision,
22    }
23}
24
25fn push_outline_paths(
26    span: &layout::Span,
27    builder: &mut tiny_skia_path::PathBuilder,
28    new_children: &mut Vec<Node>,
29    rendering_mode: ShapeRendering,
30) {
31    let builder = mem::replace(builder, tiny_skia_path::PathBuilder::new());
32
33    if let Some(path) = builder.finish().and_then(|p| {
34        Path::new(
35            String::new(),
36            span.visible,
37            span.fill.clone(),
38            span.stroke.clone(),
39            span.paint_order,
40            rendering_mode,
41            Arc::new(p),
42            Transform::default(),
43        )
44    }) {
45        new_children.push(Node::Path(Box::new(path)));
46    }
47}
48
49pub(crate) fn flatten(text: &mut Text, fontdb: &fontdb::Database) -> Option<(Group, NonZeroRect)> {
50    let mut new_children = vec![];
51
52    let rendering_mode = resolve_rendering_mode(text);
53
54    for span in &text.layouted {
55        if let Some(path) = span.overline.as_ref() {
56            let mut path = path.clone();
57            path.rendering_mode = rendering_mode;
58            new_children.push(Node::Path(Box::new(path)));
59        }
60
61        if let Some(path) = span.underline.as_ref() {
62            let mut path = path.clone();
63            path.rendering_mode = rendering_mode;
64            new_children.push(Node::Path(Box::new(path)));
65        }
66
67        // Instead of always processing each glyph separately, we always collect
68        // as many outline glyphs as possible by pushing them into the span_builder
69        // and only if we encounter a different glyph, or we reach the very end of the
70        // span to we push the actual outline paths into new_children. This way, we don't need
71        // to create a new path for every glyph if we have many consecutive glyphs
72        // with just outlines (which is the most common case).
73        let mut span_builder = tiny_skia_path::PathBuilder::new();
74
75        for glyph in &span.positioned_glyphs {
76            // A (best-effort conversion of a) COLR glyph.
77            if let Some(tree) = fontdb.colr(glyph.font, glyph.id) {
78                let mut group = Group {
79                    transform: glyph.colr_transform(),
80                    ..Group::empty()
81                };
82                // TODO: Probably need to update abs_transform of children?
83                group.children.push(Node::Group(Box::new(tree.root)));
84                group.calculate_bounding_boxes();
85
86                new_children.push(Node::Group(Box::new(group)));
87            }
88            // An SVG glyph. Will return the usvg tree containing the glyph descriptions.
89            else if let Some(tree) = fontdb.svg(glyph.font, glyph.id) {
90                push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
91
92                let mut group = Group {
93                    transform: glyph.svg_transform(),
94                    ..Group::empty()
95                };
96                // TODO: Probably need to update abs_transform of children?
97                group.children.push(Node::Group(Box::new(tree.root)));
98                group.calculate_bounding_boxes();
99
100                new_children.push(Node::Group(Box::new(group)));
101            }
102            // A bitmap glyph.
103            else if let Some(img) = fontdb.raster(glyph.font, glyph.id) {
104                push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
105
106                let transform = if img.is_sbix {
107                    glyph.sbix_transform(
108                        img.x as f32,
109                        img.y as f32,
110                        img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32,
111                        img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32,
112                        img.pixels_per_em as f32,
113                        img.image.size.height(),
114                    )
115                } else {
116                    glyph.cbdt_transform(
117                        img.x as f32,
118                        img.y as f32,
119                        img.pixels_per_em as f32,
120                        img.image.size.height(),
121                    )
122                };
123
124                let mut group = Group {
125                    transform,
126                    ..Group::empty()
127                };
128                group.children.push(Node::Image(Box::new(img.image)));
129                group.calculate_bounding_boxes();
130
131                new_children.push(Node::Group(Box::new(group)));
132            } else if let Some(outline) = fontdb
133                .outline(glyph.font, glyph.id)
134                .and_then(|p| p.transform(glyph.outline_transform()))
135            {
136                span_builder.push_path(&outline);
137            }
138        }
139
140        push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
141
142        if let Some(path) = span.line_through.as_ref() {
143            let mut path = path.clone();
144            path.rendering_mode = rendering_mode;
145            new_children.push(Node::Path(Box::new(path)));
146        }
147    }
148
149    let mut group = Group {
150        id: text.id.clone(),
151        ..Group::empty()
152    };
153
154    for child in new_children {
155        group.children.push(child);
156    }
157
158    group.calculate_bounding_boxes();
159    let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?;
160    Some((group, stroke_bbox))
161}
162
163struct PathBuilder {
164    builder: tiny_skia_path::PathBuilder,
165}
166
167impl ttf_parser::OutlineBuilder for PathBuilder {
168    fn move_to(&mut self, x: f32, y: f32) {
169        self.builder.move_to(x, y);
170    }
171
172    fn line_to(&mut self, x: f32, y: f32) {
173        self.builder.line_to(x, y);
174    }
175
176    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
177        self.builder.quad_to(x1, y1, x, y);
178    }
179
180    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
181        self.builder.cubic_to(x1, y1, x2, y2, x, y);
182    }
183
184    fn close(&mut self) {
185        self.builder.close();
186    }
187}
188
189pub(crate) trait DatabaseExt {
190    fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path>;
191    fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage>;
192    fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Tree>;
193    fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree>;
194}
195
196pub(crate) struct BitmapImage {
197    image: Image,
198    x: i16,
199    y: i16,
200    pixels_per_em: u16,
201    glyph_bbox: Option<ttf_parser::Rect>,
202    is_sbix: bool,
203}
204
205impl DatabaseExt for Database {
206    #[inline(never)]
207    fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path> {
208        self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
209            let font = ttf_parser::Face::parse(data, face_index).ok()?;
210
211            let mut builder = PathBuilder {
212                builder: tiny_skia_path::PathBuilder::new(),
213            };
214
215            font.outline_glyph(glyph_id, &mut builder)?;
216            builder.builder.finish()
217        })?
218    }
219
220    fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage> {
221        self.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
222            let font = ttf_parser::Face::parse(data, face_index).ok()?;
223            let image = font.glyph_raster_image(glyph_id, u16::MAX)?;
224
225            if image.format == RasterImageFormat::PNG {
226                let bitmap_image = BitmapImage {
227                    image: Image {
228                        id: String::new(),
229                        visible: true,
230                        size: Size::from_wh(image.width as f32, image.height as f32)?,
231                        rendering_mode: ImageRendering::OptimizeQuality,
232                        kind: ImageKind::PNG(Arc::new(image.data.into())),
233                        abs_transform: Transform::default(),
234                        abs_bounding_box: NonZeroRect::from_xywh(
235                            0.0,
236                            0.0,
237                            image.width as f32,
238                            image.height as f32,
239                        )?,
240                    },
241                    x: image.x,
242                    y: image.y,
243                    pixels_per_em: image.pixels_per_em,
244                    glyph_bbox: font.glyph_bounding_box(glyph_id),
245                    // ttf-parser always checks sbix first, so if this table exists, it was used.
246                    is_sbix: font.tables().sbix.is_some(),
247                };
248
249                return Some(bitmap_image);
250            }
251
252            None
253        })?
254    }
255
256    fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Tree> {
257        // TODO: Technically not 100% accurate because the SVG format in a OTF font
258        // is actually a subset/superset of a normal SVG, but it seems to work fine
259        // for Twitter Color Emoji, so might as well use what we already have.
260        self.with_face_data(id, |data, face_index| -> Option<Tree> {
261            let font = ttf_parser::Face::parse(data, face_index).ok()?;
262            let image = font.glyph_svg_image(glyph_id)?;
263            Tree::from_data(image.data, &Options::default()).ok()
264        })?
265    }
266
267    fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree> {
268        self.with_face_data(id, |data, face_index| -> Option<Tree> {
269            let face = ttf_parser::Face::parse(data, face_index).ok()?;
270
271            let mut svg = XmlWriter::new(xmlwriter::Options::default());
272
273            svg.start_element("svg");
274            svg.write_attribute("xmlns", "http://www.w3.org/2000/svg");
275            svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
276
277            let mut path_buf = String::with_capacity(256);
278            let gradient_index = 1;
279            let clip_path_index = 1;
280
281            svg.start_element("g");
282
283            let mut glyph_painter = GlyphPainter {
284                face: &face,
285                svg: &mut svg,
286                path_buf: &mut path_buf,
287                gradient_index,
288                clip_path_index,
289                palette_index: 0,
290                transform: ttf_parser::Transform::default(),
291                outline_transform: ttf_parser::Transform::default(),
292                transforms_stack: vec![ttf_parser::Transform::default()],
293            };
294
295            face.paint_color_glyph(
296                glyph_id,
297                0,
298                RgbaColor::new(0, 0, 0, 255),
299                &mut glyph_painter,
300            )?;
301            svg.end_element();
302
303            Tree::from_data(&svg.end_document().as_bytes(), &Options::default()).ok()
304        })?
305    }
306}