usvg/text/
colr.rs

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