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: String = format!("cp{}", self.clip_path_index);
233 self.clip_path_index += 1;
234
235 self.svg.start_element(name:"clipPath");
236 self.svg.write_attribute(name:"id", &clip_id);
237 self.svg.start_element(name:"path");
238 self.svg
239 .write_transform_attribute(name:"transform", self.outline_transform);
240 self.svg.write_attribute(name:"d", &path);
241 self.svg.end_element();
242 self.svg.end_element();
243
244 self.svg.start_element(name:"g");
245 self.svg
246 .write_attribute_fmt(name:"clip-path", fmt: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}
344