1use std::io::Read;
4
5use ttf_parser::{GlyphId, RgbaColor};
6use typst_syntax::Span;
7use usvg::tiny_skia_path;
8use xmlwriter::XmlWriter;
9
10use crate::foundations::Bytes;
11use crate::layout::{Abs, Frame, FrameItem, Point, Size};
12use crate::text::{Font, Glyph};
13use crate::visualize::{
14 ExchangeFormat, FixedStroke, Geometry, Image, RasterImage, SvgImage,
15};
16
17pub fn should_outline(font: &Font, glyph: &Glyph) -> bool {
20 let ttf = font.ttf();
21 let glyph_id = GlyphId(glyph.id);
22 (ttf.tables().glyf.is_some() || ttf.tables().cff.is_some())
23 && !ttf
24 .glyph_raster_image(glyph_id, u16::MAX)
25 .is_some_and(|img| img.format == ttf_parser::RasterImageFormat::PNG)
26 && !ttf.is_color_glyph(glyph_id)
27 && ttf.glyph_svg_image(glyph_id).is_none()
28}
29
30#[comemo::memoize]
38pub fn glyph_frame(font: &Font, glyph_id: u16) -> (Frame, bool) {
39 let upem = Abs::pt(font.units_per_em());
40 let glyph_id = GlyphId(glyph_id);
41
42 let mut frame = Frame::soft(Size::splat(upem));
43 let mut tofu = false;
44
45 if draw_glyph(&mut frame, font, upem, glyph_id).is_none()
46 && font.ttf().glyph_index(' ') != Some(glyph_id)
47 {
48 draw_fallback_tofu(&mut frame, font, upem, glyph_id);
53 tofu = true;
54 }
55
56 (frame, tofu)
57}
58
59fn draw_glyph(
61 frame: &mut Frame,
62 font: &Font,
63 upem: Abs,
64 glyph_id: GlyphId,
65) -> Option<()> {
66 let ttf = font.ttf();
67 if let Some(raster_image) = ttf
68 .glyph_raster_image(glyph_id, u16::MAX)
69 .filter(|img| img.format == ttf_parser::RasterImageFormat::PNG)
70 {
71 draw_raster_glyph(frame, font, upem, raster_image)
72 } else if ttf.is_color_glyph(glyph_id) {
73 draw_colr_glyph(frame, font, upem, glyph_id)
74 } else if ttf.glyph_svg_image(glyph_id).is_some() {
75 draw_svg_glyph(frame, font, upem, glyph_id)
76 } else {
77 None
78 }
79}
80
81fn draw_fallback_tofu(frame: &mut Frame, font: &Font, upem: Abs, glyph_id: GlyphId) {
83 let advance = font
84 .ttf()
85 .glyph_hor_advance(glyph_id)
86 .map(|advance| Abs::pt(advance as f64))
87 .unwrap_or(upem / 3.0);
88 let inset = 0.15 * advance;
89 let height = 0.7 * upem;
90 let pos = Point::new(inset, upem - height);
91 let size = Size::new(advance - inset * 2.0, height);
92 let thickness = upem / 20.0;
93 let stroke = FixedStroke { thickness, ..Default::default() };
94 let shape = Geometry::Rect(size).stroked(stroke);
95 frame.push(pos, FrameItem::Shape(shape, Span::detached()));
96}
97
98fn draw_raster_glyph(
102 frame: &mut Frame,
103 font: &Font,
104 upem: Abs,
105 raster_image: ttf_parser::RasterGlyphImage,
106) -> Option<()> {
107 let data = Bytes::new(raster_image.data.to_vec());
108 let image = Image::plain(RasterImage::plain(data, ExchangeFormat::Png).ok()?);
109
110 let y_offset = if font.info().family.to_lowercase() == "apple color emoji" {
114 20.0
115 } else {
116 -(raster_image.y as f64)
117 };
118
119 let position = Point::new(
120 upem * raster_image.x as f64 / raster_image.pixels_per_em as f64,
121 upem * y_offset / raster_image.pixels_per_em as f64,
122 );
123 let aspect_ratio = image.width() / image.height();
124 let size = Size::new(upem, upem * aspect_ratio);
125 frame.push(position, FrameItem::Image(image, size, Span::detached()));
126
127 Some(())
128}
129
130fn draw_colr_glyph(
132 frame: &mut Frame,
133 font: &Font,
134 upem: Abs,
135 glyph_id: GlyphId,
136) -> Option<()> {
137 let mut svg = XmlWriter::new(xmlwriter::Options::default());
138
139 let ttf = font.ttf();
140 let width = ttf.global_bounding_box().width() as f64;
141 let height = ttf.global_bounding_box().height() as f64;
142 let x_min = ttf.global_bounding_box().x_min as f64;
143 let y_max = ttf.global_bounding_box().y_max as f64;
144 let tx = -x_min;
145 let ty = -y_max;
146
147 svg.start_element("svg");
148 svg.write_attribute("xmlns", "http://www.w3.org/2000/svg");
149 svg.write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
150 svg.write_attribute("width", &width);
151 svg.write_attribute("height", &height);
152 svg.write_attribute_fmt("viewBox", format_args!("0 0 {width} {height}"));
153
154 let mut path_buf = String::with_capacity(256);
155 let gradient_index = 1;
156 let clip_path_index = 1;
157
158 svg.start_element("g");
159 svg.write_attribute_fmt(
160 "transform",
161 format_args!("matrix(1 0 0 -1 0 0) matrix(1 0 0 1 {tx} {ty})"),
162 );
163
164 let mut glyph_painter = GlyphPainter {
165 face: ttf,
166 svg: &mut svg,
167 path_buf: &mut path_buf,
168 gradient_index,
169 clip_path_index,
170 palette_index: 0,
171 transform: ttf_parser::Transform::default(),
172 outline_transform: ttf_parser::Transform::default(),
173 transforms_stack: vec![ttf_parser::Transform::default()],
174 };
175
176 ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
177 svg.end_element();
178
179 let data = Bytes::from_string(svg.end_document());
180 let image = Image::plain(SvgImage::new(data).ok()?);
181
182 let y_shift = Abs::pt(upem.to_pt() - y_max);
183 let position = Point::new(Abs::pt(x_min), y_shift);
184 let size = Size::new(Abs::pt(width), Abs::pt(height));
185 frame.push(position, FrameItem::Image(image, size, Span::detached()));
186
187 Some(())
188}
189
190fn draw_svg_glyph(
192 frame: &mut Frame,
193 font: &Font,
194 upem: Abs,
195 glyph_id: GlyphId,
196) -> Option<()> {
197 let mut data = font.ttf().glyph_svg_image(glyph_id)?.data;
200
201 let mut decoded = vec![];
203 if data.starts_with(&[0x1f, 0x8b]) {
204 let mut decoder = flate2::read::GzDecoder::new(data);
205 decoder.read_to_end(&mut decoded).ok()?;
206 data = &decoded;
207 }
208
209 let xml = std::str::from_utf8(data).ok()?;
211 let document = roxmltree::Document::parse(xml).ok()?;
212
213 let opts = usvg::Options::default();
215 let tree = usvg::Tree::from_xmltree(&document, &opts).ok()?;
216
217 let bbox = tree.root().bounding_box();
218 let width = bbox.width() as f64;
219 let height = bbox.height() as f64;
220 let left = bbox.left() as f64;
221 let top = bbox.top() as f64;
222
223 let mut data = tree.to_string(&usvg::WriteOptions::default());
224
225 make_svg_unsized(&mut data);
238 let wrapper_svg = format!(
239 r#"
240 <svg
241 width="{width}"
242 height="{height}"
243 viewBox="0 0 {width} {height}"
244 xmlns="http://www.w3.org/2000/svg">
245 <g transform="matrix(1 0 0 1 {tx} {ty})">
246 {inner}
247 </g>
248 </svg>
249 "#,
250 inner = data,
251 tx = -left,
252 ty = -top,
253 );
254
255 let data = Bytes::from_string(wrapper_svg);
256 let image = Image::plain(SvgImage::new(data).ok()?);
257
258 let position = Point::new(Abs::pt(left), Abs::pt(top) + upem);
259 let size = Size::new(Abs::pt(width), Abs::pt(height));
260 frame.push(position, FrameItem::Image(image, size, Span::detached()));
261
262 Some(())
263}
264
265fn make_svg_unsized(svg: &mut String) {
268 let mut viewbox_range = None;
269 let mut width_range = None;
270 let mut height_range = None;
271
272 let mut s = unscanny::Scanner::new(svg);
273
274 s.eat_until("<svg");
275 s.eat_if("<svg");
276 while !s.eat_if('>') && !s.done() {
277 s.eat_whitespace();
278 let start = s.cursor();
279 let attr_name = s.eat_until('=').trim();
280 s.eat();
282 s.eat();
283 let mut escaped = false;
284 while (escaped || !s.eat_if('"')) && !s.done() {
285 escaped = s.eat() == Some('\\');
286 }
287 match attr_name {
288 "viewBox" => viewbox_range = Some(start..s.cursor()),
289 "width" => width_range = Some(start..s.cursor()),
290 "height" => height_range = Some(start..s.cursor()),
291 _ => {}
292 }
293 }
294
295 if let Some(range) = viewbox_range {
297 svg.replace_range(range.clone(), &" ".repeat(range.len()));
298 }
299
300 if let Some(range) = width_range {
302 svg.replace_range(range.clone(), &" ".repeat(range.len()));
303 }
304
305 if let Some(range) = height_range {
307 svg.replace_range(range, "");
308 }
309}
310
311struct ColrBuilder<'a>(&'a mut String);
312
313impl ColrBuilder<'_> {
314 fn finish(&mut self) {
315 if !self.0.is_empty() {
316 self.0.pop(); }
318 }
319}
320
321impl ttf_parser::OutlineBuilder for ColrBuilder<'_> {
322 fn move_to(&mut self, x: f32, y: f32) {
323 use std::fmt::Write;
324 write!(self.0, "M {x} {y} ").unwrap()
325 }
326
327 fn line_to(&mut self, x: f32, y: f32) {
328 use std::fmt::Write;
329 write!(self.0, "L {x} {y} ").unwrap()
330 }
331
332 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
333 use std::fmt::Write;
334 write!(self.0, "Q {x1} {y1} {x} {y} ").unwrap()
335 }
336
337 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
338 use std::fmt::Write;
339 write!(self.0, "C {x1} {y1} {x2} {y2} {x} {y} ").unwrap()
340 }
341
342 fn close(&mut self) {
343 self.0.push_str("Z ")
344 }
345}
346
347pub(crate) struct GlyphPainter<'a> {
350 pub(crate) face: &'a ttf_parser::Face<'a>,
351 pub(crate) svg: &'a mut xmlwriter::XmlWriter,
352 pub(crate) path_buf: &'a mut String,
353 pub(crate) gradient_index: usize,
354 pub(crate) clip_path_index: usize,
355 pub(crate) palette_index: u16,
356 pub(crate) transform: ttf_parser::Transform,
357 pub(crate) outline_transform: ttf_parser::Transform,
358 pub(crate) transforms_stack: Vec<ttf_parser::Transform>,
359}
360
361impl<'a> GlyphPainter<'a> {
362 fn write_gradient_stops(&mut self, stops: ttf_parser::colr::GradientStopsIter) {
363 for stop in stops {
364 self.svg.start_element("stop");
365 self.svg.write_attribute("offset", &stop.stop_offset);
366 self.write_color_attribute("stop-color", stop.color);
367 let opacity = f32::from(stop.color.alpha) / 255.0;
368 self.svg.write_attribute("stop-opacity", &opacity);
369 self.svg.end_element();
370 }
371 }
372
373 fn write_color_attribute(&mut self, name: &str, color: ttf_parser::RgbaColor) {
374 self.svg.write_attribute_fmt(
375 name,
376 format_args!("rgb({}, {}, {})", color.red, color.green, color.blue),
377 );
378 }
379
380 fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform) {
381 if ts.is_default() {
382 return;
383 }
384
385 self.svg.write_attribute_fmt(
386 name,
387 format_args!("matrix({} {} {} {} {} {})", ts.a, ts.b, ts.c, ts.d, ts.e, ts.f),
388 );
389 }
390
391 fn write_spread_method_attribute(
392 &mut self,
393 extend: ttf_parser::colr::GradientExtend,
394 ) {
395 self.svg.write_attribute(
396 "spreadMethod",
397 match extend {
398 ttf_parser::colr::GradientExtend::Pad => &"pad",
399 ttf_parser::colr::GradientExtend::Repeat => &"repeat",
400 ttf_parser::colr::GradientExtend::Reflect => &"reflect",
401 },
402 );
403 }
404
405 fn paint_solid(&mut self, color: ttf_parser::RgbaColor) {
406 self.svg.start_element("path");
407 self.write_color_attribute("fill", color);
408 let opacity = f32::from(color.alpha) / 255.0;
409 self.svg.write_attribute("fill-opacity", &opacity);
410 self.write_transform_attribute("transform", self.outline_transform);
411 self.svg.write_attribute("d", self.path_buf);
412 self.svg.end_element();
413 }
414
415 fn paint_linear_gradient(&mut self, gradient: ttf_parser::colr::LinearGradient<'a>) {
416 let gradient_id = format!("lg{}", self.gradient_index);
417 self.gradient_index += 1;
418
419 let gradient_transform = paint_transform(self.outline_transform, self.transform);
420
421 self.svg.start_element("linearGradient");
428 self.svg.write_attribute("id", &gradient_id);
429 self.svg.write_attribute("x1", &gradient.x0);
430 self.svg.write_attribute("y1", &gradient.y0);
431 self.svg.write_attribute("x2", &gradient.x1);
432 self.svg.write_attribute("y2", &gradient.y1);
433 self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
434 self.write_spread_method_attribute(gradient.extend);
435 self.write_transform_attribute("gradientTransform", gradient_transform);
436 self.write_gradient_stops(
437 gradient.stops(self.palette_index, self.face.variation_coordinates()),
438 );
439 self.svg.end_element();
440
441 self.svg.start_element("path");
442 self.svg
443 .write_attribute_fmt("fill", format_args!("url(#{gradient_id})"));
444 self.write_transform_attribute("transform", self.outline_transform);
445 self.svg.write_attribute("d", self.path_buf);
446 self.svg.end_element();
447 }
448
449 fn paint_radial_gradient(&mut self, gradient: ttf_parser::colr::RadialGradient<'a>) {
450 let gradient_id = format!("rg{}", self.gradient_index);
451 self.gradient_index += 1;
452
453 let gradient_transform = paint_transform(self.outline_transform, self.transform);
454
455 self.svg.start_element("radialGradient");
456 self.svg.write_attribute("id", &gradient_id);
457 self.svg.write_attribute("cx", &gradient.x1);
458 self.svg.write_attribute("cy", &gradient.y1);
459 self.svg.write_attribute("r", &gradient.r1);
460 self.svg.write_attribute("fr", &gradient.r0);
461 self.svg.write_attribute("fx", &gradient.x0);
462 self.svg.write_attribute("fy", &gradient.y0);
463 self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
464 self.write_spread_method_attribute(gradient.extend);
465 self.write_transform_attribute("gradientTransform", gradient_transform);
466 self.write_gradient_stops(
467 gradient.stops(self.palette_index, self.face.variation_coordinates()),
468 );
469 self.svg.end_element();
470
471 self.svg.start_element("path");
472 self.svg
473 .write_attribute_fmt("fill", format_args!("url(#{gradient_id})"));
474 self.write_transform_attribute("transform", self.outline_transform);
475 self.svg.write_attribute("d", self.path_buf);
476 self.svg.end_element();
477 }
478
479 fn paint_sweep_gradient(&mut self, _: ttf_parser::colr::SweepGradient<'a>) {}
480}
481
482fn paint_transform(
483 outline_transform: ttf_parser::Transform,
484 transform: ttf_parser::Transform,
485) -> ttf_parser::Transform {
486 let outline_transform = tiny_skia_path::Transform::from_row(
487 outline_transform.a,
488 outline_transform.b,
489 outline_transform.c,
490 outline_transform.d,
491 outline_transform.e,
492 outline_transform.f,
493 );
494
495 let gradient_transform = tiny_skia_path::Transform::from_row(
496 transform.a,
497 transform.b,
498 transform.c,
499 transform.d,
500 transform.e,
501 transform.f,
502 );
503
504 let gradient_transform = outline_transform
505 .invert()
506 .unwrap_or_default()
508 .pre_concat(gradient_transform);
509
510 ttf_parser::Transform {
511 a: gradient_transform.sx,
512 b: gradient_transform.ky,
513 c: gradient_transform.kx,
514 d: gradient_transform.sy,
515 e: gradient_transform.tx,
516 f: gradient_transform.ty,
517 }
518}
519
520impl GlyphPainter<'_> {
521 fn clip_with_path(&mut self, path: &str) {
522 let clip_id = format!("cp{}", self.clip_path_index);
523 self.clip_path_index += 1;
524
525 self.svg.start_element("clipPath");
526 self.svg.write_attribute("id", &clip_id);
527 self.svg.start_element("path");
528 self.write_transform_attribute("transform", self.outline_transform);
529 self.svg.write_attribute("d", &path);
530 self.svg.end_element();
531 self.svg.end_element();
532
533 self.svg.start_element("g");
534 self.svg
535 .write_attribute_fmt("clip-path", format_args!("url(#{clip_id})"));
536 }
537}
538
539impl<'a> ttf_parser::colr::Painter<'a> for GlyphPainter<'a> {
540 fn outline_glyph(&mut self, glyph_id: ttf_parser::GlyphId) {
541 self.path_buf.clear();
542 let mut builder = ColrBuilder(self.path_buf);
543 match self.face.outline_glyph(glyph_id, &mut builder) {
544 Some(v) => v,
545 None => return,
546 };
547 builder.finish();
548
549 self.outline_transform = self.transform;
551 }
552
553 fn push_layer(&mut self, mode: ttf_parser::colr::CompositeMode) {
554 self.svg.start_element("g");
555
556 use ttf_parser::colr::CompositeMode;
557 let mode = match mode {
560 CompositeMode::SourceOver => "normal",
561 CompositeMode::Screen => "screen",
562 CompositeMode::Overlay => "overlay",
563 CompositeMode::Darken => "darken",
564 CompositeMode::Lighten => "lighten",
565 CompositeMode::ColorDodge => "color-dodge",
566 CompositeMode::ColorBurn => "color-burn",
567 CompositeMode::HardLight => "hard-light",
568 CompositeMode::SoftLight => "soft-light",
569 CompositeMode::Difference => "difference",
570 CompositeMode::Exclusion => "exclusion",
571 CompositeMode::Multiply => "multiply",
572 CompositeMode::Hue => "hue",
573 CompositeMode::Saturation => "saturation",
574 CompositeMode::Color => "color",
575 CompositeMode::Luminosity => "luminosity",
576 _ => "normal",
577 };
578 self.svg.write_attribute_fmt(
579 "style",
580 format_args!("mix-blend-mode: {mode}; isolation: isolate"),
581 );
582 }
583
584 fn pop_layer(&mut self) {
585 self.svg.end_element(); }
587
588 fn push_transform(&mut self, transform: ttf_parser::Transform) {
589 self.transforms_stack.push(self.transform);
590 self.transform = ttf_parser::Transform::combine(self.transform, transform);
591 }
592
593 fn paint(&mut self, paint: ttf_parser::colr::Paint<'a>) {
594 match paint {
595 ttf_parser::colr::Paint::Solid(color) => self.paint_solid(color),
596 ttf_parser::colr::Paint::LinearGradient(lg) => self.paint_linear_gradient(lg),
597 ttf_parser::colr::Paint::RadialGradient(rg) => self.paint_radial_gradient(rg),
598 ttf_parser::colr::Paint::SweepGradient(sg) => self.paint_sweep_gradient(sg),
599 }
600 }
601
602 fn pop_transform(&mut self) {
603 if let Some(ts) = self.transforms_stack.pop() {
604 self.transform = ts
605 }
606 }
607
608 fn push_clip(&mut self) {
609 self.clip_with_path(&self.path_buf.clone());
610 }
611
612 fn pop_clip(&mut self) {
613 self.svg.end_element();
614 }
615
616 fn push_clip_box(&mut self, clipbox: ttf_parser::colr::ClipBox) {
617 let x_min = clipbox.x_min;
618 let x_max = clipbox.x_max;
619 let y_min = clipbox.y_min;
620 let y_max = clipbox.y_max;
621
622 let clip_path = format!(
623 "M {x_min} {y_min} L {x_max} {y_min} L {x_max} {y_max} L {x_min} {y_max} Z"
624 );
625
626 self.clip_with_path(&clip_path);
627 }
628}