From f8e996bfb1f79056715e0f68c2ab0f741e86c308 Mon Sep 17 00:00:00 2001 From: Craig White Date: Sun, 4 Jun 2023 15:31:13 -0400 Subject: [PATCH] Prototype adjustment database, reverse character lookup, and fixes for a few characters --- src/autofit/afadjust.c | 140 +++++++++++++++++++++++++++++++++++++++++ src/autofit/afadjust.h | 47 ++++++++++++++ src/autofit/aflatin.c | 90 +++++++++++++++++++++++++- src/autofit/aftypes.h | 2 + src/autofit/autofit.c | 1 + src/base/ftobjs.c | 1 - 6 files changed, 277 insertions(+), 4 deletions(-) create mode 100644 src/autofit/afadjust.c create mode 100644 src/autofit/afadjust.h diff --git a/src/autofit/afadjust.c b/src/autofit/afadjust.c new file mode 100644 index 000000000..00a21a447 --- /dev/null +++ b/src/autofit/afadjust.c @@ -0,0 +1,140 @@ +#include "afadjust.h" +#include +#include +#include + +#define AF_ADJUSTMENT_DATABASE_LENGTH 12 + +/*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*/ +FT_LOCAL_ARRAY_DEF( AF_AdjustmentDatabaseEntry ) +adjustment_database[AF_ADJUSTMENT_DATABASE_LENGTH] = { + {'i', AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, + {'j', AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, + {0xC8, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*E with grave*/ + {0xCC, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*I with grave*/ + {0xD9, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*U with grave*/ + {0xE0, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*a with grave*/ + {0xEC, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*i with grave*/ + {0x114, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*E with macron*/ + {0x12A, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*I with macron*/ + {0x12B, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*i with macron*/ + {0x16A, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE}, /*U with macron*/ + {0x16B, AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE} /*u with macron*/ + /*TODO: find out why E won't work, even though it appears to be one-on-one*/ +}; + +/*Helper function: get the adjustment database entry for a codepoint*/ +FT_LOCAL_DEF( const AF_AdjustmentDatabaseEntry* ) +af_adjustment_database_lookup( FT_UInt32 codepoint ) { + for ( FT_Int entry = 0; entry < AF_ADJUSTMENT_DATABASE_LENGTH; entry++ ) { + if ( adjustment_database[entry].codepoint == codepoint ) { + return &adjustment_database[entry]; + } + } + + return NULL; +} + +FT_LOCAL_DEF( AF_VerticalSeparationAdjustmentType ) +af_lookup_vertical_seperation_type( AF_ReverseCharacterMap map, FT_Int glyph_index ) { + FT_UInt32 codepoint = af_reverse_character_map_lookup( map, glyph_index ); + const AF_AdjustmentDatabaseEntry *entry = af_adjustment_database_lookup( codepoint ); + if ( entry == NULL ) { + return AF_VERTICAL_ADJUSTMENT_NONE; + } + return entry->vertical_separation_adjustment_type; +} + +typedef struct AF_ReverseMapEntry_ { + FT_Int glyph_index; + FT_UInt32 codepoint; +} AF_ReverseMapEntry; + +typedef struct AF_ReverseCharacterMap_ { + FT_UInt length; + AF_ReverseMapEntry *entries; +} AF_ReverseCharacterMap_Rec; + +FT_LOCAL_DEF(FT_UInt32) +af_reverse_character_map_lookup( AF_ReverseCharacterMap map, FT_Int glyph_index ) { + if ( map == NULL ) { + return 0; + } + + for ( FT_UInt entry = 0; entry < map->length; entry++ ) { + if ( map->entries[entry].glyph_index == glyph_index ) { + return map->entries[entry].codepoint; + } + } + + return 0; +} + +FT_LOCAL_DEF( FT_Error ) +af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory ) { + /* Search for a unicode charmap */ + /* 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*/ + /*see find_unicode_charmap*/ + /*TODO: use GSUB lookups */ + FT_CMap unicode_charmap = NULL; + for ( FT_UInt i = 0; i < face->num_charmaps; i++ ) { + if ( face->charmaps[i]->encoding == FT_ENCODING_UNICODE ) { + unicode_charmap = FT_CMAP( face->charmaps[i] ); + } + } + + if ( unicode_charmap == NULL ) { + *map = NULL; + return FT_Err_Ok; + } + + FT_Error error; + + if ( FT_NEW( *map ) ) { + goto Exit; + } + + FT_Int capacity = 10; + FT_Int size = 0; + + if ( FT_NEW_ARRAY((*map)->entries, capacity) ) { + goto Exit; + } + for ( FT_Int i = 0; i < AF_ADJUSTMENT_DATABASE_LENGTH; i++ ) { + FT_UInt32 codepoint = adjustment_database[i].codepoint; + FT_Int glyph = unicode_charmap->clazz->char_index(unicode_charmap, codepoint); + if ( glyph == 0 ) { + continue; + } + if (size == capacity) { + capacity += capacity / 2; + if ( FT_RENEW_ARRAY((*map)->entries, size, capacity) ) { + goto Exit; + } + } + size++; + (*map)->entries[i].glyph_index = glyph; + (*map)->entries[i].codepoint = codepoint; + } + (*map)->length = size; + +Exit: + if ( error ) { + if ( *map ) { + FT_FREE( ( *map )->entries ); + } + FT_FREE( *map ); + return error; + } + + return FT_Err_Ok; +} + +FT_LOCAL_DEF( FT_Error ) +af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ) { + FT_FREE( map->entries ); + return FT_Err_Ok; +} \ No newline at end of file diff --git a/src/autofit/afadjust.h b/src/autofit/afadjust.h new file mode 100644 index 000000000..f1c8c47d3 --- /dev/null +++ b/src/autofit/afadjust.h @@ -0,0 +1,47 @@ +#ifndef AFADJUST_H_ +#define AFADJUST_H_ + +#include + +FT_BEGIN_HEADER + +/*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*/ +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*/ + /*the hinter will force the contours to have a gap of at least 1 pixel between them*/ + /*by moving the top contour up */ + 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*/ + /*or umlats, where there are 2 contours which should be moved together*/ + /*and a way of handling A and O, where the letter consists of 2 contours*/ +} AF_VerticalSeparationAdjustmentType; + +typedef struct AF_AdjustmentDatabaseEntry_ { + FT_UInt32 codepoint; + AF_VerticalSeparationAdjustmentType vertical_separation_adjustment_type; + } AF_AdjustmentDatabaseEntry; + +struct AF_ReverseCharacterMap_; + +typedef struct AF_ReverseCharacterMap_ *AF_ReverseCharacterMap; + +FT_LOCAL(AF_VerticalSeparationAdjustmentType) +af_lookup_vertical_seperation_type( AF_ReverseCharacterMap map, FT_Int glyph_index ); + +FT_LOCAL( FT_UInt32 ) +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*/ +FT_LOCAL( FT_Error ) +af_reverse_character_map_new( FT_Face face, AF_ReverseCharacterMap *map, FT_Memory memory ); + +/*free the reverse character map*/ +FT_LOCAL( FT_Error ) +af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ); + +FT_END_HEADER + +#endif \ No newline at end of file diff --git a/src/autofit/aflatin.c b/src/autofit/aflatin.c index 6f672d593..8177c66f1 100644 --- a/src/autofit/aflatin.c +++ b/src/autofit/aflatin.c @@ -22,6 +22,7 @@ #include "afglobal.h" #include "aflatin.h" #include "aferrors.h" +#include "afadjust.h" /************************************************************************** @@ -227,7 +228,6 @@ dummy->units_per_em / 100 ); axis->width_count = num_widths; } - Exit: for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ ) { @@ -1153,6 +1153,8 @@ goto Exit; } af_latin_metrics_check_digits( metrics, face ); + + af_reverse_character_map_new( face, &metrics->root.reverse_charmap, face->memory ); } Exit: @@ -1502,6 +1504,11 @@ af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT ); } + FT_CALLBACK_DEF( void ) + af_latin_metrics_done( AF_StyleMetrics metrics_ ) { + AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; + af_reverse_character_map_done( metrics->root.reverse_charmap, metrics->root.globals->face->memory ); + } /* Extract standard_width from writing system/script specific */ /* metrics class. */ @@ -2738,6 +2745,83 @@ } +void af_glyph_hints_apply_adjustments(AF_GlyphHints hints, AF_Dimension dim, FT_Int glyph_index, AF_ReverseCharacterMap reverse_charmap) { + if ( dim != AF_DIMENSION_VERT ) { + return; + } + if ( af_lookup_vertical_seperation_type( reverse_charmap, glyph_index ) == AF_VERTICAL_ADJUSTMENT_ONE_ON_ONE && + hints->num_contours == 2 ) { + + /* Figure out which contout is the higher one by finding the one */ + /* with the highest minimum y value */ + + FT_Int highest_contour = -1; + FT_Pos highest_min_y = 0; + FT_Pos current_min_y = 0; + + for ( FT_Int contour = 0; contour < hints->num_contours; contour++ ) { + AF_Point point = hints->contours[contour]; + AF_Point first_point = point; + if ( point == NULL ) { /*TODO: is this necessary?*/ + continue; + } + current_min_y = point->y; + + do { + if ( point->y < current_min_y ) { + current_min_y = point->y; + } + point = point->next; + } while ( point != first_point ); + + if ( highest_contour == -1 || current_min_y > highest_min_y ) { + highest_min_y = current_min_y; + highest_contour = contour; + } + } + + /* If there are any contours that have a maximum y coordinate */ + /* greater or equal to the minimum y coordinate of the previously found highest*/ + /* contour, bump the high contour up until the distance is one pixel */ + + FT_Int adjustment_amount = 0; + for ( FT_Int contour = 0; contour < hints->num_contours; contour++ ) { + if (contour == highest_contour) { + continue; + } + AF_Point point = hints->contours[contour]; + AF_Point first_point = point; + if ( point == NULL ) { + continue; + } + FT_Pos max_y = point->y; + + do { + if ( point->y > max_y ) { + max_y = point->y; + } + point = point->next; + } while ( point != first_point ); + + if ( max_y >= highest_min_y - 64 ) { + adjustment_amount = 64 - (highest_min_y - max_y); + } + } + + if ( adjustment_amount > 0 ) { + AF_Point point = hints->contours[highest_contour]; + AF_Point first_point = point; + if ( point != NULL ) { + do { + point->y += adjustment_amount; + point = point->next; + } while ( point != first_point ); + } + } + } +} + + /* 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 */ /* parameters influence the whole hinting process. */ @@ -3605,6 +3689,7 @@ af_glyph_hints_align_edge_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_apply_adjustments(hints, (AF_Dimension) dim, glyph_index, metrics->root.reverse_charmap); } } @@ -3633,12 +3718,11 @@ (AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init, /* style_metrics_init */ (AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale, /* style_metrics_scale */ - (AF_WritingSystem_DoneMetricsFunc) NULL, /* style_metrics_done */ + (AF_WritingSystem_DoneMetricsFunc) af_latin_metrics_done, /* style_metrics_done */ (AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */ (AF_WritingSystem_InitHintsFunc) af_latin_hints_init, /* style_hints_init */ (AF_WritingSystem_ApplyHintsFunc) af_latin_hints_apply /* style_hints_apply */ ) - /* END */ diff --git a/src/autofit/aftypes.h b/src/autofit/aftypes.h index 661519449..aca173567 100644 --- a/src/autofit/aftypes.h +++ b/src/autofit/aftypes.h @@ -39,6 +39,7 @@ #include #include "afblue.h" +#include "afadjust.h" #ifdef FT_DEBUG_AUTOFIT #include FT_CONFIG_STANDARD_LIBRARY_H @@ -417,6 +418,7 @@ extern void* af_debug_hints_; FT_Bool digits_have_same_width; AF_FaceGlobals globals; /* to access properties */ + AF_ReverseCharacterMap reverse_charmap; } AF_StyleMetricsRec; diff --git a/src/autofit/autofit.c b/src/autofit/autofit.c index 8bd609b5e..55017186d 100644 --- a/src/autofit/autofit.c +++ b/src/autofit/autofit.c @@ -30,6 +30,7 @@ #include "afmodule.c" #include "afranges.c" #include "afshaper.c" +#include "afadjust.c" /* END */ diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c index abfa3ab0e..b379868ba 100644 --- a/src/base/ftobjs.c +++ b/src/base/ftobjs.c @@ -1358,7 +1358,6 @@ driver ); } - /************************************************************************** * * @Function: