* Add trace output

* Remove assumption of exactly 2 contours
* Adjustment database lookups use binary search now
This commit is contained in:
Craig White 2023-06-25 23:24:02 -04:00
parent f8e996bfb1
commit 62378cca3a
4 changed files with 159 additions and 77 deletions

View File

@ -164,6 +164,7 @@ FT_TRACE_DEF( afhints )
FT_TRACE_DEF( afmodule ) FT_TRACE_DEF( afmodule )
FT_TRACE_DEF( aflatin ) FT_TRACE_DEF( aflatin )
FT_TRACE_DEF( afshaper ) FT_TRACE_DEF( afshaper )
FT_TRACE_DEF( afadjust )
/* SDF components */ /* SDF components */
FT_TRACE_DEF( sdf ) /* signed distance raster for outlines (ftsdf.c) */ FT_TRACE_DEF( sdf ) /* signed distance raster for outlines (ftsdf.c) */

View File

@ -2,34 +2,52 @@
#include <freetype/freetype.h> #include <freetype/freetype.h>
#include <freetype/internal/ftobjs.h> #include <freetype/internal/ftobjs.h>
#include <freetype/internal/ftmemory.h> #include <freetype/internal/ftmemory.h>
#include <freetype/internal/ftdebug.h>
#define AF_ADJUSTMENT_DATABASE_LENGTH 12 #define AF_ADJUSTMENT_DATABASE_LENGTH (sizeof(adjustment_database)/sizeof(adjustment_database[0]))
#undef FT_COMPONENT
#define FT_COMPONENT afadjust
/*TODO: find out whether capital u/U with accent entries are needed*/ /*TODO: find out whether capital u/U with accent entries are needed*/
/*the accent won't merge with the rest of the glyph because the accent mark is sitting above empty space*/ /*the accent won't merge with the rest of the glyph because the accent mark is sitting above empty space*/
FT_LOCAL_ARRAY_DEF( AF_AdjustmentDatabaseEntry ) FT_LOCAL_ARRAY_DEF( AF_AdjustmentDatabaseEntry )
adjustment_database[AF_ADJUSTMENT_DATABASE_LENGTH] = { adjustment_database[] =
{'i', AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, {
{'j', AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, {0x21, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* ! */
{0xC8, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*E with grave*/ {0x69, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* i */
{0xCC, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*I with grave*/ {0x6A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* j */
{0xD9, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*U with grave*/ {0xA1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*Inverted Exclamation Mark*/
{0xE0, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*a with grave*/ {0xBF, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*Inverted Question Mark*/
{0xEC, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*i with grave*/ {0xC0, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with grave*/
{0x114, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*E with macron*/ {0xC1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with acute*/
{0x12A, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*I with macron*/ {0xC2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with circumflex*/
{0x12B, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*i with macron*/ {0xC3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with tilde*/
{0x16A, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*U with macron*/ {0xC8, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*E with grave*/
{0x16B, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE} /*u with macron*/ {0xCC, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*I with grave*/
/*TODO: find out why E won't work, even though it appears to be one-on-one*/ {0xD9, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*U with grave*/
{0xE0, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*a with grave*/
{0xEC, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*i with grave*/
{0x114, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*E with macron*/
{0x12A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*I with macron*/
{0x12B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*i with macron*/
{0x16A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*U with macron*/
{0x16B, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP} /*u with macron*/
}; };
/*Helper function: get the adjustment database entry for a codepoint*/ /*Helper function: get the adjustment database entry for a codepoint*/
FT_LOCAL_DEF( const AF_AdjustmentDatabaseEntry* ) FT_LOCAL_DEF( const AF_AdjustmentDatabaseEntry* )
af_adjustment_database_lookup( FT_UInt32 codepoint ) { af_adjustment_database_lookup( FT_UInt32 codepoint ) {
for ( FT_Int entry = 0; entry < AF_ADJUSTMENT_DATABASE_LENGTH; entry++ ) { /* Binary search for database entry */
if ( adjustment_database[entry].codepoint == codepoint ) { FT_UInt low = 0;
return &adjustment_database[entry]; FT_UInt high = AF_ADJUSTMENT_DATABASE_LENGTH - 1;
while (high > low) {
FT_UInt mid = (low + high) / 2;
if (adjustment_database[mid].codepoint < codepoint) {
low = mid + 1;
} else if (adjustment_database[mid].codepoint > codepoint) {
high = mid - 1;
} else {
return &adjustment_database[mid];
} }
} }
@ -40,30 +58,37 @@ FT_LOCAL_DEF( AF_VerticalSeparationAdjustmentType )
af_lookup_vertical_seperation_type( AF_ReverseCharacterMap map, FT_Int glyph_index ) { af_lookup_vertical_seperation_type( AF_ReverseCharacterMap map, FT_Int glyph_index ) {
FT_UInt32 codepoint = af_reverse_character_map_lookup( map, glyph_index ); FT_UInt32 codepoint = af_reverse_character_map_lookup( map, glyph_index );
const AF_AdjustmentDatabaseEntry *entry = af_adjustment_database_lookup( codepoint ); const AF_AdjustmentDatabaseEntry *entry = af_adjustment_database_lookup( codepoint );
if ( entry == NULL ) { if ( entry == NULL )
{
return AF_VERTICAL_ADJUSTMENT_NONE; return AF_VERTICAL_ADJUSTMENT_NONE;
} }
return entry->vertical_separation_adjustment_type; return entry->vertical_separation_adjustment_type;
} }
typedef struct AF_ReverseMapEntry_ { typedef struct AF_ReverseMapEntry_
{
FT_Int glyph_index; FT_Int glyph_index;
FT_UInt32 codepoint; FT_UInt32 codepoint;
} AF_ReverseMapEntry; } AF_ReverseMapEntry;
typedef struct AF_ReverseCharacterMap_ { typedef struct AF_ReverseCharacterMap_
{
FT_UInt length; FT_UInt length;
AF_ReverseMapEntry *entries; AF_ReverseMapEntry *entries;
} AF_ReverseCharacterMap_Rec; } AF_ReverseCharacterMap_Rec;
FT_LOCAL_DEF(FT_UInt32) FT_LOCAL_DEF(FT_UInt32)
af_reverse_character_map_lookup( AF_ReverseCharacterMap map, FT_Int glyph_index ) { af_reverse_character_map_lookup( AF_ReverseCharacterMap map, FT_Int glyph_index )
if ( map == NULL ) { {
if ( map == NULL )
{
return 0; return 0;
} }
for ( FT_UInt entry = 0; entry < map->length; entry++ ) { for ( FT_UInt entry = 0; entry < map->length; entry++ )
if ( map->entries[entry].glyph_index == glyph_index ) { {
if ( map->entries[entry].glyph_index == glyph_index )
{
return map->entries[entry].codepoint; return map->entries[entry].codepoint;
} }
} }
@ -72,64 +97,87 @@ af_reverse_character_map_lookup( AF_ReverseCharacterMap map, FT_Int glyph_index
} }
FT_LOCAL_DEF( FT_Error ) FT_LOCAL_DEF( FT_Error )
af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory ) { af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory )
{
/* Search for a unicode charmap */ /* Search for a unicode charmap */
/* If there isn't one, create a blank map */ /* If there isn't one, create a blank map */
/*TODO: change this to logic that searches for a "preferred" unicode charmap that maps the most codepoints*/ /*TODO: change this to logic that searches for a "preferred" unicode charmap that maps the most codepoints*/
/*see find_unicode_charmap*/ /*see find_unicode_charmap*/
/*TODO: use GSUB lookups */ /*TODO: use GSUB lookups */
FT_TRACE4(( "af_reverse_character_map_new: building reverse character map\n" ));
FT_CMap unicode_charmap = NULL; FT_CMap unicode_charmap = NULL;
for ( FT_UInt i = 0; i < face->num_charmaps; i++ ) { for ( FT_UInt i = 0; i < face->num_charmaps; i++ )
if ( face->charmaps[i]->encoding == FT_ENCODING_UNICODE ) { {
if ( face->charmaps[i]->encoding == FT_ENCODING_UNICODE )
{
unicode_charmap = FT_CMAP( face->charmaps[i] ); unicode_charmap = FT_CMAP( face->charmaps[i] );
} }
} }
if ( unicode_charmap == NULL ) { if ( unicode_charmap == NULL )
{
*map = NULL; *map = NULL;
return FT_Err_Ok; return FT_Err_Ok;
} }
FT_Error error; FT_Error error;
if ( FT_NEW( *map ) ) { if ( FT_NEW( *map ) )
{
goto Exit; goto Exit;
} }
FT_Int capacity = 10; FT_Int capacity = 10;
FT_Int size = 0; FT_Int size = 0;
if ( FT_NEW_ARRAY((*map)->entries, capacity) ) { if ( FT_NEW_ARRAY((*map)->entries, capacity) )
{
goto Exit; goto Exit;
} }
for ( FT_Int i = 0; i < AF_ADJUSTMENT_DATABASE_LENGTH; i++ ) { #ifdef FT_DEBUG_LEVEL_TRACE
int failed_lookups = 0;
#endif
for ( FT_Int i = 0; i < AF_ADJUSTMENT_DATABASE_LENGTH; i++ )
{
FT_UInt32 codepoint = adjustment_database[i].codepoint; FT_UInt32 codepoint = adjustment_database[i].codepoint;
FT_Int glyph = unicode_charmap->clazz->char_index(unicode_charmap, codepoint); FT_Int glyph = unicode_charmap->clazz->char_index(unicode_charmap, codepoint);
if ( glyph == 0 ) { if ( glyph == 0 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
failed_lookups++;
#endif
continue; continue;
} }
if (size == capacity) { if ( size == capacity )
{
capacity += capacity / 2; capacity += capacity / 2;
if ( FT_RENEW_ARRAY((*map)->entries, size, capacity) ) { if ( FT_RENEW_ARRAY((*map)->entries, size, capacity) )
{
goto Exit; goto Exit;
} }
} }
size++; size++;
(*map)->entries[i].glyph_index = glyph; ( *map )->entries[i].glyph_index = glyph;
(*map)->entries[i].codepoint = codepoint; ( *map )->entries[i].codepoint = codepoint;
} }
(*map)->length = size; ( *map )->length = size;
Exit: Exit:
if ( error ) { if ( error )
if ( *map ) { {
FT_TRACE4(( " error while building reverse character map. Using blank map.\n" ));
if ( *map )
{
FT_FREE( ( *map )->entries ); FT_FREE( ( *map )->entries );
} }
FT_FREE( *map ); FT_FREE( *map );
return error; return error;
} }
#ifdef FT_DEBUG_LEVEL_TRACE
FT_TRACE4(( " reverse character map built successfully"\
" with %d entries and %d failed lookups.\n", size, failed_lookups ));
#endif
return FT_Err_Ok; return FT_Err_Ok;
} }
@ -137,4 +185,4 @@ FT_LOCAL_DEF( FT_Error )
af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ) { af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ) {
FT_FREE( map->entries ); FT_FREE( map->entries );
return FT_Err_Ok; return FT_Err_Ok;
} }

View File

@ -7,11 +7,11 @@ FT_BEGIN_HEADER
/*The type of adjustment that should be done to prevent cases where 2 parts of a character*/ /*The type of adjustment that should be done to prevent cases where 2 parts of a character*/
/*stacked vertically merge, even though they should be separate*/ /*stacked vertically merge, even though they should be separate*/
typedef enum AF_VerticalSeparationAdjustmentType_ { typedef enum AF_VerticalSeparationAdjustmentType_
AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE, {
/*"One on one" means that the character is expected to be one contour on top of another, where the contours should not touch*/ AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP,
/*the hinter will force the contours to have a gap of at least 1 pixel between them*/ /*This means that the hinter should find the topmost contour and push it up until its lowest point is 1 pixel*/
/*by moving the top contour up */ /*above the highest point not part of that contour.*/
AF_VERTICAL_ADJUSTMENT_NONE AF_VERTICAL_ADJUSTMENT_NONE
/*others will be needed, such as the case where the lower contour should be moved in the adjustment instead of the upper one*/ /*others will be needed, such as the case where the lower contour should be moved in the adjustment instead of the upper one*/
@ -19,10 +19,11 @@ typedef enum AF_VerticalSeparationAdjustmentType_ {
/*and a way of handling A and O, where the letter consists of 2 contours*/ /*and a way of handling A and O, where the letter consists of 2 contours*/
} AF_VerticalSeparationAdjustmentType; } AF_VerticalSeparationAdjustmentType;
typedef struct AF_AdjustmentDatabaseEntry_ { typedef struct AF_AdjustmentDatabaseEntry_
FT_UInt32 codepoint; {
AF_VerticalSeparationAdjustmentType vertical_separation_adjustment_type; FT_UInt32 codepoint;
} AF_AdjustmentDatabaseEntry; AF_VerticalSeparationAdjustmentType vertical_separation_adjustment_type;
} AF_AdjustmentDatabaseEntry;
struct AF_ReverseCharacterMap_; struct AF_ReverseCharacterMap_;
@ -37,11 +38,11 @@ af_reverse_character_map_lookup( AF_ReverseCharacterMap map, FT_Int glyph_index
/*allocate and populate the reverse character map, using the character map within the face*/ /*allocate and populate the reverse character map, using the character map within the face*/
FT_LOCAL( FT_Error ) FT_LOCAL( FT_Error )
af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory ); af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory );
/*free the reverse character map*/ /*free the reverse character map*/
FT_LOCAL( FT_Error ) FT_LOCAL( FT_Error )
af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ); af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory );
FT_END_HEADER FT_END_HEADER
#endif #endif

View File

@ -1153,7 +1153,7 @@
goto Exit; goto Exit;
} }
af_latin_metrics_check_digits( metrics, face ); af_latin_metrics_check_digits( metrics, face );
af_reverse_character_map_new( face, &metrics->root.reverse_charmap, face->memory ); af_reverse_character_map_new( face, &metrics->root.reverse_charmap, face->memory );
} }
@ -2745,13 +2745,25 @@
} }
void af_glyph_hints_apply_adjustments(AF_GlyphHints hints, AF_Dimension dim, FT_Int glyph_index, AF_ReverseCharacterMap reverse_charmap) { #undef FT_COMPONENT
if ( dim != AF_DIMENSION_VERT ) { #define FT_COMPONENT afadjust
void
af_glyph_hints_apply_vertical_separation_adjustments( AF_GlyphHints hints,
AF_Dimension dim,
FT_Int glyph_index,
AF_ReverseCharacterMap reverse_charmap )
{
if ( dim != AF_DIMENSION_VERT )
{
return; return;
} }
if ( af_lookup_vertical_seperation_type( reverse_charmap, glyph_index ) == AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE &&
hints->num_contours == 2 ) { if ( af_lookup_vertical_seperation_type( reverse_charmap, glyph_index ) == AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP
&& hints->num_contours >= 2 )
{
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments: Applying vertical adjustment: AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP\n" ));
/* Figure out which contout is the higher one by finding the one */ /* Figure out which contout is the higher one by finding the one */
/* with the highest minimum y value */ /* with the highest minimum y value */
@ -2759,22 +2771,27 @@ void af_glyph_hints_apply_adjustments(AF_GlyphHints hints, AF_Dimension dim, FT_
FT_Pos highest_min_y = 0; FT_Pos highest_min_y = 0;
FT_Pos current_min_y = 0; FT_Pos current_min_y = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ ) { for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour]; AF_Point point = hints->contours[contour];
AF_Point first_point = point; AF_Point first_point = point;
if ( point == NULL ) { /*TODO: is this necessary?*/ if ( point == NULL )
{ /*TODO: is this necessary?*/
continue; continue;
} }
current_min_y = point->y; current_min_y = point->y;
do { do
if ( point->y < current_min_y ) { {
if ( point->y < current_min_y )
{
current_min_y = point->y; current_min_y = point->y;
} }
point = point->next; point = point->next;
} while ( point != first_point ); } while ( point != first_point );
if ( highest_contour == -1 || current_min_y > highest_min_y ) { if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y; highest_min_y = current_min_y;
highest_contour = contour; highest_contour = contour;
} }
@ -2785,42 +2802,57 @@ void af_glyph_hints_apply_adjustments(AF_GlyphHints hints, AF_Dimension dim, FT_
/* contour, bump the high contour up until the distance is one pixel */ /* contour, bump the high contour up until the distance is one pixel */
FT_Int adjustment_amount = 0; FT_Int adjustment_amount = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ ) { for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
if (contour == highest_contour) { {
if ( contour == highest_contour )
{
continue; continue;
} }
AF_Point point = hints->contours[contour]; AF_Point point = hints->contours[contour];
AF_Point first_point = point; AF_Point first_point = point;
if ( point == NULL ) { if ( point == NULL )
{
continue; continue;
} }
FT_Pos max_y = point->y; FT_Pos max_y = point->y;
do { do
if ( point->y > max_y ) { {
if ( point->y > max_y )
{
max_y = point->y; max_y = point->y;
} }
point = point->next; point = point->next;
} while ( point != first_point ); } while ( point != first_point );
if ( max_y >= highest_min_y - 64 ) { if ( max_y >= highest_min_y - 64 )
adjustment_amount = 64 - (highest_min_y - max_y); {
adjustment_amount = 64 - ( highest_min_y - max_y );
} }
} }
FT_TRACE4(( " Pushing top contour %d units up\n", adjustment_amount ));
if ( adjustment_amount > 0 ) { if ( adjustment_amount > 0 ) {
AF_Point point = hints->contours[highest_contour]; AF_Point point = hints->contours[highest_contour];
AF_Point first_point = point; AF_Point first_point = point;
if ( point != NULL ) { if ( point != NULL )
do { {
do
{
point->y += adjustment_amount; point->y += adjustment_amount;
point = point->next; point = point->next;
} while ( point != first_point ); } while ( point != first_point );
} }
} }
} else {
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments: No vertical adjustment needed\n" ));
} }
} }
#undef FT_COMPONENT
#define FT_COMPONENT aflatin
/* Compute the snapped width of a given stem, ignoring very thin ones. */ /* Compute the snapped width of a given stem, ignoring very thin ones. */
/* There is a lot of voodoo in this function; changing the hard-coded */ /* There is a lot of voodoo in this function; changing the hard-coded */
@ -3689,7 +3721,7 @@ void af_glyph_hints_apply_adjustments(AF_GlyphHints hints, AF_Dimension dim, FT_
af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim ); af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim );
af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim ); af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim ); af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
af_glyph_hints_apply_adjustments(hints, (AF_Dimension) dim, glyph_index, metrics->root.reverse_charmap); af_glyph_hints_apply_vertical_separation_adjustments(hints, (AF_Dimension) dim, glyph_index, metrics->root.reverse_charmap);
} }
} }