1use 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 let mut span_builder = tiny_skia_path::PathBuilder::new();
74
75 for glyph in &span.positioned_glyphs {
76 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 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 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 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 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 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 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}