* src/autofit/*: important fixes to the auto-fitter. The output

now seems to be 100% equivalent to the auto-hinter, while being
        about 2% faster (which proves that script-specific algorithm
        selection isn't a performance problem).

        to test it, change "autohint" to "autofit" in
        <freetype/config/ftmodule.h> and recompile.

        a few more testing is needed before making this the official
        auto-hinting module
This commit is contained in:
David Turner 2004-06-04 17:41:59 +00:00
parent 56a4d87cb2
commit e664efaddd
11 changed files with 329 additions and 111 deletions

View File

@ -1,3 +1,16 @@
2004-06-04 David Turner <david@freetype.org>
* src/autofit/*: important fixes to the auto-fitter. The output
now seems to be 100% equivalent to the auto-hinter, while being
about 2% faster (which proves that script-specific algorithm
selection isn't a performance problem).
to test it, change "autohint" to "autofit" in
<freetype/config/ftmodule.h> and recompile.
a few more testing is needed before making this the official
auto-hinting module
2004-06-02 Werner Lemberg <wl@gnu.org>
* src/truetype/ttgload.c (compute_glyph_metrics): Fix compiler
@ -24,7 +37,7 @@
2004-05-17 Werner Lemberg <wl@gnu.org>
* src/base/ftbbox.c (BBox_Conic_Check): Fix boundary cases.
* src/base/ftbbox.c (BBox_Conic_Check): Fix boundary cases.
Reported by Mikey Anbary <manbary@vizrt.com>.
2004-05-15 Werner Lemberg <wl@gnu.org>
@ -173,7 +186,7 @@
consistency.
(pcf_cmap_init, pcf_cmap_done, pcf_cmap_char_index,
pcf_cmap_char_next): Don't use PCF_XXX but FT_XXX arguments which
are typecast to the proper PCF_XXX types within the function.
are typecast to the proper PCF_XXX types within the function.
Update code accordingly.
(pcf_cmap_class): Remove casts.
(PCF_Face_Done, PCF_Face_Init, PCF_Set_Pixel_Size): Don't use

View File

@ -1,8 +1,57 @@
#include "aftypes.h"
/*
* a python script used to generate the following table
*
import sys, math
units = 256
scale = units/math.pi
comma = ""
print ""
print "table of arctan( 1/2^n ) for PI = " + repr(units/65536.0) + " units"
r = [-1] + range(32)
for n in r:
if n >= 0:
x = 1.0/(2.0**n) # tangent value
else:
x = 2.0**(-n)
angle = math.atan(x) # arctangent
angle2 = angle*scale # arctangent in FT_Angle units
# determine which integer value for angle gives the best tangent
lo = int(angle2)
hi = lo + 1
tlo = math.tan(lo/scale)
thi = math.tan(hi/scale)
errlo = abs( tlo - x )
errhi = abs( thi - x )
angle2 = hi
if errlo < errhi:
angle2 = lo
if angle2 <= 0:
break
sys.stdout.write( comma + repr( int(angle2) ) )
comma = ", "
*
* end of python script
*/
/* this table was generated for AF_ANGLE_PI = 256 */
#define AF_ANGLE_MAX_ITERS 8
#define AF_TRIG_MAX_ITERS 9
#define AF_TRIG_MAX_ITERS 8
static const FT_Fixed
af_angle_arctan_table[9] =
@ -69,7 +118,7 @@
{
x = -x;
y = -y;
theta = 2 * AF_ANGLE_PI2;
theta = AF_ANGLE_PI;
}
if ( y > 0 )
@ -115,11 +164,13 @@
}
} while ( ++i < AF_TRIG_MAX_ITERS );
#if 0
/* round theta */
if ( theta >= 0 )
theta = FT_PAD_ROUND( theta, 4 );
theta = FT_PAD_ROUND( theta, 2 );
else
theta = - FT_PAD_ROUND( theta, 4 );
theta = - FT_PAD_ROUND( -theta, 2 );
#endif
vec->x = x;
vec->y = theta;
@ -212,3 +263,40 @@
}
}
}
#ifdef TEST
#include <stdio.h>
#include <math.h>
int main( void )
{
int angle;
int dist;
for ( dist = 100; dist < 1000; dist++ )
{
for ( angle = AF_ANGLE_PI; angle < AF_ANGLE_2PI*4; angle++ )
{
double a = (angle*3.1415926535)/(1.0*AF_ANGLE_PI);
int dx, dy, angle1, angle2, delta;
dx = dist * cos(a);
dy = dist * sin(a);
angle1 = ((atan2(dy,dx)*AF_ANGLE_PI)/3.1415926535);
angle2 = af_angle_atan( dx, dy );
delta = (angle2 - angle1) % AF_ANGLE_2PI;
if ( delta < 0 )
delta = -delta;
if ( delta >= 2 )
{
printf( "dist:%4d angle:%4d => (%4d,%4d) angle1:%4d angle2:%4d\n",
dist, angle, dx, dy, angle1, angle2 );
}
}
}
return 0;
}
#endif

View File

@ -3,20 +3,21 @@
static FT_Error
af_dummy_hints_init( AF_GlyphHints hints,
FT_Outline* outline,
AF_ScriptMetrics metrics )
{
return af_glyph_hints_reset( hints,
&metrics->scaler,
metrics,
outline );
af_glyph_hints_rescale( hints,
metrics );
return 0;
}
static FT_Error
af_dummy_hints_apply( AF_GlyphHints hints,
FT_Outline* outline )
{
af_glyph_hints_save( hints, outline );
FT_UNUSED( hints );
FT_UNUSED( outline );
return 0;
}

View File

@ -12,7 +12,7 @@
NULL /* do not remove */
};
#define AF_SCRIPT_LIST_DEFAULT 0 /* index of default script in 'af_script_classes' */
#define AF_SCRIPT_LIST_DEFAULT 1 /* index of default script in 'af_script_classes' */
#define AF_SCRIPT_LIST_NONE 255 /* indicates an uncovered glyph */
/*

View File

@ -10,11 +10,11 @@
switch (dir)
{
case AF_DIR_UP: result = "up"; break;
case AF_DIR_DOWN: result = "down"; break;
case AF_DIR_LEFT: result = "left"; break;
case AF_DIR_UP: result = "up"; break;
case AF_DIR_DOWN: result = "down"; break;
case AF_DIR_LEFT: result = "left"; break;
case AF_DIR_RIGHT: result = "right"; break;
default: result = "none";
default: result = "none";
}
return result;
}
@ -111,7 +111,7 @@
for ( edge = edges; edge < limit; edge++ )
{
printf ( " [ %5d | %4d | %5s | %4d | %5d | %c | %5.2f | %5.2f ]\n",
printf ( " [ %5d | %4d | %5s | %4d | %5d | %c | %5.2f | %5.2f ]\n",
edge - edges,
(int)edge->fpos,
af_dir_str( edge->dir ),
@ -314,26 +314,29 @@
FT_LOCAL_DEF( void )
af_glyph_hints_rescale( AF_GlyphHints hints,
AF_ScriptMetrics metrics )
{
hints->metrics = metrics;
}
FT_LOCAL_DEF( FT_Error )
af_glyph_hints_reset( AF_GlyphHints hints,
AF_Scaler scaler,
AF_ScriptMetrics metrics,
FT_Outline* outline )
af_glyph_hints_reload( AF_GlyphHints hints,
FT_Outline* outline )
{
FT_Error error = FT_Err_Ok;
AF_Point points;
FT_UInt old_max, new_max;
FT_Fixed x_scale = scaler->x_scale;
FT_Fixed y_scale = scaler->y_scale;
FT_Pos x_delta = scaler->x_delta;
FT_Pos y_delta = scaler->y_delta;
AF_Scaler scaler = &hints->metrics->scaler;
FT_Fixed x_scale = hints->x_scale;
FT_Fixed y_scale = hints->y_scale;
FT_Pos x_delta = hints->x_delta;
FT_Pos y_delta = hints->y_delta;
FT_Memory memory = hints->memory;
hints->metrics = metrics;
hints->scaler_flags = scaler->flags;
hints->other_flags = 0;
hints->scaler_flags = scaler->flags;
hints->num_points = 0;
hints->num_contours = 0;

View File

@ -214,11 +214,13 @@ FT_BEGIN_HEADER
/* recomputes all AF_Point in a AF_GlyphHints from the definitions
* in a source outline
*/
FT_LOCAL( void )
af_glyph_hints_rescale( AF_GlyphHints hints,
AF_ScriptMetrics metrics );
FT_LOCAL( FT_Error )
af_glyph_hints_reset( AF_GlyphHints hints,
AF_Scaler scaler,
AF_ScriptMetrics metrics,
FT_Outline* outline );
af_glyph_hints_reload( AF_GlyphHints hints,
FT_Outline* outline );
FT_LOCAL( void )
af_glyph_hints_save( AF_GlyphHints hints,

View File

@ -25,8 +25,8 @@
FT_Error error;
FT_UInt glyph_index;
AF_Dimension dim;
AF_ScalerRec scaler[1];
AF_ScriptMetricsRec dummy[1];
AF_Scaler scaler = &dummy->scaler;
glyph_index = FT_Get_Char_Index( face, 'o' );
if ( glyph_index == 0 )
@ -36,15 +36,17 @@
if ( error || face->glyph->outline.n_points <= 0 )
goto Exit;
FT_ZERO( dummy );
scaler->x_scale = scaler->y_scale = 0x10000L;
scaler->x_delta = scaler->y_delta = 0;
scaler->face = face;
scaler->render_mode = 0;
scaler->flags = 0;
error = af_glyph_hints_reset( hints, scaler,
(AF_ScriptMetrics) metrics,
&face->glyph->outline );
af_glyph_hints_rescale( hints, dummy );
error = af_glyph_hints_reload( hints, &face->glyph->outline );
if ( error )
goto Exit;
@ -324,6 +326,13 @@
if ( AF_LATIN_IS_TOP_BLUE(bb) )
blue->flags |= AF_LATIN_BLUE_TOP;
/* the following flags is used later to adjust the y and x scales
* in order to optimize the pixel grid alignment of the top of small
* letters.
*/
if ( bb == AF_LATIN_BLUE_SMALL_TOP )
blue->flags |= AF_LATIN_BLUE_ADJUSTMENT;
AF_LOG(( "-- ref = %ld, shoot = %ld\n", *blue_ref, *blue_shoot ));
}
@ -382,8 +391,44 @@
axis->org_scale = scale;
axis->org_delta = delta;
/* XXX: TODO: Correct Y and X scale according to Chester rules
/* correct X and Y scale to optimize the alignment of the top of small
* letters to the pixel grid
*/
{
AF_LatinAxis axis = &metrics->axis[ AF_DIMENSION_VERT ];
AF_LatinBlue blue = NULL;
FT_UInt nn;
for ( nn = 0; nn < axis->blue_count; nn++ )
{
if ( axis->blues[nn].flags & AF_LATIN_BLUE_ADJUSTMENT )
{
blue = &axis->blues[nn];
break;
}
}
if ( blue )
{
FT_Pos scaled = FT_MulFix( blue->shoot.org, scaler->y_scale );
FT_Pos fitted = FT_PIX_ROUND( scaled );
if ( scaled != fitted )
{
if ( dim == AF_DIMENSION_HORZ )
{
if ( fitted < scaled )
scale -= scale/50; /* x_scale = x_scale*0.98 */
}
else
{
scale = FT_MulDiv( scale, fitted, scaled );
}
}
}
}
axis->scale = scale;
axis->delta = delta;
@ -417,17 +462,41 @@
AF_LatinBlue blue = & axis->blues[nn];
FT_Pos dist;
blue->ref.cur = FT_MulFix( blue->ref.org, scale ) + delta;
blue->ref.fit = blue->ref.cur;
blue->ref.cur = FT_MulFix( blue->ref.org, scale ) + delta;
blue->ref.fit = blue->ref.cur;
blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta;
blue->shoot.fit = blue->shoot.cur;
blue->flags &= ~AF_LATIN_BLUE_ACTIVE;
/* a blue zone is only active when it is less than 3/4 pixels tall
*/
dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
if ( dist >= 48 || dist <= -48 )
blue->flags |= ~AF_LATIN_BLUE_ACTIVE;
if ( dist <= 48 && dist >= -48 )
{
FT_Pos delta, delta2;
delta = blue->shoot.org - blue->ref.org;
delta2 = delta;
if ( delta < 0 )
delta2 = -delta2;
delta2 = FT_MulFix( delta2, scale );
if ( delta2 < 32 )
delta2 = 0;
else if ( delta2 < 64 )
delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 );
else
delta2 = FT_PIX_ROUND( delta2 );
if ( delta < 0 )
delta2 = -delta2;
blue->ref.fit = FT_PIX_ROUND( blue->ref.cur );
blue->shoot.fit = blue->ref.fit + delta2;
blue->flags |= AF_LATIN_BLUE_ACTIVE;
}
}
}
}
@ -437,6 +506,9 @@
af_latin_metrics_scale( AF_LatinMetrics metrics,
AF_Scaler scaler )
{
if ( AF_SCALER_EQUAL_SCALES( scaler, &metrics->root.scaler ) )
return;
af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
}
@ -791,6 +863,7 @@
AF_Dimension dim )
{
AF_AxisHints axis = &hints->axis[dim];
AF_LatinAxis laxis = &((AF_LatinMetrics)hints->metrics)->axis[dim];
AF_Edge edges = axis->edges;
AF_Edge edge, edge_limit;
@ -825,7 +898,7 @@
/* */
/*********************************************************************/
edge_distance_threshold = FT_MulFix( hints->edge_distance_threshold,
edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
scale );
if ( edge_distance_threshold > 64 / 4 )
edge_distance_threshold = 64 / 4;
@ -1138,18 +1211,19 @@
static FT_Error
af_latin_hints_init( AF_GlyphHints hints,
FT_Outline* outline,
AF_LatinMetrics metrics )
{
FT_Error error;
FT_Render_Mode mode;
error = af_glyph_hints_reset( hints, &metrics->root.scaler,
(AF_ScriptMetrics) metrics,
outline );
if (error)
goto Exit;
af_glyph_hints_rescale( hints, (AF_ScriptMetrics)metrics );
/* correct x_scale and y_scale when needed, since they may have
* been modified af_latin_scale_dim above
*/
hints->x_scale = metrics->axis[ AF_DIMENSION_HORZ ].scale;
hints->x_delta = metrics->axis[ AF_DIMENSION_HORZ ].delta;
hints->y_scale = metrics->axis[ AF_DIMENSION_VERT ].scale;
hints->y_delta = metrics->axis[ AF_DIMENSION_VERT ].delta;
/* compute flags depending on render mode, etc...
*/
@ -1176,21 +1250,11 @@
if ( mode == FT_RENDER_MODE_MONO )
hints->other_flags |= AF_LATIN_HINTS_MONO;
/* analyze glyph outline
*/
if ( AF_HINTS_DO_HORIZONTAL(hints) )
af_latin_hints_detect_features( hints, AF_DIMENSION_HORZ );
if ( AF_HINTS_DO_VERTICAL(hints) )
{
af_latin_hints_detect_features( hints, AF_DIMENSION_VERT );
af_latin_hints_compute_blue_edges( hints, metrics );
}
Exit:
return error;
return 0;
}
/***************************************************************************/
/***************************************************************************/
/***** *****/
@ -1738,10 +1802,26 @@
FT_Outline* outline,
AF_LatinMetrics metrics )
{
FT_Error error;
AF_Dimension dim;
FT_UNUSED( metrics );
error = af_glyph_hints_reload( hints, outline );
if ( error )
goto Exit;
/* analyze glyph outline
*/
if ( AF_HINTS_DO_HORIZONTAL(hints) )
af_latin_hints_detect_features( hints, AF_DIMENSION_HORZ );
if ( AF_HINTS_DO_VERTICAL(hints) )
{
af_latin_hints_detect_features( hints, AF_DIMENSION_VERT );
af_latin_hints_compute_blue_edges( hints, metrics );
}
/* grid-fit the outline
*/
for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
{
if ( (dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL(hints)) ||
@ -1755,7 +1835,8 @@
}
af_glyph_hints_save( hints, outline );
return 0;
Exit:
return error;
}
/***************************************************************************/
@ -1768,11 +1849,12 @@
static const AF_Script_UniRangeRec af_latin_uniranges[] =
{
{ 32, 127 }, /* XXX: TODO: Add new Unicode ranges here !! */
{ 32, 127 }, /* XXX: TODO: Add new Unicode ranges here !! */
{ 160, 255 },
{ 0, 0 }
{ 0, 0 }
};
FT_LOCAL_DEF( const AF_ScriptClassRec ) af_latin_script_class =
{
AF_SCRIPT_LATIN,

View File

@ -51,9 +51,10 @@ FT_BEGIN_HEADER
enum
{
AF_LATIN_BLUE_ACTIVE = (1 << 0),
AF_LATIN_BLUE_TOP = (1 << 1),
AF_LATIN_BLUE_ACTIVE = (1 << 0),
AF_LATIN_BLUE_TOP = (1 << 1),
AF_LATIN_BLUE_ADJUSTMENT = (1 << 2), /* used for scale adjustment */
/* optimization */
AF_LATIN_BLUE_FLAG_MAX
};

View File

@ -105,16 +105,9 @@
* is needed
*/
if ( loader->transformed )
{
FT_Vector* point = slot->outline.points;
FT_Vector* limit = point + slot->outline.n_points;
for ( ; point < limit; point++ )
{
point->x += loader->trans_delta.x;
point->y += loader->trans_delta.y;
}
}
FT_Outline_Translate( &slot->outline,
loader->trans_delta.x,
loader->trans_delta.y );
/* copy the outline points in the loader's current */
/* extra points which is used to keep original glyph coordinates */
@ -157,16 +150,10 @@
/* now load the slot image into the auto-outline and run the */
/* automatic hinting process */
error = metrics->clazz->script_hints_init( hints,
&gloader->current.outline,
metrics );
if ( error )
goto Exit;
/* apply the hints */
metrics->clazz->script_hints_apply( hints,
&gloader->current.outline,
metrics );
/* we now need to hint the metrics according to the change in */
/* width/positioning that occured during the hinting process */
{
@ -175,20 +162,27 @@
AF_Edge edge1 = axis->edges; /* leftmost edge */
AF_Edge edge2 = edge1 + axis->num_edges - 1; /* rightmost edge */
if ( edge2 > edge1 )
{
old_advance = loader->pp2.x;
old_rsb = old_advance - edge2->opos;
old_lsb = edge1->opos;
new_lsb = edge1->pos;
old_advance = loader->pp2.x;
old_rsb = old_advance - edge2->opos;
old_lsb = edge1->opos;
new_lsb = edge1->pos;
loader->pp1.x = FT_PIX_ROUND( new_lsb - old_lsb );
loader->pp2.x = FT_PIX_ROUND( edge2->pos + old_rsb );
loader->pp1.x = FT_PIX_ROUND( new_lsb - old_lsb );
loader->pp2.x = FT_PIX_ROUND( edge2->pos + old_rsb );
#if 0
/* try to fix certain bad advance computations */
if ( loader->pp2.x + loader->pp1.x == edge2->pos && old_rsb > 4 )
loader->pp2.x += 64;
/* try to fix certain bad advance computations */
if ( loader->pp2.x + loader->pp1.x == edge2->pos && old_rsb > 4 )
loader->pp2.x += 64;
#endif
}
else
{
loader->pp1.x = FT_PIX_ROUND( loader->pp1.x );
loader->pp2.x = FT_PIX_ROUND( loader->pp2.x );
}
}
/* good, we simply add the glyph to our loader's base */
@ -366,7 +360,13 @@
slot->metrics.horiAdvance = FT_MulFix( slot->metrics.horiAdvance,
x_scale );
#else
slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x;
/* for mono-width fonts (like Andale, Courier, etc.) we need */
/* to keep the original rounded advance width */
if ( !FT_IS_FIXED_WIDTH( slot->face ) )
slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x;
else
slot->metrics.horiAdvance = FT_MulFix( slot->metrics.horiAdvance,
metrics->scaler.x_scale );
#endif
slot->metrics.horiAdvance = FT_PIX_ROUND( slot->metrics.horiAdvance );
@ -434,8 +434,13 @@
load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM;
load_flags &= ~FT_LOAD_RENDER;
error = metrics->clazz->script_hints_init( &loader->hints, metrics );
if ( error )
goto Exit;
error = af_loader_load_g( loader, &scaler, gindex, load_flags, 0 );
}
}
Exit:
return error;
}

View File

@ -1,3 +1,20 @@
/***************************************************************************
*
* FreeType auto-fitter
*
* (c) 2004 David Turner
*
* The auto-fitter is a complete rewrite of the old auto-hinter.
* its main feature is the ability to differentiate between different
* scripts in order to apply language-specific rules.
*
* the code has also been compartimentized into several entities that
* should make algorithmic experimentation easier than with the old
* code.
*
* finally, we get rid of the Catharon license, since this code is
* released under the FreeType one.
*/
#ifndef __AFTYPES_H__
#define __AFTYPES_H__
@ -73,7 +90,7 @@ FT_BEGIN_HEADER
typedef FT_Int AF_Angle;
#define AF_ANGLE_PI 128
#define AF_ANGLE_PI 256
#define AF_ANGLE_2PI (AF_ANGLE_PI*2)
#define AF_ANGLE_PI2 (AF_ANGLE_PI/2)
#define AF_ANGLE_PI4 (AF_ANGLE_PI/4)
@ -164,6 +181,12 @@ FT_BEGIN_HEADER
} AF_ScalerRec, *AF_Scaler;
#define AF_SCALER_EQUAL_SCALES(a,b) \
( (a)->x_scale == (b)->x_scale && \
(a)->y_scale == (b)->y_scale && \
(a)->x_delta == (b)->x_delta && \
(a)->y_delta == (b)->y_delta )
/**************************************************************************/
/**************************************************************************/
@ -228,7 +251,6 @@ FT_BEGIN_HEADER
typedef FT_Error (*AF_Script_InitHintsFunc)( AF_GlyphHints hints,
FT_Outline* outline,
AF_ScriptMetrics metrics );
typedef void (*AF_Script_ApplyHintsFunc)( AF_GlyphHints hints,

View File

@ -1,7 +1,8 @@
# compute arctangent table for CORDIC computations in fttrigon.c
import sys, math
units = 64*65536.0 # don't change !!
#units = 64*65536.0 # don't change !!
units = 256
scale = units/math.pi
shrink = 1.0
comma = ""
@ -23,7 +24,7 @@ def print_val( n, x ):
errlo = abs( alo - ax )
errhi = abs( ahi - ax )
if ( errlo < errhi ):
hi = lo
@ -44,8 +45,8 @@ for n in r:
x = 1.0/(2.0**n) # tangent value
else:
x = 2.0**(-n)
angle = math.atan(x) # arctangent
angle = math.atan(x) # arctangent
angle2 = angle*scale # arctangent in FT_Angle units
# determine which integer value for angle gives the best tangent
@ -63,12 +64,12 @@ for n in r:
if angle2 <= 0:
break
sys.stdout.write( comma + repr( int(angle2) ) )
comma = ", "
shrink = shrink * math.cos( angle2/scale)
print
print "shrink factor = " + repr( shrink )