1/*
2 * Copyright © 2022 Behdad Esfahbod
3 *
4 * This is part of HarfBuzz, a text shaping library.
5 *
6 * Permission is hereby granted, without written agreement and without
7 * license or royalty fees, to use, copy, modify, and distribute this
8 * software and its documentation for any purpose, provided that the
9 * above copyright notice and the following two paragraphs appear in
10 * all copies of this software.
11 *
12 * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
13 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
14 * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
15 * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
16 * DAMAGE.
17 *
18 * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
19 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
20 * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
21 * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
22 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 */
24
25#ifndef HB_FT_COLR_HH
26#define HB_FT_COLR_HH
27
28#include "hb.hh"
29
30#include "hb-paint-extents.hh"
31
32#include FT_COLOR_H
33
34
35static hb_paint_composite_mode_t
36_hb_ft_paint_composite_mode (FT_Composite_Mode mode)
37{
38 switch (mode)
39 {
40 case FT_COLR_COMPOSITE_CLEAR: return HB_PAINT_COMPOSITE_MODE_CLEAR;
41 case FT_COLR_COMPOSITE_SRC: return HB_PAINT_COMPOSITE_MODE_SRC;
42 case FT_COLR_COMPOSITE_DEST: return HB_PAINT_COMPOSITE_MODE_DEST;
43 case FT_COLR_COMPOSITE_SRC_OVER: return HB_PAINT_COMPOSITE_MODE_SRC_OVER;
44 case FT_COLR_COMPOSITE_DEST_OVER: return HB_PAINT_COMPOSITE_MODE_DEST_OVER;
45 case FT_COLR_COMPOSITE_SRC_IN: return HB_PAINT_COMPOSITE_MODE_SRC_IN;
46 case FT_COLR_COMPOSITE_DEST_IN: return HB_PAINT_COMPOSITE_MODE_DEST_IN;
47 case FT_COLR_COMPOSITE_SRC_OUT: return HB_PAINT_COMPOSITE_MODE_SRC_OUT;
48 case FT_COLR_COMPOSITE_DEST_OUT: return HB_PAINT_COMPOSITE_MODE_DEST_OUT;
49 case FT_COLR_COMPOSITE_SRC_ATOP: return HB_PAINT_COMPOSITE_MODE_SRC_ATOP;
50 case FT_COLR_COMPOSITE_DEST_ATOP: return HB_PAINT_COMPOSITE_MODE_DEST_ATOP;
51 case FT_COLR_COMPOSITE_XOR: return HB_PAINT_COMPOSITE_MODE_XOR;
52 case FT_COLR_COMPOSITE_PLUS: return HB_PAINT_COMPOSITE_MODE_PLUS;
53 case FT_COLR_COMPOSITE_SCREEN: return HB_PAINT_COMPOSITE_MODE_SCREEN;
54 case FT_COLR_COMPOSITE_OVERLAY: return HB_PAINT_COMPOSITE_MODE_OVERLAY;
55 case FT_COLR_COMPOSITE_DARKEN: return HB_PAINT_COMPOSITE_MODE_DARKEN;
56 case FT_COLR_COMPOSITE_LIGHTEN: return HB_PAINT_COMPOSITE_MODE_LIGHTEN;
57 case FT_COLR_COMPOSITE_COLOR_DODGE: return HB_PAINT_COMPOSITE_MODE_COLOR_DODGE;
58 case FT_COLR_COMPOSITE_COLOR_BURN: return HB_PAINT_COMPOSITE_MODE_COLOR_BURN;
59 case FT_COLR_COMPOSITE_HARD_LIGHT: return HB_PAINT_COMPOSITE_MODE_HARD_LIGHT;
60 case FT_COLR_COMPOSITE_SOFT_LIGHT: return HB_PAINT_COMPOSITE_MODE_SOFT_LIGHT;
61 case FT_COLR_COMPOSITE_DIFFERENCE: return HB_PAINT_COMPOSITE_MODE_DIFFERENCE;
62 case FT_COLR_COMPOSITE_EXCLUSION: return HB_PAINT_COMPOSITE_MODE_EXCLUSION;
63 case FT_COLR_COMPOSITE_MULTIPLY: return HB_PAINT_COMPOSITE_MODE_MULTIPLY;
64 case FT_COLR_COMPOSITE_HSL_HUE: return HB_PAINT_COMPOSITE_MODE_HSL_HUE;
65 case FT_COLR_COMPOSITE_HSL_SATURATION: return HB_PAINT_COMPOSITE_MODE_HSL_SATURATION;
66 case FT_COLR_COMPOSITE_HSL_COLOR: return HB_PAINT_COMPOSITE_MODE_HSL_COLOR;
67 case FT_COLR_COMPOSITE_HSL_LUMINOSITY: return HB_PAINT_COMPOSITE_MODE_HSL_LUMINOSITY;
68
69 case FT_COLR_COMPOSITE_MAX: HB_FALLTHROUGH;
70 default: return HB_PAINT_COMPOSITE_MODE_CLEAR;
71 }
72}
73
74typedef struct hb_ft_paint_context_t hb_ft_paint_context_t;
75
76static void
77_hb_ft_paint (hb_ft_paint_context_t *c,
78 FT_OpaquePaint opaque_paint);
79
80struct hb_ft_paint_context_t
81{
82 hb_ft_paint_context_t (const hb_ft_font_t *ft_font,
83 hb_font_t *font,
84 hb_paint_funcs_t *paint_funcs, void *paint_data,
85 FT_Color *palette,
86 unsigned palette_index,
87 hb_color_t foreground) :
88 ft_font (ft_font), font(font),
89 funcs (paint_funcs), data (paint_data),
90 palette (palette), palette_index (palette_index), foreground (foreground) {}
91
92 void recurse (FT_OpaquePaint paint)
93 {
94 if (unlikely (depth_left <= 0 || edge_count <= 0)) return;
95 depth_left--;
96 edge_count--;
97 _hb_ft_paint (this, paint);
98 depth_left++;
99 }
100
101 const hb_ft_font_t *ft_font;
102 hb_font_t *font;
103 hb_paint_funcs_t *funcs;
104 void *data;
105 FT_Color *palette;
106 unsigned palette_index;
107 hb_color_t foreground;
108 int depth_left = HB_MAX_NESTING_LEVEL;
109 int edge_count = HB_COLRV1_MAX_EDGE_COUNT;
110};
111
112static unsigned
113_hb_ft_color_line_get_color_stops (hb_color_line_t *color_line,
114 void *color_line_data,
115 unsigned int start,
116 unsigned int *count,
117 hb_color_stop_t *color_stops,
118 void *user_data)
119{
120 FT_ColorLine *cl = (FT_ColorLine *) color_line_data;
121 hb_ft_paint_context_t *c = (hb_ft_paint_context_t *) user_data;
122
123 if (count)
124 {
125 FT_ColorStop stop;
126 unsigned wrote = 0;
127 FT_ColorStopIterator iter = cl->color_stop_iterator;
128
129 if (start >= cl->color_stop_iterator.num_color_stops)
130 {
131 *count = 0;
132 return cl->color_stop_iterator.num_color_stops;
133 }
134
135 while (cl->color_stop_iterator.current_color_stop < start)
136 FT_Get_Colorline_Stops(c->ft_font->ft_face,
137 &stop,
138 &cl->color_stop_iterator);
139
140 while (count && *count &&
141 FT_Get_Colorline_Stops(c->ft_font->ft_face,
142 &stop,
143 &cl->color_stop_iterator))
144 {
145 // https://github.com/harfbuzz/harfbuzz/issues/4013
146 if (sizeof stop.stop_offset == 2)
147 color_stops->offset = stop.stop_offset / 16384.f;
148 else
149 color_stops->offset = stop.stop_offset / 65536.f;
150
151 color_stops->is_foreground = stop.color.palette_index == 0xFFFF;
152 if (color_stops->is_foreground)
153 color_stops->color = HB_COLOR (hb_color_get_blue (c->foreground),
154 hb_color_get_green (c->foreground),
155 hb_color_get_red (c->foreground),
156 (hb_color_get_alpha (c->foreground) * stop.color.alpha) >> 14);
157 else
158 {
159 hb_color_t color;
160 if (c->funcs->custom_palette_color (c->data, stop.color.palette_index, &color))
161 {
162 color_stops->color = HB_COLOR (hb_color_get_blue (color),
163 hb_color_get_green (color),
164 hb_color_get_red (color),
165 (hb_color_get_alpha (color) * stop.color.alpha) >> 14);
166 }
167 else
168 {
169 FT_Color ft_color = c->palette[stop.color.palette_index];
170 color_stops->color = HB_COLOR (ft_color.blue,
171 ft_color.green,
172 ft_color.red,
173 (ft_color.alpha * stop.color.alpha) >> 14);
174 }
175 }
176
177 color_stops++;
178 wrote++;
179 }
180
181 *count = wrote;
182
183 // reset the iterator for next time
184 cl->color_stop_iterator = iter;
185 }
186
187 return cl->color_stop_iterator.num_color_stops;
188}
189
190static hb_paint_extend_t
191_hb_ft_color_line_get_extend (hb_color_line_t *color_line,
192 void *color_line_data,
193 void *user_data)
194{
195 FT_ColorLine *c = (FT_ColorLine *) color_line_data;
196 switch (c->extend)
197 {
198 default:
199 case FT_COLR_PAINT_EXTEND_PAD: return HB_PAINT_EXTEND_PAD;
200 case FT_COLR_PAINT_EXTEND_REPEAT: return HB_PAINT_EXTEND_REPEAT;
201 case FT_COLR_PAINT_EXTEND_REFLECT: return HB_PAINT_EXTEND_REFLECT;
202 }
203}
204
205void
206_hb_ft_paint (hb_ft_paint_context_t *c,
207 FT_OpaquePaint opaque_paint)
208{
209 FT_Face ft_face = c->ft_font->ft_face;
210 FT_COLR_Paint paint;
211 if (!FT_Get_Paint (ft_face, opaque_paint, &paint))
212 return;
213
214 switch (paint.format)
215 {
216 case FT_COLR_PAINTFORMAT_COLR_LAYERS:
217 {
218 FT_OpaquePaint other_paint = {0};
219 while (FT_Get_Paint_Layers (ft_face,
220 &paint.u.colr_layers.layer_iterator,
221 &other_paint))
222 {
223 c->funcs->push_group (c->data);
224 c->recurse (other_paint);
225 c->funcs->pop_group (c->data, HB_PAINT_COMPOSITE_MODE_SRC_OVER);
226 }
227 }
228 break;
229 case FT_COLR_PAINTFORMAT_SOLID:
230 {
231 bool is_foreground = paint.u.solid.color.palette_index == 0xFFFF;
232 hb_color_t color;
233 if (is_foreground)
234 color = HB_COLOR (hb_color_get_blue (c->foreground),
235 hb_color_get_green (c->foreground),
236 hb_color_get_red (c->foreground),
237 (hb_color_get_alpha (c->foreground) * paint.u.solid.color.alpha) >> 14);
238 else
239 {
240 if (c->funcs->custom_palette_color (c->data, paint.u.solid.color.palette_index, &color))
241 {
242 color = HB_COLOR (hb_color_get_blue (color),
243 hb_color_get_green (color),
244 hb_color_get_red (color),
245 (hb_color_get_alpha (color) * paint.u.solid.color.alpha) >> 14);
246 }
247 else
248 {
249 FT_Color ft_color = c->palette[paint.u.solid.color.palette_index];
250 color = HB_COLOR (ft_color.blue,
251 ft_color.green,
252 ft_color.red,
253 (ft_color.alpha * paint.u.solid.color.alpha) >> 14);
254 }
255 }
256 c->funcs->color (c->data, is_foreground, color);
257 }
258 break;
259 case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT:
260 {
261 hb_color_line_t cl = {
262 &paint.u.linear_gradient.colorline,
263 _hb_ft_color_line_get_color_stops, c,
264 _hb_ft_color_line_get_extend, nullptr
265 };
266
267 c->funcs->linear_gradient (c->data, &cl,
268 paint.u.linear_gradient.p0.x / 65536.f,
269 paint.u.linear_gradient.p0.y / 65536.f,
270 paint.u.linear_gradient.p1.x / 65536.f,
271 paint.u.linear_gradient.p1.y / 65536.f,
272 paint.u.linear_gradient.p2.x / 65536.f,
273 paint.u.linear_gradient.p2.y / 65536.f);
274 }
275 break;
276 case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT:
277 {
278 hb_color_line_t cl = {
279 &paint.u.linear_gradient.colorline,
280 _hb_ft_color_line_get_color_stops, c,
281 _hb_ft_color_line_get_extend, nullptr
282 };
283
284 c->funcs->radial_gradient (c->data, &cl,
285 paint.u.radial_gradient.c0.x / 65536.f,
286 paint.u.radial_gradient.c0.y / 65536.f,
287 paint.u.radial_gradient.r0 / 65536.f,
288 paint.u.radial_gradient.c1.x / 65536.f,
289 paint.u.radial_gradient.c1.y / 65536.f,
290 paint.u.radial_gradient.r1 / 65536.f);
291 }
292 break;
293 case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT:
294 {
295 hb_color_line_t cl = {
296 &paint.u.linear_gradient.colorline,
297 _hb_ft_color_line_get_color_stops, c,
298 _hb_ft_color_line_get_extend, nullptr
299 };
300
301 c->funcs->sweep_gradient (c->data, &cl,
302 paint.u.sweep_gradient.center.x / 65536.f,
303 paint.u.sweep_gradient.center.y / 65536.f,
304 (paint.u.sweep_gradient.start_angle / 65536.f + 1) * HB_PI,
305 (paint.u.sweep_gradient.end_angle / 65536.f + 1) * HB_PI);
306 }
307 break;
308 case FT_COLR_PAINTFORMAT_GLYPH:
309 {
310 c->funcs->push_inverse_root_transform (c->data, c->font);
311 c->ft_font->lock.unlock ();
312 c->funcs->push_clip_glyph (c->data, paint.u.glyph.glyphID, c->font);
313 c->ft_font->lock.lock ();
314 c->funcs->push_root_transform (c->data, c->font);
315 c->recurse (paint.u.glyph.paint);
316 c->funcs->pop_transform (c->data);
317 c->funcs->pop_clip (c->data);
318 c->funcs->pop_transform (c->data);
319 }
320 break;
321 case FT_COLR_PAINTFORMAT_COLR_GLYPH:
322 {
323 FT_OpaquePaint other_paint = {0};
324 if (FT_Get_Color_Glyph_Paint (ft_face, paint.u.colr_glyph.glyphID,
325 FT_COLOR_NO_ROOT_TRANSFORM,
326 &other_paint))
327 {
328 bool has_clip_box;
329 FT_ClipBox clip_box;
330 has_clip_box = FT_Get_Color_Glyph_ClipBox (ft_face, paint.u.colr_glyph.glyphID, &clip_box);
331
332 if (has_clip_box)
333 {
334 /* The FreeType ClipBox is in scaled coordinates, whereas we need
335 * unscaled clipbox here. Oh well...
336 */
337
338 float upem = c->font->face->get_upem ();
339 float xscale = upem / (c->font->x_scale ? c->font->x_scale : upem);
340 float yscale = upem / (c->font->y_scale ? c->font->y_scale : upem);
341
342 c->funcs->push_clip_rectangle (c->data,
343 clip_box.bottom_left.x * xscale,
344 clip_box.bottom_left.y * yscale,
345 clip_box.top_right.x * xscale,
346 clip_box.top_right.y * yscale);
347 }
348
349 c->recurse (other_paint);
350
351 if (has_clip_box)
352 c->funcs->pop_clip (c->data);
353 }
354 }
355 break;
356 case FT_COLR_PAINTFORMAT_TRANSFORM:
357 {
358 c->funcs->push_transform (c->data,
359 paint.u.transform.affine.xx / 65536.f,
360 paint.u.transform.affine.yx / 65536.f,
361 paint.u.transform.affine.xy / 65536.f,
362 paint.u.transform.affine.yy / 65536.f,
363 paint.u.transform.affine.dx / 65536.f,
364 paint.u.transform.affine.dy / 65536.f);
365 c->recurse (paint.u.transform.paint);
366 c->funcs->pop_transform (c->data);
367 }
368 break;
369 case FT_COLR_PAINTFORMAT_TRANSLATE:
370 {
371 float dx = paint.u.translate.dx / 65536.f;
372 float dy = paint.u.translate.dy / 65536.f;
373
374 bool p1 = c->funcs->push_translate (c->data, dx, dy);
375 c->recurse (paint.u.translate.paint);
376 if (p1) c->funcs->pop_transform (c->data);
377 }
378 break;
379 case FT_COLR_PAINTFORMAT_SCALE:
380 {
381 float dx = paint.u.scale.center_x / 65536.f;
382 float dy = paint.u.scale.center_y / 65536.f;
383 float sx = paint.u.scale.scale_x / 65536.f;
384 float sy = paint.u.scale.scale_y / 65536.f;
385
386 bool p1 = c->funcs->push_translate (c->data, +dx, +dy);
387 bool p2 = c->funcs->push_scale (c->data, sx, sy);
388 bool p3 = c->funcs->push_translate (c->data, -dx, -dy);
389 c->recurse (paint.u.scale.paint);
390 if (p3) c->funcs->pop_transform (c->data);
391 if (p2) c->funcs->pop_transform (c->data);
392 if (p1) c->funcs->pop_transform (c->data);
393 }
394 break;
395 case FT_COLR_PAINTFORMAT_ROTATE:
396 {
397 float dx = paint.u.rotate.center_x / 65536.f;
398 float dy = paint.u.rotate.center_y / 65536.f;
399 float a = paint.u.rotate.angle / 65536.f;
400
401 bool p1 = c->funcs->push_translate (c->data, +dx, +dy);
402 bool p2 = c->funcs->push_rotate (c->data, a);
403 bool p3 = c->funcs->push_translate (c->data, -dx, -dy);
404 c->recurse (paint.u.rotate.paint);
405 if (p3) c->funcs->pop_transform (c->data);
406 if (p2) c->funcs->pop_transform (c->data);
407 if (p1) c->funcs->pop_transform (c->data);
408 }
409 break;
410 case FT_COLR_PAINTFORMAT_SKEW:
411 {
412 float dx = paint.u.skew.center_x / 65536.f;
413 float dy = paint.u.skew.center_y / 65536.f;
414 float sx = paint.u.skew.x_skew_angle / 65536.f;
415 float sy = paint.u.skew.y_skew_angle / 65536.f;
416
417 bool p1 = c->funcs->push_translate (c->data, +dx, +dy);
418 bool p2 = c->funcs->push_skew (c->data, sx, sy);
419 bool p3 = c->funcs->push_translate (c->data, -dx, -dy);
420 c->recurse (paint.u.skew.paint);
421 if (p3) c->funcs->pop_transform (c->data);
422 if (p2) c->funcs->pop_transform (c->data);
423 if (p1) c->funcs->pop_transform (c->data);
424 }
425 break;
426 case FT_COLR_PAINTFORMAT_COMPOSITE:
427 {
428 c->recurse (paint.u.composite.backdrop_paint);
429 c->funcs->push_group (c->data);
430 c->recurse (paint.u.composite.source_paint);
431 c->funcs->pop_group (c->data, _hb_ft_paint_composite_mode (paint.u.composite.composite_mode));
432 }
433 break;
434
435 case FT_COLR_PAINT_FORMAT_MAX: break;
436 default: HB_FALLTHROUGH;
437 case FT_COLR_PAINTFORMAT_UNSUPPORTED: break;
438 }
439}
440
441
442static bool
443hb_ft_paint_glyph_colr (hb_font_t *font,
444 void *font_data,
445 hb_codepoint_t gid,
446 hb_paint_funcs_t *paint_funcs, void *paint_data,
447 unsigned int palette_index,
448 hb_color_t foreground,
449 void *user_data)
450{
451 const hb_ft_font_t *ft_font = (const hb_ft_font_t *) font_data;
452 FT_Face ft_face = ft_font->ft_face;
453
454 /* Face is locked. */
455
456 FT_Error error;
457 FT_Color* palette;
458 FT_LayerIterator iterator;
459
460 FT_Bool have_layers;
461 FT_UInt layer_glyph_index;
462 FT_UInt layer_color_index;
463
464 error = FT_Palette_Select(ft_face, palette_index, &palette);
465 if (error)
466 palette = NULL;
467
468 /* COLRv1 */
469 FT_OpaquePaint paint = {0};
470 if (FT_Get_Color_Glyph_Paint (ft_face, gid,
471 FT_COLOR_NO_ROOT_TRANSFORM,
472 &paint))
473 {
474 hb_ft_paint_context_t c (ft_font, font,
475 paint_funcs, paint_data,
476 palette, palette_index, foreground);
477
478 bool is_bounded = true;
479 FT_ClipBox clip_box;
480 if (FT_Get_Color_Glyph_ClipBox (ft_face, gid, &clip_box))
481 {
482 c.funcs->push_clip_rectangle (c.data,
483 clip_box.bottom_left.x +
484 roundf (hb_min (font->slant_xy * clip_box.bottom_left.y,
485 font->slant_xy * clip_box.top_left.y)),
486 clip_box.bottom_left.y,
487 clip_box.top_right.x +
488 roundf (hb_max (font->slant_xy * clip_box.bottom_right.y,
489 font->slant_xy * clip_box.top_right.y)),
490 clip_box.top_right.y);
491 }
492 else
493 {
494
495 auto *extents_funcs = hb_paint_extents_get_funcs ();
496 hb_paint_extents_context_t extents_data;
497 hb_ft_paint_context_t ce (ft_font, font,
498 extents_funcs, &extents_data,
499 palette, palette_index, foreground);
500 ce.funcs->push_root_transform (ce.data, font);
501 ce.recurse (paint);
502 ce.funcs->pop_transform (ce.data);
503 hb_extents_t extents = extents_data.get_extents ();
504 is_bounded = extents_data.is_bounded ();
505
506 c.funcs->push_clip_rectangle (c.data,
507 extents.xmin,
508 extents.ymin,
509 extents.xmax,
510 extents.ymax);
511 }
512
513 c.funcs->push_root_transform (c.data, font);
514
515 if (is_bounded)
516 c.recurse (paint);
517
518 c.funcs->pop_transform (c.data);
519 c.funcs->pop_clip (c.data);
520
521 return true;
522 }
523
524 /* COLRv0 */
525 iterator.p = NULL;
526 have_layers = FT_Get_Color_Glyph_Layer(ft_face,
527 gid,
528 &layer_glyph_index,
529 &layer_color_index,
530 &iterator);
531
532 if (palette && have_layers)
533 {
534 do
535 {
536 hb_bool_t is_foreground = true;
537 hb_color_t color = foreground;
538
539 if ( layer_color_index != 0xFFFF )
540 {
541 FT_Color layer_color = palette[layer_color_index];
542 color = HB_COLOR (layer_color.blue,
543 layer_color.green,
544 layer_color.red,
545 layer_color.alpha);
546 is_foreground = false;
547 }
548
549 ft_font->lock.unlock ();
550 paint_funcs->push_clip_glyph (paint_data, layer_glyph_index, font);
551 ft_font->lock.lock ();
552 paint_funcs->color (paint_data, is_foreground, color);
553 paint_funcs->pop_clip (paint_data);
554
555 } while (FT_Get_Color_Glyph_Layer(ft_face,
556 gid,
557 &layer_glyph_index,
558 &layer_color_index,
559 &iterator));
560 return true;
561 }
562
563 return false;
564}
565
566
567#endif /* HB_FT_COLR_HH */
568