1 | // Copyright 2024 the Resvg Authors |
2 | // SPDX-License-Identifier: Apache-2.0 OR MIT |
3 | |
4 | use crate::parser::OptionLog; |
5 | use rustybuzz::ttf_parser; |
6 | |
7 | struct Builder<'a>(&'a mut String); |
8 | |
9 | impl Builder<'_> { |
10 | fn finish(&mut self) { |
11 | if !self.0.is_empty() { |
12 | self.0.pop(); // remove trailing space |
13 | } |
14 | } |
15 | } |
16 | |
17 | impl 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 | |
43 | trait 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 | |
49 | impl 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. |
84 | pub(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 | |
96 | impl<'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 | |
192 | fn 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 | |
230 | impl 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 | |
250 | impl<'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 | |