/***************************************************************************/ | |
/* */ | |
/* hbshim.c */ | |
/* */ | |
/* HarfBuzz interface for accessing OpenType features (body). */ | |
/* */ | |
/* Copyright 2013-2015 by */ | |
/* David Turner, Robert Wilhelm, and Werner Lemberg. */ | |
/* */ | |
/* This file is part of the FreeType project, and may only be used, */ | |
/* modified, and distributed under the terms of the FreeType project */ | |
/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ | |
/* this file you indicate that you have read the license and */ | |
/* understand and accept it fully. */ | |
/* */ | |
/***************************************************************************/ | |
#include <ft2build.h> | |
#include FT_FREETYPE_H | |
#include "afglobal.h" | |
#include "aftypes.h" | |
#include "hbshim.h" | |
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ | |
/*************************************************************************/ | |
/* */ | |
/* The macro FT_COMPONENT is used in trace mode. It is an implicit */ | |
/* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log */ | |
/* messages during execution. */ | |
/* */ | |
#undef FT_COMPONENT | |
#define FT_COMPONENT trace_afharfbuzz | |
/* | |
* We use `sets' (in the HarfBuzz sense, which comes quite near to the | |
* usual mathematical meaning) to manage both lookups and glyph indices. | |
* | |
* 1. For each coverage, collect lookup IDs in a set. Note that an | |
* auto-hinter `coverage' is represented by one `feature', and a | |
* feature consists of an arbitrary number of (font specific) `lookup's | |
* that actually do the mapping job. Please check the OpenType | |
* specification for more details on features and lookups. | |
* | |
* 2. Create glyph ID sets from the corresponding lookup sets. | |
* | |
* 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed | |
* with all lookups specific to the OpenType script activated. It | |
* relies on the order of AF_DEFINE_STYLE_CLASS entries so that | |
* special coverages (like `oldstyle figures') don't get overwritten. | |
* | |
*/ | |
/* load coverage tags */ | |
#undef COVERAGE | |
#define COVERAGE( name, NAME, description, \ | |
tag1, tag2, tag3, tag4 ) \ | |
static const hb_tag_t name ## _coverage[] = \ | |
{ \ | |
HB_TAG( tag1, tag2, tag3, tag4 ), \ | |
HB_TAG_NONE \ | |
}; | |
#include "afcover.h" | |
/* define mapping between coverage tags and AF_Coverage */ | |
#undef COVERAGE | |
#define COVERAGE( name, NAME, description, \ | |
tag1, tag2, tag3, tag4 ) \ | |
name ## _coverage, | |
static const hb_tag_t* coverages[] = | |
{ | |
#include "afcover.h" | |
NULL /* AF_COVERAGE_DEFAULT */ | |
}; | |
/* load HarfBuzz script tags */ | |
#undef SCRIPT | |
#define SCRIPT( s, S, d, h, sc1, sc2, sc3 ) h, | |
static const hb_script_t scripts[] = | |
{ | |
#include "afscript.h" | |
}; | |
FT_Error | |
af_get_coverage( AF_FaceGlobals globals, | |
AF_StyleClass style_class, | |
FT_UShort* gstyles ) | |
{ | |
hb_face_t* face; | |
hb_set_t* gsub_lookups; /* GSUB lookups for a given script */ | |
hb_set_t* gsub_glyphs; /* glyphs covered by GSUB lookups */ | |
hb_set_t* gpos_lookups; /* GPOS lookups for a given script */ | |
hb_set_t* gpos_glyphs; /* glyphs covered by GPOS lookups */ | |
hb_script_t script; | |
const hb_tag_t* coverage_tags; | |
hb_tag_t script_tags[] = { HB_TAG_NONE, | |
HB_TAG_NONE, | |
HB_TAG_NONE, | |
HB_TAG_NONE }; | |
hb_codepoint_t idx; | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
int count; | |
#endif | |
if ( !globals || !style_class || !gstyles ) | |
return FT_THROW( Invalid_Argument ); | |
face = hb_font_get_face( globals->hb_font ); | |
gsub_lookups = hb_set_create(); | |
gsub_glyphs = hb_set_create(); | |
gpos_lookups = hb_set_create(); | |
gpos_glyphs = hb_set_create(); | |
coverage_tags = coverages[style_class->coverage]; | |
script = scripts[style_class->script]; | |
/* Convert a HarfBuzz script tag into the corresponding OpenType */ | |
/* tag or tags -- some Indic scripts like Devanagari have an old */ | |
/* and a new set of features. */ | |
hb_ot_tags_from_script( script, | |
&script_tags[0], | |
&script_tags[1] ); | |
/* `hb_ot_tags_from_script' usually returns HB_OT_TAG_DEFAULT_SCRIPT */ | |
/* as the second tag. We change that to HB_TAG_NONE except for the */ | |
/* default script. */ | |
if ( style_class->script == globals->module->default_script && | |
style_class->coverage == AF_COVERAGE_DEFAULT ) | |
{ | |
if ( script_tags[0] == HB_TAG_NONE ) | |
script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT; | |
else | |
{ | |
if ( script_tags[1] == HB_TAG_NONE ) | |
script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT; | |
else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT ) | |
script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT; | |
} | |
} | |
else | |
{ | |
if ( script_tags[1] == HB_OT_TAG_DEFAULT_SCRIPT ) | |
script_tags[1] = HB_TAG_NONE; | |
} | |
hb_ot_layout_collect_lookups( face, | |
HB_OT_TAG_GSUB, | |
script_tags, | |
NULL, | |
coverage_tags, | |
gsub_lookups ); | |
if ( hb_set_is_empty( gsub_lookups ) ) | |
goto Exit; /* nothing to do */ | |
hb_ot_layout_collect_lookups( face, | |
HB_OT_TAG_GPOS, | |
script_tags, | |
NULL, | |
coverage_tags, | |
gpos_lookups ); | |
FT_TRACE4(( "GSUB lookups (style `%s'):\n" | |
" ", | |
af_style_names[style_class->style] )); | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
count = 0; | |
#endif | |
for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, &idx ); ) | |
{ | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
FT_TRACE4(( " %d", idx )); | |
count++; | |
#endif | |
/* get output coverage of GSUB feature */ | |
hb_ot_layout_lookup_collect_glyphs( face, | |
HB_OT_TAG_GSUB, | |
idx, | |
NULL, | |
NULL, | |
NULL, | |
gsub_glyphs ); | |
} | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
if ( !count ) | |
FT_TRACE4(( " (none)" )); | |
FT_TRACE4(( "\n\n" )); | |
#endif | |
FT_TRACE4(( "GPOS lookups (style `%s'):\n" | |
" ", | |
af_style_names[style_class->style] )); | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
count = 0; | |
#endif | |
for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gpos_lookups, &idx ); ) | |
{ | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
FT_TRACE4(( " %d", idx )); | |
count++; | |
#endif | |
/* get input coverage of GPOS feature */ | |
hb_ot_layout_lookup_collect_glyphs( face, | |
HB_OT_TAG_GPOS, | |
idx, | |
NULL, | |
gpos_glyphs, | |
NULL, | |
NULL ); | |
} | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
if ( !count ) | |
FT_TRACE4(( " (none)" )); | |
FT_TRACE4(( "\n\n" )); | |
#endif | |
/* | |
* We now check whether we can construct blue zones, using glyphs | |
* covered by the feature only. In case there is not a single zone | |
* (this is, not a single character is covered), we skip this coverage. | |
* | |
*/ | |
if ( style_class->coverage != AF_COVERAGE_DEFAULT ) | |
{ | |
AF_Blue_Stringset bss = style_class->blue_stringset; | |
const AF_Blue_StringRec* bs = &af_blue_stringsets[bss]; | |
FT_Bool found = 0; | |
for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ ) | |
{ | |
const char* p = &af_blue_strings[bs->string]; | |
while ( *p ) | |
{ | |
hb_codepoint_t ch; | |
GET_UTF8_CHAR( ch, p ); | |
for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, | |
&idx ); ) | |
{ | |
hb_codepoint_t gidx = FT_Get_Char_Index( globals->face, ch ); | |
if ( hb_ot_layout_lookup_would_substitute( face, idx, | |
&gidx, 1, 1 ) ) | |
{ | |
found = 1; | |
break; | |
} | |
} | |
} | |
} | |
if ( !found ) | |
{ | |
FT_TRACE4(( " no blue characters found; style skipped\n" )); | |
goto Exit; | |
} | |
} | |
/* | |
* Various OpenType features might use the same glyphs at different | |
* vertical positions; for example, superscript and subscript glyphs | |
* could be the same. However, the auto-hinter is completely | |
* agnostic of OpenType features after the feature analysis has been | |
* completed: The engine then simply receives a glyph index and returns a | |
* hinted and usually rendered glyph. | |
* | |
* Consider the superscript feature of font `pala.ttf': Some of the | |
* glyphs are `real', this is, they have a zero vertical offset, but | |
* most of them are small caps glyphs shifted up to the superscript | |
* position (this is, the `sups' feature is present in both the GSUB and | |
* GPOS tables). The code for blue zones computation actually uses a | |
* feature's y offset so that the `real' glyphs get correct hints. But | |
* later on it is impossible to decide whether a glyph index belongs to, | |
* say, the small caps or superscript feature. | |
* | |
* For this reason, we don't assign a style to a glyph if the current | |
* feature covers the glyph in both the GSUB and the GPOS tables. This | |
* is quite a broad condition, assuming that | |
* | |
* (a) glyphs that get used in multiple features are present in a | |
* feature without vertical shift, | |
* | |
* and | |
* | |
* (b) a feature's GPOS data really moves the glyph vertically. | |
* | |
* Not fulfilling condition (a) makes a font larger; it would also | |
* reduce the number of glyphs that could be addressed directly without | |
* using OpenType features, so this assumption is rather strong. | |
* | |
* Condition (b) is much weaker, and there might be glyphs which get | |
* missed. However, the OpenType features we are going to handle are | |
* primarily located in GSUB, and HarfBuzz doesn't provide an API to | |
* directly get the necessary information from the GPOS table. A | |
* possible solution might be to directly parse the GPOS table to find | |
* out whether a glyph gets shifted vertically, but this is something I | |
* would like to avoid if not really necessary. | |
* | |
* Note that we don't follow this logic for the default coverage. | |
* Complex scripts like Devanagari have mandatory GPOS features to | |
* position many glyph elements, using mark-to-base or mark-to-ligature | |
* tables; the number of glyphs missed due to condition (b) would be far | |
* too large. | |
* | |
*/ | |
if ( style_class->coverage != AF_COVERAGE_DEFAULT ) | |
hb_set_subtract( gsub_glyphs, gpos_glyphs ); | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
FT_TRACE4(( " glyphs without GPOS data (`*' means already assigned)" )); | |
count = 0; | |
#endif | |
for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_glyphs, &idx ); ) | |
{ | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
if ( !( count % 10 ) ) | |
FT_TRACE4(( "\n" | |
" " )); | |
FT_TRACE4(( " %d", idx )); | |
count++; | |
#endif | |
/* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */ | |
/* can be arbitrary: some fonts use fake indices for processing */ | |
/* internal to GSUB or GPOS, which is fully valid */ | |
if ( idx >= (hb_codepoint_t)globals->glyph_count ) | |
continue; | |
if ( gstyles[idx] == AF_STYLE_UNASSIGNED ) | |
gstyles[idx] = (FT_UShort)style_class->style; | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
else | |
FT_TRACE4(( "*" )); | |
#endif | |
} | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
if ( !count ) | |
FT_TRACE4(( "\n" | |
" (none)" )); | |
FT_TRACE4(( "\n\n" )); | |
#endif | |
Exit: | |
hb_set_destroy( gsub_lookups ); | |
hb_set_destroy( gsub_glyphs ); | |
hb_set_destroy( gpos_lookups ); | |
hb_set_destroy( gpos_glyphs ); | |
return FT_Err_Ok; | |
} | |
/* construct HarfBuzz features */ | |
#undef COVERAGE | |
#define COVERAGE( name, NAME, description, \ | |
tag1, tag2, tag3, tag4 ) \ | |
static const hb_feature_t name ## _feature[] = \ | |
{ \ | |
{ \ | |
HB_TAG( tag1, tag2, tag3, tag4 ), \ | |
1, 0, (unsigned int)-1 \ | |
} \ | |
}; | |
#include "afcover.h" | |
/* define mapping between HarfBuzz features and AF_Coverage */ | |
#undef COVERAGE | |
#define COVERAGE( name, NAME, description, \ | |
tag1, tag2, tag3, tag4 ) \ | |
name ## _feature, | |
static const hb_feature_t* features[] = | |
{ | |
#include "afcover.h" | |
NULL /* AF_COVERAGE_DEFAULT */ | |
}; | |
FT_Error | |
af_get_char_index( AF_StyleMetrics metrics, | |
FT_ULong charcode, | |
FT_ULong *codepoint, | |
FT_Long *y_offset ) | |
{ | |
AF_StyleClass style_class; | |
const hb_feature_t* feature; | |
FT_ULong in_idx, out_idx; | |
if ( !metrics ) | |
return FT_THROW( Invalid_Argument ); | |
in_idx = FT_Get_Char_Index( metrics->globals->face, charcode ); | |
style_class = metrics->style_class; | |
feature = features[style_class->coverage]; | |
if ( feature ) | |
{ | |
FT_Int upem = (FT_Int)metrics->globals->face->units_per_EM; | |
hb_font_t* font = metrics->globals->hb_font; | |
hb_buffer_t* buf = hb_buffer_create(); | |
uint32_t c = (uint32_t)charcode; | |
hb_glyph_info_t* ginfo; | |
hb_glyph_position_t* gpos; | |
unsigned int gcount; | |
/* we shape at a size of units per EM; this means font units */ | |
hb_font_set_scale( font, upem, upem ); | |
/* XXX: is this sufficient for a single character of any script? */ | |
hb_buffer_set_direction( buf, HB_DIRECTION_LTR ); | |
hb_buffer_set_script( buf, scripts[style_class->script] ); | |
/* we add one character to `buf' ... */ | |
hb_buffer_add_utf32( buf, &c, 1, 0, 1 ); | |
/* ... and apply one feature */ | |
hb_shape( font, buf, feature, 1 ); | |
ginfo = hb_buffer_get_glyph_infos( buf, &gcount ); | |
gpos = hb_buffer_get_glyph_positions( buf, &gcount ); | |
out_idx = ginfo[0].codepoint; | |
/* getting the same index indicates no substitution, */ | |
/* which means that the glyph isn't available in the feature */ | |
if ( in_idx == out_idx ) | |
{ | |
*codepoint = 0; | |
*y_offset = 0; | |
} | |
else | |
{ | |
*codepoint = out_idx; | |
*y_offset = gpos[0].y_offset; | |
} | |
hb_buffer_destroy( buf ); | |
#ifdef FT_DEBUG_LEVEL_TRACE | |
if ( gcount > 1 ) | |
FT_TRACE1(( "af_get_char_index:" | |
" input character mapped to multiple glyphs\n" )); | |
#endif | |
} | |
else | |
{ | |
*codepoint = in_idx; | |
*y_offset = 0; | |
} | |
return FT_Err_Ok; | |
} | |
#else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ | |
FT_Error | |
af_get_coverage( AF_FaceGlobals globals, | |
AF_StyleClass style_class, | |
FT_UShort* gstyles ) | |
{ | |
FT_UNUSED( globals ); | |
FT_UNUSED( style_class ); | |
FT_UNUSED( gstyles ); | |
return FT_Err_Ok; | |
} | |
FT_Error | |
af_get_char_index( AF_StyleMetrics metrics, | |
FT_ULong charcode, | |
FT_ULong *codepoint, | |
FT_Long *y_offset ) | |
{ | |
FT_Face face; | |
if ( !metrics ) | |
return FT_THROW( Invalid_Argument ); | |
face = metrics->globals->face; | |
*codepoint = FT_Get_Char_Index( face, charcode ); | |
*y_offset = 0; | |
return FT_Err_Ok; | |
} | |
#endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ | |
/* END */ |