1use std::mem;
5use std::sync::Arc;
6
7use fontdb::{Database, ID};
8use rustybuzz::ttf_parser;
9use rustybuzz::ttf_parser::{GlyphId, RasterImageFormat, RgbaColor};
10use tiny_skia_path::{NonZeroRect, Size, Transform};
11use xmlwriter::XmlWriter;
12
13use crate::text::colr::GlyphPainter;
14use crate::*;
15
16fn 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
24fn 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 = mem::replace(builder, tiny_skia_path::PathBuilder::new());
31
32 if let Some(path) = builder.finish().and_then(|p| {
33 Path::new(
34 String::new(),
35 span.visible,
36 span.fill.clone(),
37 span.stroke.clone(),
38 span.paint_order,
39 rendering_mode,
40 Arc::new(p),
41 Transform::default(),
42 )
43 }) {
44 new_children.push(Node::Path(Box::new(path)));
45 }
46}
47
48pub(crate) fn flatten(text: &mut Text, cache: &mut Cache) -> 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 let mut span_builder = tiny_skia_path::PathBuilder::new();
73
74 let has_explicit_variations = !span.variations.is_empty();
77
78 for glyph in &span.positioned_glyphs {
79 if let Some(tree) = cache.fontdb_colr(glyph.font, glyph.id) {
81 let mut group = Group {
82 transform: glyph.colr_transform(),
83 ..Group::empty()
84 };
85 group.children.push(Node::Group(Box::new(tree.root)));
87 group.calculate_bounding_boxes();
88
89 new_children.push(Node::Group(Box::new(group)));
90 }
91 else if let Some(node) = cache.fontdb_svg(glyph.font, glyph.id) {
93 push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
94
95 let mut group = Group {
96 transform: glyph.svg_transform(),
97 ..Group::empty()
98 };
99 group.children.push(node);
101 group.calculate_bounding_boxes();
102
103 new_children.push(Node::Group(Box::new(group)));
104 }
105 else if let Some(img) = cache.fontdb_raster(glyph.font, glyph.id) {
107 push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
108
109 let transform = if img.is_sbix {
110 glyph.sbix_transform(
111 img.x as f32,
112 img.y as f32,
113 img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32,
114 img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32,
115 img.pixels_per_em as f32,
116 img.image.size.height(),
117 )
118 } else {
119 glyph.cbdt_transform(
120 img.x as f32,
121 img.y as f32,
122 img.pixels_per_em as f32,
123 img.image.size.height(),
124 )
125 };
126
127 let mut group = Group {
128 transform,
129 ..Group::empty()
130 };
131 group.children.push(Node::Image(Box::new(img.image)));
132 group.calculate_bounding_boxes();
133
134 new_children.push(Node::Group(Box::new(group)));
135 } else {
136 let needs_variations = has_explicit_variations
138 || (span.font_optical_sizing == crate::FontOpticalSizing::Auto
139 && cache.has_opsz_axis(glyph.font));
140
141 let outline = if needs_variations {
142 cache.fontdb.outline_with_variations(
143 glyph.font,
144 glyph.id,
145 &span.variations,
146 glyph.font_size(),
147 span.font_optical_sizing,
148 )
149 } else {
150 cache.fontdb_outline(glyph.font, glyph.id)
151 };
152
153 if let Some(outline) = outline.and_then(|p| p.transform(glyph.outline_transform()))
154 {
155 span_builder.push_path(&outline);
156 }
157 }
158 }
159
160 push_outline_paths(span, &mut span_builder, &mut new_children, rendering_mode);
161
162 if let Some(path) = span.line_through.as_ref() {
163 let mut path = path.clone();
164 path.rendering_mode = rendering_mode;
165 new_children.push(Node::Path(Box::new(path)));
166 }
167 }
168
169 let mut group = Group {
170 id: text.id.clone(),
171 ..Group::empty()
172 };
173
174 for child in new_children {
175 group.children.push(child);
176 }
177
178 group.calculate_bounding_boxes();
179 let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?;
180 Some((group, stroke_bbox))
181}
182
183struct PathBuilder {
184 builder: tiny_skia_path::PathBuilder,
185}
186
187impl ttf_parser::OutlineBuilder for PathBuilder {
188 fn move_to(&mut self, x: f32, y: f32) {
189 self.builder.move_to(x, y);
190 }
191
192 fn line_to(&mut self, x: f32, y: f32) {
193 self.builder.line_to(x, y);
194 }
195
196 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
197 self.builder.quad_to(x1, y1, x, y);
198 }
199
200 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
201 self.builder.cubic_to(x1, y1, x2, y2, x, y);
202 }
203
204 fn close(&mut self) {
205 self.builder.close();
206 }
207}
208
209pub(crate) trait DatabaseExt {
210 fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path>;
211 fn outline_with_variations(
212 &self,
213 id: ID,
214 glyph_id: GlyphId,
215 variations: &[crate::FontVariation],
216 font_size: f32,
217 font_optical_sizing: crate::FontOpticalSizing,
218 ) -> Option<tiny_skia_path::Path>;
219 fn has_opsz_axis(&self, id: ID) -> bool;
220 fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage>;
221 fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node>;
222 fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree>;
223}
224
225#[derive(Clone)]
226pub(crate) struct BitmapImage {
227 image: Image,
228 x: i16,
229 y: i16,
230 pixels_per_em: u16,
231 glyph_bbox: Option<ttf_parser::Rect>,
232 is_sbix: bool,
233}
234
235impl DatabaseExt for Database {
236 #[inline(never)]
237 fn outline(&self, id: ID, glyph_id: GlyphId) -> Option<tiny_skia_path::Path> {
238 self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
239 let mut font = ttf_parser::Face::parse(data, face_index).ok()?;
240
241 if font.is_variable() {
243 for axis in font.variation_axes() {
244 font.set_variation(axis.tag, axis.def_value);
245 }
246 }
247
248 let mut builder = PathBuilder {
249 builder: tiny_skia_path::PathBuilder::new(),
250 };
251
252 font.outline_glyph(glyph_id, &mut builder)?;
253 builder.builder.finish()
254 })?
255 }
256
257 #[inline(never)]
258 fn outline_with_variations(
259 &self,
260 id: ID,
261 glyph_id: GlyphId,
262 variations: &[crate::FontVariation],
263 font_size: f32,
264 font_optical_sizing: crate::FontOpticalSizing,
265 ) -> Option<tiny_skia_path::Path> {
266 self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
267 let mut font = ttf_parser::Face::parse(data, face_index).ok()?;
268
269 for v in variations {
270 font.set_variation(ttf_parser::Tag::from_bytes(&v.tag), v.value);
271 }
272
273 if font_optical_sizing == crate::FontOpticalSizing::Auto {
275 let has_explicit_opsz = variations.iter().any(|v| v.tag == *b"opsz");
276 if !has_explicit_opsz {
277 if let Some(axes) = font.tables().fvar {
279 let has_opsz_axis = axes
280 .axes
281 .into_iter()
282 .any(|axis| axis.tag == ttf_parser::Tag::from_bytes(b"opsz"));
283 if has_opsz_axis {
284 font.set_variation(ttf_parser::Tag::from_bytes(b"opsz"), font_size);
285 }
286 }
287 }
288 }
289
290 let mut builder = PathBuilder {
291 builder: tiny_skia_path::PathBuilder::new(),
292 };
293
294 font.outline_glyph(glyph_id, &mut builder)?;
295 builder.builder.finish()
296 })?
297 }
298
299 fn has_opsz_axis(&self, id: ID) -> bool {
300 self.with_face_data(id, |data, face_index| -> Option<bool> {
301 let font = ttf_parser::Face::parse(data, face_index).ok()?;
302 let has_opsz = font.tables().fvar.map_or(false, |axes| {
303 axes.axes
304 .into_iter()
305 .any(|axis| axis.tag == ttf_parser::Tag::from_bytes(b"opsz"))
306 });
307 Some(has_opsz)
308 })
309 .flatten()
310 .unwrap_or(false)
311 }
312
313 fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage> {
314 self.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
315 let font = ttf_parser::Face::parse(data, face_index).ok()?;
316 let image = font.glyph_raster_image(glyph_id, u16::MAX)?;
317
318 if image.format == RasterImageFormat::PNG {
319 let bitmap_image = BitmapImage {
320 image: Image {
321 id: String::new(),
322 visible: true,
323 size: Size::from_wh(image.width as f32, image.height as f32)?,
324 rendering_mode: ImageRendering::OptimizeQuality,
325 kind: ImageKind::PNG(Arc::new(image.data.into())),
326 abs_transform: Transform::default(),
327 abs_bounding_box: NonZeroRect::from_xywh(
328 0.0,
329 0.0,
330 image.width as f32,
331 image.height as f32,
332 )?,
333 },
334 x: image.x,
335 y: image.y,
336 pixels_per_em: image.pixels_per_em,
337 glyph_bbox: font.glyph_bounding_box(glyph_id),
338 is_sbix: font.tables().sbix.is_some(),
340 };
341
342 return Some(bitmap_image);
343 }
344
345 None
346 })?
347 }
348
349 fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node> {
350 self.with_face_data(id, |data, face_index| -> Option<Node> {
357 let font = ttf_parser::Face::parse(data, face_index).ok()?;
358 let image = font.glyph_svg_image(glyph_id)?;
359 let tree = Tree::from_data(image.data, &Options::default()).ok()?;
360
361 let node = if image.start_glyph_id == image.end_glyph_id {
365 Node::Group(Box::new(tree.root))
366 } else {
367 tree.node_by_id(&format!("glyph{}", glyph_id.0))
368 .log_none(|| {
369 log::warn!("Failed to find SVG glyph node for glyph {}", glyph_id.0);
370 })
371 .cloned()?
372 };
373
374 Some(node)
375 })?
376 }
377
378 fn colr(&self, id: ID, glyph_id: GlyphId) -> Option<Tree> {
379 self.with_face_data(id, |data, face_index| -> Option<Tree> {
380 let face = ttf_parser::Face::parse(data, face_index).ok()?;
381
382 let mut svg = XmlWriter::new(xmlwriter::Options::default());
383
384 svg.start_element("svg");
385 svg.write_attribute("xmlns", "http://www.w3.org/2000/svg");
386 svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
387
388 let mut path_buf = String::with_capacity(256);
389 let gradient_index = 1;
390 let clip_path_index = 1;
391
392 svg.start_element("g");
393
394 let mut glyph_painter = GlyphPainter {
395 face: &face,
396 svg: &mut svg,
397 path_buf: &mut path_buf,
398 gradient_index,
399 clip_path_index,
400 palette_index: 0,
401 transform: ttf_parser::Transform::default(),
402 outline_transform: ttf_parser::Transform::default(),
403 transforms_stack: vec![ttf_parser::Transform::default()],
404 };
405
406 face.paint_color_glyph(
407 glyph_id,
408 0,
409 RgbaColor::new(0, 0, 0, 255),
410 &mut glyph_painter,
411 )?;
412 svg.end_element();
413
414 Tree::from_data(svg.end_document().as_bytes(), &Options::default()).ok()
415 })?
416 }
417}