1use std::mem;
5use std::sync::Arc;
6
7use crate::GlyphId;
8use fontdb::{Database, ID};
9use skrifa::MetadataProvider;
10use skrifa::Tag;
11use skrifa::bitmap::{BitmapData, BitmapFormat};
12use skrifa::outline::{DrawSettings, OutlinePen};
13use skrifa::prelude::LocationRef;
14use skrifa::raw::TableProvider as _;
15use skrifa::raw::types::BoundingBox;
16use svgtypes::Color;
17use tiny_skia_path::{NonZeroRect, Size, Transform};
18use xmlwriter::XmlWriter;
19
20use crate::text::OPSZ;
21use crate::text::colr::GlyphPainter;
22use crate::*;
23
24fn resolve_rendering_mode(text: &Text) -> ShapeRendering {
25 match text.rendering_mode {
26 TextRendering::OptimizeSpeed => ShapeRendering::CrispEdges,
27 TextRendering::OptimizeLegibility => ShapeRendering::GeometricPrecision,
28 TextRendering::GeometricPrecision => ShapeRendering::GeometricPrecision,
29 }
30}
31
32fn effective_variations(
38 cache: &mut Cache,
39 span: &layout::Span,
40 glyph: &layout::PositionedGlyph,
41) -> Vec<FontVariation> {
42 let mut variations = span.variations.clone();
43 if span.font_optical_sizing == crate::FontOpticalSizing::Auto
44 && !variations.iter().any(|v| &v.tag == b"opsz")
45 && cache.has_opsz_axis(glyph.font)
46 {
47 variations.push(FontVariation::new(*b"opsz", glyph.font_size()));
48 }
49 variations
50}
51
52fn push_outline_paths(
53 span: &layout::Span,
54 builder: &mut tiny_skia_path::PathBuilder,
55 new_children: &mut Vec<Node>,
56 rendering_mode: ShapeRendering,
57 abs_transform: Transform,
58) {
59 let builder = mem::replace(builder, tiny_skia_path::PathBuilder::new());
60
61 if let Some(path) = builder.finish().and_then(|p| {
62 Path::new(
63 String::new(),
64 span.visible,
65 span.fill.clone(),
66 span.stroke.clone(),
67 span.paint_order,
68 rendering_mode,
69 Arc::new(p),
70 abs_transform,
71 )
72 }) {
73 new_children.push(Node::Path(Box::new(path)));
74 }
75}
76
77pub(crate) fn flatten(text: &mut Text, cache: &mut Cache) -> Option<(Group, NonZeroRect)> {
78 let mut new_children = vec![];
79
80 let abs_transform = text.abs_transform;
81 let rendering_mode = resolve_rendering_mode(text);
82
83 for span in &text.layouted {
84 if let Some(path) = span.overline.as_ref() {
85 let mut path = path.clone();
86 path.rendering_mode = rendering_mode;
87 new_children.push(Node::Path(Box::new(path)));
88 }
89
90 if let Some(path) = span.underline.as_ref() {
91 let mut path = path.clone();
92 path.rendering_mode = rendering_mode;
93 new_children.push(Node::Path(Box::new(path)));
94 }
95
96 let mut span_builder = tiny_skia_path::PathBuilder::new();
103
104 for glyph in &span.positioned_glyphs {
105 let variations = effective_variations(cache, span, glyph);
106
107 if let Some(tree) = cache.fontdb_colr(glyph.font, glyph.id, &variations) {
109 let mut group = Group {
110 transform: glyph.colr_transform(),
111 ..Group::empty()
112 };
113 group.children.push(Node::Group(Box::new(tree.root)));
116 group.calculate_bounding_boxes();
117
118 new_children.push(Node::Group(Box::new(group)));
119 }
120 else if let Some(node) = cache.fontdb_svg(glyph.font, glyph.id) {
122 push_outline_paths(
123 span,
124 &mut span_builder,
125 &mut new_children,
126 rendering_mode,
127 abs_transform,
128 );
129
130 let mut group = Group {
131 transform: glyph.svg_transform(),
132 ..Group::empty()
133 };
134 group.children.push(node);
135 group.calculate_bounding_boxes();
136
137 new_children.push(Node::Group(Box::new(group)));
138 }
139 else if let Some(img) = cache.fontdb_raster(glyph.font, glyph.id) {
141 push_outline_paths(
142 span,
143 &mut span_builder,
144 &mut new_children,
145 rendering_mode,
146 abs_transform,
147 );
148
149 let transform = if img.is_sbix {
150 glyph.sbix_transform(
151 img.x as f32,
152 img.y as f32,
153 img.glyph_bbox.map(|bbox| bbox.x_min).unwrap_or(0) as f32,
154 img.glyph_bbox.map(|bbox| bbox.y_min).unwrap_or(0) as f32,
155 img.pixels_per_em as f32,
156 img.image.size.height(),
157 )
158 } else {
159 glyph.cbdt_transform(img.x as f32, img.y as f32, img.pixels_per_em as f32)
160 };
161
162 let mut group = Group {
163 transform,
164 ..Group::empty()
165 };
166 group.children.push(Node::Image(Box::new(img.image)));
167 group.calculate_bounding_boxes();
168
169 new_children.push(Node::Group(Box::new(group)));
170 } else {
171 let outline = cache.fontdb_outline(glyph.font, glyph.id, &variations);
172
173 if let Some(outline) = outline.and_then(|p| p.transform(glyph.outline_transform()))
174 {
175 span_builder.push_path(&outline);
176 }
177 }
178 }
179
180 push_outline_paths(
181 span,
182 &mut span_builder,
183 &mut new_children,
184 rendering_mode,
185 abs_transform,
186 );
187
188 if let Some(path) = span.line_through.as_ref() {
189 let mut path = path.clone();
190 path.rendering_mode = rendering_mode;
191 new_children.push(Node::Path(Box::new(path)));
192 }
193 }
194
195 let mut group = Group {
196 id: text.id.clone(),
197 ..Group::empty()
198 };
199
200 for child in new_children {
201 group.children.push(child);
202 }
203
204 group.calculate_bounding_boxes();
205 let stroke_bbox = group.stroke_bounding_box().to_non_zero_rect()?;
206 Some((group, stroke_bbox))
207}
208
209#[derive(Default)]
210struct PathBuilder {
211 builder: tiny_skia_path::PathBuilder,
212}
213
214impl OutlinePen for PathBuilder {
215 fn move_to(&mut self, x: f32, y: f32) {
216 self.builder.move_to(x, y);
217 }
218
219 fn line_to(&mut self, x: f32, y: f32) {
220 self.builder.line_to(x, y);
221 }
222
223 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
224 self.builder.quad_to(cx0, cy0, x, y);
225 }
226
227 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
228 self.builder.cubic_to(cx0, cy0, cx1, cy1, x, y);
229 }
230
231 fn close(&mut self) {
232 self.builder.close();
233 }
234}
235
236pub(crate) trait DatabaseExt {
237 fn outline(
238 &self,
239 id: ID,
240 glyph_id: GlyphId,
241 variations: &[crate::FontVariation],
242 ) -> Option<tiny_skia_path::Path>;
243 fn has_opsz_axis(&self, id: ID) -> bool;
244 fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage>;
245 fn svg(&self, id: ID, glyph_id: GlyphId) -> Option<Node>;
246 fn colr(&self, id: ID, glyph_id: GlyphId, variations: &[crate::FontVariation]) -> Option<Tree>;
247}
248
249#[derive(Clone)]
250pub(crate) struct BitmapImage {
251 image: Image,
252 x: i16,
253 y: i16,
254 pixels_per_em: u16,
255 glyph_bbox: Option<BoundingBox<i16>>,
256 is_sbix: bool,
257}
258
259impl DatabaseExt for Database {
260 #[inline(never)]
261 fn outline(
262 &self,
263 id: ID,
264 glyph_id: GlyphId,
265 variations: &[crate::FontVariation],
266 ) -> Option<tiny_skia_path::Path> {
267 self.with_face_data(id, |data, face_index| -> Option<tiny_skia_path::Path> {
268 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
269 let outline = font.outline_glyphs().get(glyph_id.into())?;
270
271 let mut builder = PathBuilder::default();
272
273 let size = skrifa::prelude::Size::unscaled();
274 let location = font.axes().location(
278 variations
279 .iter()
280 .map(|v| (Tag::from_be_bytes(v.tag), v.value)),
281 );
282 outline
283 .draw(DrawSettings::unhinted(size, &location), &mut builder)
284 .ok()?;
285
286 builder.builder.finish()
287 })?
288 }
289
290 fn has_opsz_axis(&self, id: ID) -> bool {
291 self.with_face_data(id, |data, face_index| -> Option<bool> {
292 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
293 Some(font.axes().get_by_tag(OPSZ).is_some())
294 })
295 .flatten()
296 .unwrap_or(false)
297 }
298
299 fn raster(&self, id: ID, glyph_id: GlyphId) -> Option<BitmapImage> {
300 self.with_face_data(id, |data, face_index| -> Option<BitmapImage> {
301 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
302 let bitmap_strikes = font.bitmap_strikes();
303
304 let size = skrifa::prelude::Size::unscaled();
306 let location = LocationRef::default();
307 let image = bitmap_strikes.glyph_for_size(size, glyph_id.into())?;
308
309 match image.data {
310 BitmapData::Png(data) => {
311 let metrics = font.glyph_metrics(size, location);
312 let bounding_box = metrics.bounds(glyph_id.into()).map(|bbox| BoundingBox {
313 x_min: bbox.x_min as i16,
314 y_min: bbox.y_min as i16,
315 x_max: bbox.x_max as i16,
316 y_max: bbox.y_max as i16,
317 });
318
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(data.to_vec())),
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.inner_bearing_x as i16,
335 y: image.inner_bearing_y as i16,
336 pixels_per_em: image.ppem_x as u16,
337 glyph_bbox: bounding_box,
338 is_sbix: bitmap_strikes.format() == Some(BitmapFormat::Sbix),
339 };
340
341 Some(bitmap_image)
342 }
343 BitmapData::Bgra(_) | BitmapData::Mask(_) => None,
345 }
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> {
359 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
360 let svg_table = font.svg().ok()?;
361 let image_data = svg_table.glyph_data(glyph_id.into()).ok()??;
362 let tree = Tree::from_data(image_data, &Options::default()).ok()?;
363
364 let document_list = svg_table.svg_document_list().ok()?;
368 let doc_record = document_list.document_records().iter().find(|r| {
369 (r.start_glyph_id.get().to_u32()..=r.end_glyph_id.get().to_u32())
370 .contains(&glyph_id.0)
371 })?;
372 let node = if doc_record.start_glyph_id == doc_record.end_glyph_id {
373 Node::Group(Box::new(tree.root))
374 } else {
375 tree.node_by_id(&format!("glyph{}", glyph_id.0))
376 .log_none(|| {
377 log::warn!("Failed to find SVG glyph node for glyph {}", glyph_id.0);
378 })
379 .cloned()?
380 };
381
382 Some(node)
383 })?
384 }
385
386 fn colr(&self, id: ID, glyph_id: GlyphId, variations: &[crate::FontVariation]) -> Option<Tree> {
387 self.with_face_data(id, |data, face_index| -> Option<Tree> {
388 let font = skrifa::FontRef::from_index(data, face_index).ok()?;
389
390 let location = font.axes().location(
391 variations
392 .iter()
393 .map(|v| (Tag::from_be_bytes(v.tag), v.value)),
394 );
395
396 let mut svg = XmlWriter::new(xmlwriter::Options::default());
397
398 svg.start_element("svg");
399 svg.write_attribute("xmlns", "http://www.w3.org/2000/svg");
400 svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
401
402 let mut path_buf = String::with_capacity(256);
403 let gradient_index = 1;
404 let clip_path_index = 1;
405
406 svg.start_element("g");
407
408 let mut glyph_painter = GlyphPainter {
409 font: &font,
410 location: LocationRef::from(&location),
411 svg: &mut svg,
412 path_buf: &mut path_buf,
413 gradient_index,
414 clip_path_index,
415 foreground_color: Color::new_rgba(0, 0, 0, 255),
416 transform: skrifa::color::Transform::default(),
417 outline_transform: skrifa::color::Transform::default(),
418 transforms_stack: vec![skrifa::color::Transform::default()],
419 clip_stack: Vec::new(),
420 };
421
422 font.color_glyphs()
423 .get(glyph_id.into())?
424 .paint(&location, &mut glyph_painter)
425 .ok()?;
426 svg.end_element();
427
428 Tree::from_data(svg.end_document().as_bytes(), &Options::default()).ok()
429 })?
430 }
431}