usvg/text/
colr.rs

1use crate::parser::OptionLog;
2use rustybuzz::ttf_parser;
3
4struct Builder<'a>(&'a mut String);
5
6impl Builder<'_> {
7    fn finish(&mut self) {
8        if !self.0.is_empty() {
9            self.0.pop(); // remove trailing space
10        }
11    }
12}
13
14impl ttf_parser::OutlineBuilder for Builder<'_> {
15    fn move_to(&mut self, x: f32, y: f32) {
16        use std::fmt::Write;
17        write!(self.0, "M {} {} ", x, y).unwrap()
18    }
19
20    fn line_to(&mut self, x: f32, y: f32) {
21        use std::fmt::Write;
22        write!(self.0, "L {} {} ", x, y).unwrap()
23    }
24
25    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
26        use std::fmt::Write;
27        write!(self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap()
28    }
29
30    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
31        use std::fmt::Write;
32        write!(self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap()
33    }
34
35    fn close(&mut self) {
36        self.0.push_str("Z ")
37    }
38}
39
40trait XmlWriterExt {
41    fn write_color_attribute(&mut self, name: &str, ts: ttf_parser::RgbaColor);
42    fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform);
43    fn write_spread_method_attribute(&mut self, method: ttf_parser::colr::GradientExtend);
44}
45
46impl XmlWriterExt for xmlwriter::XmlWriter {
47    fn write_color_attribute(&mut self, name: &str, color: ttf_parser::RgbaColor) {
48        self.write_attribute_fmt(
49            name,
50            format_args!("rgb({}, {}, {})", color.red, color.green, color.blue),
51        );
52    }
53
54    fn write_transform_attribute(&mut self, name: &str, ts: ttf_parser::Transform) {
55        if ts.is_default() {
56            return;
57        }
58
59        self.write_attribute_fmt(
60            name,
61            format_args!(
62                "matrix({} {} {} {} {} {})",
63                ts.a, ts.b, ts.c, ts.d, ts.e, ts.f
64            ),
65        );
66    }
67
68    fn write_spread_method_attribute(&mut self, extend: ttf_parser::colr::GradientExtend) {
69        self.write_attribute(
70            "spreadMethod",
71            match extend {
72                ttf_parser::colr::GradientExtend::Pad => &"pad",
73                ttf_parser::colr::GradientExtend::Repeat => &"repeat",
74                ttf_parser::colr::GradientExtend::Reflect => &"reflect",
75            },
76        );
77    }
78}
79
80// NOTE: This is only a best-effort translation of COLR into SVG.
81pub(crate) struct GlyphPainter<'a> {
82    pub(crate) face: &'a ttf_parser::Face<'a>,
83    pub(crate) svg: &'a mut xmlwriter::XmlWriter,
84    pub(crate) path_buf: &'a mut String,
85    pub(crate) gradient_index: usize,
86    pub(crate) clip_path_index: usize,
87    pub(crate) palette_index: u16,
88    pub(crate) transform: ttf_parser::Transform,
89    pub(crate) outline_transform: ttf_parser::Transform,
90    pub(crate) transforms_stack: Vec<ttf_parser::Transform>,
91}
92
93impl<'a> GlyphPainter<'a> {
94    fn write_gradient_stops(&mut self, stops: ttf_parser::colr::GradientStopsIter) {
95        for stop in stops {
96            self.svg.start_element("stop");
97            self.svg.write_attribute("offset", &stop.stop_offset);
98            self.svg.write_color_attribute("stop-color", stop.color);
99            let opacity = f32::from(stop.color.alpha) / 255.0;
100            self.svg.write_attribute("stop-opacity", &opacity);
101            self.svg.end_element();
102        }
103    }
104
105    fn paint_solid(&mut self, color: ttf_parser::RgbaColor) {
106        self.svg.start_element("path");
107        self.svg.write_color_attribute("fill", color);
108        let opacity = f32::from(color.alpha) / 255.0;
109        self.svg.write_attribute("fill-opacity", &opacity);
110        self.svg
111            .write_transform_attribute("transform", self.outline_transform);
112        self.svg.write_attribute("d", self.path_buf);
113        self.svg.end_element();
114    }
115
116    fn paint_linear_gradient(&mut self, gradient: ttf_parser::colr::LinearGradient<'a>) {
117        let gradient_id = format!("lg{}", self.gradient_index);
118        self.gradient_index += 1;
119
120        let gradient_transform = paint_transform(self.outline_transform, self.transform);
121
122        // TODO: We ignore x2, y2. Have to apply them somehow.
123        // TODO: The way spreadMode works in ttf and svg is a bit different. In SVG, the spreadMode
124        // will always be applied based on x1/y1 and x2/y2. However, in TTF the spreadMode will
125        // be applied from the first/last stop. So if we have a gradient with x1=0 x2=1, and
126        // a stop at x=0.4 and x=0.6, then in SVG we will always see a padding, while in ttf
127        // we will see the actual spreadMode. We need to account for that somehow.
128        self.svg.start_element("linearGradient");
129        self.svg.write_attribute("id", &gradient_id);
130        self.svg.write_attribute("x1", &gradient.x0);
131        self.svg.write_attribute("y1", &gradient.y0);
132        self.svg.write_attribute("x2", &gradient.x1);
133        self.svg.write_attribute("y2", &gradient.y1);
134        self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
135        self.svg.write_spread_method_attribute(gradient.extend);
136        self.svg
137            .write_transform_attribute("gradientTransform", gradient_transform);
138        self.write_gradient_stops(
139            gradient.stops(self.palette_index, self.face.variation_coordinates()),
140        );
141        self.svg.end_element();
142
143        self.svg.start_element("path");
144        self.svg
145            .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id));
146        self.svg
147            .write_transform_attribute("transform", self.outline_transform);
148        self.svg.write_attribute("d", self.path_buf);
149        self.svg.end_element();
150    }
151
152    fn paint_radial_gradient(&mut self, gradient: ttf_parser::colr::RadialGradient<'a>) {
153        let gradient_id = format!("rg{}", self.gradient_index);
154        self.gradient_index += 1;
155
156        self.svg.start_element("radialGradient");
157        self.svg.write_attribute("id", &gradient_id);
158        self.svg.write_attribute("cx", &gradient.x1);
159        self.svg.write_attribute("cy", &gradient.y1);
160        self.svg.write_attribute("r", &gradient.r1);
161        self.svg.write_attribute("fr", &gradient.r0);
162        self.svg.write_attribute("fx", &gradient.x0);
163        self.svg.write_attribute("fy", &gradient.y0);
164        self.svg.write_attribute("gradientUnits", &"userSpaceOnUse");
165        self.svg.write_spread_method_attribute(gradient.extend);
166        self.svg
167            .write_transform_attribute("gradientTransform", self.transform);
168        self.write_gradient_stops(
169            gradient.stops(self.palette_index, self.face.variation_coordinates()),
170        );
171        self.svg.end_element();
172
173        self.svg.start_element("path");
174        self.svg
175            .write_attribute_fmt("fill", format_args!("url(#{})", gradient_id));
176        self.svg
177            .write_transform_attribute("transform", self.outline_transform);
178        self.svg.write_attribute("d", self.path_buf);
179        self.svg.end_element();
180    }
181
182    fn paint_sweep_gradient(&mut self, _: ttf_parser::colr::SweepGradient<'a>) {
183        println!("Warning: sweep gradients are not supported.")
184    }
185}
186
187fn paint_transform(
188    outline_transform: ttf_parser::Transform,
189    transform: ttf_parser::Transform,
190) -> ttf_parser::Transform {
191    let outline_transform = tiny_skia_path::Transform::from_row(
192        outline_transform.a,
193        outline_transform.b,
194        outline_transform.c,
195        outline_transform.d,
196        outline_transform.e,
197        outline_transform.f,
198    );
199
200    let gradient_transform = tiny_skia_path::Transform::from_row(
201        transform.a,
202        transform.b,
203        transform.c,
204        transform.d,
205        transform.e,
206        transform.f,
207    );
208
209    let gradient_transform = outline_transform
210        .invert()
211        .log_none(|| log::warn!("Failed to calculate transform for gradient in glyph."))
212        .unwrap_or_default()
213        .pre_concat(gradient_transform);
214
215    ttf_parser::Transform {
216        a: gradient_transform.sx,
217        b: gradient_transform.ky,
218        c: gradient_transform.kx,
219        d: gradient_transform.sy,
220        e: gradient_transform.tx,
221        f: gradient_transform.ty,
222    }
223}
224
225impl GlyphPainter<'_> {
226    fn clip_with_path(&mut self, path: &str) {
227        let clip_id = format!("cp{}", self.clip_path_index);
228        self.clip_path_index += 1;
229
230        self.svg.start_element("clipPath");
231        self.svg.write_attribute("id", &clip_id);
232        self.svg.start_element("path");
233        self.svg
234            .write_transform_attribute("transform", self.outline_transform);
235        self.svg.write_attribute("d", &path);
236        self.svg.end_element();
237        self.svg.end_element();
238
239        self.svg.start_element("g");
240        self.svg
241            .write_attribute_fmt("clip-path", format_args!("url(#{})", clip_id));
242    }
243}
244
245impl<'a> ttf_parser::colr::Painter<'a> for GlyphPainter<'a> {
246    fn outline_glyph(&mut self, glyph_id: ttf_parser::GlyphId) {
247        self.path_buf.clear();
248        let mut builder = Builder(self.path_buf);
249        match self.face.outline_glyph(glyph_id, &mut builder) {
250            Some(v) => v,
251            None => return,
252        };
253        builder.finish();
254
255        // We have to write outline using the current transform.
256        self.outline_transform = self.transform;
257    }
258
259    fn push_layer(&mut self, mode: ttf_parser::colr::CompositeMode) {
260        self.svg.start_element("g");
261
262        use ttf_parser::colr::CompositeMode;
263        // TODO: Need to figure out how to represent the other blend modes
264        // in SVG.
265        let mode = match mode {
266            CompositeMode::SourceOver => "normal",
267            CompositeMode::Screen => "screen",
268            CompositeMode::Overlay => "overlay",
269            CompositeMode::Darken => "darken",
270            CompositeMode::Lighten => "lighten",
271            CompositeMode::ColorDodge => "color-dodge",
272            CompositeMode::ColorBurn => "color-burn",
273            CompositeMode::HardLight => "hard-light",
274            CompositeMode::SoftLight => "soft-light",
275            CompositeMode::Difference => "difference",
276            CompositeMode::Exclusion => "exclusion",
277            CompositeMode::Multiply => "multiply",
278            CompositeMode::Hue => "hue",
279            CompositeMode::Saturation => "saturation",
280            CompositeMode::Color => "color",
281            CompositeMode::Luminosity => "luminosity",
282            _ => {
283                println!("Warning: unsupported blend mode: {:?}", mode);
284                "normal"
285            }
286        };
287        self.svg.write_attribute_fmt(
288            "style",
289            format_args!("mix-blend-mode: {}; isolation: isolate", mode),
290        );
291    }
292
293    fn pop_layer(&mut self) {
294        self.svg.end_element(); // g
295    }
296
297    fn push_translate(&mut self, tx: f32, ty: f32) {
298        self.push_transform(ttf_parser::Transform::new(1.0, 0.0, 0.0, 1.0, tx, ty));
299    }
300
301    fn push_scale(&mut self, sx: f32, sy: f32) {
302        self.push_transform(ttf_parser::Transform::new(sx, 0.0, 0.0, sy, 0.0, 0.0));
303    }
304
305    fn push_rotate(&mut self, angle: f32) {
306        let cc = (angle * std::f32::consts::PI).cos();
307        let ss = (angle * std::f32::consts::PI).sin();
308        self.push_transform(ttf_parser::Transform::new(cc, ss, -ss, cc, 0.0, 0.0));
309    }
310
311    fn push_skew(&mut self, skew_x: f32, skew_y: f32) {
312        let x = (-skew_x * std::f32::consts::PI).tan();
313        let y = (skew_y * std::f32::consts::PI).tan();
314        self.push_transform(ttf_parser::Transform::new(1.0, y, x, 1.0, 0.0, 0.0));
315    }
316
317    fn push_transform(&mut self, transform: ttf_parser::Transform) {
318        self.transforms_stack.push(self.transform);
319        self.transform = ttf_parser::Transform::combine(self.transform, transform);
320    }
321
322    fn paint(&mut self, paint: ttf_parser::colr::Paint<'a>) {
323        match paint {
324            ttf_parser::colr::Paint::Solid(color) => self.paint_solid(color),
325            ttf_parser::colr::Paint::LinearGradient(lg) => self.paint_linear_gradient(lg),
326            ttf_parser::colr::Paint::RadialGradient(rg) => self.paint_radial_gradient(rg),
327            ttf_parser::colr::Paint::SweepGradient(sg) => self.paint_sweep_gradient(sg),
328        }
329    }
330
331    fn pop_transform(&mut self) {
332        if let Some(ts) = self.transforms_stack.pop() {
333            self.transform = ts
334        }
335    }
336
337    fn push_clip(&mut self) {
338        self.clip_with_path(&self.path_buf.clone());
339    }
340
341    fn pop_clip(&mut self) {
342        self.svg.end_element();
343    }
344
345    fn push_clip_box(&mut self, clipbox: ttf_parser::colr::ClipBox) {
346        let x_min = clipbox.x_min;
347        let x_max = clipbox.x_max;
348        let y_min = clipbox.y_min;
349        let y_max = clipbox.y_max;
350
351        let clip_path = format!(
352            "M {} {} L {} {} L {} {} L {} {} Z",
353            x_min, y_min, x_max, y_min, x_max, y_max, x_min, y_max
354        );
355
356        self.clip_with_path(&clip_path);
357    }
358}