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