1use crate::parser::OptionLog;
5use skrifa::instance::LocationRef;
6use skrifa::prelude::Size;
7use skrifa::raw::types::Point;
8use skrifa::{
9 MetadataProvider,
10 color::{Brush, ColorStop, Extend, Transform},
11 outline::DrawSettings,
12};
13use std::fmt::Write as _;
14use svgtypes::Color;
15
16use super::transform::{skrifa_to_tsp_transform, tsp_to_skrifa_transform};
17
18struct Builder<'a> {
19 path: &'a mut String,
20 min_x: f32,
21 min_y: f32,
22 max_x: f32,
23 max_y: f32,
24}
25
26impl<'a> Builder<'a> {
27 fn new(path: &'a mut String) -> Self {
28 Self {
29 path,
30 min_x: f32::MAX,
31 min_y: f32::MAX,
32 max_x: f32::MIN,
33 max_y: f32::MIN,
34 }
35 }
36
37 fn add_point(&mut self, x: f32, y: f32) {
38 self.min_x = self.min_x.min(x);
39 self.min_y = self.min_y.min(y);
40 self.max_x = self.max_x.max(x);
41 self.max_y = self.max_y.max(y);
42 }
43
44 fn bounds(&self) -> Option<tiny_skia_path::Rect> {
48 if self.min_x <= self.max_x && self.min_y <= self.max_y {
49 tiny_skia_path::Rect::from_ltrb(self.min_x, self.min_y, self.max_x, self.max_y)
50 } else {
51 None
52 }
53 }
54
55 fn finish(&mut self) {
56 if !self.path.is_empty() {
57 self.path.pop(); }
59 }
60}
61
62impl skrifa::outline::OutlinePen for Builder<'_> {
63 fn move_to(&mut self, x: f32, y: f32) {
64 self.add_point(x, y);
65 write!(self.path, "M {} {} ", x, y).unwrap();
66 }
67
68 fn line_to(&mut self, x: f32, y: f32) {
69 self.add_point(x, y);
70 write!(self.path, "L {} {} ", x, y).unwrap();
71 }
72
73 fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
74 self.add_point(cx0, cy0);
75 self.add_point(x, y);
76 write!(self.path, "Q {} {} {} {} ", cx0, cy0, x, y).unwrap();
77 }
78
79 fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
80 self.add_point(cx0, cy0);
81 self.add_point(cx1, cy1);
82 self.add_point(x, y);
83 write!(self.path, "C {} {} {} {} {} {} ", cx0, cy0, cx1, cy1, x, y).unwrap();
84 }
85
86 fn close(&mut self) {
87 self.path.push_str("Z ");
88 }
89}
90
91trait XmlWriterExt {
92 fn write_color_attribute(&mut self, name: &str, ts: Color);
93 fn write_transform_attribute(&mut self, name: &str, ts: Transform);
94 fn write_spread_method_attribute(&mut self, method: Extend);
95}
96
97impl XmlWriterExt for xmlwriter::XmlWriter {
98 fn write_color_attribute(&mut self, name: &str, color: Color) {
99 self.write_attribute_fmt(
100 name,
101 format_args!("rgb({}, {}, {})", color.red, color.green, color.blue),
102 );
103 }
104
105 fn write_transform_attribute(&mut self, name: &str, ts: Transform) {
106 if ts == Transform::default() {
107 return;
108 }
109
110 self.write_attribute_fmt(
111 name,
112 format_args!(
113 "matrix({} {} {} {} {} {})",
114 ts.xx, ts.yx, ts.xy, ts.yy, ts.dx, ts.dy
115 ),
116 );
117 }
118
119 fn write_spread_method_attribute(&mut self, extend: Extend) {
120 self.write_attribute(
121 "spreadMethod",
122 match extend {
123 Extend::Pad => "pad",
124 Extend::Repeat => "repeat",
125 Extend::Reflect => "reflect",
126 Extend::Unknown => return,
127 },
128 );
129 }
130}
131
132pub(crate) struct GlyphPainter<'a> {
134 pub(crate) font: &'a skrifa::FontRef<'a>,
135 pub(crate) location: LocationRef<'a>,
137 pub(crate) svg: &'a mut xmlwriter::XmlWriter,
138 pub(crate) path_buf: &'a mut String,
139 pub(crate) gradient_index: usize,
140 pub(crate) clip_path_index: usize,
141 pub(crate) foreground_color: Color,
142 pub(crate) transform: Transform,
143 pub(crate) outline_transform: Transform,
144 pub(crate) transforms_stack: Vec<Transform>,
145 pub(crate) clip_stack: Vec<Option<tiny_skia_path::Rect>>,
148}
149
150impl<'a> GlyphPainter<'a> {
151 fn write_gradient_stops(&mut self, stops: &[ColorStop]) {
152 for stop in stops {
153 let color = self.palette_index_to_color(stop.palette_index, stop.alpha);
154 self.svg.start_element("stop");
155 self.svg.write_attribute("offset", &stop.offset);
156 self.svg.write_color_attribute("stop-color", color);
157 let opacity = f32::from(color.alpha) / 255.0;
158 self.svg.write_attribute("stop-opacity", &opacity);
159 self.svg.end_element();
160 }
161 }
162
163 fn paint_solid(&mut self, color: Color) {
164 self.svg.start_element("path");
165 self.svg.write_color_attribute("fill", color);
166 let opacity = f32::from(color.alpha) / 255.0;
167 self.svg.write_attribute("fill-opacity", &opacity);
168 self.svg
169 .write_transform_attribute("transform", self.outline_transform);
170 self.svg.write_attribute("d", self.path_buf);
171 self.svg.end_element();
172 }
173
174 fn paint_linear_gradient(
175 &mut self,
176 p0: Point<f32>,
177 p1: Point<f32>,
178 color_stops: &[ColorStop],
179 extend: Extend,
180 ) {
181 let gradient_id = format!("lg{}", self.gradient_index);
182 self.gradient_index += 1;
183
184 let gradient_transform = paint_transform(self.outline_transform, self.transform);
185
186 self.svg.start_element("linearGradient");
187 self.svg.write_attribute("id", &gradient_id);
188 self.svg.write_attribute("x1", &p0.x);
189 self.svg.write_attribute("y1", &p0.y);
190 self.svg.write_attribute("x2", &p1.x);
191 self.svg.write_attribute("y2", &p1.y);
192 self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
193 self.svg.write_spread_method_attribute(extend);
194 self.svg
195 .write_transform_attribute("gradientTransform", gradient_transform);
196 self.write_gradient_stops(color_stops);
197 self.svg.end_element();
198
199 self.svg.start_element("path");
200 self.svg
201 .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id));
202 self.svg
203 .write_transform_attribute("transform", self.outline_transform);
204 self.svg.write_attribute("d", self.path_buf);
205 self.svg.end_element();
206 }
207
208 fn paint_radial_gradient(
209 &mut self,
210 c0: Point<f32>,
211 r0: f32,
212 c1: Point<f32>,
213 r1: f32,
214 color_stops: &[ColorStop],
215 extend: Extend,
216 ) {
217 let gradient_id = format!("rg{}", self.gradient_index);
218 self.gradient_index += 1;
219
220 let gradient_transform = paint_transform(self.outline_transform, self.transform);
221
222 self.svg.start_element("radialGradient");
228 self.svg.write_attribute("id", &gradient_id);
229 self.svg.write_attribute("cx", &c1.x);
230 self.svg.write_attribute("cy", &c1.y);
231 self.svg.write_attribute("r", &r1);
232 self.svg.write_attribute("fr", &r0);
233 self.svg.write_attribute("fx", &c0.x);
234 self.svg.write_attribute("fy", &c0.y);
235 self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
236 self.svg.write_spread_method_attribute(extend);
237 self.svg
238 .write_transform_attribute("gradientTransform", gradient_transform);
239 self.write_gradient_stops(color_stops);
240 self.svg.end_element();
241
242 self.svg.start_element("path");
243 self.svg
244 .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id));
245 self.svg
246 .write_transform_attribute("transform", self.outline_transform);
247 self.svg.write_attribute("d", self.path_buf);
248 self.svg.end_element();
249 }
250
251 fn paint_sweep_gradient(
252 &mut self,
253 _c0: Point<f32>,
254 _start_angle: f32,
255 _end_angle: f32,
256 _color_stops: &[ColorStop],
257 _extend: Extend,
258 ) {
259 }
262}
263
264fn paint_transform(outline_transform: Transform, transform: Transform) -> Transform {
265 let outline_transform = skrifa_to_tsp_transform(outline_transform);
266 let gradient_transform = skrifa_to_tsp_transform(transform);
267
268 let gradient_transform = outline_transform
269 .invert()
270 .log_none(|| log::warn!("Failed to calculate transform for gradient in glyph."))
271 .unwrap_or_default()
272 .pre_concat(gradient_transform);
273
274 tsp_to_skrifa_transform(gradient_transform)
275}
276
277fn map_rect(
279 rect: tiny_skia_path::Rect,
280 ts: tiny_skia_path::Transform,
281) -> Option<tiny_skia_path::Rect> {
282 let mut points = [
283 tiny_skia_path::Point::from_xy(rect.left(), rect.top()),
284 tiny_skia_path::Point::from_xy(rect.right(), rect.top()),
285 tiny_skia_path::Point::from_xy(rect.left(), rect.bottom()),
286 tiny_skia_path::Point::from_xy(rect.right(), rect.bottom()),
287 ];
288 ts.map_points(&mut points);
289 let min_x = points.iter().map(|p| p.x).fold(f32::MAX, f32::min);
290 let min_y = points.iter().map(|p| p.y).fold(f32::MAX, f32::min);
291 let max_x = points.iter().map(|p| p.x).fold(f32::MIN, f32::max);
292 let max_y = points.iter().map(|p| p.y).fold(f32::MIN, f32::max);
293 tiny_skia_path::Rect::from_ltrb(min_x, min_y, max_x, max_y)
294}
295
296fn intersect_rects(
298 a: tiny_skia_path::Rect,
299 b: tiny_skia_path::Rect,
300) -> Option<tiny_skia_path::Rect> {
301 tiny_skia_path::Rect::from_ltrb(
302 a.left().max(b.left()),
303 a.top().max(b.top()),
304 a.right().min(b.right()),
305 a.bottom().min(b.bottom()),
306 )
307}
308
309impl GlyphPainter<'_> {
310 fn clip_with_path(&mut self, path: &str) {
311 let clip_id = format!("cp{}", self.clip_path_index);
312 self.clip_path_index += 1;
313
314 self.svg.start_element("clipPath");
315 self.svg.write_attribute("id", &clip_id);
316 self.svg.start_element("path");
317 self.svg
318 .write_transform_attribute("transform", self.outline_transform);
319 self.svg.write_attribute("d", &path);
320 self.svg.end_element();
321 self.svg.end_element();
322
323 self.svg.start_element("g");
324 self.svg
325 .write_attribute_fmt("clip-path", format_args!("url(#{})", clip_id));
326 }
327
328 fn outline_glyph(&mut self, glyph_id: skrifa::GlyphId) -> Option<tiny_skia_path::Rect> {
333 self.path_buf.clear();
334
335 let mut bounds = None;
336 let outlined = if let Some(outliner) = self.font.outline_glyphs().get(glyph_id) {
337 let mut builder = Builder::new(self.path_buf);
338 let size = Size::unscaled();
339 let ok = outliner
340 .draw(DrawSettings::unhinted(size, self.location), &mut builder)
341 .is_ok();
342 if ok {
343 builder.finish();
344 bounds = builder.bounds();
345 }
346 ok
347 } else {
348 false
349 };
350 if !outlined {
351 self.path_buf.clear();
353 }
354
355 self.outline_transform = self.transform;
357
358 bounds
359 }
360
361 fn paint_brush(&mut self, brush: Brush<'_>) {
363 match brush {
364 Brush::Solid {
365 palette_index,
366 alpha,
367 } => {
368 let color = self.palette_index_to_color(palette_index, alpha);
369 self.paint_solid(color);
370 }
371 Brush::LinearGradient {
372 p0,
373 p1,
374 color_stops,
375 extend,
376 } => self.paint_linear_gradient(p0, p1, color_stops, extend),
377 Brush::RadialGradient {
378 c0,
379 r0,
380 c1,
381 r1,
382 color_stops,
383 extend,
384 } => self.paint_radial_gradient(c0, r0, c1, r1, color_stops, extend),
385
386 Brush::SweepGradient {
387 c0,
388 start_angle,
389 end_angle,
390 color_stops,
391 extend,
392 } => self.paint_sweep_gradient(c0, start_angle, end_angle, color_stops, extend),
393 }
394 }
395
396 fn palette_index_to_color(&self, palette_index: u16, alpha: f32) -> Color {
397 let lookup = || -> Option<Color> {
398 let palettes = self.font.color_palettes();
401 let palette = palettes.get(0)?;
402 let color = palette.colors().get(palette_index as usize)?;
403 Some(Color {
404 red: color.red,
405 blue: color.blue,
406 green: color.green,
407 alpha: color.alpha,
408 })
409 };
410
411 let mut color = if palette_index == u16::MAX {
412 self.foreground_color
413 } else {
414 lookup().unwrap_or(self.foreground_color)
415 };
416
417 color.alpha = ((color.alpha as f32) * alpha) as u8;
419
420 color
421 }
422}
423
424impl<'a> skrifa::color::ColorPainter for GlyphPainter<'a> {
425 fn push_transform(&mut self, transform: Transform) {
426 self.transforms_stack.push(self.transform);
427 self.transform = self.transform * transform;
428 }
429
430 fn pop_transform(&mut self) {
431 if let Some(ts) = self.transforms_stack.pop() {
432 self.transform = ts;
433 }
434 }
435
436 fn fill_glyph(
437 &mut self,
438 glyph_id: skrifa::GlyphId,
439 brush_transform: Option<Transform>,
440 brush: Brush<'_>,
441 ) {
442 self.outline_glyph(glyph_id);
446
447 if let Some(brush_transform) = brush_transform {
448 self.push_transform(brush_transform);
449 self.paint_brush(brush);
450 self.pop_transform();
451 } else {
452 self.paint_brush(brush);
453 }
454 }
455
456 fn push_clip_glyph(&mut self, glyph_id: skrifa::GlyphId) {
457 let bounds = self.outline_glyph(glyph_id);
458
459 let path = self.path_buf.clone();
463 self.clip_with_path(&path);
464
465 let root_bounds =
466 bounds.and_then(|b| map_rect(b, skrifa_to_tsp_transform(self.outline_transform)));
467 self.clip_stack.push(root_bounds);
468 }
469
470 fn push_clip_box(&mut self, clip_box: skrifa::raw::types::BoundingBox<f32>) {
471 let x_min = clip_box.x_min;
472 let x_max = clip_box.x_max;
473 let y_min = clip_box.y_min;
474 let y_max = clip_box.y_max;
475
476 let clip_path = format!(
477 "M {} {} L {} {} L {} {} L {} {} Z",
478 x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max
479 );
480
481 self.outline_transform = self.transform;
483 self.clip_with_path(&clip_path);
484
485 let bounds = tiny_skia_path::Rect::from_ltrb(x_min, y_min, x_max, y_max)
486 .and_then(|b| map_rect(b, skrifa_to_tsp_transform(self.outline_transform)));
487 self.clip_stack.push(bounds);
488 }
489
490 fn pop_clip(&mut self) {
491 self.svg.end_element();
492 self.clip_stack.pop();
493 }
494
495 fn fill(&mut self, brush: Brush<'_>) {
496 if self.clip_stack.is_empty() {
500 log::warn!("Unclipped COLR fills are not supported.");
501 return;
502 }
503
504 let mut region: Option<tiny_skia_path::Rect> = None;
505 for bounds in &self.clip_stack {
506 let Some(bounds) = bounds else { return };
508 region = Some(match region {
509 Some(region) => match intersect_rects(region, *bounds) {
510 Some(r) => r,
511 None => return,
513 },
514 None => *bounds,
515 });
516 }
517 let Some(region) = region else { return };
518
519 self.path_buf.clear();
520 write!(
521 self.path_buf,
522 "M {} {} L {} {} L {} {} L {} {} Z",
523 region.left(),
524 region.top(),
525 region.right(),
526 region.top(),
527 region.right(),
528 region.bottom(),
529 region.left(),
530 region.bottom()
531 )
532 .unwrap();
533
534 self.outline_transform = Transform::default();
536
537 self.paint_brush(brush);
538 }
539
540 fn push_layer(&mut self, composite_mode: skrifa::color::CompositeMode) {
541 use skrifa::color::CompositeMode;
542 let composite_mode = match composite_mode {
544 CompositeMode::SrcOver => "normal",
545 CompositeMode::Screen => "screen",
546 CompositeMode::Overlay => "overlay",
547 CompositeMode::Darken => "darken",
548 CompositeMode::Lighten => "lighten",
549 CompositeMode::ColorDodge => "color-dodge",
550 CompositeMode::ColorBurn => "color-burn",
551 CompositeMode::HardLight => "hard-light",
552 CompositeMode::SoftLight => "soft-light",
553 CompositeMode::Difference => "difference",
554 CompositeMode::Exclusion => "exclusion",
555 CompositeMode::Multiply => "multiply",
556 CompositeMode::HslHue => "hue",
557 CompositeMode::HslSaturation => "saturation",
558 CompositeMode::HslColor => "color",
559 CompositeMode::HslLuminosity => "luminosity",
560 _ => {
561 "normal"
565 }
566 };
567
568 self.svg.start_element("g");
569 self.svg.write_attribute_fmt(
570 "style",
571 format_args!("mix-blend-mode: {}; isolation: isolate", composite_mode),
572 );
573 }
574
575 fn pop_layer(&mut self) {
576 self.svg.end_element();
577 }
578}