7fa51b5535
Adding some trivial error checking. Adding/Fixing tracing levels.
1348 lines
51 KiB
C
1348 lines
51 KiB
C
/***************************************************************************/
|
|
/* */
|
|
/* t1hinter.c */
|
|
/* */
|
|
/* Type 1 hinter (body). */
|
|
/* */
|
|
/* Copyright 1996-2000 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. */
|
|
/* */
|
|
/***************************************************************************/
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* The Hinter is in charge of fitting th scaled outline to the pixel */
|
|
/* grid in order to considerably improve the quality of the Type 1 font */
|
|
/* driver's output. */
|
|
/* */
|
|
/*************************************************************************/
|
|
|
|
|
|
#include <freetype/internal/ftdebug.h>
|
|
|
|
|
|
#ifdef FT_FLAT_COMPILE
|
|
|
|
#include "t1objs.h"
|
|
#include "t1hinter.h"
|
|
|
|
#else
|
|
|
|
#include <type1/t1objs.h>
|
|
#include <type1/t1hinter.h>
|
|
|
|
#endif
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* 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_t1hint
|
|
|
|
|
|
#undef ONE_PIXEL
|
|
#define ONE_PIXEL 64
|
|
|
|
#undef ROUND
|
|
#define ROUND( x ) ( ( x + ONE_PIXEL / 2 ) & -ONE_PIXEL )
|
|
|
|
#undef SCALE
|
|
#define SCALE( val ) FT_MulFix( val, scale )
|
|
|
|
/* various constants used to describe the alignment of a horizontal */
|
|
/* stem with regards to the blue zones */
|
|
|
|
#define T1_ALIGN_NONE 0
|
|
#define T1_ALIGN_BOTTOM 1
|
|
#define T1_ALIGN_TOP 2
|
|
#define T1_ALIGN_BOTH 3
|
|
|
|
|
|
/* very simple bubble sort (not a lot of elements, mostly */
|
|
/* pre-sorted, no need for quicksort) */
|
|
|
|
static
|
|
void t1_sort_blues( FT_Int* blues,
|
|
FT_Int count )
|
|
{
|
|
FT_Int i, swap;
|
|
FT_Int* cur;
|
|
|
|
|
|
for ( i = 2; i < count; i += 2 )
|
|
{
|
|
cur = blues + i;
|
|
do
|
|
{
|
|
if ( cur[-1] < cur[0] )
|
|
break;
|
|
|
|
swap = cur[-2]; cur[-2] = cur[0]; cur[0] = swap;
|
|
swap = cur[-1]; cur[-1] = cur[1]; cur[1] = swap;
|
|
cur -= 2;
|
|
|
|
} while ( cur > blues );
|
|
}
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_set_blue_zones */
|
|
/* */
|
|
/* <Description> */
|
|
/* Sets a size object's blue zones during reset. This will compute */
|
|
/* the `snap' zone corresponding to each blue zone. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* size :: A handle to target size object. */
|
|
/* */
|
|
/* <Return> */
|
|
/* FreeType error code. 0 means success. */
|
|
/* */
|
|
/* <Note> */
|
|
/* This functions does the following: */
|
|
/* */
|
|
/* 1. It extracts the bottom and top blue zones from the face object. */
|
|
/* */
|
|
/* 2. Each zone is then grown by `BlueFuzz', overlapping is */
|
|
/* eliminated by adjusting the zone edges appropriately. */
|
|
/* */
|
|
/* 3. For each zone, we keep its original font units position, its */
|
|
/* original scaled position, as well as its grown/adjusted edges. */
|
|
/* */
|
|
static
|
|
FT_Error t1_set_blue_zones( T1_Size size )
|
|
{
|
|
T1_Face face = (T1_Face)size->root.face;
|
|
T1_Private* priv = &face->type1.private_dict;
|
|
FT_Int n;
|
|
FT_Int blues[24];
|
|
FT_Int num_bottom;
|
|
FT_Int num_top;
|
|
FT_Int num_blues;
|
|
T1_Size_Hints* hints = size->hints;
|
|
T1_Snap_Zone* zone;
|
|
FT_Pos pix, orus;
|
|
FT_Pos min, max, threshold;
|
|
FT_Fixed scale;
|
|
FT_Bool is_bottom;
|
|
|
|
|
|
/***********************************************************************/
|
|
/* */
|
|
/* copy bottom and top blue zones in local arrays */
|
|
/* */
|
|
|
|
/* First of all, check the sizes of the /BlueValues and /OtherBlues */
|
|
/* tables. They all must contain an even number of arguments. */
|
|
if ( priv->num_other_blues & 1 ||
|
|
priv->num_blue_values & 1 )
|
|
{
|
|
FT_ERROR(( "t1_set_blue_zones: odd number of blue values\n" ));
|
|
return T1_Err_Syntax_Error;
|
|
}
|
|
|
|
/* copy the bottom blue zones from /OtherBlues */
|
|
num_top = 0;
|
|
num_bottom = priv->num_other_blues;
|
|
|
|
for ( n = 0; n < num_bottom; n++ )
|
|
blues[n] = priv->other_blues[n];
|
|
|
|
/* add the first blue zone in /BlueValues to the table */
|
|
num_top = priv->num_blue_values - 2;
|
|
if ( num_top >= 0 )
|
|
{
|
|
blues[num_bottom ] = priv->blue_values[0];
|
|
blues[num_bottom + 1] = priv->blue_values[1];
|
|
|
|
num_bottom += 2;
|
|
}
|
|
|
|
/* sort the bottom blue zones */
|
|
t1_sort_blues( blues, num_bottom );
|
|
|
|
hints->num_bottom_zones = num_bottom >> 1;
|
|
|
|
/* now copy the /BlueValues to the top of the blues array */
|
|
if ( num_top > 0 )
|
|
{
|
|
for ( n = 0; n < num_top; n++ )
|
|
blues[num_bottom + n] = priv->blue_values[n + 2];
|
|
|
|
/* sort the top blue zones */
|
|
t1_sort_blues( blues + num_bottom, num_top );
|
|
}
|
|
else
|
|
num_top = 0;
|
|
|
|
num_blues = num_top + num_bottom;
|
|
hints->num_blue_zones = ( num_blues ) >> 1;
|
|
|
|
/***********************************************************************/
|
|
/* */
|
|
/* build blue snap zones from the local blues arrays */
|
|
/* */
|
|
|
|
scale = size->root.metrics.y_scale;
|
|
zone = hints->blue_zones;
|
|
threshold = ONE_PIXEL / 4; /* 0.25 pixels */
|
|
|
|
for ( n = 0; n < num_blues; n += 2, zone++ )
|
|
{
|
|
is_bottom = n < num_bottom ? 1 : 0;
|
|
|
|
orus = blues[n + is_bottom]; /* get alignement coordinate */
|
|
pix = SCALE( orus ); /* scale it */
|
|
|
|
min = SCALE( blues[n ] - priv->blue_fuzz );
|
|
max = SCALE( blues[n + 1] + priv->blue_fuzz );
|
|
|
|
if ( min > pix - threshold )
|
|
min = pix - threshold;
|
|
if ( max < pix + threshold )
|
|
max = pix + threshold;
|
|
|
|
zone->orus = orus;
|
|
zone->pix = pix;
|
|
zone->min = min;
|
|
zone->max = max;
|
|
}
|
|
|
|
/* adjust edges in case of overlap */
|
|
zone = hints->blue_zones;
|
|
for ( n = 0; n < num_blues - 2; n += 2, zone++ )
|
|
{
|
|
if ( n != num_bottom - 2 &&
|
|
zone[0].max > zone[1].min )
|
|
zone[0].max = zone[1].min = ( zone[0].pix + zone[1].pix ) / 2;
|
|
}
|
|
|
|
/* compare the current pixel size with the BlueScale value */
|
|
/* to know whether to supress overshoots */
|
|
|
|
hints->supress_overshoots =
|
|
size->root.metrics.y_ppem < FT_MulFix( 1000, priv->blue_scale );
|
|
|
|
#ifdef FT_DEBUG_LEVEL_TRACE
|
|
|
|
/* now print the new blue values in tracing mode */
|
|
|
|
FT_TRACE2(( "Blue Zones for size object at $%08lx:\n", (long)size ));
|
|
FT_TRACE2(( " orus pix min max\n" ));
|
|
FT_TRACE2(( "-------------------------------\n" ));
|
|
|
|
zone = hints->blue_zones;
|
|
for ( n = 0; n < hints->num_blue_zones; n++ )
|
|
{
|
|
FT_TRACE2(( " %3d %.2f %.2f %.2f\n",
|
|
zone->orus,
|
|
zone->pix / 64.0,
|
|
zone->min / 64.0,
|
|
zone->max / 64.0 ));
|
|
zone++;
|
|
}
|
|
FT_TRACE2(( "\nOvershoots are %s\n\n",
|
|
hints->supress_overshoots ? "supressed" : "active" ));
|
|
|
|
#endif /* DEBUG_LEVEL_TRACE */
|
|
|
|
return T1_Err_Ok;
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_set_snap_zones */
|
|
/* */
|
|
/* <Description> */
|
|
/* This function set a size object's stem snap zones. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* size :: A handle to the target size object. */
|
|
/* */
|
|
/* <Return> */
|
|
/* FreeType error code. 0 means success. */
|
|
/* */
|
|
/* <Note> */
|
|
/* This function performs the following: */
|
|
/* */
|
|
/* 1. It reads and scales the stem snap widths from the parent face. */
|
|
/* */
|
|
/* 2. A `snap zone' is computed for each snap width, by `growing' it */
|
|
/* with a threshold of 1/2 pixel. Overlapping is avoided through */
|
|
/* proper edge adjustment. */
|
|
/* */
|
|
/* 3. Each width whose zone contain the scaled standard set width is */
|
|
/* removed from the table. */
|
|
/* */
|
|
/* 4. Finally, the standard set width is scaled, and its correponding */
|
|
/* `snap zone' is inserted into the sorted snap zones table. */
|
|
/* */
|
|
static
|
|
FT_Error t1_set_snap_zones( T1_Size size )
|
|
{
|
|
FT_Int n, direction, n_zones, num_zones;
|
|
T1_Snap_Zone* zone;
|
|
T1_Snap_Zone* base_zone;
|
|
FT_Short* orgs;
|
|
FT_Pos standard_width;
|
|
FT_Fixed scale;
|
|
|
|
T1_Face face = (T1_Face)size->root.face;
|
|
T1_Private* priv = &face->type1.private_dict;
|
|
T1_Size_Hints* hints = size->hints;
|
|
|
|
|
|
/* start with horizontal snap zones */
|
|
direction = 0;
|
|
standard_width = priv->standard_width[0];
|
|
n_zones = priv->num_snap_widths;
|
|
base_zone = hints->snap_widths;
|
|
orgs = priv->snap_widths;
|
|
scale = size->root.metrics.x_scale;
|
|
|
|
while ( direction < 2 )
|
|
{
|
|
/*********************************************************************/
|
|
/* */
|
|
/* Read and scale stem snap widths table from the physical font */
|
|
/* record. */
|
|
/* */
|
|
|
|
FT_Pos prev, orus, pix, min, max, threshold;
|
|
|
|
|
|
threshold = ONE_PIXEL / 4;
|
|
zone = base_zone;
|
|
|
|
if ( n_zones > 0 )
|
|
{
|
|
orus = *orgs++;
|
|
pix = SCALE( orus );
|
|
min = pix - threshold;
|
|
max = pix + threshold;
|
|
|
|
zone->orus = orus;
|
|
zone->pix = pix;
|
|
zone->min = min;
|
|
prev = pix;
|
|
|
|
for ( n = 1; n < n_zones; n++ )
|
|
{
|
|
orus = *orgs++;
|
|
pix = SCALE( orus );
|
|
|
|
if ( pix - prev < 2 * threshold )
|
|
{
|
|
min = max = ( pix + prev ) / 2;
|
|
}
|
|
else
|
|
min = pix - threshold;
|
|
|
|
zone->max = max;
|
|
zone++;
|
|
zone->orus = orus;
|
|
zone->pix = pix;
|
|
zone->min = min;
|
|
|
|
max = pix + threshold;
|
|
prev = pix;
|
|
}
|
|
zone->max = max;
|
|
}
|
|
|
|
#ifdef FT_DEBUG_LEVEL_TRACE
|
|
|
|
/* print the scaled stem snap values in tracing mode */
|
|
|
|
FT_TRACE2(( "Set_Snap_Zones: first %s pass\n",
|
|
direction ? "vertical" : "horizontal" ));
|
|
|
|
FT_TRACE2(( "Scaled original stem snap zones:\n" ));
|
|
FT_TRACE2(( " orus pix min max\n" ));
|
|
FT_TRACE2(( "-----------------------------\n" ));
|
|
|
|
zone = base_zone;
|
|
for ( n = 0; n < n_zones; n++, zone++ )
|
|
FT_TRACE2(( " %3d %.2f %.2f %.2f\n",
|
|
zone->orus,
|
|
zone->pix / 64.0,
|
|
zone->min / 64.0,
|
|
zone->max / 64.0 ));
|
|
FT_TRACE2(( "\n" ));
|
|
|
|
FT_TRACE2(( "Standard width = %d\n", standard_width ));
|
|
|
|
#endif /* FT_DEBUG_LEVEL_TRACE */
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* Now, each snap width which is in the range of the standard set */
|
|
/* width will be removed from the list. */
|
|
/* */
|
|
|
|
if ( standard_width > 0 )
|
|
{
|
|
T1_Snap_Zone* parent;
|
|
FT_Pos std_pix, std_min, std_max;
|
|
|
|
|
|
std_pix = SCALE( standard_width );
|
|
|
|
std_min = std_pix - threshold;
|
|
std_max = std_pix + threshold;
|
|
|
|
num_zones = 0;
|
|
zone = base_zone;
|
|
parent = base_zone;
|
|
|
|
for ( n = 0; n < n_zones; n++ )
|
|
{
|
|
if ( zone->pix >= std_min && zone->pix <= std_max )
|
|
{
|
|
/* this zone must be removed from the list */
|
|
if ( std_min > zone->min )
|
|
std_min = zone->min;
|
|
if ( std_max < zone->max )
|
|
std_max = zone->max;
|
|
}
|
|
else
|
|
{
|
|
*parent++ = *zone;
|
|
num_zones++;
|
|
}
|
|
zone++;
|
|
}
|
|
|
|
/*******************************************************************/
|
|
/* */
|
|
/* Now, insert the standard width zone */
|
|
/* */
|
|
|
|
zone = base_zone + num_zones;
|
|
while ( zone > base_zone && zone[-1].pix > std_max )
|
|
{
|
|
zone[0] = zone[-1];
|
|
zone--;
|
|
}
|
|
|
|
/* check border zones */
|
|
if ( zone > base_zone && zone[-1].max > std_min )
|
|
zone[-1].max = std_min;
|
|
|
|
if ( zone < base_zone + num_zones && zone[1].min < std_max )
|
|
zone[1].min = std_max;
|
|
|
|
zone->orus = standard_width;
|
|
zone->pix = std_pix;
|
|
zone->min = std_min;
|
|
zone->max = std_max;
|
|
|
|
num_zones++;
|
|
}
|
|
else
|
|
num_zones = n_zones;
|
|
|
|
/* save total number of stem snaps now */
|
|
if ( direction )
|
|
hints->num_snap_heights = num_zones;
|
|
else
|
|
hints->num_snap_widths = num_zones;
|
|
|
|
#ifdef FT_DEBUG_LEVEL_TRACE
|
|
|
|
/* print the scaled stem snap values in tracing mode */
|
|
|
|
FT_TRACE2(( "Set_Snap_Zones: second %s pass\n",
|
|
direction ? "vertical" : "horizontal" ));
|
|
|
|
FT_TRACE2(( "Scaled clipped stem snap zones:\n" ));
|
|
FT_TRACE2(( " orus pix min max\n" ));
|
|
FT_TRACE2(( "-----------------------------\n" ));
|
|
|
|
zone = base_zone;
|
|
for ( n = 0; n < num_zones; n++, zone++ )
|
|
FT_TRACE2(( " %3d %.2f %.2f %.2f\n",
|
|
zone->orus,
|
|
zone->pix / 64.0,
|
|
zone->min / 64.0,
|
|
zone->max / 64.0 ));
|
|
FT_TRACE2(( "\n" ));
|
|
|
|
FT_TRACE2(( "Standard width = %d\n", standard_width ));
|
|
|
|
#endif /* FT_DEBUG_LEVEL_TRACE */
|
|
|
|
/* continue with vertical snap zone */
|
|
direction++;
|
|
standard_width = priv->standard_height[0];
|
|
n_zones = priv->num_snap_heights;
|
|
base_zone = hints->snap_heights;
|
|
orgs = priv->snap_heights;
|
|
scale = size->root.metrics.y_scale;
|
|
}
|
|
|
|
return T1_Err_Ok;
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_New_Size_Hinter */
|
|
/* */
|
|
/* <Description> */
|
|
/* Allocates a new hinter structure for a given size object. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* size :: A handle to the target size object. */
|
|
/* */
|
|
/* <Return> */
|
|
/* FreeType Error code. 0 means success. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
FT_Error T1_New_Size_Hinter( T1_Size size )
|
|
{
|
|
FT_Memory memory = size->root.face->memory;
|
|
|
|
|
|
return MEM_Alloc( size->hints, sizeof ( *size->hints ) );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_Done_Size_Hinter */
|
|
/* */
|
|
/* <Description> */
|
|
/* Releases a given size object's hinter structure. */
|
|
/* */
|
|
/* <Input> */
|
|
/* size :: A handle to the target size object. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
void T1_Done_Size_Hinter( T1_Size size )
|
|
{
|
|
FT_Memory memory = size->root.face->memory;
|
|
|
|
|
|
FREE( size->hints );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_Reset_Size_Hinter */
|
|
/* */
|
|
/* <Description> */
|
|
/* Recomputes hinting information when a given size object has */
|
|
/* changed its resolutions/char sizes/pixel sizes. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* size :: A handle to the size object. */
|
|
/* */
|
|
/* <Return> */
|
|
/* FreeType error code. 0 means success. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
FT_Error T1_Reset_Size_Hinter( T1_Size size )
|
|
{
|
|
return t1_set_blue_zones( size ) || t1_set_snap_zones( size );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_New_Glyph_Hinter */
|
|
/* */
|
|
/* <Description> */
|
|
/* Allocates a new hinter structure for a given glyph slot. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* glyph :: A handle to the target glyph slot. */
|
|
/* */
|
|
/* <Return> */
|
|
/* FreeType error code. 0 means success. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
FT_Error T1_New_Glyph_Hinter( T1_GlyphSlot glyph )
|
|
{
|
|
FT_Memory memory = glyph->root.face->memory;
|
|
|
|
|
|
return MEM_Alloc( glyph->hints, sizeof ( *glyph->hints ) );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_Done_Glyph_Hinter */
|
|
/* */
|
|
/* <Description> */
|
|
/* Releases a given glyph slot's hinter structure. */
|
|
/* */
|
|
/* <Input> */
|
|
/* glyph :: A handle to the glyph slot. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
void T1_Done_Glyph_Hinter( T1_GlyphSlot glyph )
|
|
{
|
|
FT_Memory memory = glyph->root.face->memory;
|
|
|
|
|
|
FREE( glyph->hints );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/********** **********/
|
|
/********** HINTED GLYPH LOADER **********/
|
|
/********** **********/
|
|
/********** The following code is in charge of the first **********/
|
|
/********** and second pass when loading a single outline **********/
|
|
/********** **********/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
|
|
static
|
|
FT_Error t1_hinter_ignore( void )
|
|
{
|
|
/* do nothing, used for `dotsection' which is unsupported for now */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static
|
|
FT_Error t1_hinter_stem( T1_Builder* builder,
|
|
FT_Pos pos,
|
|
FT_Int width,
|
|
FT_Bool vertical )
|
|
{
|
|
T1_Stem_Table* stem_table;
|
|
T1_Stem_Hint* stems;
|
|
T1_Stem_Hint* cur_stem;
|
|
FT_Int min, max, n, num_stems;
|
|
FT_Bool new_stem;
|
|
T1_Glyph_Hints* hinter = builder->glyph->hints;
|
|
|
|
|
|
/* select the appropriate stem array */
|
|
stem_table = vertical ? &hinter->vert_stems : &hinter->hori_stems;
|
|
stems = stem_table->stems;
|
|
num_stems = stem_table->num_stems;
|
|
|
|
/* Compute minimum and maximum coord for the stem */
|
|
min = pos + ( vertical
|
|
? builder->left_bearing.x
|
|
: builder->left_bearing.y );
|
|
|
|
if ( width >= 0 )
|
|
max = min + width;
|
|
else
|
|
{
|
|
/* a negative width indicates a `ghost' stem */
|
|
if ( width == -21 )
|
|
min += width;
|
|
|
|
max = min;
|
|
}
|
|
|
|
/* Now scan the array. If we find a stem with the same borders */
|
|
/* simply activate it. */
|
|
cur_stem = stems;
|
|
new_stem = 1;
|
|
|
|
for ( n = 0; n < num_stems; n++, cur_stem++ )
|
|
{
|
|
if ( cur_stem->min_edge.orus == min &&
|
|
cur_stem->max_edge.orus == max )
|
|
{
|
|
/* This stem is already in the table, simply activate it */
|
|
if ( ( cur_stem->hint_flags & T1_HINT_FLAG_ACTIVE ) == 0 )
|
|
{
|
|
cur_stem->hint_flags |= T1_HINT_FLAG_ACTIVE;
|
|
stem_table->num_active++;
|
|
}
|
|
new_stem = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* add a new stem to the array if necessary */
|
|
if ( new_stem )
|
|
{
|
|
if ( cur_stem >= stems + T1_HINTER_MAX_EDGES )
|
|
{
|
|
FT_ERROR(( "t1_hinter_stem: too many stems in glyph charstring\n" ));
|
|
return T1_Err_Syntax_Error;
|
|
}
|
|
|
|
/* on the first pass, we record the stem, otherwise, this is */
|
|
/* a bug in the glyph loader! */
|
|
if ( builder->pass == 0 )
|
|
{
|
|
cur_stem->min_edge.orus = min;
|
|
cur_stem->max_edge.orus = max;
|
|
cur_stem->hint_flags = T1_HINT_FLAG_ACTIVE;
|
|
|
|
stem_table->num_stems++;
|
|
stem_table->num_active++;
|
|
}
|
|
else
|
|
{
|
|
FT_ERROR(( "t1_hinter_stem:" ));
|
|
FT_ERROR(( " fatal glyph loader bug -- pass2-stem\n" ));
|
|
return T1_Err_Syntax_Error;
|
|
}
|
|
}
|
|
|
|
return T1_Err_Ok;
|
|
}
|
|
|
|
|
|
static
|
|
FT_Error t1_hinter_stem3( T1_Builder* builder,
|
|
FT_Pos pos0,
|
|
FT_Int width0,
|
|
FT_Pos pos1,
|
|
FT_Int width1,
|
|
FT_Pos pos2,
|
|
FT_Int width2,
|
|
FT_Bool vertical )
|
|
{
|
|
/* For now, simply call `stem' 3 times */
|
|
return t1_hinter_stem( builder, pos0, width0, vertical ) ||
|
|
t1_hinter_stem( builder, pos1, width1, vertical ) ||
|
|
t1_hinter_stem( builder, pos2, width2, vertical );
|
|
}
|
|
|
|
|
|
static
|
|
FT_Error t1_hinter_changehints( T1_Builder* builder )
|
|
{
|
|
FT_Int dimension;
|
|
T1_Stem_Table* stem_table;
|
|
T1_Glyph_Hints* hinter = builder->glyph->hints;
|
|
|
|
|
|
/* If we are in the second pass of glyph hinting, we must */
|
|
/* call the function T1_Hint_Points() on the builder in order */
|
|
/* to force the fit the latest points to the pixel grid. */
|
|
if ( builder->pass == 1 )
|
|
T1_Hint_Points( builder );
|
|
|
|
/* Simply de-activate all hints in all arrays */
|
|
stem_table = &hinter->hori_stems;
|
|
|
|
for ( dimension = 2; dimension > 0; dimension-- )
|
|
{
|
|
T1_Stem_Hint* cur = stem_table->stems;
|
|
T1_Stem_Hint* limit = cur + stem_table->num_stems;
|
|
|
|
|
|
for ( ; cur < limit; cur++ )
|
|
cur->hint_flags &= ~T1_HINT_FLAG_ACTIVE;
|
|
|
|
stem_table->num_active = 0;
|
|
stem_table = &hinter->vert_stems;
|
|
}
|
|
|
|
return T1_Err_Ok;
|
|
}
|
|
|
|
|
|
const T1_Hinter_Funcs t1_hinter_funcs =
|
|
{
|
|
(T1_Hinter_ChangeHints)t1_hinter_changehints,
|
|
(T1_Hinter_DotSection) t1_hinter_ignore,
|
|
(T1_Hinter_Stem) t1_hinter_stem,
|
|
(T1_Hinter_Stem3) t1_hinter_stem3
|
|
};
|
|
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/********** *********/
|
|
/********** *********/
|
|
/********** STEM HINTS MANAGEMENT *********/
|
|
/********** *********/
|
|
/********** The following code is in charge of computing *********/
|
|
/********** the placement of each scaled stem hint. *********/
|
|
/********** *********/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_sort_hints */
|
|
/* */
|
|
/* <Description> */
|
|
/* Sorta the list of active stems in increasing order, through the */
|
|
/* `sort' indexing table. */
|
|
/* */
|
|
/* <InOut> */
|
|
/* table :: A stem hints table. */
|
|
/* */
|
|
static
|
|
void t1_sort_hints( T1_Stem_Table* table )
|
|
{
|
|
FT_Int num_stems = table->num_stems;
|
|
FT_Int num_active = 0;
|
|
FT_Int* sort = table->sort;
|
|
T1_Stem_Hint* stems = table->stems;
|
|
FT_Int n;
|
|
|
|
|
|
/* record active stems in sort table */
|
|
for ( n = 0; n < num_stems; n++ )
|
|
{
|
|
if ( stems[n].hint_flags & T1_HINT_FLAG_ACTIVE )
|
|
sort[num_active++] = n;
|
|
}
|
|
|
|
/* Now sort the indices. There are usually very few stems, */
|
|
/* and they are pre-sorted in 90% cases, so we choose a */
|
|
/* simple bubble sort (quicksort would be slower). */
|
|
for ( n = 1; n < num_active; n++ )
|
|
{
|
|
FT_Int p = n - 1;
|
|
T1_Stem_Hint* cur = stems + sort[n];
|
|
|
|
|
|
do
|
|
{
|
|
FT_Int swap;
|
|
T1_Stem_Hint* prev = stems + sort[p];
|
|
|
|
|
|
/* note that by definition, the active stems cannot overlap */
|
|
/* so we simply compare their `min' to sort them (we could compare */
|
|
/* their max values also; this wouldn't change anything). */
|
|
if ( prev->min_edge.orus <= cur->min_edge.orus )
|
|
break;
|
|
|
|
/* swap elements */
|
|
swap = sort[p ];
|
|
sort[p ] = sort[p + 1];
|
|
sort[p + 1] = swap;
|
|
p--;
|
|
|
|
} while ( p >= 0 );
|
|
}
|
|
|
|
table->num_active = num_active;
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_hint_horizontal_stems */
|
|
/* */
|
|
/* <Description> */
|
|
/* Computes the location of each scaled horizontal stem hint. This */
|
|
/* takes care of the blue zones and the horizontal stem snap table. */
|
|
/* */
|
|
/* <Input> */
|
|
/* table :: The horizontal stem hints table. */
|
|
/* */
|
|
/* hints :: The current size's hint structure. */
|
|
/* */
|
|
/* blueShift :: The value of the /BlueShift as taken from the face */
|
|
/* object. */
|
|
/* */
|
|
/* scale :: The 16.16 scale used to convert outline units to */
|
|
/* 26.6 pixels. */
|
|
/* */
|
|
/* <Note> */
|
|
/* For now, all stems are hinted independently from each other. It */
|
|
/* might be necessary, for better performance, to introduce the */
|
|
/* notion of `controlled' hints describing things like counter-stems, */
|
|
/* stem3, as well as overlapping stems control. */
|
|
/* */
|
|
static
|
|
void t1_hint_horizontal_stems( T1_Stem_Table* table,
|
|
T1_Size_Hints* hints,
|
|
FT_Pos blueShift,
|
|
FT_Fixed scale )
|
|
{
|
|
T1_Stem_Hint* stem = table->stems;
|
|
T1_Stem_Hint* limit = stem + table->num_stems;
|
|
|
|
|
|
/* first of all, scale the blueShift */
|
|
blueShift = SCALE( blueShift );
|
|
|
|
/* then scan the horizontal stem table */
|
|
for ( ; stem < limit; stem++ )
|
|
{
|
|
FT_Pos bottom_orus = stem->min_edge.orus;
|
|
FT_Pos top_orus = stem->max_edge.orus;
|
|
|
|
FT_Pos top_pix = SCALE( top_orus );
|
|
FT_Pos bottom_pix = SCALE( bottom_orus );
|
|
FT_Pos width_pix = top_pix - bottom_pix;
|
|
|
|
FT_Pos bottom = bottom_pix;
|
|
FT_Pos top = top_pix;
|
|
FT_Int align = T1_ALIGN_NONE;
|
|
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* Snap pixel width if in stem snap range */
|
|
/* */
|
|
|
|
{
|
|
T1_Snap_Zone* zone = hints->snap_heights;
|
|
T1_Snap_Zone* zone_limit = zone + hints->num_snap_heights;
|
|
FT_Pos best_dist = 32000;
|
|
T1_Snap_Zone* best_zone = 0;
|
|
|
|
|
|
for ( ; zone < zone_limit; zone++ )
|
|
{
|
|
FT_Pos dist;
|
|
|
|
|
|
dist = width_pix - zone->min;
|
|
if ( dist < 0 )
|
|
dist = -dist;
|
|
if ( dist < best_dist )
|
|
{
|
|
best_zone = zone;
|
|
best_dist = dist;
|
|
}
|
|
}
|
|
|
|
if ( best_zone )
|
|
{
|
|
if ( width_pix > best_zone->pix )
|
|
{
|
|
width_pix -= 0x20;
|
|
if ( width_pix < best_zone->pix )
|
|
width_pix = best_zone->pix;
|
|
}
|
|
else
|
|
{
|
|
width_pix += 0x20;
|
|
if ( width_pix > best_zone->pix )
|
|
width_pix = best_zone->pix;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* round width - minimum 1 pixel if this isn't a ghost stem */
|
|
/* */
|
|
|
|
if ( width_pix > 0 )
|
|
width_pix = width_pix < ONE_PIXEL ? ONE_PIXEL : ROUND( width_pix );
|
|
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* Now check for bottom blue zones alignement */
|
|
/* */
|
|
|
|
{
|
|
FT_Int num_blues = hints->num_bottom_zones;
|
|
T1_Snap_Zone* blue = hints->blue_zones;
|
|
T1_Snap_Zone* blue_limit = blue + num_blues;
|
|
|
|
|
|
for ( ; blue < blue_limit; blue++ )
|
|
{
|
|
if ( bottom_pix < blue->min )
|
|
break;
|
|
|
|
if ( bottom_pix <= blue->max )
|
|
{
|
|
align = T1_ALIGN_BOTTOM;
|
|
bottom = ROUND( blue->pix );
|
|
|
|
/* implement blue shift */
|
|
if ( !hints->supress_overshoots )
|
|
{
|
|
FT_Pos delta = blue->pix - bottom_pix;
|
|
|
|
|
|
delta = delta < blueShift ? 0 : ROUND( delta );
|
|
bottom -= delta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* check for top blue zones alignement */
|
|
/* */
|
|
|
|
{
|
|
FT_Int num_blues = hints->num_blue_zones -
|
|
hints->num_bottom_zones;
|
|
|
|
T1_Snap_Zone* blue = hints->blue_zones +
|
|
hints->num_bottom_zones;
|
|
|
|
T1_Snap_Zone* blue_limit = blue + num_blues;
|
|
|
|
|
|
for ( ; blue < blue_limit; blue++ )
|
|
{
|
|
if ( top_pix < blue->min )
|
|
break;
|
|
|
|
if ( top_pix <= blue->max )
|
|
{
|
|
align |= T1_ALIGN_TOP;
|
|
top = ROUND( blue->pix );
|
|
|
|
/* implement blue shift */
|
|
if ( !hints->supress_overshoots )
|
|
{
|
|
FT_Pos delta = top - blue->pix;
|
|
|
|
|
|
delta = delta < blueShift ? 0 : ROUND( delta );
|
|
top += delta;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************************************************************/
|
|
/* */
|
|
/* compute the hinted stem position, according to its alignment */
|
|
/* */
|
|
|
|
switch ( align )
|
|
{
|
|
case T1_ALIGN_BOTTOM: /* bottom zone alignment */
|
|
bottom_pix = bottom;
|
|
top_pix = bottom + width_pix;
|
|
break;
|
|
|
|
case T1_ALIGN_TOP: /* top zone alignment */
|
|
top_pix = top;
|
|
bottom_pix = top - width_pix;
|
|
break;
|
|
|
|
case T1_ALIGN_BOTH: /* bottom+top zone alignment */
|
|
bottom_pix = bottom;
|
|
top_pix = top;
|
|
break;
|
|
|
|
default: /* no alignment */
|
|
/* XXX TODO: Add management of controlled stems */
|
|
bottom = ( SCALE( bottom_orus + top_orus ) - width_pix ) / 2;
|
|
|
|
bottom_pix = ROUND( bottom );
|
|
top_pix = bottom_pix + width_pix;
|
|
}
|
|
|
|
stem->min_edge.pix = bottom_pix;
|
|
stem->max_edge.pix = top_pix;
|
|
}
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_hint_vertical_stems */
|
|
/* */
|
|
/* <Description> */
|
|
/* Computes the location of each scaled vertical stem hint. This */
|
|
/* takes care of the vertical stem snap table. */
|
|
/* */
|
|
/* <Input> */
|
|
/* table :: The vertical stem hints table. */
|
|
/* hints :: The current size's hint structure. */
|
|
/* scale :: The 16.16 scale used to convert outline units to */
|
|
/* 26.6 pixels. */
|
|
/* */
|
|
/* <Note> */
|
|
/* For now, all stems are hinted independently from each other. It */
|
|
/* might be necessary, for better performance, to introduce the */
|
|
/* notion of `controlled' hints describing things like counter-stems, */
|
|
/* stem3 as well as overlapping stems control. */
|
|
/* */
|
|
static
|
|
void t1_hint_vertical_stems( T1_Stem_Table* table,
|
|
T1_Size_Hints* hints,
|
|
FT_Fixed scale )
|
|
{
|
|
T1_Stem_Hint* stem = table->stems;
|
|
T1_Stem_Hint* limit = stem + table->num_stems;
|
|
|
|
|
|
for ( ; stem < limit; stem++ )
|
|
{
|
|
FT_Pos stem_left = stem->min_edge.orus;
|
|
FT_Pos stem_right = stem->max_edge.orus;
|
|
FT_Pos width_pix, left;
|
|
|
|
|
|
width_pix = SCALE( stem_right - stem_left );
|
|
|
|
/* Snap pixel width if in stem snap range */
|
|
{
|
|
T1_Snap_Zone* zone = hints->snap_heights;
|
|
T1_Snap_Zone* zone_limit = zone + hints->num_snap_heights;
|
|
FT_Pos best_dist = 32000;
|
|
T1_Snap_Zone* best_zone = 0;
|
|
|
|
|
|
for ( ; zone < zone_limit; zone++ )
|
|
{
|
|
FT_Pos dist;
|
|
|
|
|
|
dist = width_pix - zone->min;
|
|
if ( dist < 0 )
|
|
dist = -dist;
|
|
if ( dist < best_dist )
|
|
{
|
|
best_zone = zone;
|
|
best_dist = dist;
|
|
}
|
|
}
|
|
|
|
if ( best_zone )
|
|
{
|
|
if ( width_pix > best_zone->pix )
|
|
{
|
|
width_pix -= 0x20;
|
|
if ( width_pix < best_zone->pix )
|
|
width_pix = best_zone->pix;
|
|
}
|
|
else
|
|
{
|
|
width_pix += 0x20;
|
|
if ( width_pix > best_zone->pix )
|
|
width_pix = best_zone->pix;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* round width - minimum 1 pixel if this isn't a ghost stem */
|
|
if ( width_pix > 0 )
|
|
width_pix = width_pix < ONE_PIXEL ? ONE_PIXEL
|
|
: ROUND( width_pix );
|
|
|
|
/* now place the snapped and rounded stem */
|
|
|
|
/* XXX TODO: implement controlled stems for the overlapping */
|
|
/* cases */
|
|
|
|
left = ( SCALE( stem_left + stem_right ) - width_pix ) / 2;
|
|
|
|
stem->min_edge.pix = ROUND( left );
|
|
stem->max_edge.pix = stem->min_edge.pix + width_pix;
|
|
}
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* t1_hint_point */
|
|
/* */
|
|
/* <Description> */
|
|
/* Grid-fit a coordinate with regards to a given stem hints table. */
|
|
/* */
|
|
/* <Input> */
|
|
/* table :: The source stem hints table. */
|
|
/* coord :: The original coordinate, expressed in font units. */
|
|
/* scale :: The 16.16 scale used to convert font units into */
|
|
/* 26.6 pixels. */
|
|
/* */
|
|
/* <Return> */
|
|
/* The hinted/scaled value in 26.6 pixels. */
|
|
/* */
|
|
/* <Note> */
|
|
/* For now, all stems are hinted independently from each other. It */
|
|
/* might be necessary, for better performance, to introduce the */
|
|
/* notion of `controlled' hints describing things like counter-stems, */
|
|
/* stem3 as well as overlapping stems control. */
|
|
/* */
|
|
static
|
|
FT_Pos t1_hint_point( T1_Stem_Table* table,
|
|
FT_Pos coord,
|
|
FT_Fixed scale )
|
|
{
|
|
FT_Int num_active = table->num_active;
|
|
FT_Int n;
|
|
T1_Stem_Hint* prev = 0;
|
|
T1_Stem_Hint* cur = 0;
|
|
T1_Edge* min;
|
|
T1_Edge* max;
|
|
FT_Pos delta;
|
|
|
|
|
|
/* only hint when there is at least one stem defined */
|
|
if ( num_active <= 0 )
|
|
return SCALE( coord );
|
|
|
|
/* scan the stem table to determine placement of coordinate */
|
|
/* relative to the list of sorted and stems */
|
|
for ( n = 0; n < num_active; n++, prev = cur )
|
|
{
|
|
cur = table->stems + table->sort[n];
|
|
|
|
/* is it on the left of the current edge? */
|
|
delta = cur->min_edge.orus - coord;
|
|
if ( delta == 0 )
|
|
return cur->min_edge.pix;
|
|
|
|
if ( delta > 0 )
|
|
{
|
|
/* if this is the left of the first edge, simply shift */
|
|
if ( !prev )
|
|
return cur->min_edge.pix - SCALE( delta );
|
|
|
|
/* otherwise, interpolate between the maximum of the */
|
|
/* previous stem, and the minimum of the current one */
|
|
min = &prev->max_edge;
|
|
max = &cur->min_edge;
|
|
|
|
goto Interpolate;
|
|
}
|
|
|
|
/* is it within the current edge? */
|
|
delta = cur->max_edge.orus - coord;
|
|
if ( delta == 0 )
|
|
return cur->max_edge.pix;
|
|
|
|
if ( delta > 0 )
|
|
{
|
|
/* interpolate within the stem */
|
|
min = &cur->min_edge;
|
|
max = &cur->max_edge;
|
|
|
|
goto Interpolate;
|
|
}
|
|
}
|
|
|
|
/* apparently, this coordinate is on the right of the last stem */
|
|
delta = coord - cur->max_edge.orus;
|
|
return cur->max_edge.pix + SCALE( delta );
|
|
|
|
Interpolate:
|
|
return min->pix + FT_MulDiv( coord - min->orus,
|
|
max->pix - min->pix,
|
|
max->orus - min->orus );
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_Hint_Points */
|
|
/* */
|
|
/* <Description> */
|
|
/* This function grid-fits several points in a given Type 1 builder */
|
|
/* at once. */
|
|
/* */
|
|
/* <Input> */
|
|
/* builder :: A handle to target Type 1 builder. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
void T1_Hint_Points( T1_Builder* builder )
|
|
{
|
|
FT_Int first = builder->hint_point;
|
|
FT_Int last = builder->current->n_points - 1;
|
|
|
|
T1_Size size = builder->size;
|
|
FT_Fixed scale_x = size->root.metrics.x_scale;
|
|
FT_Fixed scale_y = size->root.metrics.y_scale;
|
|
|
|
T1_Glyph_Hints* hints = builder->glyph->hints;
|
|
T1_Stem_Table* hori_stems = &hints->hori_stems;
|
|
T1_Stem_Table* vert_stems = &hints->vert_stems;
|
|
|
|
FT_Vector* cur = builder->current->points + first;
|
|
FT_Vector* limit = cur + last - first + 1;
|
|
|
|
|
|
/* first of all, sort the active stem hints */
|
|
t1_sort_hints( hori_stems );
|
|
t1_sort_hints( vert_stems );
|
|
|
|
for ( ; cur < limit; cur++ )
|
|
{
|
|
cur->x = t1_hint_point( vert_stems, cur->x, scale_x );
|
|
cur->y = t1_hint_point( hori_stems, cur->y, scale_y );
|
|
}
|
|
|
|
builder->hint_point = builder->current->n_points;
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/* */
|
|
/* <Function> */
|
|
/* T1_Hint_Stems */
|
|
/* */
|
|
/* <Description> */
|
|
/* This function is used to compute the location of each stem hint */
|
|
/* between the first and second passes of the glyph loader on the */
|
|
/* charstring. */
|
|
/* */
|
|
/* <Input> */
|
|
/* builder :: A handle to the target builder. */
|
|
/* */
|
|
LOCAL_FUNC
|
|
void T1_Hint_Stems( T1_Builder* builder )
|
|
{
|
|
T1_Glyph_Hints* hints = builder->glyph->hints;
|
|
T1_Private* priv = &builder->face->type1.private_dict;
|
|
|
|
T1_Size size = builder->size;
|
|
FT_Fixed scale_x = size->root.metrics.x_scale;
|
|
FT_Fixed scale_y = size->root.metrics.y_scale;
|
|
|
|
|
|
t1_hint_horizontal_stems( &hints->hori_stems,
|
|
builder->size->hints,
|
|
priv->blue_shift,
|
|
scale_y );
|
|
|
|
t1_hint_vertical_stems( &hints->vert_stems,
|
|
builder->size->hints,
|
|
scale_x );
|
|
}
|
|
|
|
|
|
/* END */
|