* 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( aflatin )
FT_TRACE_DEF( afshaper )
FT_TRACE_DEF( afadjust )
/* SDF components */
FT_TRACE_DEF( sdf ) /* signed distance raster for outlines (ftsdf.c) */

View File

@ -2,34 +2,52 @@
#include <freetype/freetype.h>
#include <freetype/internal/ftobjs.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*/
/*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*/
adjustment_database[] =
{
{0x21, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* ! */
{0x69, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* i */
{0x6A, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /* j */
{0xA1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*Inverted Exclamation Mark*/
{0xBF, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*Inverted Question Mark*/
{0xC0, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with grave*/
{0xC1, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with acute*/
{0xC2, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with circumflex*/
{0xC3, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*A with tilde*/
{0xC8, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*E with grave*/
{0xCC, AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP}, /*I with grave*/
{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*/
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];
/* Binary search for database entry */
FT_UInt low = 0;
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 ) {
FT_UInt32 codepoint = af_reverse_character_map_lookup( map, glyph_index );
const AF_AdjustmentDatabaseEntry *entry = af_adjustment_database_lookup( codepoint );
if ( entry == NULL ) {
if ( entry == NULL )
{
return AF_VERTICAL_ADJUSTMENT_NONE;
}
return entry->vertical_separation_adjustment_type;
}
typedef struct AF_ReverseMapEntry_ {
typedef struct AF_ReverseMapEntry_
{
FT_Int glyph_index;
FT_UInt32 codepoint;
} AF_ReverseMapEntry;
typedef struct AF_ReverseCharacterMap_ {
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 ) {
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 ) {
for ( FT_UInt entry = 0; entry < map->length; entry++ )
{
if ( map->entries[entry].glyph_index == glyph_index )
{
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 )
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 */
/* 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_TRACE4(( "af_reverse_character_map_new: building reverse character map\n" ));
FT_CMap unicode_charmap = NULL;
for ( FT_UInt i = 0; i < face->num_charmaps; i++ ) {
if ( face->charmaps[i]->encoding == FT_ENCODING_UNICODE ) {
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 ) {
if ( unicode_charmap == NULL )
{
*map = NULL;
return FT_Err_Ok;
}
FT_Error error;
if ( FT_NEW( *map ) ) {
if ( FT_NEW( *map ) )
{
goto Exit;
}
FT_Int capacity = 10;
FT_Int size = 0;
if ( FT_NEW_ARRAY((*map)->entries, capacity) ) {
if ( FT_NEW_ARRAY((*map)->entries, capacity) )
{
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_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;
}
if (size == capacity) {
if ( size == capacity )
{
capacity += capacity / 2;
if ( FT_RENEW_ARRAY((*map)->entries, size, capacity) ) {
if ( FT_RENEW_ARRAY((*map)->entries, size, capacity) )
{
goto Exit;
}
}
size++;
(*map)->entries[i].glyph_index = glyph;
(*map)->entries[i].codepoint = codepoint;
( *map )->entries[i].glyph_index = glyph;
( *map )->entries[i].codepoint = codepoint;
}
(*map)->length = size;
( *map )->length = size;
Exit:
if ( error ) {
if ( *map ) {
if ( error )
{
FT_TRACE4(( " error while building reverse character map. Using blank map.\n" ));
if ( *map )
{
FT_FREE( ( *map )->entries );
}
FT_FREE( *map );
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;
}
@ -137,4 +185,4 @@ FT_LOCAL_DEF( FT_Error )
af_reverse_character_map_done( AF_ReverseCharacterMap map, FT_Memory memory ) {
FT_FREE( map->entries );
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*/
/*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 */
typedef enum AF_VerticalSeparationAdjustmentType_
{
AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP,
/*This means that the hinter should find the topmost contour and push it up until its lowest point is 1 pixel*/
/*above the highest point not part of that contour.*/
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*/
@ -19,10 +19,11 @@ typedef enum AF_VerticalSeparationAdjustmentType_ {
/*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;
typedef struct AF_AdjustmentDatabaseEntry_
{
FT_UInt32 codepoint;
AF_VerticalSeparationAdjustmentType vertical_separation_adjustment_type;
} AF_AdjustmentDatabaseEntry;
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*/
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
#endif

View File

@ -1153,7 +1153,7 @@
goto Exit;
}
af_latin_metrics_check_digits( metrics, face );
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) {
if ( dim != AF_DIMENSION_VERT ) {
#undef FT_COMPONENT
#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;
}
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 */
/* 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 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 first_point = point;
if ( point == NULL ) { /*TODO: is this necessary?*/
if ( point == NULL )
{ /*TODO: is this necessary?*/
continue;
}
current_min_y = point->y;
do {
if ( point->y < current_min_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 ) {
if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y;
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 */
FT_Int adjustment_amount = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ ) {
if (contour == highest_contour) {
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 ) {
if ( point == NULL )
{
continue;
}
FT_Pos max_y = point->y;
do {
if ( point->y > max_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 ( max_y >= highest_min_y - 64 )
{
adjustment_amount = 64 - ( highest_min_y - max_y );
}
}
FT_TRACE4(( " Pushing top contour %d units up\n", adjustment_amount ));
if ( adjustment_amount > 0 ) {
AF_Point point = hints->contours[highest_contour];
AF_Point first_point = point;
if ( point != NULL ) {
do {
if ( point != NULL )
{
do
{
point->y += adjustment_amount;
point = point->next;
} 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. */
/* 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_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);
af_glyph_hints_apply_vertical_separation_adjustments(hints, (AF_Dimension) dim, glyph_index, metrics->root.reverse_charmap);
}
}