| /**************************************************************************** |
| * |
| * pshglob.c |
| * |
| * PostScript hinter global hinting management (body). |
| * Inspired by the new auto-hinter module. |
| * |
| * Copyright (C) 2001-2022 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 <freetype/freetype.h> |
| #include <freetype/internal/ftobjs.h> |
| #include <freetype/internal/ftcalc.h> |
| #include "pshglob.h" |
| |
| #ifdef DEBUG_HINTER |
| PSH_Globals ps_debug_globals = NULL; |
| #endif |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** STANDARD WIDTHS *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| |
| /* scale the widths/heights table */ |
| static void |
| psh_globals_scale_widths( PSH_Globals globals, |
| FT_UInt direction ) |
| { |
| PSH_Dimension dim = &globals->dimension[direction]; |
| PSH_Widths stdw = &dim->stdw; |
| FT_UInt count = stdw->count; |
| PSH_Width width = stdw->widths; |
| PSH_Width stand = width; /* standard width/height */ |
| FT_Fixed scale = dim->scale_mult; |
| |
| |
| if ( count > 0 ) |
| { |
| width->cur = FT_MulFix( width->org, scale ); |
| width->fit = FT_PIX_ROUND( width->cur ); |
| |
| width++; |
| count--; |
| |
| for ( ; count > 0; count--, width++ ) |
| { |
| FT_Pos w, dist; |
| |
| |
| w = FT_MulFix( width->org, scale ); |
| dist = w - stand->cur; |
| |
| if ( dist < 0 ) |
| dist = -dist; |
| |
| if ( dist < 128 ) |
| w = stand->cur; |
| |
| width->cur = w; |
| width->fit = FT_PIX_ROUND( w ); |
| } |
| } |
| } |
| |
| |
| #if 0 |
| |
| /* org_width is in font units, result in device pixels, 26.6 format */ |
| FT_LOCAL_DEF( FT_Pos ) |
| psh_dimension_snap_width( PSH_Dimension dimension, |
| FT_Int org_width ) |
| { |
| FT_UInt n; |
| FT_Pos width = FT_MulFix( org_width, dimension->scale_mult ); |
| FT_Pos best = 64 + 32 + 2; |
| FT_Pos reference = width; |
| |
| |
| for ( n = 0; n < dimension->stdw.count; n++ ) |
| { |
| FT_Pos w; |
| FT_Pos dist; |
| |
| |
| w = dimension->stdw.widths[n].cur; |
| dist = width - w; |
| if ( dist < 0 ) |
| dist = -dist; |
| if ( dist < best ) |
| { |
| best = dist; |
| reference = w; |
| } |
| } |
| |
| if ( width >= reference ) |
| { |
| width -= 0x21; |
| if ( width < reference ) |
| width = reference; |
| } |
| else |
| { |
| width += 0x21; |
| if ( width > reference ) |
| width = reference; |
| } |
| |
| return width; |
| } |
| |
| #endif /* 0 */ |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** BLUE ZONES *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| static void |
| psh_blues_set_zones_0( PSH_Blues target, |
| FT_Bool is_others, |
| FT_UInt read_count, |
| FT_Short* read, |
| PSH_Blue_Table top_table, |
| PSH_Blue_Table bot_table ) |
| { |
| FT_UInt count_top = top_table->count; |
| FT_UInt count_bot = bot_table->count; |
| FT_Bool first = 1; |
| |
| FT_UNUSED( target ); |
| |
| |
| for ( ; read_count > 1; read_count -= 2 ) |
| { |
| FT_Int reference, delta; |
| FT_UInt count; |
| PSH_Blue_Zone zones, zone; |
| FT_Bool top; |
| |
| |
| /* read blue zone entry, and select target top/bottom zone */ |
| top = 0; |
| if ( first || is_others ) |
| { |
| reference = read[1]; |
| delta = read[0] - reference; |
| |
| zones = bot_table->zones; |
| count = count_bot; |
| first = 0; |
| } |
| else |
| { |
| reference = read[0]; |
| delta = read[1] - reference; |
| |
| zones = top_table->zones; |
| count = count_top; |
| top = 1; |
| } |
| |
| /* insert into sorted table */ |
| zone = zones; |
| for ( ; count > 0; count--, zone++ ) |
| { |
| if ( reference < zone->org_ref ) |
| break; |
| |
| if ( reference == zone->org_ref ) |
| { |
| FT_Int delta0 = zone->org_delta; |
| |
| |
| /* we have two zones on the same reference position -- */ |
| /* only keep the largest one */ |
| if ( delta < 0 ) |
| { |
| if ( delta < delta0 ) |
| zone->org_delta = delta; |
| } |
| else |
| { |
| if ( delta > delta0 ) |
| zone->org_delta = delta; |
| } |
| goto Skip; |
| } |
| } |
| |
| for ( ; count > 0; count-- ) |
| zone[count] = zone[count-1]; |
| |
| zone->org_ref = reference; |
| zone->org_delta = delta; |
| |
| if ( top ) |
| count_top++; |
| else |
| count_bot++; |
| |
| Skip: |
| read += 2; |
| } |
| |
| top_table->count = count_top; |
| bot_table->count = count_bot; |
| } |
| |
| |
| /* Re-read blue zones from the original fonts and store them into our */ |
| /* private structure. This function re-orders, sanitizes, and */ |
| /* fuzz-expands the zones as well. */ |
| static void |
| psh_blues_set_zones( PSH_Blues target, |
| FT_UInt count, |
| FT_Short* blues, |
| FT_UInt count_others, |
| FT_Short* other_blues, |
| FT_Int fuzz, |
| FT_Int family ) |
| { |
| PSH_Blue_Table top_table, bot_table; |
| FT_UInt count_top, count_bot; |
| |
| |
| if ( family ) |
| { |
| top_table = &target->family_top; |
| bot_table = &target->family_bottom; |
| } |
| else |
| { |
| top_table = &target->normal_top; |
| bot_table = &target->normal_bottom; |
| } |
| |
| /* read the input blue zones, and build two sorted tables */ |
| /* (one for the top zones, the other for the bottom zones) */ |
| top_table->count = 0; |
| bot_table->count = 0; |
| |
| /* first, the blues */ |
| psh_blues_set_zones_0( target, 0, |
| count, blues, top_table, bot_table ); |
| psh_blues_set_zones_0( target, 1, |
| count_others, other_blues, top_table, bot_table ); |
| |
| count_top = top_table->count; |
| count_bot = bot_table->count; |
| |
| /* sanitize top table */ |
| if ( count_top > 0 ) |
| { |
| PSH_Blue_Zone zone = top_table->zones; |
| |
| |
| for ( count = count_top; count > 0; count--, zone++ ) |
| { |
| FT_Int delta; |
| |
| |
| if ( count > 1 ) |
| { |
| delta = zone[1].org_ref - zone[0].org_ref; |
| if ( zone->org_delta > delta ) |
| zone->org_delta = delta; |
| } |
| |
| zone->org_bottom = zone->org_ref; |
| zone->org_top = zone->org_delta + zone->org_ref; |
| } |
| } |
| |
| /* sanitize bottom table */ |
| if ( count_bot > 0 ) |
| { |
| PSH_Blue_Zone zone = bot_table->zones; |
| |
| |
| for ( count = count_bot; count > 0; count--, zone++ ) |
| { |
| FT_Int delta; |
| |
| |
| if ( count > 1 ) |
| { |
| delta = zone[0].org_ref - zone[1].org_ref; |
| if ( zone->org_delta < delta ) |
| zone->org_delta = delta; |
| } |
| |
| zone->org_top = zone->org_ref; |
| zone->org_bottom = zone->org_delta + zone->org_ref; |
| } |
| } |
| |
| /* expand top and bottom tables with blue fuzz */ |
| { |
| FT_Int dim, top, bot, delta; |
| PSH_Blue_Zone zone; |
| |
| |
| zone = top_table->zones; |
| count = count_top; |
| |
| for ( dim = 1; dim >= 0; dim-- ) |
| { |
| if ( count > 0 ) |
| { |
| /* expand the bottom of the lowest zone normally */ |
| zone->org_bottom -= fuzz; |
| |
| /* expand the top and bottom of intermediate zones; */ |
| /* checking that the interval is smaller than the fuzz */ |
| top = zone->org_top; |
| |
| for ( count--; count > 0; count-- ) |
| { |
| bot = zone[1].org_bottom; |
| delta = bot - top; |
| |
| if ( delta / 2 < fuzz ) |
| zone[0].org_top = zone[1].org_bottom = top + delta / 2; |
| else |
| { |
| zone[0].org_top = top + fuzz; |
| zone[1].org_bottom = bot - fuzz; |
| } |
| |
| zone++; |
| top = zone->org_top; |
| } |
| |
| /* expand the top of the highest zone normally */ |
| zone->org_top = top + fuzz; |
| } |
| zone = bot_table->zones; |
| count = count_bot; |
| } |
| } |
| } |
| |
| |
| /* reset the blues table when the device transform changes */ |
| static void |
| psh_blues_scale_zones( PSH_Blues blues, |
| FT_Fixed scale, |
| FT_Pos delta ) |
| { |
| FT_UInt count; |
| FT_UInt num; |
| PSH_Blue_Table table = NULL; |
| |
| /* */ |
| /* Determine whether we need to suppress overshoots or */ |
| /* not. We simply need to compare the vertical scale */ |
| /* parameter to the raw bluescale value. Here is why: */ |
| /* */ |
| /* We need to suppress overshoots for all pointsizes. */ |
| /* At 300dpi that satisfies: */ |
| /* */ |
| /* pointsize < 240*bluescale + 0.49 */ |
| /* */ |
| /* This corresponds to: */ |
| /* */ |
| /* pixelsize < 1000*bluescale + 49/24 */ |
| /* */ |
| /* scale*EM_Size < 1000*bluescale + 49/24 */ |
| /* */ |
| /* However, for normal Type 1 fonts, EM_Size is 1000! */ |
| /* We thus only check: */ |
| /* */ |
| /* scale < bluescale + 49/24000 */ |
| /* */ |
| /* which we shorten to */ |
| /* */ |
| /* "scale < bluescale" */ |
| /* */ |
| /* Note that `blue_scale' is stored 1000 times its real */ |
| /* value, and that `scale' converts from font units to */ |
| /* fractional pixels. */ |
| /* */ |
| |
| /* 1000 / 64 = 125 / 8 */ |
| if ( scale >= 0x20C49BAL ) |
| blues->no_overshoots = FT_BOOL( scale < blues->blue_scale * 8 / 125 ); |
| else |
| blues->no_overshoots = FT_BOOL( scale * 125 < blues->blue_scale * 8 ); |
| |
| /* */ |
| /* The blue threshold is the font units distance under */ |
| /* which overshoots are suppressed due to the BlueShift */ |
| /* even if the scale is greater than BlueScale. */ |
| /* */ |
| /* It is the smallest distance such that */ |
| /* */ |
| /* dist <= BlueShift && dist*scale <= 0.5 pixels */ |
| /* */ |
| { |
| FT_Int threshold = blues->blue_shift; |
| |
| |
| while ( threshold > 0 && FT_MulFix( threshold, scale ) > 32 ) |
| threshold--; |
| |
| blues->blue_threshold = threshold; |
| } |
| |
| for ( num = 0; num < 4; num++ ) |
| { |
| PSH_Blue_Zone zone; |
| |
| |
| switch ( num ) |
| { |
| case 0: |
| table = &blues->normal_top; |
| break; |
| case 1: |
| table = &blues->normal_bottom; |
| break; |
| case 2: |
| table = &blues->family_top; |
| break; |
| default: |
| table = &blues->family_bottom; |
| break; |
| } |
| |
| zone = table->zones; |
| count = table->count; |
| for ( ; count > 0; count--, zone++ ) |
| { |
| zone->cur_top = FT_MulFix( zone->org_top, scale ) + delta; |
| zone->cur_bottom = FT_MulFix( zone->org_bottom, scale ) + delta; |
| zone->cur_ref = FT_MulFix( zone->org_ref, scale ) + delta; |
| zone->cur_delta = FT_MulFix( zone->org_delta, scale ); |
| |
| /* round scaled reference position */ |
| zone->cur_ref = FT_PIX_ROUND( zone->cur_ref ); |
| |
| #if 0 |
| if ( zone->cur_ref > zone->cur_top ) |
| zone->cur_ref -= 64; |
| else if ( zone->cur_ref < zone->cur_bottom ) |
| zone->cur_ref += 64; |
| #endif |
| } |
| } |
| |
| /* process the families now */ |
| |
| for ( num = 0; num < 2; num++ ) |
| { |
| PSH_Blue_Zone zone1, zone2; |
| FT_UInt count1, count2; |
| PSH_Blue_Table normal, family; |
| |
| |
| switch ( num ) |
| { |
| case 0: |
| normal = &blues->normal_top; |
| family = &blues->family_top; |
| break; |
| |
| default: |
| normal = &blues->normal_bottom; |
| family = &blues->family_bottom; |
| } |
| |
| zone1 = normal->zones; |
| count1 = normal->count; |
| |
| for ( ; count1 > 0; count1--, zone1++ ) |
| { |
| /* try to find a family zone whose reference position is less */ |
| /* than 1 pixel far from the current zone */ |
| zone2 = family->zones; |
| count2 = family->count; |
| |
| for ( ; count2 > 0; count2--, zone2++ ) |
| { |
| FT_Pos Delta; |
| |
| |
| Delta = zone1->org_ref - zone2->org_ref; |
| if ( Delta < 0 ) |
| Delta = -Delta; |
| |
| if ( FT_MulFix( Delta, scale ) < 64 ) |
| { |
| zone1->cur_top = zone2->cur_top; |
| zone1->cur_bottom = zone2->cur_bottom; |
| zone1->cur_ref = zone2->cur_ref; |
| zone1->cur_delta = zone2->cur_delta; |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| |
| /* calculate the maximum height of given blue zones */ |
| static FT_Short |
| psh_calc_max_height( FT_UInt num, |
| const FT_Short* values, |
| FT_Short cur_max ) |
| { |
| FT_UInt count; |
| |
| |
| for ( count = 0; count < num; count += 2 ) |
| { |
| FT_Short cur_height = values[count + 1] - values[count]; |
| |
| |
| if ( cur_height > cur_max ) |
| cur_max = cur_height; |
| } |
| |
| return cur_max; |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| psh_blues_snap_stem( PSH_Blues blues, |
| FT_Int stem_top, |
| FT_Int stem_bot, |
| PSH_Alignment alignment ) |
| { |
| PSH_Blue_Table table; |
| FT_UInt count; |
| FT_Pos delta; |
| PSH_Blue_Zone zone; |
| FT_Int no_shoots; |
| |
| |
| alignment->align = PSH_BLUE_ALIGN_NONE; |
| |
| no_shoots = blues->no_overshoots; |
| |
| /* look up stem top in top zones table */ |
| table = &blues->normal_top; |
| count = table->count; |
| zone = table->zones; |
| |
| for ( ; count > 0; count--, zone++ ) |
| { |
| delta = SUB_LONG( stem_top, zone->org_bottom ); |
| if ( delta < -blues->blue_fuzz ) |
| break; |
| |
| if ( stem_top <= zone->org_top + blues->blue_fuzz ) |
| { |
| if ( no_shoots || delta <= blues->blue_threshold ) |
| { |
| alignment->align |= PSH_BLUE_ALIGN_TOP; |
| alignment->align_top = zone->cur_ref; |
| } |
| break; |
| } |
| } |
| |
| /* look up stem bottom in bottom zones table */ |
| table = &blues->normal_bottom; |
| count = table->count; |
| zone = table->zones + count-1; |
| |
| for ( ; count > 0; count--, zone-- ) |
| { |
| delta = SUB_LONG( zone->org_top, stem_bot ); |
| if ( delta < -blues->blue_fuzz ) |
| break; |
| |
| if ( stem_bot >= zone->org_bottom - blues->blue_fuzz ) |
| { |
| if ( no_shoots || delta < blues->blue_threshold ) |
| { |
| alignment->align |= PSH_BLUE_ALIGN_BOT; |
| alignment->align_bot = zone->cur_ref; |
| } |
| break; |
| } |
| } |
| } |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** GLOBAL HINTS *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| static void |
| psh_globals_destroy( PSH_Globals globals ) |
| { |
| if ( globals ) |
| { |
| FT_Memory memory; |
| |
| |
| memory = globals->memory; |
| globals->dimension[0].stdw.count = 0; |
| globals->dimension[1].stdw.count = 0; |
| |
| globals->blues.normal_top.count = 0; |
| globals->blues.normal_bottom.count = 0; |
| globals->blues.family_top.count = 0; |
| globals->blues.family_bottom.count = 0; |
| |
| FT_FREE( globals ); |
| |
| #ifdef DEBUG_HINTER |
| ps_debug_globals = NULL; |
| #endif |
| } |
| } |
| |
| |
| static FT_Error |
| psh_globals_new( FT_Memory memory, |
| T1_Private* priv, |
| PSH_Globals *aglobals ) |
| { |
| PSH_Globals globals = NULL; |
| FT_Error error; |
| |
| |
| if ( !FT_NEW( globals ) ) |
| { |
| FT_UInt count; |
| FT_Short* read; |
| |
| |
| globals->memory = memory; |
| |
| /* copy standard widths */ |
| { |
| PSH_Dimension dim = &globals->dimension[1]; |
| PSH_Width write = dim->stdw.widths; |
| |
| |
| write->org = priv->standard_width[0]; |
| write++; |
| |
| read = priv->snap_widths; |
| for ( count = priv->num_snap_widths; count > 0; count-- ) |
| { |
| write->org = *read; |
| write++; |
| read++; |
| } |
| |
| dim->stdw.count = priv->num_snap_widths + 1; |
| } |
| |
| /* copy standard heights */ |
| { |
| PSH_Dimension dim = &globals->dimension[0]; |
| PSH_Width write = dim->stdw.widths; |
| |
| |
| write->org = priv->standard_height[0]; |
| write++; |
| read = priv->snap_heights; |
| for ( count = priv->num_snap_heights; count > 0; count-- ) |
| { |
| write->org = *read; |
| write++; |
| read++; |
| } |
| |
| dim->stdw.count = priv->num_snap_heights + 1; |
| } |
| |
| /* copy blue zones */ |
| psh_blues_set_zones( &globals->blues, priv->num_blue_values, |
| priv->blue_values, priv->num_other_blues, |
| priv->other_blues, priv->blue_fuzz, 0 ); |
| |
| psh_blues_set_zones( &globals->blues, priv->num_family_blues, |
| priv->family_blues, priv->num_family_other_blues, |
| priv->family_other_blues, priv->blue_fuzz, 1 ); |
| |
| /* limit the BlueScale value to `1 / max_of_blue_zone_heights' */ |
| { |
| FT_Fixed max_scale; |
| FT_Short max_height = 1; |
| |
| |
| max_height = psh_calc_max_height( priv->num_blue_values, |
| priv->blue_values, |
| max_height ); |
| max_height = psh_calc_max_height( priv->num_other_blues, |
| priv->other_blues, |
| max_height ); |
| max_height = psh_calc_max_height( priv->num_family_blues, |
| priv->family_blues, |
| max_height ); |
| max_height = psh_calc_max_height( priv->num_family_other_blues, |
| priv->family_other_blues, |
| max_height ); |
| |
| /* BlueScale is scaled 1000 times */ |
| max_scale = FT_DivFix( 1000, max_height ); |
| globals->blues.blue_scale = priv->blue_scale < max_scale |
| ? priv->blue_scale |
| : max_scale; |
| } |
| |
| globals->blues.blue_shift = priv->blue_shift; |
| globals->blues.blue_fuzz = priv->blue_fuzz; |
| |
| globals->dimension[0].scale_mult = 0; |
| globals->dimension[0].scale_delta = 0; |
| globals->dimension[1].scale_mult = 0; |
| globals->dimension[1].scale_delta = 0; |
| |
| #ifdef DEBUG_HINTER |
| ps_debug_globals = globals; |
| #endif |
| } |
| |
| *aglobals = globals; |
| return error; |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| psh_globals_set_scale( PSH_Globals globals, |
| FT_Fixed x_scale, |
| FT_Fixed y_scale, |
| FT_Fixed x_delta, |
| FT_Fixed y_delta ) |
| { |
| PSH_Dimension dim; |
| |
| |
| dim = &globals->dimension[0]; |
| if ( x_scale != dim->scale_mult || |
| x_delta != dim->scale_delta ) |
| { |
| dim->scale_mult = x_scale; |
| dim->scale_delta = x_delta; |
| |
| psh_globals_scale_widths( globals, 0 ); |
| } |
| |
| dim = &globals->dimension[1]; |
| if ( y_scale != dim->scale_mult || |
| y_delta != dim->scale_delta ) |
| { |
| dim->scale_mult = y_scale; |
| dim->scale_delta = y_delta; |
| |
| psh_globals_scale_widths( globals, 1 ); |
| psh_blues_scale_zones( &globals->blues, y_scale, y_delta ); |
| } |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| psh_globals_funcs_init( PSH_Globals_FuncsRec* funcs ) |
| { |
| funcs->create = psh_globals_new; |
| funcs->set_scale = psh_globals_set_scale; |
| funcs->destroy = psh_globals_destroy; |
| } |
| |
| |
| /* END */ |