1 /****************************************************************************
5 * HarfBuzz interface for accessing OpenType features (body).
7 * Copyright (C) 2013-2020 by
8 * David Turner, Robert Wilhelm, and Werner Lemberg.
10 * This file is part of the FreeType project, and may only be used,
11 * modified, and distributed under the terms of the FreeType project
12 * license, LICENSE.TXT. By continuing to use, modify, or distribute
13 * this file you indicate that you have read the license and
14 * understand and accept it fully.
19 #include <freetype/freetype.h>
20 #include <freetype/ftadvanc.h>
25 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
28 /**************************************************************************
30 * The macro FT_COMPONENT is used in trace mode. It is an implicit
31 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
32 * messages during execution.
35 #define FT_COMPONENT afshaper
39 * We use `sets' (in the HarfBuzz sense, which comes quite near to the
40 * usual mathematical meaning) to manage both lookups and glyph indices.
42 * 1. For each coverage, collect lookup IDs in a set. Note that an
43 * auto-hinter `coverage' is represented by one `feature', and a
44 * feature consists of an arbitrary number of (font specific) `lookup's
45 * that actually do the mapping job. Please check the OpenType
46 * specification for more details on features and lookups.
48 * 2. Create glyph ID sets from the corresponding lookup sets.
50 * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed
51 * with all lookups specific to the OpenType script activated. It
52 * relies on the order of AF_DEFINE_STYLE_CLASS entries so that
53 * special coverages (like `oldstyle figures') don't get overwritten.
58 /* load coverage tags */
60 #define COVERAGE( name, NAME, description, \
61 tag1, tag2, tag3, tag4 ) \
62 static const hb_tag_t name ## _coverage[] = \
64 HB_TAG( tag1, tag2, tag3, tag4 ), \
72 /* define mapping between coverage tags and AF_Coverage */
74 #define COVERAGE( name, NAME, description, \
75 tag1, tag2, tag3, tag4 ) \
79 static const hb_tag_t* coverages[] =
83 NULL /* AF_COVERAGE_DEFAULT */
87 /* load HarfBuzz script tags */
89 #define SCRIPT( s, S, d, h, H, ss ) h,
92 static const hb_script_t scripts[] =
99 af_shaper_get_coverage( AF_FaceGlobals globals,
100 AF_StyleClass style_class,
102 FT_Bool default_script )
106 hb_set_t* gsub_lookups = NULL; /* GSUB lookups for a given script */
107 hb_set_t* gsub_glyphs = NULL; /* glyphs covered by GSUB lookups */
108 hb_set_t* gpos_lookups = NULL; /* GPOS lookups for a given script */
109 hb_set_t* gpos_glyphs = NULL; /* glyphs covered by GPOS lookups */
112 const hb_tag_t* coverage_tags;
113 hb_tag_t script_tags[] = { HB_TAG_NONE,
119 #ifdef FT_DEBUG_LEVEL_TRACE
124 if ( !globals || !style_class || !gstyles )
125 return FT_THROW( Invalid_Argument );
127 face = hb_font_get_face( globals->hb_font );
129 coverage_tags = coverages[style_class->coverage];
130 script = scripts[style_class->script];
132 /* Convert a HarfBuzz script tag into the corresponding OpenType */
133 /* tag or tags -- some Indic scripts like Devanagari have an old */
134 /* and a new set of features. */
135 hb_ot_tags_from_script( script,
139 /* `hb_ot_tags_from_script' usually returns HB_OT_TAG_DEFAULT_SCRIPT */
140 /* as the second tag. We change that to HB_TAG_NONE except for the */
141 /* default script. */
142 if ( default_script )
144 if ( script_tags[0] == HB_TAG_NONE )
145 script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT;
148 if ( script_tags[1] == HB_TAG_NONE )
149 script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT;
150 else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT )
151 script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT;
156 /* we use non-standard tags like `khms' for special purposes; */
157 /* HarfBuzz maps them to `DFLT', which we don't want to handle here */
158 if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT )
161 if ( script_tags[1] == HB_OT_TAG_DEFAULT_SCRIPT )
162 script_tags[1] = HB_TAG_NONE;
165 gsub_lookups = hb_set_create();
166 hb_ot_layout_collect_lookups( face,
173 if ( hb_set_is_empty( gsub_lookups ) )
174 goto Exit; /* nothing to do */
176 FT_TRACE4(( "GSUB lookups (style `%s'):\n"
178 af_style_names[style_class->style] ));
180 #ifdef FT_DEBUG_LEVEL_TRACE
184 gsub_glyphs = hb_set_create();
185 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, &idx ); )
187 #ifdef FT_DEBUG_LEVEL_TRACE
188 FT_TRACE4(( " %d", idx ));
192 /* get output coverage of GSUB feature */
193 hb_ot_layout_lookup_collect_glyphs( face,
202 #ifdef FT_DEBUG_LEVEL_TRACE
204 FT_TRACE4(( " (none)" ));
205 FT_TRACE4(( "\n\n" ));
208 FT_TRACE4(( "GPOS lookups (style `%s'):\n"
210 af_style_names[style_class->style] ));
212 gpos_lookups = hb_set_create();
213 hb_ot_layout_collect_lookups( face,
220 #ifdef FT_DEBUG_LEVEL_TRACE
224 gpos_glyphs = hb_set_create();
225 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gpos_lookups, &idx ); )
227 #ifdef FT_DEBUG_LEVEL_TRACE
228 FT_TRACE4(( " %d", idx ));
232 /* get input coverage of GPOS feature */
233 hb_ot_layout_lookup_collect_glyphs( face,
242 #ifdef FT_DEBUG_LEVEL_TRACE
244 FT_TRACE4(( " (none)" ));
245 FT_TRACE4(( "\n\n" ));
249 * We now check whether we can construct blue zones, using glyphs
250 * covered by the feature only. In case there is not a single zone
251 * (this is, not a single character is covered), we skip this coverage.
254 if ( style_class->coverage != AF_COVERAGE_DEFAULT )
256 AF_Blue_Stringset bss = style_class->blue_stringset;
257 const AF_Blue_StringRec* bs = &af_blue_stringsets[bss];
262 for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
264 const char* p = &af_blue_strings[bs->string];
272 GET_UTF8_CHAR( ch, p );
274 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups,
277 hb_codepoint_t gidx = FT_Get_Char_Index( globals->face, ch );
280 if ( hb_ot_layout_lookup_would_substitute( face, idx,
292 FT_TRACE4(( " no blue characters found; style skipped\n" ));
298 * Various OpenType features might use the same glyphs at different
299 * vertical positions; for example, superscript and subscript glyphs
300 * could be the same. However, the auto-hinter is completely
301 * agnostic of OpenType features after the feature analysis has been
302 * completed: The engine then simply receives a glyph index and returns a
303 * hinted and usually rendered glyph.
305 * Consider the superscript feature of font `pala.ttf': Some of the
306 * glyphs are `real', this is, they have a zero vertical offset, but
307 * most of them are small caps glyphs shifted up to the superscript
308 * position (this is, the `sups' feature is present in both the GSUB and
309 * GPOS tables). The code for blue zones computation actually uses a
310 * feature's y offset so that the `real' glyphs get correct hints. But
311 * later on it is impossible to decide whether a glyph index belongs to,
312 * say, the small caps or superscript feature.
314 * For this reason, we don't assign a style to a glyph if the current
315 * feature covers the glyph in both the GSUB and the GPOS tables. This
316 * is quite a broad condition, assuming that
318 * (a) glyphs that get used in multiple features are present in a
319 * feature without vertical shift,
323 * (b) a feature's GPOS data really moves the glyph vertically.
325 * Not fulfilling condition (a) makes a font larger; it would also
326 * reduce the number of glyphs that could be addressed directly without
327 * using OpenType features, so this assumption is rather strong.
329 * Condition (b) is much weaker, and there might be glyphs which get
330 * missed. However, the OpenType features we are going to handle are
331 * primarily located in GSUB, and HarfBuzz doesn't provide an API to
332 * directly get the necessary information from the GPOS table. A
333 * possible solution might be to directly parse the GPOS table to find
334 * out whether a glyph gets shifted vertically, but this is something I
335 * would like to avoid if not really necessary.
337 * Note that we don't follow this logic for the default coverage.
338 * Complex scripts like Devanagari have mandatory GPOS features to
339 * position many glyph elements, using mark-to-base or mark-to-ligature
340 * tables; the number of glyphs missed due to condition (b) would be far
344 if ( style_class->coverage != AF_COVERAGE_DEFAULT )
345 hb_set_subtract( gsub_glyphs, gpos_glyphs );
347 #ifdef FT_DEBUG_LEVEL_TRACE
348 FT_TRACE4(( " glyphs without GPOS data (`*' means already assigned)" ));
352 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_glyphs, &idx ); )
354 #ifdef FT_DEBUG_LEVEL_TRACE
355 if ( !( count % 10 ) )
359 FT_TRACE4(( " %d", idx ));
363 /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */
364 /* can be arbitrary: some fonts use fake indices for processing */
365 /* internal to GSUB or GPOS, which is fully valid */
366 if ( idx >= (hb_codepoint_t)globals->glyph_count )
369 if ( gstyles[idx] == AF_STYLE_UNASSIGNED )
370 gstyles[idx] = (FT_UShort)style_class->style;
371 #ifdef FT_DEBUG_LEVEL_TRACE
377 #ifdef FT_DEBUG_LEVEL_TRACE
381 FT_TRACE4(( "\n\n" ));
385 hb_set_destroy( gsub_lookups );
386 hb_set_destroy( gsub_glyphs );
387 hb_set_destroy( gpos_lookups );
388 hb_set_destroy( gpos_glyphs );
394 /* construct HarfBuzz features */
396 #define COVERAGE( name, NAME, description, \
397 tag1, tag2, tag3, tag4 ) \
398 static const hb_feature_t name ## _feature[] = \
401 HB_TAG( tag1, tag2, tag3, tag4 ), \
402 1, 0, (unsigned int)-1 \
410 /* define mapping between HarfBuzz features and AF_Coverage */
412 #define COVERAGE( name, NAME, description, \
413 tag1, tag2, tag3, tag4 ) \
417 static const hb_feature_t* features[] =
421 NULL /* AF_COVERAGE_DEFAULT */
426 af_shaper_buf_create( FT_Face face )
430 return (void*)hb_buffer_create();
435 af_shaper_buf_destroy( FT_Face face,
440 hb_buffer_destroy( (hb_buffer_t*)buf );
445 af_shaper_get_cluster( const char* p,
446 AF_StyleMetrics metrics,
448 unsigned int* count )
450 AF_StyleClass style_class;
451 const hb_feature_t* feature;
456 hb_buffer_t* buf = (hb_buffer_t*)buf_;
458 hb_codepoint_t dummy;
461 upem = (FT_Int)metrics->globals->face->units_per_EM;
462 style_class = metrics->style_class;
463 feature = features[style_class->coverage];
465 font = metrics->globals->hb_font;
467 /* we shape at a size of units per EM; this means font units */
468 hb_font_set_scale( font, upem, upem );
473 /* count bytes up to next space (or end of buffer) */
475 while ( !( *q == ' ' || *q == '\0' ) )
476 GET_UTF8_CHAR( dummy, q );
477 len = (int)( q - p );
479 /* feed character(s) to the HarfBuzz buffer */
480 hb_buffer_clear_contents( buf );
481 hb_buffer_add_utf8( buf, p, len, 0, len );
483 /* we let HarfBuzz guess the script and writing direction */
484 hb_buffer_guess_segment_properties( buf );
486 /* shape buffer, which means conversion from character codes to */
487 /* glyph indices, possibly applying a feature */
488 hb_shape( font, buf, feature, feature ? 1 : 0 );
492 hb_buffer_t* hb_buf = metrics->globals->hb_buf;
495 hb_glyph_info_t* ginfo;
497 unsigned int hb_gcount;
498 hb_glyph_info_t* hb_ginfo;
501 /* we have to check whether applying a feature does actually change */
502 /* glyph indices; otherwise the affected glyph or glyphs aren't */
503 /* available at all in the feature */
505 hb_buffer_clear_contents( hb_buf );
506 hb_buffer_add_utf8( hb_buf, p, len, 0, len );
507 hb_buffer_guess_segment_properties( hb_buf );
508 hb_shape( font, hb_buf, NULL, 0 );
510 ginfo = hb_buffer_get_glyph_infos( buf, &gcount );
511 hb_ginfo = hb_buffer_get_glyph_infos( hb_buf, &hb_gcount );
513 if ( gcount == hb_gcount )
518 for (i = 0; i < gcount; i++ )
519 if ( ginfo[i].codepoint != hb_ginfo[i].codepoint )
524 /* both buffers have identical glyph indices */
525 hb_buffer_clear_contents( buf );
530 *count = hb_buffer_get_length( buf );
532 #ifdef FT_DEBUG_LEVEL_TRACE
533 if ( feature && *count > 1 )
534 FT_TRACE1(( "af_shaper_get_cluster:"
535 " input character mapped to multiple glyphs\n" ));
543 af_shaper_get_elem( AF_StyleMetrics metrics,
549 hb_buffer_t* buf = (hb_buffer_t*)buf_;
550 hb_glyph_info_t* ginfo;
551 hb_glyph_position_t* gpos;
554 FT_UNUSED( metrics );
557 ginfo = hb_buffer_get_glyph_infos( buf, &gcount );
558 gpos = hb_buffer_get_glyph_positions( buf, &gcount );
564 *advance = gpos[idx].x_advance;
566 *y_offset = gpos[idx].y_offset;
568 return ginfo[idx].codepoint;
572 #else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */
576 af_shaper_get_coverage( AF_FaceGlobals globals,
577 AF_StyleClass style_class,
579 FT_Bool default_script )
581 FT_UNUSED( globals );
582 FT_UNUSED( style_class );
583 FT_UNUSED( gstyles );
584 FT_UNUSED( default_script );
591 af_shaper_buf_create( FT_Face face )
600 af_shaper_buf_destroy( FT_Face face,
609 af_shaper_get_cluster( const char* p,
610 AF_StyleMetrics metrics,
612 unsigned int* count )
614 FT_Face face = metrics->globals->face;
615 FT_ULong ch, dummy = 0;
616 FT_ULong* buf = (FT_ULong*)buf_;
622 GET_UTF8_CHAR( ch, p );
624 /* since we don't have an engine to handle clusters, */
625 /* we scan the characters but return zero */
626 while ( !( *p == ' ' || *p == '\0' ) )
627 GET_UTF8_CHAR( dummy, p );
636 *buf = FT_Get_Char_Index( face, ch );
645 af_shaper_get_elem( AF_StyleMetrics metrics,
651 FT_Face face = metrics->globals->face;
652 FT_ULong glyph_index = *(FT_ULong*)buf_;
658 FT_Get_Advance( face,
662 FT_LOAD_IGNORE_TRANSFORM,
672 #endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */