2 * Copyright © 2022 Behdad Esfahbod
4 * This is part of HarfBuzz, a text shaping library.
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.
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
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.
30 #include "hb-paint-extents.hh"
35 static hb_paint_composite_mode_t
36 _hb_ft_paint_composite_mode (FT_Composite_Mode mode
)
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
;
69 case FT_COLR_COMPOSITE_MAX
: HB_FALLTHROUGH
;
70 default: return HB_PAINT_COMPOSITE_MODE_CLEAR
;
74 typedef struct hb_ft_paint_context_t hb_ft_paint_context_t
;
77 _hb_ft_paint (hb_ft_paint_context_t
*c
,
78 FT_OpaquePaint opaque_paint
);
80 struct hb_ft_paint_context_t
82 hb_ft_paint_context_t (const hb_ft_font_t
*ft_font
,
84 hb_paint_funcs_t
*paint_funcs
, void *paint_data
,
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
) {}
92 void recurse (FT_OpaquePaint paint
)
94 if (unlikely (depth_left
<= 0 || edge_count
<= 0)) return;
97 _hb_ft_paint (this, paint
);
101 const hb_ft_font_t
*ft_font
;
103 hb_paint_funcs_t
*funcs
;
106 unsigned palette_index
;
107 hb_color_t foreground
;
108 hb_map_t current_glyphs
;
109 hb_map_t current_layers
;
110 int depth_left
= HB_MAX_NESTING_LEVEL
;
111 int edge_count
= HB_COLRV1_MAX_EDGE_COUNT
;
115 _hb_ft_color_line_get_color_stops (hb_color_line_t
*color_line
,
116 void *color_line_data
,
119 hb_color_stop_t
*color_stops
,
122 FT_ColorLine
*cl
= (FT_ColorLine
*) color_line_data
;
123 hb_ft_paint_context_t
*c
= (hb_ft_paint_context_t
*) user_data
;
129 FT_ColorStopIterator iter
= cl
->color_stop_iterator
;
131 if (start
>= cl
->color_stop_iterator
.num_color_stops
)
134 return cl
->color_stop_iterator
.num_color_stops
;
137 while (cl
->color_stop_iterator
.current_color_stop
< start
)
138 FT_Get_Colorline_Stops(c
->ft_font
->ft_face
,
140 &cl
->color_stop_iterator
);
142 while (count
&& *count
&&
143 FT_Get_Colorline_Stops(c
->ft_font
->ft_face
,
145 &cl
->color_stop_iterator
))
147 // https://github.com/harfbuzz/harfbuzz/issues/4013
148 if (sizeof stop
.stop_offset
== 2)
149 color_stops
->offset
= stop
.stop_offset
/ 16384.f
;
151 color_stops
->offset
= stop
.stop_offset
/ 65536.f
;
153 color_stops
->is_foreground
= stop
.color
.palette_index
== 0xFFFF;
154 if (color_stops
->is_foreground
)
155 color_stops
->color
= HB_COLOR (hb_color_get_blue (c
->foreground
),
156 hb_color_get_green (c
->foreground
),
157 hb_color_get_red (c
->foreground
),
158 (hb_color_get_alpha (c
->foreground
) * stop
.color
.alpha
) >> 14);
162 if (c
->funcs
->custom_palette_color (c
->data
, stop
.color
.palette_index
, &color
))
164 color_stops
->color
= HB_COLOR (hb_color_get_blue (color
),
165 hb_color_get_green (color
),
166 hb_color_get_red (color
),
167 (hb_color_get_alpha (color
) * stop
.color
.alpha
) >> 14);
171 FT_Color ft_color
= c
->palette
[stop
.color
.palette_index
];
172 color_stops
->color
= HB_COLOR (ft_color
.blue
,
175 (ft_color
.alpha
* stop
.color
.alpha
) >> 14);
185 // reset the iterator for next time
186 cl
->color_stop_iterator
= iter
;
189 return cl
->color_stop_iterator
.num_color_stops
;
192 static hb_paint_extend_t
193 _hb_ft_color_line_get_extend (hb_color_line_t
*color_line
,
194 void *color_line_data
,
197 FT_ColorLine
*c
= (FT_ColorLine
*) color_line_data
;
201 case FT_COLR_PAINT_EXTEND_PAD
: return HB_PAINT_EXTEND_PAD
;
202 case FT_COLR_PAINT_EXTEND_REPEAT
: return HB_PAINT_EXTEND_REPEAT
;
203 case FT_COLR_PAINT_EXTEND_REFLECT
: return HB_PAINT_EXTEND_REFLECT
;
208 _hb_ft_paint (hb_ft_paint_context_t
*c
,
209 FT_OpaquePaint opaque_paint
)
211 FT_Face ft_face
= c
->ft_font
->ft_face
;
213 if (!FT_Get_Paint (ft_face
, opaque_paint
, &paint
))
216 switch (paint
.format
)
218 case FT_COLR_PAINTFORMAT_COLR_LAYERS
:
220 FT_OpaquePaint other_paint
= {0};
221 while (FT_Get_Paint_Layers (ft_face
,
222 &paint
.u
.colr_layers
.layer_iterator
,
225 unsigned i
= paint
.u
.colr_layers
.layer_iterator
.layer
;
227 if (unlikely (c
->current_layers
.has (i
)))
230 c
->current_layers
.add (i
);
232 c
->funcs
->push_group (c
->data
);
233 c
->recurse (other_paint
);
234 c
->funcs
->pop_group (c
->data
, HB_PAINT_COMPOSITE_MODE_SRC_OVER
);
236 c
->current_layers
.del (i
);
240 case FT_COLR_PAINTFORMAT_SOLID
:
242 bool is_foreground
= paint
.u
.solid
.color
.palette_index
== 0xFFFF;
245 color
= HB_COLOR (hb_color_get_blue (c
->foreground
),
246 hb_color_get_green (c
->foreground
),
247 hb_color_get_red (c
->foreground
),
248 (hb_color_get_alpha (c
->foreground
) * paint
.u
.solid
.color
.alpha
) >> 14);
251 if (c
->funcs
->custom_palette_color (c
->data
, paint
.u
.solid
.color
.palette_index
, &color
))
253 color
= HB_COLOR (hb_color_get_blue (color
),
254 hb_color_get_green (color
),
255 hb_color_get_red (color
),
256 (hb_color_get_alpha (color
) * paint
.u
.solid
.color
.alpha
) >> 14);
260 FT_Color ft_color
= c
->palette
[paint
.u
.solid
.color
.palette_index
];
261 color
= HB_COLOR (ft_color
.blue
,
264 (ft_color
.alpha
* paint
.u
.solid
.color
.alpha
) >> 14);
267 c
->funcs
->color (c
->data
, is_foreground
, color
);
270 case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT
:
272 hb_color_line_t cl
= {
273 &paint
.u
.linear_gradient
.colorline
,
274 _hb_ft_color_line_get_color_stops
, c
,
275 _hb_ft_color_line_get_extend
, nullptr
278 c
->funcs
->linear_gradient (c
->data
, &cl
,
279 paint
.u
.linear_gradient
.p0
.x
/ 65536.f
,
280 paint
.u
.linear_gradient
.p0
.y
/ 65536.f
,
281 paint
.u
.linear_gradient
.p1
.x
/ 65536.f
,
282 paint
.u
.linear_gradient
.p1
.y
/ 65536.f
,
283 paint
.u
.linear_gradient
.p2
.x
/ 65536.f
,
284 paint
.u
.linear_gradient
.p2
.y
/ 65536.f
);
287 case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT
:
289 hb_color_line_t cl
= {
290 &paint
.u
.linear_gradient
.colorline
,
291 _hb_ft_color_line_get_color_stops
, c
,
292 _hb_ft_color_line_get_extend
, nullptr
295 c
->funcs
->radial_gradient (c
->data
, &cl
,
296 paint
.u
.radial_gradient
.c0
.x
/ 65536.f
,
297 paint
.u
.radial_gradient
.c0
.y
/ 65536.f
,
298 paint
.u
.radial_gradient
.r0
/ 65536.f
,
299 paint
.u
.radial_gradient
.c1
.x
/ 65536.f
,
300 paint
.u
.radial_gradient
.c1
.y
/ 65536.f
,
301 paint
.u
.radial_gradient
.r1
/ 65536.f
);
304 case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT
:
306 hb_color_line_t cl
= {
307 &paint
.u
.linear_gradient
.colorline
,
308 _hb_ft_color_line_get_color_stops
, c
,
309 _hb_ft_color_line_get_extend
, nullptr
312 c
->funcs
->sweep_gradient (c
->data
, &cl
,
313 paint
.u
.sweep_gradient
.center
.x
/ 65536.f
,
314 paint
.u
.sweep_gradient
.center
.y
/ 65536.f
,
315 (paint
.u
.sweep_gradient
.start_angle
/ 65536.f
+ 1) * HB_PI
,
316 (paint
.u
.sweep_gradient
.end_angle
/ 65536.f
+ 1) * HB_PI
);
319 case FT_COLR_PAINTFORMAT_GLYPH
:
321 c
->funcs
->push_inverse_root_transform (c
->data
, c
->font
);
322 c
->ft_font
->lock
.unlock ();
323 c
->funcs
->push_clip_glyph (c
->data
, paint
.u
.glyph
.glyphID
, c
->font
);
324 c
->ft_font
->lock
.lock ();
325 c
->funcs
->push_root_transform (c
->data
, c
->font
);
326 c
->recurse (paint
.u
.glyph
.paint
);
327 c
->funcs
->pop_transform (c
->data
);
328 c
->funcs
->pop_clip (c
->data
);
329 c
->funcs
->pop_transform (c
->data
);
332 case FT_COLR_PAINTFORMAT_COLR_GLYPH
:
334 hb_codepoint_t gid
= paint
.u
.colr_glyph
.glyphID
;
336 if (unlikely (c
->current_glyphs
.has (gid
)))
339 c
->current_glyphs
.add (gid
);
341 c
->funcs
->push_inverse_root_transform (c
->data
, c
->font
);
342 c
->ft_font
->lock
.unlock ();
343 if (c
->funcs
->color_glyph (c
->data
, gid
, c
->font
))
345 c
->ft_font
->lock
.lock ();
346 c
->funcs
->pop_transform (c
->data
);
347 c
->current_glyphs
.del (gid
);
350 c
->ft_font
->lock
.lock ();
351 c
->funcs
->pop_transform (c
->data
);
353 FT_OpaquePaint other_paint
= {0};
354 if (FT_Get_Color_Glyph_Paint (ft_face
, gid
,
355 FT_COLOR_NO_ROOT_TRANSFORM
,
360 has_clip_box
= FT_Get_Color_Glyph_ClipBox (ft_face
, paint
.u
.colr_glyph
.glyphID
, &clip_box
);
364 /* The FreeType ClipBox is in scaled coordinates, whereas we need
365 * unscaled clipbox here. Oh well...
368 float upem
= c
->font
->face
->get_upem ();
369 float xscale
= upem
/ (c
->font
->x_scale
? c
->font
->x_scale
: upem
);
370 float yscale
= upem
/ (c
->font
->y_scale
? c
->font
->y_scale
: upem
);
372 c
->funcs
->push_clip_rectangle (c
->data
,
373 clip_box
.bottom_left
.x
* xscale
,
374 clip_box
.bottom_left
.y
* yscale
,
375 clip_box
.top_right
.x
* xscale
,
376 clip_box
.top_right
.y
* yscale
);
379 c
->recurse (other_paint
);
382 c
->funcs
->pop_clip (c
->data
);
384 c
->current_glyphs
.del (gid
);
388 case FT_COLR_PAINTFORMAT_TRANSFORM
:
390 c
->funcs
->push_transform (c
->data
,
391 paint
.u
.transform
.affine
.xx
/ 65536.f
,
392 paint
.u
.transform
.affine
.yx
/ 65536.f
,
393 paint
.u
.transform
.affine
.xy
/ 65536.f
,
394 paint
.u
.transform
.affine
.yy
/ 65536.f
,
395 paint
.u
.transform
.affine
.dx
/ 65536.f
,
396 paint
.u
.transform
.affine
.dy
/ 65536.f
);
397 c
->recurse (paint
.u
.transform
.paint
);
398 c
->funcs
->pop_transform (c
->data
);
401 case FT_COLR_PAINTFORMAT_TRANSLATE
:
403 float dx
= paint
.u
.translate
.dx
/ 65536.f
;
404 float dy
= paint
.u
.translate
.dy
/ 65536.f
;
406 bool p1
= c
->funcs
->push_translate (c
->data
, dx
, dy
);
407 c
->recurse (paint
.u
.translate
.paint
);
408 if (p1
) c
->funcs
->pop_transform (c
->data
);
411 case FT_COLR_PAINTFORMAT_SCALE
:
413 float dx
= paint
.u
.scale
.center_x
/ 65536.f
;
414 float dy
= paint
.u
.scale
.center_y
/ 65536.f
;
415 float sx
= paint
.u
.scale
.scale_x
/ 65536.f
;
416 float sy
= paint
.u
.scale
.scale_y
/ 65536.f
;
418 bool p1
= c
->funcs
->push_translate (c
->data
, +dx
, +dy
);
419 bool p2
= c
->funcs
->push_scale (c
->data
, sx
, sy
);
420 bool p3
= c
->funcs
->push_translate (c
->data
, -dx
, -dy
);
421 c
->recurse (paint
.u
.scale
.paint
);
422 if (p3
) c
->funcs
->pop_transform (c
->data
);
423 if (p2
) c
->funcs
->pop_transform (c
->data
);
424 if (p1
) c
->funcs
->pop_transform (c
->data
);
427 case FT_COLR_PAINTFORMAT_ROTATE
:
429 float dx
= paint
.u
.rotate
.center_x
/ 65536.f
;
430 float dy
= paint
.u
.rotate
.center_y
/ 65536.f
;
431 float a
= paint
.u
.rotate
.angle
/ 65536.f
;
433 bool p1
= c
->funcs
->push_translate (c
->data
, +dx
, +dy
);
434 bool p2
= c
->funcs
->push_rotate (c
->data
, a
);
435 bool p3
= c
->funcs
->push_translate (c
->data
, -dx
, -dy
);
436 c
->recurse (paint
.u
.rotate
.paint
);
437 if (p3
) c
->funcs
->pop_transform (c
->data
);
438 if (p2
) c
->funcs
->pop_transform (c
->data
);
439 if (p1
) c
->funcs
->pop_transform (c
->data
);
442 case FT_COLR_PAINTFORMAT_SKEW
:
444 float dx
= paint
.u
.skew
.center_x
/ 65536.f
;
445 float dy
= paint
.u
.skew
.center_y
/ 65536.f
;
446 float sx
= paint
.u
.skew
.x_skew_angle
/ 65536.f
;
447 float sy
= paint
.u
.skew
.y_skew_angle
/ 65536.f
;
449 bool p1
= c
->funcs
->push_translate (c
->data
, +dx
, +dy
);
450 bool p2
= c
->funcs
->push_skew (c
->data
, sx
, sy
);
451 bool p3
= c
->funcs
->push_translate (c
->data
, -dx
, -dy
);
452 c
->recurse (paint
.u
.skew
.paint
);
453 if (p3
) c
->funcs
->pop_transform (c
->data
);
454 if (p2
) c
->funcs
->pop_transform (c
->data
);
455 if (p1
) c
->funcs
->pop_transform (c
->data
);
458 case FT_COLR_PAINTFORMAT_COMPOSITE
:
460 c
->recurse (paint
.u
.composite
.backdrop_paint
);
461 c
->funcs
->push_group (c
->data
);
462 c
->recurse (paint
.u
.composite
.source_paint
);
463 c
->funcs
->pop_group (c
->data
, _hb_ft_paint_composite_mode (paint
.u
.composite
.composite_mode
));
467 case FT_COLR_PAINT_FORMAT_MAX
: break;
468 default: HB_FALLTHROUGH
;
469 case FT_COLR_PAINTFORMAT_UNSUPPORTED
: break;
475 hb_ft_paint_glyph_colr (hb_font_t
*font
,
478 hb_paint_funcs_t
*paint_funcs
, void *paint_data
,
479 unsigned int palette_index
,
480 hb_color_t foreground
,
483 const hb_ft_font_t
*ft_font
= (const hb_ft_font_t
*) font_data
;
484 FT_Face ft_face
= ft_font
->ft_face
;
486 /* Face is locked. */
490 FT_LayerIterator iterator
;
493 FT_UInt layer_glyph_index
;
494 FT_UInt layer_color_index
;
496 error
= FT_Palette_Select(ft_face
, palette_index
, &palette
);
501 FT_OpaquePaint paint
= {0};
502 if (FT_Get_Color_Glyph_Paint (ft_face
, gid
,
503 FT_COLOR_NO_ROOT_TRANSFORM
,
506 hb_ft_paint_context_t
c (ft_font
, font
,
507 paint_funcs
, paint_data
,
508 palette
, palette_index
, foreground
);
509 c
.current_glyphs
.add (gid
);
511 bool is_bounded
= true;
513 if (FT_Get_Color_Glyph_ClipBox (ft_face
, gid
, &clip_box
))
515 c
.funcs
->push_clip_rectangle (c
.data
,
516 clip_box
.bottom_left
.x
+
517 roundf (hb_min (font
->slant_xy
* clip_box
.bottom_left
.y
,
518 font
->slant_xy
* clip_box
.top_left
.y
)),
519 clip_box
.bottom_left
.y
,
520 clip_box
.top_right
.x
+
521 roundf (hb_max (font
->slant_xy
* clip_box
.bottom_right
.y
,
522 font
->slant_xy
* clip_box
.top_right
.y
)),
523 clip_box
.top_right
.y
);
528 auto *extents_funcs
= hb_paint_extents_get_funcs ();
529 hb_paint_extents_context_t extents_data
;
530 hb_ft_paint_context_t
ce (ft_font
, font
,
531 extents_funcs
, &extents_data
,
532 palette
, palette_index
, foreground
);
533 ce
.current_glyphs
.add (gid
);
534 ce
.funcs
->push_root_transform (ce
.data
, font
);
536 ce
.funcs
->pop_transform (ce
.data
);
537 hb_extents_t extents
= extents_data
.get_extents ();
538 is_bounded
= extents_data
.is_bounded ();
540 c
.funcs
->push_clip_rectangle (c
.data
,
547 c
.funcs
->push_root_transform (c
.data
, font
);
552 c
.funcs
->pop_transform (c
.data
);
553 c
.funcs
->pop_clip (c
.data
);
560 have_layers
= FT_Get_Color_Glyph_Layer(ft_face
,
566 if (palette
&& have_layers
)
570 hb_bool_t is_foreground
= true;
571 hb_color_t color
= foreground
;
573 if ( layer_color_index
!= 0xFFFF )
575 FT_Color layer_color
= palette
[layer_color_index
];
576 color
= HB_COLOR (layer_color
.blue
,
580 is_foreground
= false;
583 ft_font
->lock
.unlock ();
584 paint_funcs
->push_clip_glyph (paint_data
, layer_glyph_index
, font
);
585 ft_font
->lock
.lock ();
586 paint_funcs
->color (paint_data
, is_foreground
, color
);
587 paint_funcs
->pop_clip (paint_data
);
589 } while (FT_Get_Color_Glyph_Layer(ft_face
,
601 #endif /* HB_FT_COLR_HH */