blob: 1d8648acab78ca143b9ed889964127e630e43b07 [file] [log] [blame]
/*******************************************************************
*
* t1hinter.c 1.2
*
* Type1 hinter.
*
* Copyright 1996-1999 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 <ftdebug.h>
#include <t1objs.h>
#include <t1hinter.h>
#undef FT_COMPONENT
#define FT_COMPONENT trace_t1hint /* for debugging/tracing */
#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
/************************************************************************
*
* <Function>
* t1_set_blue_zones
*
* <Description>
* Set a size object's blue zones during reset. This will compute
* the "snap" zone corresponding to each blue zone.
*
* <Input>
* size :: handle to target size object
*
* <Return>
* 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.
*
************************************************************************/
/* ultra simple bubble sort (not a lot of elements, mostly */
/* pre-sorted, no need for quicksort) */
static
void t1_sort_blues( T1_Int* blues,
T1_Int count )
{
T1_Int i, swap;
T1_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 );
}
}
static
T1_Error t1_set_blue_zones( T1_Size size )
{
T1_Face face = (T1_Face)size->root.face;
T1_Font* priv = &face->type1;
T1_Int n;
T1_Int blues[24];
T1_Int num_bottom;
T1_Int num_top;
T1_Int num_blues;
T1_Size_Hints* hints = size->hints;
T1_Snap_Zone* zone;
T1_Pos pix, orus;
T1_Pos min, max, threshold;
T1_Fixed scale;
T1_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_blues & 1 )
{
FT_ERROR(( "T1.Copy_Blues : 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_blues - 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 wether to supress overshoots.. */
hints->supress_overshoots =
( size->root.metrics.y_ppem < FT_MulFix(1000,priv->blue_scale) );
/* Now print the new blue values in tracing mode */
#ifdef FT_DEBUG_LEVEL_TRACE
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(( "\nOver shoots 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.
*
* <Input>
* size :: handle to target size object
*
* <Return>
* 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 a 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
T1_Error t1_set_snap_zones( T1_Size size )
{
T1_Int n, direction, n_zones, num_zones;
T1_Snap_Zone* zone;
T1_Snap_Zone* base_zone;
T1_Short* orgs;
T1_Pos standard_width;
T1_Fixed scale;
T1_Face face = (T1_Face)size->root.face;
T1_Font* priv = &face->type1;
T1_Size_Hints* hints = size->hints;
/* start with horizontal snap zones */
direction = 0;
standard_width = priv->standard_width;
n_zones = priv->num_snap_widths;
base_zone = hints->snap_widths;
orgs = priv->stem_snap_widths;
scale = size->root.metrics.x_scale;
while (direction < 2)
{
/*****************************************************************/
/* */
/* Read and scale stem snap widths table from the physical */
/* font record. */
/* */
T1_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;
}
/* print the scaled stem snap values in tracing modes */
#ifdef FT_DEBUG_LEVEL_TRACE
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
/*****************************************************************/
/* */
/* 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;
T1_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;
/* print the scaled stem snap values in tracing modes */
#ifdef FT_DEBUG_LEVEL_TRACE
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
/* continue with vertical snap zone */
direction++;
standard_width = priv->standard_height;
n_zones = priv->num_snap_heights;
base_zone = hints->snap_heights;
orgs = priv->stem_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
*
* <Input>
* size :: handle to target size object
*
* <Return>
* Error code. 0 means success
*
************************************************************************/
LOCAL_FUNC
T1_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 :: handle to 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
*
* <Input>
* size :: handle to size object
*
* <Return>
* Error code. 0 means success
*
************************************************************************/
LOCAL_FUNC
T1_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
*
* <Input>
* glyph :: handle to target glyph slot
*
* <Return>
* Error code. 0 means success
*
************************************************************************/
LOCAL_FUNC
T1_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 :: handle to 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
T1_Error t1_hinter_ignore( void )
{
/* do nothing, used for "dotsection" which is unsupported for now */
return 0;
}
static
T1_Error t1_hinter_stem( T1_Builder* builder,
T1_Pos pos,
T1_Int width,
T1_Bool vertical )
{
T1_Stem_Table* stem_table;
T1_Stem_Hint* stems;
T1_Stem_Hint* cur_stem;
T1_Int min, max, n, num_stems;
T1_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 orus 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 when necessary */
if (new_stem)
{
if (cur_stem >= stems + T1_HINTER_MAX_EDGES)
{
FT_ERROR(( "T1.Hinter : 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 : fatal glyph loader bug - pass2-stem\n" ));
return T1_Err_Syntax_Error;
}
}
return T1_Err_Ok;
}
static
T1_Error t1_hinter_stem3( T1_Builder* builder,
T1_Pos pos0,
T1_Int width0,
T1_Pos pos1,
T1_Int width1,
T1_Pos pos2,
T1_Int width2,
T1_Bool vertical )
{
/* For now, don't be elitist and 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
T1_Error t1_hinter_changehints( T1_Builder* builder )
{
T1_Int dimension;
T1_Stem_Table* stem_table;
T1_Glyph_Hints* hinter = builder->glyph->hints;
/* if we're 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;
}
LOCAL_FUNC
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>
* Sort the list of active stems in increasing order, through
* the "sort" indexing table
*
* <Input>
* table :: a stem hints table
*
************************************************************************/
static
void t1_sort_hints( T1_Stem_Table* table )
{
T1_Int num_stems = table->num_stems;
T1_Int num_active = 0;
T1_Int* sort = table->sort;
T1_Stem_Hint* stems = table->stems;
T1_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++ )
{
T1_Int p = n-1;
T1_Stem_Hint* cur = stems + sort[n];
do
{
T1_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, 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>
* Compute 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,
T1_Pos blueShift,
T1_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++ )
{
T1_Pos bottom_orus = stem->min_edge.orus;
T1_Pos top_orus = stem->max_edge.orus;
T1_Pos top_pix = SCALE( top_orus );
T1_Pos bottom_pix = SCALE( bottom_orus );
T1_Pos width_pix = top_pix - bottom_pix;
T1_Pos bottom = bottom_pix;
T1_Pos top = top_pix;
T1_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;
for ( ; zone < zone_limit; zone++ )
{
if ( width_pix < zone->min )
break;
if ( width_pix <= zone->max )
{
width_pix = zone->pix;
break;
}
}
}
/******************************************************************/
/* 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 */
{
T1_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 );
/* implements blue shift */
if (!hints->supress_overshoots)
{
T1_Pos delta = blue->pix - bottom_pix;
delta = ( delta < blueShift ? 0 : ROUND( delta ) );
bottom -= delta;
}
}
}
}
/******************************************************************/
/* Check for top blue zones alignement */
{
T1_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 );
/* implements blue shift */
if (!hints->supress_overshoots)
{
T1_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 alignement */
bottom_pix = bottom;
top_pix = bottom + width_pix;
break;
case T1_ALIGN_TOP: /* top zone alignement */
top_pix = top;
bottom_pix = top - width_pix;
break;
case T1_ALIGN_BOTH: /* bottom+top zone alignement */
bottom_pix = bottom;
top_pix = top;
break;
default: /* no alignement */
/* XXXX : 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>
* Compute 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.
*
************************************************************************/
/* compute the location of each scaled vertical stem hint. */
/* Take care of blue zones and stem snap table */
static
void t1_hint_vertical_stems( T1_Stem_Table* table,
T1_Size_Hints* hints,
T1_Fixed scale )
{
T1_Stem_Hint* stem = table->stems;
T1_Stem_Hint* limit = stem + table->num_stems;
for ( ; stem < limit; stem++ )
{
T1_Pos stem_left = stem->min_edge.orus;
T1_Pos stem_right = stem->max_edge.orus;
T1_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_widths;
T1_Snap_Zone* zone_limit = zone + hints->num_snap_widths;
for ( ; zone < zone_limit; zone++ )
{
if ( width_pix < zone->min )
break;
if ( width_pix <= zone->max )
{
width_pix = zone->pix;
break;
}
}
}
/* 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 */
/* XXXX : 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 :: 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
T1_Pos t1_hint_point( T1_Stem_Table* table,
T1_Pos coord,
T1_Fixed scale )
{
T1_Int num_active = table->num_active;
T1_Int n;
T1_Stem_Hint* prev = 0;
T1_Stem_Hint* cur = 0;
T1_Edge* min;
T1_Edge* max;
T1_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 );
}
#if 1
/************************************************************************
*
* <Function>
* T1_Hint_Points
*
* <Description>
* this function grid-fits several points in a given Type 1 builder
* at once.
*
* <Input>
* builder :: handle to target Type 1 builder
* first :: first point to hint in builder's current outline
* last :: last point to hint in builder's current outline
*
************************************************************************/
LOCAL_FUNC
void T1_Hint_Points( T1_Builder* builder )
{
T1_Int first = builder->hint_point;
T1_Int last = builder->current.n_points-1;
T1_Size size = builder->size;
T1_Fixed scale_x = size->root.metrics.x_scale;
T1_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;
T1_Vector* cur = builder->current.points + first;
T1_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 :: handle to target builder
*
************************************************************************/
LOCAL_FUNC
void T1_Hint_Stems( T1_Builder* builder )
{
T1_Glyph_Hints* hints = builder->glyph->hints;
T1_Font* priv = &builder->face->type1;
T1_Size size = builder->size;
T1_Fixed scale_x = size->root.metrics.x_scale;
T1_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 );
}
#endif