freetype2/src/autofit/aflatin.c

4269 lines
127 KiB
C

/****************************************************************************
*
* aflatin.c
*
* Auto-fitter hinting routines for latin writing system (body).
*
* Copyright (C) 2003-2023 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/ftadvanc.h>
#include <freetype/internal/ftdebug.h>
#include "afglobal.h"
#include "aflatin.h"
#include "aferrors.h"
#include "afadjust.h"
/**************************************************************************
*
* 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 aflatin
/* needed for computation of round vs. flat segments */
#define FLAT_THRESHOLD( x ) ( x / 14 )
/*************************************************************************/
/*************************************************************************/
/***** *****/
/***** L A T I N G L O B A L M E T R I C S *****/
/***** *****/
/*************************************************************************/
/*************************************************************************/
/* Find segments and links, compute all stem widths, and initialize */
/* standard width and height for the glyph with given charcode. */
FT_LOCAL_DEF( void )
af_latin_metrics_init_widths( AF_LatinMetrics metrics,
FT_Face face )
{
/* scan the array of segments in each direction */
AF_GlyphHintsRec hints[1];
FT_TRACE5(( "\n" ));
FT_TRACE5(( "latin standard widths computation (style `%s')\n",
af_style_names[metrics->root.style_class->style] ));
FT_TRACE5(( "=====================================================\n" ));
FT_TRACE5(( "\n" ));
af_glyph_hints_init( hints, face->memory );
metrics->axis[AF_DIMENSION_HORZ].width_count = 0;
metrics->axis[AF_DIMENSION_VERT].width_count = 0;
{
FT_Error error;
FT_ULong glyph_index;
int dim;
AF_LatinMetricsRec dummy[1];
AF_Scaler scaler = &dummy->root.scaler;
AF_StyleClass style_class = metrics->root.style_class;
AF_ScriptClass script_class = af_script_classes[style_class->script];
/* If HarfBuzz is not available, we need a pointer to a single */
/* unsigned long value. */
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
void* shaper_buf;
#else
FT_ULong shaper_buf_;
void* shaper_buf = &shaper_buf_;
#endif
const char* p;
#ifdef FT_DEBUG_LEVEL_TRACE
FT_ULong ch = 0;
#endif
p = script_class->standard_charstring;
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
shaper_buf = af_shaper_buf_create( face );
#endif
/*
* We check a list of standard characters to catch features like
* `c2sc' (small caps from caps) that don't contain lowercase letters
* by definition, or other features that mainly operate on numerals.
* The first match wins.
*/
glyph_index = 0;
while ( *p )
{
unsigned int num_idx;
#ifdef FT_DEBUG_LEVEL_TRACE
const char* p_old;
#endif
while ( *p == ' ' )
p++;
#ifdef FT_DEBUG_LEVEL_TRACE
p_old = p;
GET_UTF8_CHAR( ch, p_old );
#endif
/* reject input that maps to more than a single glyph */
p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
if ( num_idx > 1 )
continue;
/* otherwise exit loop if we have a result */
glyph_index = af_shaper_get_elem( &metrics->root,
shaper_buf,
0,
NULL,
NULL );
if ( glyph_index )
break;
}
af_shaper_buf_destroy( face, shaper_buf );
if ( !glyph_index )
{
FT_TRACE5(( "standard character missing;"
" using fallback stem widths\n" ));
goto Exit;
}
FT_TRACE5(( "standard character: U+%04lX (glyph index %ld)\n",
ch, glyph_index ));
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
if ( error || face->glyph->outline.n_points <= 0 )
goto Exit;
FT_ZERO( dummy );
dummy->units_per_em = metrics->units_per_em;
scaler->x_scale = 0x10000L;
scaler->y_scale = 0x10000L;
scaler->x_delta = 0;
scaler->y_delta = 0;
scaler->face = face;
scaler->render_mode = FT_RENDER_MODE_NORMAL;
scaler->flags = 0;
af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy );
error = af_glyph_hints_reload( hints, &face->glyph->outline );
if ( error )
goto Exit;
for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
{
AF_LatinAxis axis = &metrics->axis[dim];
AF_AxisHints axhints = &hints->axis[dim];
AF_Segment seg, limit, link;
FT_UInt num_widths = 0;
error = af_latin_hints_compute_segments( hints,
(AF_Dimension)dim );
if ( error )
goto Exit;
/*
* We assume that the glyphs selected for the stem width
* computation are `featureless' enough so that the linking
* algorithm works fine without adjustments of its scoring
* function.
*/
af_latin_hints_link_segments( hints,
0,
NULL,
(AF_Dimension)dim );
seg = axhints->segments;
limit = FT_OFFSET( seg, axhints->num_segments );
for ( ; seg < limit; seg++ )
{
link = seg->link;
/* we only consider stem segments there! */
if ( link && link->link == seg && link > seg )
{
FT_Pos dist;
dist = seg->pos - link->pos;
if ( dist < 0 )
dist = -dist;
if ( num_widths < AF_LATIN_MAX_WIDTHS )
axis->widths[num_widths++].org = dist;
}
}
/* this also replaces multiple almost identical stem widths */
/* with a single one (the value 100 is heuristic) */
af_sort_and_quantize_widths( &num_widths, axis->widths,
dummy->units_per_em / 100 );
axis->width_count = num_widths;
}
Exit:
for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ )
{
AF_LatinAxis axis = &metrics->axis[dim];
FT_Pos stdw;
stdw = ( axis->width_count > 0 ) ? axis->widths[0].org
: AF_LATIN_CONSTANT( metrics, 50 );
/* let's try 20% of the smallest width */
axis->edge_distance_threshold = stdw / 5;
axis->standard_width = stdw;
axis->extra_light = 0;
#ifdef FT_DEBUG_LEVEL_TRACE
{
FT_UInt i;
FT_TRACE5(( "%s widths:\n",
dim == AF_DIMENSION_VERT ? "horizontal"
: "vertical" ));
FT_TRACE5(( " %ld (standard)", axis->standard_width ));
for ( i = 1; i < axis->width_count; i++ )
FT_TRACE5(( " %ld", axis->widths[i].org ));
FT_TRACE5(( "\n" ));
}
#endif
}
}
FT_TRACE5(( "\n" ));
af_glyph_hints_done( hints );
}
static void
af_latin_sort_blue( FT_UInt count,
AF_LatinBlue* table )
{
FT_UInt i, j;
AF_LatinBlue swap;
/* we sort from bottom to top */
for ( i = 1; i < count; i++ )
{
for ( j = i; j > 0; j-- )
{
FT_Pos a, b;
if ( table[j - 1]->flags & ( AF_LATIN_BLUE_TOP |
AF_LATIN_BLUE_SUB_TOP ) )
a = table[j - 1]->ref.org;
else
a = table[j - 1]->shoot.org;
if ( table[j]->flags & ( AF_LATIN_BLUE_TOP |
AF_LATIN_BLUE_SUB_TOP ) )
b = table[j]->ref.org;
else
b = table[j]->shoot.org;
if ( b >= a )
break;
swap = table[j];
table[j] = table[j - 1];
table[j - 1] = swap;
}
}
}
/* Find all blue zones. Flat segments give the reference points, */
/* round segments the overshoot positions. */
static int
af_latin_metrics_init_blues( AF_LatinMetrics metrics,
FT_Face face )
{
FT_Pos flats [AF_BLUE_STRING_MAX_LEN];
FT_Pos rounds[AF_BLUE_STRING_MAX_LEN];
FT_UInt num_flats;
FT_UInt num_rounds;
AF_LatinBlue blue;
FT_Error error;
AF_LatinAxis axis = &metrics->axis[AF_DIMENSION_VERT];
FT_Outline outline;
AF_StyleClass sc = metrics->root.style_class;
AF_Blue_Stringset bss = sc->blue_stringset;
const AF_Blue_StringRec* bs = &af_blue_stringsets[bss];
FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );
/* If HarfBuzz is not available, we need a pointer to a single */
/* unsigned long value. */
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
void* shaper_buf;
#else
FT_ULong shaper_buf_;
void* shaper_buf = &shaper_buf_;
#endif
/* we walk over the blue character strings as specified in the */
/* style's entry in the `af_blue_stringset' array */
FT_TRACE5(( "latin blue zones computation\n" ));
FT_TRACE5(( "============================\n" ));
FT_TRACE5(( "\n" ));
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
shaper_buf = af_shaper_buf_create( face );
#endif
for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
{
const char* p = &af_blue_strings[bs->string];
FT_Pos* blue_ref;
FT_Pos* blue_shoot;
FT_Pos ascender;
FT_Pos descender;
#ifdef FT_DEBUG_LEVEL_TRACE
{
FT_Bool have_flag = 0;
FT_TRACE5(( "blue zone %d", axis->blue_count ));
if ( bs->properties )
{
FT_TRACE5(( " (" ));
if ( AF_LATIN_IS_TOP_BLUE( bs ) )
{
FT_TRACE5(( "top" ));
have_flag = 1;
}
else if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
{
FT_TRACE5(( "sub top" ));
have_flag = 1;
}
if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
{
if ( have_flag )
FT_TRACE5(( ", " ));
FT_TRACE5(( "neutral" ));
have_flag = 1;
}
if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
{
if ( have_flag )
FT_TRACE5(( ", " ));
FT_TRACE5(( "small top" ));
have_flag = 1;
}
if ( AF_LATIN_IS_LONG_BLUE( bs ) )
{
if ( have_flag )
FT_TRACE5(( ", " ));
FT_TRACE5(( "long" ));
}
FT_TRACE5(( ")" ));
}
FT_TRACE5(( ":\n" ));
}
#endif /* FT_DEBUG_LEVEL_TRACE */
num_flats = 0;
num_rounds = 0;
ascender = 0;
descender = 0;
while ( *p )
{
FT_ULong glyph_index;
FT_Long y_offset;
FT_Int best_point, best_contour_first, best_contour_last;
FT_Vector* points;
FT_Pos best_y_extremum; /* same as points.y */
FT_Bool best_round = 0;
unsigned int i, num_idx;
#ifdef FT_DEBUG_LEVEL_TRACE
const char* p_old;
FT_ULong ch;
#endif
while ( *p == ' ' )
p++;
#ifdef FT_DEBUG_LEVEL_TRACE
p_old = p;
GET_UTF8_CHAR( ch, p_old );
#endif
p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
if ( !num_idx )
{
FT_TRACE5(( " U+%04lX unavailable\n", ch ));
continue;
}
if ( AF_LATIN_IS_TOP_BLUE( bs ) )
best_y_extremum = FT_INT_MIN;
else
best_y_extremum = FT_INT_MAX;
/* iterate over all glyph elements of the character cluster */
/* and get the data of the `biggest' one */
for ( i = 0; i < num_idx; i++ )
{
FT_Pos best_y;
FT_Bool round = 0;
/* load the character in the face -- skip unknown or empty ones */
glyph_index = af_shaper_get_elem( &metrics->root,
shaper_buf,
i,
NULL,
&y_offset );
if ( glyph_index == 0 )
{
FT_TRACE5(( " U+%04lX unavailable\n", ch ));
continue;
}
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE );
outline = face->glyph->outline;
/* reject glyphs that don't produce any rendering */
if ( error || outline.n_points <= 2 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
if ( num_idx == 1 )
FT_TRACE5(( " U+%04lX contains no (usable) outlines\n", ch ));
else
FT_TRACE5(( " component %d of cluster starting with U+%04lX"
" contains no (usable) outlines\n", i, ch ));
#endif
continue;
}
/* now compute min or max point indices and coordinates */
points = outline.points;
best_point = -1;
best_contour_first = -1;
best_contour_last = -1;
best_y = 0; /* make compiler happy */
{
FT_Int nn;
FT_Int pp, first, last;
last = -1;
for ( nn = 0; nn < outline.n_contours; nn++ )
{
first = last + 1;
last = outline.contours[nn];
/* Avoid single-point contours since they are never */
/* rasterized. In some fonts, they correspond to mark */
/* attachment points that are way outside of the glyph's */
/* real outline. */
if ( last <= first )
continue;
if ( AF_LATIN_IS_TOP_BLUE( bs ) ||
AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
{
for ( pp = first; pp <= last; pp++ )
{
if ( best_point < 0 || points[pp].y > best_y )
{
best_point = pp;
best_y = points[pp].y;
ascender = FT_MAX( ascender, best_y + y_offset );
}
else
descender = FT_MIN( descender, points[pp].y + y_offset );
}
}
else
{
for ( pp = first; pp <= last; pp++ )
{
if ( best_point < 0 || points[pp].y < best_y )
{
best_point = pp;
best_y = points[pp].y;
descender = FT_MIN( descender, best_y + y_offset );
}
else
ascender = FT_MAX( ascender, points[pp].y + y_offset );
}
}
if ( best_point > best_contour_last )
{
best_contour_first = first;
best_contour_last = last;
}
}
}
/* now check whether the point belongs to a straight or round */
/* segment; we first need to find in which contour the extremum */
/* lies, then inspect its previous and next points */
if ( best_point >= 0 )
{
FT_Pos best_x = points[best_point].x;
FT_Int prev, next;
FT_Int best_segment_first, best_segment_last;
FT_Int best_on_point_first, best_on_point_last;
FT_Pos dist;
best_segment_first = best_point;
best_segment_last = best_point;
if ( FT_CURVE_TAG( outline.tags[best_point] ) == FT_CURVE_TAG_ON )
{
best_on_point_first = best_point;
best_on_point_last = best_point;
}
else
{
best_on_point_first = -1;
best_on_point_last = -1;
}
/* look for the previous and next points on the contour */
/* that are not on the same Y coordinate, then threshold */
/* the `closeness'... */
prev = best_point;
next = prev;
do
{
if ( prev > best_contour_first )
prev--;
else
prev = best_contour_last;
dist = FT_ABS( points[prev].y - best_y );
/* accept a small distance or a small angle (both values are */
/* heuristic; value 20 corresponds to approx. 2.9 degrees) */
if ( dist > 5 )
if ( FT_ABS( points[prev].x - best_x ) <= 20 * dist )
break;
best_segment_first = prev;
if ( FT_CURVE_TAG( outline.tags[prev] ) == FT_CURVE_TAG_ON )
{
best_on_point_first = prev;
if ( best_on_point_last < 0 )
best_on_point_last = prev;
}
} while ( prev != best_point );
do
{
if ( next < best_contour_last )
next++;
else
next = best_contour_first;
dist = FT_ABS( points[next].y - best_y );
if ( dist > 5 )
if ( FT_ABS( points[next].x - best_x ) <= 20 * dist )
break;
best_segment_last = next;
if ( FT_CURVE_TAG( outline.tags[next] ) == FT_CURVE_TAG_ON )
{
best_on_point_last = next;
if ( best_on_point_first < 0 )
best_on_point_first = next;
}
} while ( next != best_point );
if ( AF_LATIN_IS_LONG_BLUE( bs ) )
{
/* If this flag is set, we have an additional constraint to */
/* get the blue zone distance: Find a segment of the topmost */
/* (or bottommost) contour that is longer than a heuristic */
/* threshold. This ensures that small bumps in the outline */
/* are ignored (for example, the `vertical serifs' found in */
/* many Hebrew glyph designs). */
/* If this segment is long enough, we are done. Otherwise, */
/* search the segment next to the extremum that is long */
/* enough, has the same direction, and a not too large */
/* vertical distance from the extremum. Note that the */
/* algorithm doesn't check whether the found segment is */
/* actually the one (vertically) nearest to the extremum. */
/* heuristic threshold value */
FT_Pos length_threshold = metrics->units_per_em / 25;
dist = FT_ABS( points[best_segment_last].x -
points[best_segment_first].x );
if ( dist < length_threshold &&
best_segment_last - best_segment_first + 2 <=
best_contour_last - best_contour_first )
{
/* heuristic threshold value */
FT_Pos height_threshold = metrics->units_per_em / 4;
FT_Int first;
FT_Int last;
FT_Bool hit;
/* we intentionally declare these two variables */
/* outside of the loop since various compilers emit */
/* incorrect warning messages otherwise, talking about */
/* `possibly uninitialized variables' */
FT_Int p_first = 0; /* make compiler happy */
FT_Int p_last = 0;
FT_Bool left2right;
/* compute direction */
prev = best_point;
do
{
if ( prev > best_contour_first )
prev--;
else
prev = best_contour_last;
if ( points[prev].x != best_x )
break;
} while ( prev != best_point );
/* skip glyph for the degenerate case */
if ( prev == best_point )
continue;
left2right = FT_BOOL( points[prev].x < points[best_point].x );
first = best_segment_last;
last = first;
hit = 0;
do
{
FT_Bool l2r;
FT_Pos d;
if ( !hit )
{
/* no hit; adjust first point */
first = last;
/* also adjust first and last on point */
if ( FT_CURVE_TAG( outline.tags[first] ) ==
FT_CURVE_TAG_ON )
{
p_first = first;
p_last = first;
}
else
{
p_first = -1;
p_last = -1;
}
hit = 1;
}
if ( last < best_contour_last )
last++;
else
last = best_contour_first;
if ( FT_ABS( best_y - points[first].y ) > height_threshold )
{
/* vertical distance too large */
hit = 0;
continue;
}
/* same test as above */
dist = FT_ABS( points[last].y - points[first].y );
if ( dist > 5 )
if ( FT_ABS( points[last].x - points[first].x ) <=
20 * dist )
{
hit = 0;
continue;
}
if ( FT_CURVE_TAG( outline.tags[last] ) == FT_CURVE_TAG_ON )
{
p_last = last;
if ( p_first < 0 )
p_first = last;
}
l2r = FT_BOOL( points[first].x < points[last].x );
d = FT_ABS( points[last].x - points[first].x );
if ( l2r == left2right &&
d >= length_threshold )
{
/* all constraints are met; update segment after */
/* finding its end */
do
{
if ( last < best_contour_last )
last++;
else
last = best_contour_first;
d = FT_ABS( points[last].y - points[first].y );
if ( d > 5 )
if ( FT_ABS( points[next].x - points[first].x ) <=
20 * dist )
{
if ( last > best_contour_first )
last--;
else
last = best_contour_last;
break;
}
p_last = last;
if ( FT_CURVE_TAG( outline.tags[last] ) ==
FT_CURVE_TAG_ON )
{
p_last = last;
if ( p_first < 0 )
p_first = last;
}
} while ( last != best_segment_first );
best_y = points[first].y;
best_segment_first = first;
best_segment_last = last;
best_on_point_first = p_first;
best_on_point_last = p_last;
break;
}
} while ( last != best_segment_first );
}
}
/* for computing blue zones, we add the y offset as returned */
/* by the currently used OpenType feature -- for example, */
/* superscript glyphs might be identical to subscript glyphs */
/* with a vertical shift */
best_y += y_offset;
#ifdef FT_DEBUG_LEVEL_TRACE
if ( num_idx == 1 )
FT_TRACE5(( " U+%04lX: best_y = %5ld", ch, best_y ));
else
FT_TRACE5(( " component %d of cluster starting with U+%04lX:"
" best_y = %5ld", i, ch, best_y ));
#endif
/* now set the `round' flag depending on the segment's kind: */
/* */
/* - if the horizontal distance between the first and last */
/* `on' point is larger than a heuristic threshold */
/* we have a flat segment */
/* - if either the first or the last point of the segment is */
/* an `off' point, the segment is round, otherwise it is */
/* flat */
if ( best_on_point_first >= 0 &&
best_on_point_last >= 0 &&
( FT_ABS( points[best_on_point_last].x -
points[best_on_point_first].x ) ) >
flat_threshold )
round = 0;
else
round = FT_BOOL(
FT_CURVE_TAG( outline.tags[best_segment_first] ) !=
FT_CURVE_TAG_ON ||
FT_CURVE_TAG( outline.tags[best_segment_last] ) !=
FT_CURVE_TAG_ON );
if ( round && AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
{
/* only use flat segments for a neutral blue zone */
FT_TRACE5(( " (round, skipped)\n" ));
continue;
}
FT_TRACE5(( " (%s)\n", round ? "round" : "flat" ));
}
if ( AF_LATIN_IS_TOP_BLUE( bs ) )
{
if ( best_y > best_y_extremum )
{
best_y_extremum = best_y;
best_round = round;
}
}
else
{
if ( best_y < best_y_extremum )
{
best_y_extremum = best_y;
best_round = round;
}
}
} /* end for loop */
if ( !( best_y_extremum == FT_INT_MIN ||
best_y_extremum == FT_INT_MAX ) )
{
if ( best_round )
rounds[num_rounds++] = best_y_extremum;
else
flats[num_flats++] = best_y_extremum;
}
} /* end while loop */
if ( num_flats == 0 && num_rounds == 0 )
{
/*
* we couldn't find a single glyph to compute this blue zone,
* we will simply ignore it then
*/
FT_TRACE5(( " empty\n" ));
continue;
}
/* we have computed the contents of the `rounds' and `flats' tables, */
/* now determine the reference and overshoot position of the blue -- */
/* we simply take the median value after a simple sort */
af_sort_pos( num_rounds, rounds );
af_sort_pos( num_flats, flats );
blue = &axis->blues[axis->blue_count];
blue_ref = &blue->ref.org;
blue_shoot = &blue->shoot.org;
axis->blue_count++;
if ( num_flats == 0 )
{
*blue_ref =
*blue_shoot = rounds[num_rounds / 2];
}
else if ( num_rounds == 0 )
{
*blue_ref =
*blue_shoot = flats[num_flats / 2];
}
else
{
*blue_ref = flats [num_flats / 2];
*blue_shoot = rounds[num_rounds / 2];
}
/* there are sometimes problems: if the overshoot position of top */
/* zones is under its reference position, or the opposite for bottom */
/* zones. We must thus check everything there and correct the errors */
if ( *blue_shoot != *blue_ref )
{
FT_Pos ref = *blue_ref;
FT_Pos shoot = *blue_shoot;
FT_Bool over_ref = FT_BOOL( shoot > ref );
if ( ( AF_LATIN_IS_TOP_BLUE( bs ) ||
AF_LATIN_IS_SUB_TOP_BLUE( bs) ) ^ over_ref )
{
*blue_ref =
*blue_shoot = ( shoot + ref ) / 2;
FT_TRACE5(( " [overshoot smaller than reference,"
" taking mean value]\n" ));
}
}
blue->ascender = ascender;
blue->descender = descender;
blue->flags = 0;
if ( AF_LATIN_IS_TOP_BLUE( bs ) )
blue->flags |= AF_LATIN_BLUE_TOP;
if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) )
blue->flags |= AF_LATIN_BLUE_SUB_TOP;
if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) )
blue->flags |= AF_LATIN_BLUE_NEUTRAL;
/*
* The following flag 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 ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) )
blue->flags |= AF_LATIN_BLUE_ADJUSTMENT;
FT_TRACE5(( " -> reference = %ld\n", *blue_ref ));
FT_TRACE5(( " overshoot = %ld\n", *blue_shoot ));
} /* end for loop */
af_shaper_buf_destroy( face, shaper_buf );
if ( axis->blue_count )
{
/* we finally check whether blue zones are ordered; */
/* `ref' and `shoot' values of two blue zones must not overlap */
FT_UInt i;
AF_LatinBlue blue_sorted[AF_BLUE_STRINGSET_MAX_LEN];
for ( i = 0; i < axis->blue_count; i++ )
blue_sorted[i] = &axis->blues[i];
/* sort bottoms of blue zones... */
af_latin_sort_blue( axis->blue_count, blue_sorted );
/* ...and adjust top values if necessary */
for ( i = 0; i < axis->blue_count - 1; i++ )
{
FT_Pos* a;
FT_Pos* b;
#ifdef FT_DEBUG_LEVEL_TRACE
FT_Bool a_is_top = 0;
#endif
if ( blue_sorted[i]->flags & ( AF_LATIN_BLUE_TOP |
AF_LATIN_BLUE_SUB_TOP ) )
{
a = &blue_sorted[i]->shoot.org;
#ifdef FT_DEBUG_LEVEL_TRACE
a_is_top = 1;
#endif
}
else
a = &blue_sorted[i]->ref.org;
if ( blue_sorted[i + 1]->flags & ( AF_LATIN_BLUE_TOP |
AF_LATIN_BLUE_SUB_TOP ) )
b = &blue_sorted[i + 1]->shoot.org;
else
b = &blue_sorted[i + 1]->ref.org;
if ( *a > *b )
{
*a = *b;
FT_TRACE5(( "blue zone overlap:"
" adjusting %s %td to %ld\n",
a_is_top ? "overshoot" : "reference",
blue_sorted[i] - axis->blues,
*a ));
}
}
FT_TRACE5(( "\n" ));
return 0;
}
else
{
/* disable hinting for the current style if there are no blue zones */
AF_FaceGlobals globals = metrics->root.globals;
FT_UShort* gstyles = globals->glyph_styles;
FT_UInt i;
FT_TRACE5(( "no blue zones found:"
" hinting disabled for this style\n" ));
for ( i = 0; i < globals->glyph_count; i++ )
{
if ( ( gstyles[i] & AF_STYLE_MASK ) == sc->style )
gstyles[i] = AF_STYLE_NONE_DFLT;
}
FT_TRACE5(( "\n" ));
return 1;
}
}
/* Check whether all ASCII digits have the same advance width. */
FT_LOCAL_DEF( void )
af_latin_metrics_check_digits( AF_LatinMetrics metrics,
FT_Face face )
{
FT_Bool started = 0, same_width = 1;
FT_Long advance = 0, old_advance = 0;
/* If HarfBuzz is not available, we need a pointer to a single */
/* unsigned long value. */
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
void* shaper_buf;
#else
FT_ULong shaper_buf_;
void* shaper_buf = &shaper_buf_;
#endif
/* in all supported charmaps, digits have character codes 0x30-0x39 */
const char digits[] = "0 1 2 3 4 5 6 7 8 9";
const char* p;
p = digits;
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
shaper_buf = af_shaper_buf_create( face );
#endif
while ( *p )
{
FT_ULong glyph_index;
unsigned int num_idx;
/* reject input that maps to more than a single glyph */
p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx );
if ( num_idx > 1 )
continue;
glyph_index = af_shaper_get_elem( &metrics->root,
shaper_buf,
0,
&advance,
NULL );
if ( !glyph_index )
continue;
if ( started )
{
if ( advance != old_advance )
{
same_width = 0;
break;
}
}
else
{
old_advance = advance;
started = 1;
}
}
af_shaper_buf_destroy( face, shaper_buf );
metrics->root.digits_have_same_width = same_width;
}
/* Initialize global metrics. */
FT_LOCAL_DEF( FT_Error )
af_latin_metrics_init( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
FT_Face face )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
FT_Error error = FT_Err_Ok;
FT_CharMap oldmap = face->charmap;
metrics->units_per_em = face->units_per_EM;
if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) )
{
af_latin_metrics_init_widths( metrics, face );
if ( af_latin_metrics_init_blues( metrics, face ) )
{
/* use internal error code to indicate missing blue zones */
error = -1;
goto Exit;
}
af_latin_metrics_check_digits( metrics, face );
}
af_reverse_character_map_new( &metrics->root.reverse_charmap, metrics->root.globals );
Exit:
face->charmap = oldmap;
return error;
}
/* Adjust scaling value, then scale and shift widths */
/* and blue zones (if applicable) for given dimension. */
static void
af_latin_metrics_scale_dim( AF_LatinMetrics metrics,
AF_Scaler scaler,
AF_Dimension dim )
{
FT_Fixed scale;
FT_Pos delta;
AF_LatinAxis axis;
FT_UInt nn;
if ( dim == AF_DIMENSION_HORZ )
{
scale = scaler->x_scale;
delta = scaler->x_delta;
}
else
{
scale = scaler->y_scale;
delta = scaler->y_delta;
}
axis = &metrics->axis[dim];
if ( axis->org_scale == scale && axis->org_delta == delta )
return;
axis->org_scale = scale;
axis->org_delta = delta;
/*
* 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;
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_Pos threshold;
FT_Pos fitted;
FT_UInt limit;
FT_UInt ppem;
scaled = FT_MulFix( blue->shoot.org, scale );
ppem = metrics->root.scaler.face->size->metrics.x_ppem;
limit = metrics->root.globals->increase_x_height;
threshold = 40;
/* if the `increase-x-height' property is active, */
/* we round up much more often */
if ( limit &&
ppem <= limit &&
ppem >= AF_PROP_INCREASE_X_HEIGHT_MIN )
threshold = 52;
fitted = ( scaled + threshold ) & ~63;
if ( scaled != fitted )
{
#if 0
if ( dim == AF_DIMENSION_HORZ )
{
if ( fitted < scaled )
scale -= scale / 50; /* scale *= 0.98 */
}
else
#endif
if ( dim == AF_DIMENSION_VERT )
{
FT_Pos max_height;
FT_Pos dist;
FT_Fixed new_scale;
new_scale = FT_MulDiv( scale, fitted, scaled );
/* the scaling should not change the result by more than two pixels */
max_height = metrics->units_per_em;
for ( nn = 0; nn < Axis->blue_count; nn++ )
{
max_height = FT_MAX( max_height, Axis->blues[nn].ascender );
max_height = FT_MAX( max_height, -Axis->blues[nn].descender );
}
dist = FT_ABS( FT_MulFix( max_height, new_scale - scale ) );
dist &= ~127;
if ( dist == 0 )
{
FT_TRACE5(( "af_latin_metrics_scale_dim:"
" x height alignment (style `%s'):\n",
af_style_names[metrics->root.style_class->style] ));
FT_TRACE5(( " "
" vertical scaling changed"
" from %.5f to %.5f (by %ld%%)\n",
(double)scale / 65536,
(double)new_scale / 65536,
( fitted - scaled ) * 100 / scaled ));
FT_TRACE5(( "\n" ));
scale = new_scale;
}
#ifdef FT_DEBUG_LEVEL_TRACE
else
{
FT_TRACE5(( "af_latin_metrics_scale_dim:"
" x height alignment (style `%s'):\n",
af_style_names[metrics->root.style_class->style] ));
FT_TRACE5(( " "
" excessive vertical scaling abandoned\n" ));
FT_TRACE5(( "\n" ));
}
#endif
}
}
}
}
axis->scale = scale;
axis->delta = delta;
if ( dim == AF_DIMENSION_HORZ )
{
metrics->root.scaler.x_scale = scale;
metrics->root.scaler.x_delta = delta;
}
else
{
metrics->root.scaler.y_scale = scale;
metrics->root.scaler.y_delta = delta;
}
FT_TRACE5(( "%s widths (style `%s')\n",
dim == AF_DIMENSION_HORZ ? "horizontal" : "vertical",
af_style_names[metrics->root.style_class->style] ));
/* scale the widths */
for ( nn = 0; nn < axis->width_count; nn++ )
{
AF_Width width = axis->widths + nn;
width->cur = FT_MulFix( width->org, scale );
width->fit = width->cur;
FT_TRACE5(( " %ld scaled to %.2f\n",
width->org,
(double)width->cur / 64 ));
}
FT_TRACE5(( "\n" ));
/* an extra-light axis corresponds to a standard width that is */
/* smaller than 5/8 pixels */
axis->extra_light =
FT_BOOL( FT_MulFix( axis->standard_width, scale ) < 32 + 8 );
#ifdef FT_DEBUG_LEVEL_TRACE
if ( axis->extra_light )
{
FT_TRACE5(( "`%s' style is extra light (at current resolution)\n",
af_style_names[metrics->root.style_class->style] ));
FT_TRACE5(( "\n" ));
}
#endif
if ( dim == AF_DIMENSION_VERT )
{
#ifdef FT_DEBUG_LEVEL_TRACE
if ( axis->blue_count )
FT_TRACE5(( "blue zones (style `%s')\n",
af_style_names[metrics->root.style_class->style] ));
#endif
/* scale the blue zones */
for ( nn = 0; nn < axis->blue_count; nn++ )
{
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->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 if it is less than 3/4 pixels tall */
dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale );
if ( dist <= 48 && dist >= -48 )
{
#if 0
FT_Pos delta1;
#endif
FT_Pos delta2;
/* use discrete values for blue zone widths */
#if 0
/* generic, original code */
delta1 = blue->shoot.org - blue->ref.org;
delta2 = delta1;
if ( delta1 < 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 ( delta1 < 0 )
delta2 = -delta2;
blue->ref.fit = FT_PIX_ROUND( blue->ref.cur );
blue->shoot.fit = blue->ref.fit + delta2;
#else
/* simplified version due to abs(dist) <= 48 */
delta2 = dist;
if ( dist < 0 )
delta2 = -delta2;
if ( delta2 < 32 )
delta2 = 0;
else if ( delta2 < 48 )
delta2 = 32;
else
delta2 = 64;
if ( dist < 0 )
delta2 = -delta2;
blue->ref.fit = FT_PIX_ROUND( blue->ref.cur );
blue->shoot.fit = blue->ref.fit - delta2;
#endif
blue->flags |= AF_LATIN_BLUE_ACTIVE;
}
}
/* use sub-top blue zone only if it doesn't overlap with */
/* another (non-sup-top) blue zone; otherwise, the */
/* effect would be similar to a neutral blue zone, which */
/* is not desired here */
for ( nn = 0; nn < axis->blue_count; nn++ )
{
AF_LatinBlue blue = &axis->blues[nn];
FT_UInt i;
if ( !( blue->flags & AF_LATIN_BLUE_SUB_TOP ) )
continue;
if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
continue;
for ( i = 0; i < axis->blue_count; i++ )
{
AF_LatinBlue b = &axis->blues[i];
if ( b->flags & AF_LATIN_BLUE_SUB_TOP )
continue;
if ( !( b->flags & AF_LATIN_BLUE_ACTIVE ) )
continue;
if ( b->ref.fit <= blue->shoot.fit &&
b->shoot.fit >= blue->ref.fit )
{
blue->flags &= ~AF_LATIN_BLUE_ACTIVE;
break;
}
}
}
#ifdef FT_DEBUG_LEVEL_TRACE
for ( nn = 0; nn < axis->blue_count; nn++ )
{
AF_LatinBlue blue = &axis->blues[nn];
FT_TRACE5(( " reference %d: %ld scaled to %.2f%s\n",
nn,
blue->ref.org,
(double)blue->ref.fit / 64,
( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
: " (inactive)" ));
FT_TRACE5(( " overshoot %d: %ld scaled to %.2f%s\n",
nn,
blue->shoot.org,
(double)blue->shoot.fit / 64,
( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? ""
: " (inactive)" ));
}
#endif
}
}
FT_CALLBACK_DEF( void )
af_latin_metrics_done( AF_StyleMetrics metrics_ ) {
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
af_reverse_character_map_done( metrics->root.reverse_charmap, metrics->root.globals->face->memory );
}
/* Scale global values in both directions. */
FT_LOCAL_DEF( void )
af_latin_metrics_scale( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
AF_Scaler scaler )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
metrics->root.scaler.render_mode = scaler->render_mode;
metrics->root.scaler.face = scaler->face;
metrics->root.scaler.flags = scaler->flags;
af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ );
af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT );
}
/* Extract standard_width from writing system/script specific */
/* metrics class. */
FT_CALLBACK_DEF( void )
af_latin_get_standard_widths( AF_StyleMetrics metrics_, /* AF_LatinMetrics */
FT_Pos* stdHW,
FT_Pos* stdVW )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
if ( stdHW )
*stdHW = metrics->axis[AF_DIMENSION_VERT].standard_width;
if ( stdVW )
*stdVW = metrics->axis[AF_DIMENSION_HORZ].standard_width;
}
/*************************************************************************/
/*************************************************************************/
/***** *****/
/***** L A T I N G L Y P H A N A L Y S I S *****/
/***** *****/
/*************************************************************************/
/*************************************************************************/
/* Walk over all contours and compute its segments. */
FT_LOCAL_DEF( FT_Error )
af_latin_hints_compute_segments( AF_GlyphHints hints,
AF_Dimension dim )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics;
AF_AxisHints axis = &hints->axis[dim];
FT_Memory memory = hints->memory;
FT_Error error = FT_Err_Ok;
AF_Segment segment = NULL;
AF_SegmentRec seg0;
AF_Point* contour = hints->contours;
AF_Point* contour_limit = contour + hints->num_contours;
AF_Direction major_dir, segment_dir;
FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em );
FT_ZERO( &seg0 );
seg0.score = 32000;
seg0.flags = AF_EDGE_NORMAL;
major_dir = (AF_Direction)FT_ABS( axis->major_dir );
segment_dir = major_dir;
axis->num_segments = 0;
/* set up (u,v) in each point */
if ( dim == AF_DIMENSION_HORZ )
{
AF_Point point = hints->points;
AF_Point limit = point + hints->num_points;
for ( ; point < limit; point++ )
{
point->u = point->fx;
point->v = point->fy;
}
}
else
{
AF_Point point = hints->points;
AF_Point limit = point + hints->num_points;
for ( ; point < limit; point++ )
{
point->u = point->fy;
point->v = point->fx;
}
}
/* do each contour separately */
for ( ; contour < contour_limit; contour++ )
{
AF_Point point = contour[0];
AF_Point last = point->prev;
int on_edge = 0;
/* we call values measured along a segment (point->v) */
/* `coordinates', and values orthogonal to it (point->u) */
/* `positions' */
FT_Pos min_pos = 32000;
FT_Pos max_pos = -32000;
FT_Pos min_coord = 32000;
FT_Pos max_coord = -32000;
FT_UShort min_flags = AF_FLAG_NONE;
FT_UShort max_flags = AF_FLAG_NONE;
FT_Pos min_on_coord = 32000;
FT_Pos max_on_coord = -32000;
FT_Bool passed;
AF_Segment prev_segment = NULL;
FT_Pos prev_min_pos = min_pos;
FT_Pos prev_max_pos = max_pos;
FT_Pos prev_min_coord = min_coord;
FT_Pos prev_max_coord = max_coord;
FT_UShort prev_min_flags = min_flags;
FT_UShort prev_max_flags = max_flags;
FT_Pos prev_min_on_coord = min_on_coord;
FT_Pos prev_max_on_coord = max_on_coord;
if ( FT_ABS( last->out_dir ) == major_dir &&
FT_ABS( point->out_dir ) == major_dir )
{
/* we are already on an edge, try to locate its start */
last = point;
for (;;)
{
point = point->prev;
if ( FT_ABS( point->out_dir ) != major_dir )
{
point = point->next;
break;
}
if ( point == last )
break;
}
}
last = point;
passed = 0;
for (;;)
{
FT_Pos u, v;
if ( on_edge )
{
/* get minimum and maximum position */
u = point->u;
if ( u < min_pos )
min_pos = u;
if ( u > max_pos )
max_pos = u;
/* get minimum and maximum coordinate together with flags */
v = point->v;
if ( v < min_coord )
{
min_coord = v;
min_flags = point->flags;
}
if ( v > max_coord )
{
max_coord = v;
max_flags = point->flags;
}
/* get minimum and maximum coordinate of `on' points */
if ( !( point->flags & AF_FLAG_CONTROL ) )
{
v = point->v;
if ( v < min_on_coord )
min_on_coord = v;
if ( v > max_on_coord )
max_on_coord = v;
}
if ( point->out_dir != segment_dir || point == last )
{
/* check whether the new segment's start point is identical to */
/* the previous segment's end point; for example, this might */
/* happen for spikes */
if ( !prev_segment || segment->first != prev_segment->last )
{
/* points are different: we are just leaving an edge, thus */
/* record a new segment */
segment->last = point;
segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 );
segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );
/* a segment is round if either its first or last point */
/* is a control point, and the length of the on points */
/* inbetween doesn't exceed a heuristic limit */
if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
( max_on_coord - min_on_coord ) < flat_threshold )
segment->flags |= AF_EDGE_ROUND;
segment->min_coord = (FT_Short)min_coord;
segment->max_coord = (FT_Short)max_coord;
segment->height = segment->max_coord - segment->min_coord;
prev_segment = segment;
prev_min_pos = min_pos;
prev_max_pos = max_pos;
prev_min_coord = min_coord;
prev_max_coord = max_coord;
prev_min_flags = min_flags;
prev_max_flags = max_flags;
prev_min_on_coord = min_on_coord;
prev_max_on_coord = max_on_coord;
}
else
{
/* points are the same: we don't create a new segment but */
/* merge the current segment with the previous one */
if ( prev_segment->last->in_dir == point->in_dir )
{
/* we have identical directions (this can happen for */
/* degenerate outlines that move zig-zag along the main */
/* axis without changing the coordinate value of the other */
/* axis, and where the segments have just been merged): */
/* unify segments */
/* update constraints */
if ( prev_min_pos < min_pos )
min_pos = prev_min_pos;
if ( prev_max_pos > max_pos )
max_pos = prev_max_pos;
if ( prev_min_coord < min_coord )
{
min_coord = prev_min_coord;
min_flags = prev_min_flags;
}
if ( prev_max_coord > max_coord )
{
max_coord = prev_max_coord;
max_flags = prev_max_flags;
}
if ( prev_min_on_coord < min_on_coord )
min_on_coord = prev_min_on_coord;
if ( prev_max_on_coord > max_on_coord )
max_on_coord = prev_max_on_coord;
prev_segment->last = point;
prev_segment->pos = (FT_Short)( ( min_pos +
max_pos ) >> 1 );
prev_segment->delta = (FT_Short)( ( max_pos -
min_pos ) >> 1 );
if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
( max_on_coord - min_on_coord ) < flat_threshold )
prev_segment->flags |= AF_EDGE_ROUND;
else
prev_segment->flags &= ~AF_EDGE_ROUND;
prev_segment->min_coord = (FT_Short)min_coord;
prev_segment->max_coord = (FT_Short)max_coord;
prev_segment->height = prev_segment->max_coord -
prev_segment->min_coord;
}
else
{
/* we have different directions; use the properties of the */
/* longer segment and discard the other one */
if ( FT_ABS( prev_max_coord - prev_min_coord ) >
FT_ABS( max_coord - min_coord ) )
{
/* discard current segment */
if ( min_pos < prev_min_pos )
prev_min_pos = min_pos;
if ( max_pos > prev_max_pos )
prev_max_pos = max_pos;
prev_segment->last = point;
prev_segment->pos = (FT_Short)( ( prev_min_pos +
prev_max_pos ) >> 1 );
prev_segment->delta = (FT_Short)( ( prev_max_pos -
prev_min_pos ) >> 1 );
}
else
{
/* discard previous segment */
if ( prev_min_pos < min_pos )
min_pos = prev_min_pos;
if ( prev_max_pos > max_pos )
max_pos = prev_max_pos;
segment->last = point;
segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 );
segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 );
if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL &&
( max_on_coord - min_on_coord ) < flat_threshold )
segment->flags |= AF_EDGE_ROUND;
segment->min_coord = (FT_Short)min_coord;
segment->max_coord = (FT_Short)max_coord;
segment->height = segment->max_coord -
segment->min_coord;
*prev_segment = *segment;
prev_min_pos = min_pos;
prev_max_pos = max_pos;
prev_min_coord = min_coord;
prev_max_coord = max_coord;
prev_min_flags = min_flags;
prev_max_flags = max_flags;
prev_min_on_coord = min_on_coord;
prev_max_on_coord = max_on_coord;
}
}
axis->num_segments--;
}
on_edge = 0;
segment = NULL;
/* fall through */
}
}
/* now exit if we are at the start/end point */
if ( point == last )
{
if ( passed )
break;
passed = 1;
}
/* if we are not on an edge, check whether the major direction */
/* coincides with the current point's `out' direction, or */
/* whether we have a single-point contour */
if ( !on_edge &&
( FT_ABS( point->out_dir ) == major_dir ||
point == point->prev ) )
{
/*
* For efficiency, we restrict the number of segments to 1000,
* which is a heuristic value: it is very unlikely that a glyph
* with so many segments can be hinted in a sensible way.
* Reasons:
*
* - The glyph has really 1000 segments; this implies that it has
* at least 2000 outline points. Assuming 'normal' fonts that
* have superfluous points optimized away, viewing such a glyph
* only makes sense at large magnifications where hinting
* isn't applied anyway.
*
* - We have a broken glyph. Hinting doesn't make sense in this
* case either.
*/
if ( axis->num_segments > 1000 )
{
FT_TRACE0(( "af_latin_hints_compute_segments:"
" more than 1000 segments in this glyph;\n" ));
FT_TRACE0(( " "
" hinting is suppressed\n" ));
axis->num_segments = 0;
return FT_Err_Ok;
}
/* this is the start of a new segment! */
segment_dir = (AF_Direction)point->out_dir;
error = af_axis_hints_new_segment( axis, memory, &segment );
if ( error )
goto Exit;
/* clear all segment fields */
segment[0] = seg0;
segment->dir = (FT_Char)segment_dir;
segment->first = point;
segment->last = point;
/* `af_axis_hints_new_segment' reallocates memory, */
/* thus we have to refresh the `prev_segment' pointer */
if ( prev_segment )
prev_segment = segment - 1;
min_pos = max_pos = point->u;
min_coord = max_coord = point->v;
min_flags = max_flags = point->flags;
if ( point->flags & AF_FLAG_CONTROL )
{
min_on_coord = 32000;
max_on_coord = -32000;
}
else
min_on_coord = max_on_coord = point->v;
on_edge = 1;
if ( point == point->prev )
{
/* we have a one-point segment: this is a one-point */
/* contour with `in' and `out' direction set to */
/* AF_DIR_NONE */
segment->pos = (FT_Short)min_pos;
if (point->flags & AF_FLAG_CONTROL)
segment->flags |= AF_EDGE_ROUND;
segment->min_coord = (FT_Short)point->v;
segment->max_coord = (FT_Short)point->v;
segment->height = 0;
on_edge = 0;
segment = NULL;
}
}
point = point->next;
}
} /* contours */
/* now slightly increase the height of segments if this makes */
/* sense -- this is used to better detect and ignore serifs */
{
AF_Segment segments = axis->segments;
AF_Segment segments_end = FT_OFFSET( segments, axis->num_segments );
for ( segment = segments; segment < segments_end; segment++ )
{
AF_Point first = segment->first;
AF_Point last = segment->last;
FT_Pos first_v = first->v;
FT_Pos last_v = last->v;
if ( first_v < last_v )
{
AF_Point p;
p = first->prev;
if ( p->v < first_v )
segment->height = (FT_Short)( segment->height +
( ( first_v - p->v ) >> 1 ) );
p = last->next;
if ( p->v > last_v )
segment->height = (FT_Short)( segment->height +
( ( p->v - last_v ) >> 1 ) );
}
else
{
AF_Point p;
p = first->prev;
if ( p->v > first_v )
segment->height = (FT_Short)( segment->height +
( ( p->v - first_v ) >> 1 ) );
p = last->next;
if ( p->v < last_v )
segment->height = (FT_Short)( segment->height +
( ( last_v - p->v ) >> 1 ) );
}
}
}
Exit:
return error;
}
/* Link segments to form stems and serifs. If `width_count' and */
/* `widths' are non-zero, use them to fine-tune the scoring function. */
FT_LOCAL_DEF( void )
af_latin_hints_link_segments( AF_GlyphHints hints,
FT_UInt width_count,
AF_WidthRec* widths,
AF_Dimension dim )
{
AF_AxisHints axis = &hints->axis[dim];
AF_Segment segments = axis->segments;
AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments );
FT_Pos len_threshold, len_score, dist_score, max_width;
AF_Segment seg1, seg2;
if ( width_count )
max_width = widths[width_count - 1].org;
else
max_width = 0;
/* a heuristic value to set up a minimum value for overlapping */
len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 );
if ( len_threshold == 0 )
len_threshold = 1;
/* a heuristic value to weight lengths */
len_score = AF_LATIN_CONSTANT( hints->metrics, 6000 );
/* a heuristic value to weight distances (no call to */
/* AF_LATIN_CONSTANT needed, since we work on multiples */
/* of the stem width) */
dist_score = 3000;
/* now compare each segment to the others */
for ( seg1 = segments; seg1 < segment_limit; seg1++ )
{
if ( seg1->dir != axis->major_dir )
continue;
/* search for stems having opposite directions, */
/* with seg1 to the `left' of seg2 */
for ( seg2 = segments; seg2 < segment_limit; seg2++ )
{
FT_Pos pos1 = seg1->pos;
FT_Pos pos2 = seg2->pos;
if ( seg1->dir + seg2->dir == 0 && pos2 > pos1 )
{
/* compute distance between the two segments */
FT_Pos min = seg1->min_coord;
FT_Pos max = seg1->max_coord;
FT_Pos len;
if ( min < seg2->min_coord )
min = seg2->min_coord;
if ( max > seg2->max_coord )
max = seg2->max_coord;
/* compute maximum coordinate difference of the two segments */
/* (that is, how much they overlap) */
len = max - min;
if ( len >= len_threshold )
{
/*
* The score is the sum of two demerits indicating the
* `badness' of a fit, measured along the segments' main axis
* and orthogonal to it, respectively.
*
* - The less overlapping along the main axis, the worse it
* is, causing a larger demerit.
*
* - The nearer the orthogonal distance to a stem width, the
* better it is, causing a smaller demerit. For simplicity,
* however, we only increase the demerit for values that
* exceed the largest stem width.
*/
FT_Pos dist = pos2 - pos1;
FT_Pos dist_demerit, score;
if ( max_width )
{
/* distance demerits are based on multiples of `max_width'; */
/* we scale by 1024 for getting more precision */
FT_Pos delta = ( dist << 10 ) / max_width - ( 1 << 10 );
if ( delta > 10000 )
dist_demerit = 32000;
else if ( delta > 0 )
dist_demerit = delta * delta / dist_score;
else
dist_demerit = 0;
}
else
dist_demerit = dist; /* default if no widths available */
score = dist_demerit + len_score / len;
/* and we search for the smallest score */
if ( score < seg1->score )
{
seg1->score = score;
seg1->link = seg2;
}
if ( score < seg2->score )
{
seg2->score = score;
seg2->link = seg1;
}
}
}
}
}
/* now compute the `serif' segments, cf. explanations in `afhints.h' */
for ( seg1 = segments; seg1 < segment_limit; seg1++ )
{
seg2 = seg1->link;
if ( seg2 )
{
if ( seg2->link != seg1 )
{
seg1->link = NULL;
seg1->serif = seg2->link;
}
}
}
}
/* Link segments to edges, using feature analysis for selection. */
FT_LOCAL_DEF( FT_Error )
af_latin_hints_compute_edges( AF_GlyphHints hints,
AF_Dimension dim )
{
AF_AxisHints axis = &hints->axis[dim];
FT_Error error = FT_Err_Ok;
FT_Memory memory = hints->memory;
AF_LatinAxis laxis = &((AF_LatinMetrics)hints->metrics)->axis[dim];
AF_StyleClass style_class = hints->metrics->style_class;
AF_ScriptClass script_class = af_script_classes[style_class->script];
FT_Bool top_to_bottom_hinting = 0;
AF_Segment segments = axis->segments;
AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments );
AF_Segment seg;
#if 0
AF_Direction up_dir;
#endif
FT_Fixed scale;
FT_Pos edge_distance_threshold;
FT_Pos segment_length_threshold;
FT_Pos segment_width_threshold;
axis->num_edges = 0;
scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale
: hints->y_scale;
#if 0
up_dir = ( dim == AF_DIMENSION_HORZ ) ? AF_DIR_UP
: AF_DIR_RIGHT;
#endif
if ( dim == AF_DIMENSION_VERT )
top_to_bottom_hinting = script_class->top_to_bottom_hinting;
/*
* We ignore all segments that are less than 1 pixel in length
* to avoid many problems with serif fonts. We compute the
* corresponding threshold in font units.
*/
if ( dim == AF_DIMENSION_HORZ )
segment_length_threshold = FT_DivFix( 64, hints->y_scale );
else
segment_length_threshold = 0;
/*
* Similarly, we ignore segments that have a width delta
* larger than 0.5px (i.e., a width larger than 1px).
*/
segment_width_threshold = FT_DivFix( 32, scale );
/**********************************************************************
*
* We begin by generating a sorted table of edges for the current
* direction. To do so, we simply scan each segment and try to find
* an edge in our table that corresponds to its position.
*
* If no edge is found, we create and insert a new edge in the
* sorted table. Otherwise, we simply add the segment to the edge's
* list which gets processed in the second step to compute the
* edge's properties.
*
* Note that the table of edges is sorted along the segment/edge
* position.
*
*/
/* assure that edge distance threshold is at most 0.25px */
edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold,
scale );
if ( edge_distance_threshold > 64 / 4 )
edge_distance_threshold = 64 / 4;
edge_distance_threshold = FT_DivFix( edge_distance_threshold,
scale );
for ( seg = segments; seg < segment_limit; seg++ )
{
AF_Edge found = NULL;
FT_UInt ee;
/* ignore too short segments, too wide ones, and, in this loop, */
/* one-point segments without a direction */
if ( seg->height < segment_length_threshold ||
seg->delta > segment_width_threshold ||
seg->dir == AF_DIR_NONE )
continue;
/* A special case for serif edges: If they are smaller than */
/* 1.5 pixels we ignore them. */
if ( seg->serif &&
2 * seg->height < 3 * segment_length_threshold )
continue;
/* look for an edge corresponding to the segment */
for ( ee = 0; ee < axis->num_edges; ee++ )
{
AF_Edge edge = axis->edges + ee;
FT_Pos dist;
dist = seg->pos - edge->fpos;
if ( dist < 0 )
dist = -dist;
if ( dist < edge_distance_threshold && edge->dir == seg->dir )
{
found = edge;
break;
}
}
if ( !found )
{
AF_Edge edge;
/* insert a new edge in the list and */
/* sort according to the position */
error = af_axis_hints_new_edge( axis, seg->pos,
(AF_Direction)seg->dir,
top_to_bottom_hinting,
memory, &edge );
if ( error )
goto Exit;
/* add the segment to the new edge's list */
FT_ZERO( edge );
edge->first = seg;
edge->last = seg;
edge->dir = seg->dir;
edge->fpos = seg->pos;
edge->opos = FT_MulFix( seg->pos, scale );
edge->pos = edge->opos;
seg->edge_next = seg;
}
else
{
/* if an edge was found, simply add the segment to the edge's */
/* list */
seg->edge_next = found->first;
found->last->edge_next = seg;
found->last = seg;
}
}
/* we loop again over all segments to catch one-point segments */
/* without a direction: if possible, link them to existing edges */
for ( seg = segments; seg < segment_limit; seg++ )
{
AF_Edge found = NULL;
FT_UInt ee;
if ( seg->dir != AF_DIR_NONE )
continue;
/* look for an edge corresponding to the segment */
for ( ee = 0; ee < axis->num_edges; ee++ )
{
AF_Edge edge = axis->edges + ee;
FT_Pos dist;
dist = seg->pos - edge->fpos;
if ( dist < 0 )
dist = -dist;
if ( dist < edge_distance_threshold )
{
found = edge;
break;
}
}
/* one-point segments without a match are ignored */
if ( found )
{
seg->edge_next = found->first;
found->last->edge_next = seg;
found->last = seg;
}
}
/*******************************************************************
*
* Good, we now compute each edge's properties according to the
* segments found on its position. Basically, these are
*
* - the edge's main direction
* - stem edge, serif edge or both (which defaults to stem then)
* - rounded edge, straight or both (which defaults to straight)
* - link for edge
*
*/
/* first of all, set the `edge' field in each segment -- this is */
/* required in order to compute edge links */
/*
* Note that removing this loop and setting the `edge' field of each
* segment directly in the code above slows down execution speed for
* some reasons on platforms like the Sun.
*/
{
AF_Edge edges = axis->edges;
AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges );
AF_Edge edge;
for ( edge = edges; edge < edge_limit; edge++ )
{
seg = edge->first;
if ( seg )
do
{
seg->edge = edge;
seg = seg->edge_next;
} while ( seg != edge->first );
}
/* now compute each edge properties */
for ( edge = edges; edge < edge_limit; edge++ )
{
FT_Int is_round = 0; /* does it contain round segments? */
FT_Int is_straight = 0; /* does it contain straight segments? */
#if 0
FT_Pos ups = 0; /* number of upwards segments */
FT_Pos downs = 0; /* number of downwards segments */
#endif
seg = edge->first;
do
{
FT_Bool is_serif;
/* check for roundness of segment */
if ( seg->flags & AF_EDGE_ROUND )
is_round++;
else
is_straight++;
#if 0
/* check for segment direction */
if ( seg->dir == up_dir )
ups += seg->max_coord - seg->min_coord;
else
downs += seg->max_coord - seg->min_coord;
#endif
/* check for links -- if seg->serif is set, then seg->link must */
/* be ignored */
is_serif = FT_BOOL( seg->serif &&
seg->serif->edge &&
seg->serif->edge != edge );
if ( ( seg->link && seg->link->edge ) || is_serif )
{
AF_Edge edge2;
AF_Segment seg2;
edge2 = edge->link;
seg2 = seg->link;
if ( is_serif )
{
seg2 = seg->serif;
edge2 = edge->serif;
}
if ( edge2 )
{
FT_Pos edge_delta;
FT_Pos seg_delta;
edge_delta = edge->fpos - edge2->fpos;
if ( edge_delta < 0 )
edge_delta = -edge_delta;
seg_delta = seg->pos - seg2->pos;
if ( seg_delta < 0 )
seg_delta = -seg_delta;
if ( seg_delta < edge_delta )
edge2 = seg2->edge;
}
else
edge2 = seg2->edge;
if ( is_serif )
{
edge->serif = edge2;
edge2->flags |= AF_EDGE_SERIF;
}
else
edge->link = edge2;
}
seg = seg->edge_next;
} while ( seg != edge->first );
/* set the round/straight flags */
edge->flags = AF_EDGE_NORMAL;
if ( is_round > 0 && is_round >= is_straight )
edge->flags |= AF_EDGE_ROUND;
#if 0
/* set the edge's main direction */
edge->dir = AF_DIR_NONE;
if ( ups > downs )
edge->dir = (FT_Char)up_dir;
else if ( ups < downs )
edge->dir = (FT_Char)-up_dir;
else if ( ups == downs )
edge->dir = 0; /* both up and down! */
#endif
/* get rid of serifs if link is set */
/* XXX: This gets rid of many unpleasant artefacts! */
/* Example: the `c' in cour.pfa at size 13 */
if ( edge->serif && edge->link )
edge->serif = NULL;
}
}
Exit:
return error;
}
/* Detect segments and edges for given dimension. */
FT_LOCAL_DEF( FT_Error )
af_latin_hints_detect_features( AF_GlyphHints hints,
FT_UInt width_count,
AF_WidthRec* widths,
AF_Dimension dim )
{
FT_Error error;
error = af_latin_hints_compute_segments( hints, dim );
if ( !error )
{
af_latin_hints_link_segments( hints, width_count, widths, dim );
error = af_latin_hints_compute_edges( hints, dim );
}
return error;
}
/* Compute all edges which lie within blue zones. */
static void
af_latin_hints_compute_blue_edges( AF_GlyphHints hints,
AF_LatinMetrics metrics )
{
AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT];
AF_Edge edge = axis->edges;
AF_Edge edge_limit = FT_OFFSET( edge, axis->num_edges );
AF_LatinAxis latin = &metrics->axis[AF_DIMENSION_VERT];
FT_Fixed scale = latin->scale;
/* compute which blue zones are active, i.e. have their scaled */
/* size < 3/4 pixels */
/* for each horizontal edge search the blue zone which is closest */
for ( ; edge < edge_limit; edge++ )
{
FT_UInt bb;
AF_Width best_blue = NULL;
FT_Bool best_blue_is_neutral = 0;
FT_Pos best_dist; /* initial threshold */
/* compute the initial threshold as a fraction of the EM size */
/* (the value 40 is heuristic) */
best_dist = FT_MulFix( metrics->units_per_em / 40, scale );
/* assure a minimum distance of 0.5px */
if ( best_dist > 64 / 2 )
best_dist = 64 / 2;
for ( bb = 0; bb < latin->blue_count; bb++ )
{
AF_LatinBlue blue = latin->blues + bb;
FT_Bool is_top_blue, is_neutral_blue, is_major_dir;
/* skip inactive blue zones (i.e., those that are too large) */
if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) )
continue;
/* if it is a top zone, check for right edges (against the major */
/* direction); if it is a bottom zone, check for left edges (in */
/* the major direction) -- this assumes the TrueType convention */
/* for the orientation of contours */
is_top_blue =
(FT_Byte)( ( blue->flags & ( AF_LATIN_BLUE_TOP |
AF_LATIN_BLUE_SUB_TOP ) ) != 0 );
is_neutral_blue =
(FT_Byte)( ( blue->flags & AF_LATIN_BLUE_NEUTRAL ) != 0);
is_major_dir =
FT_BOOL( edge->dir == axis->major_dir );
/* neutral blue zones are handled for both directions */
if ( is_top_blue ^ is_major_dir || is_neutral_blue )
{
FT_Pos dist;
/* first of all, compare it to the reference position */
dist = edge->fpos - blue->ref.org;
if ( dist < 0 )
dist = -dist;
dist = FT_MulFix( dist, scale );
if ( dist < best_dist )
{
best_dist = dist;
best_blue = &blue->ref;
best_blue_is_neutral = is_neutral_blue;
}
/* now compare it to the overshoot position and check whether */
/* the edge is rounded, and whether the edge is over the */
/* reference position of a top zone, or under the reference */
/* position of a bottom zone (provided we don't have a */
/* neutral blue zone) */
if ( edge->flags & AF_EDGE_ROUND &&
dist != 0 &&
!is_neutral_blue )
{
FT_Bool is_under_ref = FT_BOOL( edge->fpos < blue->ref.org );
if ( is_top_blue ^ is_under_ref )
{
dist = edge->fpos - blue->shoot.org;
if ( dist < 0 )
dist = -dist;
dist = FT_MulFix( dist, scale );
if ( dist < best_dist )
{
best_dist = dist;
best_blue = &blue->shoot;
best_blue_is_neutral = is_neutral_blue;
}
}
}
}
}
if ( best_blue )
{
edge->blue_edge = best_blue;
if ( best_blue_is_neutral )
edge->flags |= AF_EDGE_NEUTRAL;
}
}
}
/* Initalize hinting engine. */
static FT_Error
af_latin_hints_init( AF_GlyphHints hints,
AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
FT_Render_Mode mode;
FT_UInt32 scaler_flags, other_flags;
FT_Face face = metrics->root.scaler.face;
af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics );
/*
* correct x_scale and y_scale if needed, since they may have
* been modified by `af_latin_metrics_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. */
mode = metrics->root.scaler.render_mode;
scaler_flags = hints->scaler_flags;
other_flags = 0;
/*
* We snap the width of vertical stems for the monochrome and
* horizontal LCD rendering targets only.
*/
if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD )
other_flags |= AF_LATIN_HINTS_HORZ_SNAP;
/*
* We snap the width of horizontal stems for the monochrome and
* vertical LCD rendering targets only.
*/
if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V )
other_flags |= AF_LATIN_HINTS_VERT_SNAP;
/*
* We adjust stems to full pixels unless in `light' or `lcd' mode.
*/
if ( mode != FT_RENDER_MODE_LIGHT && mode != FT_RENDER_MODE_LCD )
other_flags |= AF_LATIN_HINTS_STEM_ADJUST;
if ( mode == FT_RENDER_MODE_MONO )
other_flags |= AF_LATIN_HINTS_MONO;
/*
* In `light' or `lcd' mode we disable horizontal hinting completely.
* We also do it if the face is italic.
*
* However, if warping is enabled (which only works in `light' hinting
* mode), advance widths get adjusted, too.
*/
if ( mode == FT_RENDER_MODE_LIGHT || mode == FT_RENDER_MODE_LCD ||
( face->style_flags & FT_STYLE_FLAG_ITALIC ) != 0 )
scaler_flags |= AF_SCALER_FLAG_NO_HORIZONTAL;
hints->scaler_flags = scaler_flags;
hints->other_flags = other_flags;
return FT_Err_Ok;
}
/*************************************************************************/
/*************************************************************************/
/***** *****/
/***** L A T I N G L Y P H G R I D - F I T T I N G *****/
/***** *****/
/*************************************************************************/
/*************************************************************************/
/* Snap a given width in scaled coordinates to one of the */
/* current standard widths. */
static FT_Pos
af_latin_snap_width( AF_Width widths,
FT_UInt count,
FT_Pos width )
{
FT_UInt n;
FT_Pos best = 64 + 32 + 2;
FT_Pos reference = width;
FT_Pos scaled;
for ( n = 0; n < count; n++ )
{
FT_Pos w;
FT_Pos dist;
w = widths[n].cur;
dist = width - w;
if ( dist < 0 )
dist = -dist;
if ( dist < best )
{
best = dist;
reference = w;
}
}
scaled = FT_PIX_ROUND( reference );
if ( width >= reference )
{
if ( width < scaled + 48 )
width = reference;
}
else
{
if ( width > scaled - 48 )
width = reference;
}
return width;
}
#undef FT_COMPONENT
#define FT_COMPONENT afadjust
static void
af_move_contour_vertically( AF_Point contour,
FT_Int movement )
{
AF_Point point = contour;
AF_Point first_point = point;
if ( point != NULL )
{
do
{
point->y += movement;
point = point->next;
} while ( point != first_point );
}
}
static FT_Int
af_find_highest_contour( AF_GlyphHints hints ) {
FT_Int highest_contour = -1;
FT_Pos highest_min_y = 0;
FT_Pos current_min_y = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( point == NULL )
{
continue;
}
current_min_y = point->y;
do
{
if ( point->y < current_min_y )
{
current_min_y = point->y;
}
point = point->next;
} while ( point != first_point );
if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y;
highest_contour = contour;
}
}
return highest_contour;
}
static void
af_remove_segments_containing_point(AF_GlyphHints hints, AF_Point point)
{
AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT];
AF_Segment segments = axis->segments;
for ( FT_Int i = 0; i < axis->num_segments; i++ )
{
AF_Segment seg = &segments[i];
FT_Bool remove = 0;
AF_Point p = seg->first;
while ( 1 )
{
if ( p == point )
{
remove = 1;
break;
}
if (p == seg->last) {
break;
}
p = p->next;
}
if ( remove )
{
/* first, check the first and last fields of the edge */
AF_Edge edge = seg->edge;
if ( edge->first == seg && edge->last == seg )
{
/* The edge only consists of the segment to be removed. remove the edge*/
*edge = axis->edges[--axis->num_edges];
}
else
{
if ( edge->first == seg )
{
edge->first = seg->edge_next;
}
if ( edge->last == seg )
{
edge->last = edge->first;
while ( edge->last->edge_next != seg )
{
edge->last = edge->last->edge_next;
}
}
}
/* Now, delete the segment */
*seg = axis->segments[--axis->num_segments];
i--; /* we have to check the new segment at this position */
}
}
}
/*remove all segments containing points on the tilde contour*/
static void
af_latin_remove_tilde_points_from_edges( AF_GlyphHints hints,
FT_Int glyph_index )
{
FT_Int highest_contour = af_find_highest_contour(hints);
AF_Point first_point = hints->contours[highest_contour];
/* search for any curve tips that are on a y extrema, and delete any
segments that contain this point.*/
AF_Point p = first_point;
do
{
p = p->next;
if ( /*!(p->flags & AF_FLAG_CONTROL)
&& p->prev->y == p->y && p->next->y == p->y
&& p->prev->flags & AF_FLAG_CONTROL
&& p->next->flags & AF_FLAG_CONTROL*/ 1 )
{
FT_TRACE4(("%p", p));
af_remove_segments_containing_point( hints, p );
}
} while ( p != first_point );
}
/*
The tilde unflattening algorithm sometimes goes too far and makes an
unusually high tilde, where decreasing the ppem will increase the height
instead of a steady decrease in height as less pixels are used.
The n tilde on times new roman with forced autofitting on,
16.5-18 ppem font size exhibits this behaviour.
*/
void
af_latin_stretch_tildes( AF_GlyphHints hints )
{
FT_Int highest_contour = af_find_highest_contour( hints );
AF_Point p = hints->contours[highest_contour];
AF_Point first_point = p;
FT_Pos min_y, max_y;
min_y = max_y = p->y;
FT_Short min_fy, max_fy;
min_fy = max_fy = p->fy;
do
{
p = p->next;
if ( p->y < min_y )
{
min_y = p->y;
}
if ( p->y > max_y )
{
max_y = p->y;
}
if ( p->fy < min_fy )
{
min_fy = p->fy;
}
if ( p->fy > max_fy )
{
max_fy = p->fy;
}
}
while ( p != first_point );
FT_Pos min_measurement = 32000;
FT_UInt measurements_taken = 0;
do
{
p = p->next;
if ( !(p->flags & AF_FLAG_CONTROL)
&& p->prev->y == p->y && p->next->y == p->y
&& p->y != min_y && p->y != max_y
&& p->prev->flags & AF_FLAG_CONTROL
&& p->next->flags & AF_FLAG_CONTROL )
{
/* This point could be a candidate. Find the next and previous on-curve */
/* points, and make sure they are both either above or below the point, */
/* Then make the measurement */
AF_Point prevOn = p->prev;
AF_Point nextOn = p->next;
while ( prevOn->flags & AF_FLAG_CONTROL )
{
prevOn = prevOn->prev;
}
while ( nextOn->flags & AF_FLAG_CONTROL )
{
nextOn = nextOn->next;
}
FT_Pos measurement;
if ( nextOn->y > p->y && prevOn->y > p->y )
{
measurement = p->y - min_y;
}
else if ( nextOn->y < p->y && prevOn->y < p->y )
{
measurement = max_y - p->y;
}
else
{
continue;
}
if (measurement < min_measurement)
{
min_measurement = measurement;
}
measurements_taken++;
}
}
while ( p != first_point );
//touch all points
p = first_point;
do
{
p = p->next;
if ( !(p->flags & AF_FLAG_CONTROL) )
p->flags |= AF_FLAG_TOUCH_Y;
}
while ( p != first_point );
FT_Pos height = max_y - min_y;
FT_Pos target_height = min_measurement + 64;
if ( height >= target_height )
{
return;
}
p = first_point;
do
{
p = p->next;
p->y = ((p->y - min_y) * target_height / height) + min_y;
p->fy = ((p->fy - min_fy) * target_height / height) + min_fy;
p->oy = p->y;
}
while ( p != first_point );
FT_Pos new_min_y, new_max_y;
new_min_y = new_max_y = first_point->y;
p = first_point;
do {
p = p->next;
if ( p->y < new_min_y )
{
new_min_y = p->y;
}
if ( p->y > new_max_y )
{
new_max_y = p->y;
}
}
while ( p != first_point );
}
/*
As part of af_latin_stretch_tildes, all points in the tilde
are marked as touched, so the existing grid fitting will leave
the tilde misaligned with the grid.
This function moves the tilde contour down to be grid fitted.
We assume that if moving the tilde down would cause it to touch or
overlap another countour, the vertical adjustment step will fix it
Because the vertical adjustment step comes after all other grid fitting
steps, the top edge of the contour under the tilde is usually aligned with
a horizontal gridline. The vertical gap enforced by the vertical adjustment
is exactly one pixel, so if the top edge of the contour below the tilde is on a
gridline, the resulting tilde contour will also be grid aligned.
But in cases where the gap is already big enough so that the vertical
adjustment does nothing, this function ensures that even without the
intervention of the vertical adjustment step, the tilde will be
grid aligned.
*/
void
af_latin_align_tildes( AF_GlyphHints hints ) {
FT_Int highest_contour = af_find_highest_contour( hints );
AF_Point p = hints->contours[highest_contour];
AF_Point first_point = p;
FT_Pos min_y, max_y;
do
{
p = p->next;
if ( p->y < min_y )
{
min_y = p->y;
}
if ( p->y > max_y )
{
max_y = p->y;
}
}
while ( p != first_point );
//FT_Pos mid_y = ( min_y + max_y ) / 2;
FT_Pos min_y_rounded = FT_PIX_ROUND( min_y );
FT_Pos delta = min_y_rounded - min_y;
do
{
p = p->next;
p->y += delta;
}
while ( p != first_point );
}
/*True if the given contour overlaps horizontally with the bounding box
Of all other contours combined.
This is a helper for af_glyph_hints_apply_vertical_separation_adjustments */
FT_Bool
af_check_contour_horizontal_overlap( AF_GlyphHints hints,
FT_Int contour_index )
{
FT_Pos contour_max_x = -32000;
FT_Pos contour_min_x = 32000;
FT_Pos others_max_x = -32000;
FT_Pos others_min_x = 32000;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point first_point = hints->contours[contour];
AF_Point p = first_point;
do
{
p = p->next;
if ( contour == contour_index )
{
if ( p->x < contour_min_x )
{
contour_min_x = p->x;
}
if ( p->x > contour_max_x )
{
contour_max_x = p->x;
}
}
else /* ( contour != contour_index ) */
{
if ( p->x < others_min_x )
{
others_min_x = p->x;
}
if ( p->x > others_max_x )
{
others_max_x = p->x;
}
}
}
while (p != first_point);
}
FT_Bool horizontal_overlap =
(others_min_x <= contour_max_x && contour_max_x <= others_max_x) ||
(others_min_x <= contour_min_x && contour_min_x <= others_max_x) ||
(contour_max_x >= others_max_x && contour_min_x <= others_min_x);
return horizontal_overlap;
}
void
af_glyph_hints_apply_vertical_separation_adjustments( AF_GlyphHints hints,
AF_Dimension dim,
FT_Int glyph_index,
AF_ReverseCharacterMap reverse_charmap )
{
FT_TRACE4(("Enter af_glyph_hints_apply_vertical_separation_adjustments\n"));
if ( dim != AF_DIMENSION_VERT )
{
return;
}
if ( af_lookup_vertical_seperation_type( reverse_charmap, glyph_index ) == AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP
&& hints->num_contours >= 2 )
{
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments: Applying vertical adjustment: AF_VERTICAL_ADJUSTMENT_TOP_CONTOUR_UP\n" ));
/* Figure out which contout is the higher one by finding the one */
/* with the highest minimum y value */
FT_Int highest_contour = -1;
FT_Pos highest_min_y = 0;
FT_Pos current_min_y = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( point == NULL )
{
continue;
}
current_min_y = point->y;
do
{
if ( point->y < current_min_y )
{
current_min_y = point->y;
}
point = point->next;
} while ( point != first_point );
if ( highest_contour == -1 || current_min_y > highest_min_y )
{
highest_min_y = current_min_y;
highest_contour = contour;
}
}
/* check for a horizontal overtap between the top contour and the rest */
/* if there is no overlap, do not adjust. */
FT_Bool horizontal_overlap = af_check_contour_horizontal_overlap( hints, highest_contour );
if (!horizontal_overlap) {
FT_TRACE4(( " Top contour does not horizontally overlap with other contours. Skipping adjustment.\n" ));
return;
}
/* If there are any contours that have a maximum y coordinate */
/* greater or equal to the minimum y coordinate of the previously found highest*/
/* contour, bump the high contour up until the distance is one pixel */
FT_Int adjustment_amount = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
if ( contour == highest_contour )
{
continue;
}
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( point == NULL )
{
continue;
}
FT_Pos max_y = point->y;
do
{
if ( point->y > max_y )
{
max_y = point->y;
}
point = point->next;
} while ( point != first_point );
if ( max_y >= highest_min_y - 64 )
{
adjustment_amount = 64 - ( highest_min_y - max_y );
}
}
FT_TRACE4(( " Calculated adjustment amount %d\n", adjustment_amount ));
if ( adjustment_amount > 64 )
{
FT_TRACE4(( " Calculated adjustment amount %d was more than threshold of 64. Not adjusting\n", adjustment_amount ));
}
else if ( adjustment_amount > 0 )
{
FT_TRACE4(( " Pushing top contour %d units up\n", adjustment_amount ));
af_move_contour_vertically(hints->contours[highest_contour], adjustment_amount);
}
} else if ( af_lookup_vertical_seperation_type( reverse_charmap, glyph_index ) == AF_VERTICAL_ADJUSTMENT_BOTTOM_CONTOUR_DOWN
&& hints->num_contours >= 2 )
{
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments: Applying vertical adjustment: AF_VERTICAL_ADJUSTMENT_BOTTOM_CONTOUR_DOWN\n" ));
/*Find lowest contour*/
FT_Int lowest_contour = -1;
FT_Pos lowest_max_y = 0;
FT_Pos current_max_y = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( point == NULL )
{
continue;
}
current_max_y = point->y;
do
{
if ( point->y > current_max_y )
{
current_max_y = point->y;
}
point = point->next;
} while ( point != first_point );
if ( lowest_contour == -1 || current_max_y < lowest_max_y )
{
lowest_max_y = current_max_y;
lowest_contour = contour;
}
}
FT_Int adjustment_amount = 0;
for ( FT_Int contour = 0; contour < hints->num_contours; contour++ )
{
if ( contour == lowest_contour )
{
continue;
}
AF_Point point = hints->contours[contour];
AF_Point first_point = point;
if ( point == NULL )
{
continue;
}
FT_Pos min_y = point->y;
do
{
if ( point->y < min_y )
{
min_y = point->y;
}
point = point->next;
} while ( point != first_point );
if ( min_y <= lowest_max_y - 64 )
{
adjustment_amount = 64 - ( min_y - lowest_max_y );
}
if ( adjustment_amount > 64 )
{
FT_TRACE4(( " Calculated adjustment amount %d was more than threshold of 64. Not adjusting\n", adjustment_amount ));
}
else if ( adjustment_amount > 0 )
{
FT_TRACE4(( " Pushing bottom contour %d units down\n", adjustment_amount ));
af_move_contour_vertically(hints->contours[lowest_contour], -adjustment_amount);
}
}
}
else
{
FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments: No vertical adjustment needed\n" ));
}
FT_TRACE4(("Exit af_glyph_hints_apply_vertical_separation_adjustments\n"));
}
#undef FT_COMPONENT
#define FT_COMPONENT aflatin
/* Compute the snapped width of a given stem, ignoring very thin ones. */
/* There is a lot of voodoo in this function; changing the hard-coded */
/* parameters influence the whole hinting process. */
static FT_Pos
af_latin_compute_stem_width( AF_GlyphHints hints,
AF_Dimension dim,
FT_Pos width,
FT_Pos base_delta,
FT_UInt base_flags,
FT_UInt stem_flags )
{
AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics;
AF_LatinAxis axis = &metrics->axis[dim];
FT_Pos dist = width;
FT_Int sign = 0;
FT_Int vertical = ( dim == AF_DIMENSION_VERT );
if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) ||
axis->extra_light )
return width;
if ( dist < 0 )
{
dist = -width;
sign = 1;
}
if ( ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) ||
( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) )
{
/* smooth hinting process: very lightly quantize the stem width */
/* leave the widths of serifs alone */
if ( ( stem_flags & AF_EDGE_SERIF ) &&
vertical &&
( dist < 3 * 64 ) )
goto Done_Width;
else if ( base_flags & AF_EDGE_ROUND )
{
if ( dist < 80 )
dist = 64;
}
else if ( dist < 56 )
dist = 56;
if ( axis->width_count > 0 )
{
FT_Pos delta;
/* compare to standard width */
delta = dist - axis->widths[0].cur;
if ( delta < 0 )
delta = -delta;
if ( delta < 40 )
{
dist = axis->widths[0].cur;
if ( dist < 48 )
dist = 48;
goto Done_Width;
}
if ( dist < 3 * 64 )
{
delta = dist & 63;
dist &= -64;
if ( delta < 10 )
dist += delta;
else if ( delta < 32 )
dist += 10;
else if ( delta < 54 )
dist += 54;
else
dist += delta;
}
else
{
/* A stem's end position depends on two values: the start */
/* position and the stem length. The former gets usually */
/* rounded to the grid, while the latter gets rounded also if it */
/* exceeds a certain length (see below in this function). This */
/* `double rounding' can lead to a great difference to the */
/* original, unhinted position; this normally doesn't matter for */
/* large PPEM values, but for small sizes it can easily make */
/* outlines collide. For this reason, we adjust the stem length */
/* by a small amount depending on the PPEM value in case the */
/* former and latter rounding both point into the same */
/* direction. */
FT_Pos bdelta = 0;
if ( ( ( width > 0 ) && ( base_delta > 0 ) ) ||
( ( width < 0 ) && ( base_delta < 0 ) ) )
{
FT_UInt ppem = metrics->root.scaler.face->size->metrics.x_ppem;
if ( ppem < 10 )
bdelta = base_delta;
else if ( ppem < 30 )
bdelta = ( base_delta * (FT_Pos)( 30 - ppem ) ) / 20;
if ( bdelta < 0 )
bdelta = -bdelta;
}
dist = ( dist - bdelta + 32 ) & ~63;
}
}
}
else
{
/* strong hinting process: snap the stem width to integer pixels */
FT_Pos org_dist = dist;
dist = af_latin_snap_width( axis->widths, axis->width_count, dist );
if ( vertical )
{
/* in the case of vertical hinting, always round */
/* the stem heights to integer pixels */
if ( dist >= 64 )
dist = ( dist + 16 ) & ~63;
else
dist = 64;
}
else
{
if ( AF_LATIN_HINTS_DO_MONO( hints ) )
{
/* monochrome horizontal hinting: snap widths to integer pixels */
/* with a different threshold */
if ( dist < 64 )
dist = 64;
else
dist = ( dist + 32 ) & ~63;
}
else
{
/* for horizontal anti-aliased hinting, we adopt a more subtle */
/* approach: we strengthen small stems, round stems whose size */
/* is between 1 and 2 pixels to an integer, otherwise nothing */
if ( dist < 48 )
dist = ( dist + 64 ) >> 1;
else if ( dist < 128 )
{
/* We only round to an integer width if the corresponding */
/* distortion is less than 1/4 pixel. Otherwise this */
/* makes everything worse since the diagonals, which are */
/* not hinted, appear a lot bolder or thinner than the */
/* vertical stems. */
FT_Pos delta;
dist = ( dist + 22 ) & ~63;
delta = dist - org_dist;
if ( delta < 0 )
delta = -delta;
if ( delta >= 16 )
{
dist = org_dist;
if ( dist < 48 )
dist = ( dist + 64 ) >> 1;
}
}
else
/* round otherwise to prevent color fringes in LCD mode */
dist = ( dist + 32 ) & ~63;
}
}
}
Done_Width:
if ( sign )
dist = -dist;
return dist;
}
/* Align one stem edge relative to the previous stem edge. */
static void
af_latin_align_linked_edge( AF_GlyphHints hints,
AF_Dimension dim,
AF_Edge base_edge,
AF_Edge stem_edge )
{
FT_Pos dist, base_delta;
FT_Pos fitted_width;
dist = stem_edge->opos - base_edge->opos;
base_delta = base_edge->pos - base_edge->opos;
fitted_width = af_latin_compute_stem_width( hints, dim,
dist, base_delta,
base_edge->flags,
stem_edge->flags );
stem_edge->pos = base_edge->pos + fitted_width;
FT_TRACE5(( " LINK: edge %td (opos=%.2f) linked to %.2f,"
" dist was %.2f, now %.2f\n",
stem_edge - hints->axis[dim].edges,
(double)stem_edge->opos / 64, (double)stem_edge->pos / 64,
(double)dist / 64, (double)fitted_width / 64 ));
}
/* Shift the coordinates of the `serif' edge by the same amount */
/* as the corresponding `base' edge has been moved already. */
static void
af_latin_align_serif_edge( AF_GlyphHints hints,
AF_Edge base,
AF_Edge serif )
{
FT_UNUSED( hints );
serif->pos = base->pos + ( serif->opos - base->opos );
}
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** E D G E H I N T I N G ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/*************************************************************************/
/* The main grid-fitting routine. */
static void
af_latin_hint_edges( AF_GlyphHints hints,
AF_Dimension dim )
{
AF_AxisHints axis = &hints->axis[dim];
AF_Edge edges = axis->edges;
AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges );
FT_PtrDist n_edges;
AF_Edge edge;
AF_Edge anchor = NULL;
FT_Int has_serifs = 0;
AF_StyleClass style_class = hints->metrics->style_class;
AF_ScriptClass script_class = af_script_classes[style_class->script];
FT_Bool top_to_bottom_hinting = 0;
#ifdef FT_DEBUG_LEVEL_TRACE
FT_UInt num_actions = 0;
#endif
FT_TRACE5(( "latin %s edge hinting (style `%s')\n",
dim == AF_DIMENSION_VERT ? "horizontal" : "vertical",
af_style_names[hints->metrics->style_class->style] ));
if ( dim == AF_DIMENSION_VERT )
top_to_bottom_hinting = script_class->top_to_bottom_hinting;
/* we begin by aligning all stems relative to the blue zone */
/* if needed -- that's only for horizontal edges */
if ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_BLUES( hints ) )
{
for ( edge = edges; edge < edge_limit; edge++ )
{
AF_Width blue;
AF_Edge edge1, edge2; /* these edges form the stem to check */
if ( edge->flags & AF_EDGE_DONE )
continue;
edge1 = NULL;
edge2 = edge->link;
/*
* If a stem contains both a neutral and a non-neutral blue zone,
* skip the neutral one. Otherwise, outlines with different
* directions might be incorrectly aligned at the same vertical
* position.
*
* If we have two neutral blue zones, skip one of them.
*
*/
if ( edge->blue_edge && edge2 && edge2->blue_edge )
{
FT_Byte neutral = edge->flags & AF_EDGE_NEUTRAL;
FT_Byte neutral2 = edge2->flags & AF_EDGE_NEUTRAL;
if ( neutral2 )
{
edge2->blue_edge = NULL;
edge2->flags &= ~AF_EDGE_NEUTRAL;
}
else if ( neutral )
{
edge->blue_edge = NULL;
edge->flags &= ~AF_EDGE_NEUTRAL;
}
}
blue = edge->blue_edge;
if ( blue )
edge1 = edge;
/* flip edges if the other edge is aligned to a blue zone */
else if ( edge2 && edge2->blue_edge )
{
blue = edge2->blue_edge;
edge1 = edge2;
edge2 = edge;
}
if ( !edge1 )
continue;
#ifdef FT_DEBUG_LEVEL_TRACE
if ( !anchor )
FT_TRACE5(( " BLUE_ANCHOR: edge %td (opos=%.2f) snapped to %.2f,"
" was %.2f (anchor=edge %td)\n",
edge1 - edges,
(double)edge1->opos / 64, (double)blue->fit / 64,
(double)edge1->pos / 64, edge - edges ));
else
FT_TRACE5(( " BLUE: edge %td (opos=%.2f) snapped to %.2f,"
" was %.2f\n",
edge1 - edges,
(double)edge1->opos / 64, (double)blue->fit / 64,
(double)edge1->pos / 64 ));
num_actions++;
#endif
edge1->pos = blue->fit;
edge1->flags |= AF_EDGE_DONE;
if ( edge2 && !edge2->blue_edge )
{
af_latin_align_linked_edge( hints, dim, edge1, edge2 );
edge2->flags |= AF_EDGE_DONE;
#ifdef FT_DEBUG_LEVEL_TRACE
num_actions++;
#endif
}
if ( !anchor )
anchor = edge;
}
}
/* now we align all other stem edges, trying to maintain the */
/* relative order of stems in the glyph */
for ( edge = edges; edge < edge_limit; edge++ )
{
AF_Edge edge2;
if ( edge->flags & AF_EDGE_DONE )
continue;
/* skip all non-stem edges */
edge2 = edge->link;
if ( !edge2 )
{
has_serifs++;
continue;
}
/* now align the stem */
/* this should not happen, but it's better to be safe */
if ( edge2->blue_edge )
{
FT_TRACE5(( " ASSERTION FAILED for edge %td\n", edge2 - edges ));
af_latin_align_linked_edge( hints, dim, edge2, edge );
edge->flags |= AF_EDGE_DONE;
#ifdef FT_DEBUG_LEVEL_TRACE
num_actions++;
#endif
continue;
}
if ( !anchor )
{
/* if we reach this if clause, no stem has been aligned yet */
FT_Pos org_len, org_center, cur_len;
FT_Pos cur_pos1, error1, error2, u_off, d_off;
org_len = edge2->opos - edge->opos;
cur_len = af_latin_compute_stem_width( hints, dim,
org_len, 0,
edge->flags,
edge2->flags );
/* some voodoo to specially round edges for small stem widths; */
/* the idea is to align the center of a stem, then shifting */
/* the stem edges to suitable positions */
if ( cur_len <= 64 )
{
/* width <= 1px */
u_off = 32;
d_off = 32;
}
else
{
/* 1px < width < 1.5px */
u_off = 38;
d_off = 26;
}
if ( cur_len < 96 )
{
org_center = edge->opos + ( org_len >> 1 );
cur_pos1 = FT_PIX_ROUND( org_center );
error1 = org_center - ( cur_pos1 - u_off );
if ( error1 < 0 )
error1 = -error1;
error2 = org_center - ( cur_pos1 + d_off );
if ( error2 < 0 )
error2 = -error2;
if ( error1 < error2 )
cur_pos1 -= u_off;
else
cur_pos1 += d_off;
edge->pos = cur_pos1 - cur_len / 2;
edge2->pos = edge->pos + cur_len;
}
else
edge->pos = FT_PIX_ROUND( edge->opos );
anchor = edge;
edge->flags |= AF_EDGE_DONE;
FT_TRACE5(( " ANCHOR: edge %td (opos=%.2f) and %td (opos=%.2f)"
" snapped to %.2f and %.2f\n",
edge - edges, (double)edge->opos / 64,
edge2 - edges, (double)edge2->opos / 64,
(double)edge->pos / 64, (double)edge2->pos / 64 ));
af_latin_align_linked_edge( hints, dim, edge, edge2 );
#ifdef FT_DEBUG_LEVEL_TRACE
num_actions += 2;
#endif
}
else
{
FT_Pos org_pos, org_len, org_center, cur_len;
FT_Pos cur_pos1, cur_pos2, delta1, delta2;
org_pos = anchor->pos + ( edge->opos - anchor->opos );
org_len = edge2->opos - edge->opos;
org_center = org_pos + ( org_len >> 1 );
cur_len = af_latin_compute_stem_width( hints, dim,
org_len, 0,
edge->flags,
edge2->flags );
if ( edge2->flags & AF_EDGE_DONE )
{
FT_TRACE5(( " ADJUST: edge %td (pos=%.2f) moved to %.2f\n",
edge - edges, (double)edge->pos / 64,
(double)( edge2->pos - cur_len ) / 64 ));
edge->pos = edge2->pos - cur_len;
}
else if ( cur_len < 96 )
{
FT_Pos u_off, d_off;
cur_pos1 = FT_PIX_ROUND( org_center );
if ( cur_len <= 64 )
{
u_off = 32;
d_off = 32;
}
else
{
u_off = 38;
d_off = 26;
}
delta1 = org_center - ( cur_pos1 - u_off );
if ( delta1 < 0 )
delta1 = -delta1;
delta2 = org_center - ( cur_pos1 + d_off );
if ( delta2 < 0 )
delta2 = -delta2;
if ( delta1 < delta2 )
cur_pos1 -= u_off;
else
cur_pos1 += d_off;
edge->pos = cur_pos1 - cur_len / 2;
edge2->pos = cur_pos1 + cur_len / 2;
FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
" snapped to %.2f and %.2f\n",
edge - edges, (double)edge->opos / 64,
edge2 - edges, (double)edge2->opos / 64,
(double)edge->pos / 64, (double)edge2->pos / 64 ));
}
else
{
org_pos = anchor->pos + ( edge->opos - anchor->opos );
org_len = edge2->opos - edge->opos;
org_center = org_pos + ( org_len >> 1 );
cur_len = af_latin_compute_stem_width( hints, dim,
org_len, 0,
edge->flags,
edge2->flags );
cur_pos1 = FT_PIX_ROUND( org_pos );
delta1 = cur_pos1 + ( cur_len >> 1 ) - org_center;
if ( delta1 < 0 )
delta1 = -delta1;
cur_pos2 = FT_PIX_ROUND( org_pos + org_len ) - cur_len;
delta2 = cur_pos2 + ( cur_len >> 1 ) - org_center;
if ( delta2 < 0 )
delta2 = -delta2;
edge->pos = ( delta1 < delta2 ) ? cur_pos1 : cur_pos2;
edge2->pos = edge->pos + cur_len;
FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)"
" snapped to %.2f and %.2f\n",
edge - edges, (double)edge->opos / 64,
edge2 - edges, (double)edge2->opos / 64,
(double)edge->pos / 64, (double)edge2->pos / 64 ));
}
#ifdef FT_DEBUG_LEVEL_TRACE
num_actions++;
#endif
edge->flags |= AF_EDGE_DONE;
edge2->flags |= AF_EDGE_DONE;
if ( edge > edges &&
( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
: ( edge->pos < edge[-1].pos ) ) )
{
/* don't move if stem would (almost) disappear otherwise; */
/* the ad-hoc value 16 corresponds to 1/4px */
if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
edge - edges,
(double)edge->pos / 64,
(double)edge[-1].pos / 64 ));
num_actions++;
#endif
edge->pos = edge[-1].pos;
}
}
}
}
/* make sure that lowercase m's maintain their symmetry */
/* In general, lowercase m's have six vertical edges if they are sans */
/* serif, or twelve if they are with serifs. This implementation is */
/* based on that assumption, and seems to work very well with most */
/* faces. However, if for a certain face this assumption is not */
/* true, the m is just rendered like before. In addition, any stem */
/* correction will only be applied to symmetrical glyphs (even if the */
/* glyph is not an m), so the potential for unwanted distortion is */
/* relatively low. */
/* We don't handle horizontal edges since we can't easily assure that */
/* the third (lowest) stem aligns with the base line; it might end up */
/* one pixel higher or lower. */
n_edges = edge_limit - edges;
if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) )
{
AF_Edge edge1, edge2, edge3;
FT_Pos dist1, dist2, span, delta;
if ( n_edges == 6 )
{
edge1 = edges;
edge2 = edges + 2;
edge3 = edges + 4;
}
else
{
edge1 = edges + 1;
edge2 = edges + 5;
edge3 = edges + 9;
}
dist1 = edge2->opos - edge1->opos;
dist2 = edge3->opos - edge2->opos;
span = dist1 - dist2;
if ( span < 0 )
span = -span;
if ( span < 8 )
{
delta = edge3->pos - ( 2 * edge2->pos - edge1->pos );
edge3->pos -= delta;
if ( edge3->link )
edge3->link->pos -= delta;
/* move the serifs along with the stem */
if ( n_edges == 12 )
{
( edges + 8 )->pos -= delta;
( edges + 11 )->pos -= delta;
}
edge3->flags |= AF_EDGE_DONE;
if ( edge3->link )
edge3->link->flags |= AF_EDGE_DONE;
}
}
if ( has_serifs || !anchor )
{
/*
* now hint the remaining edges (serifs and single) in order
* to complete our processing
*/
for ( edge = edges; edge < edge_limit; edge++ )
{
FT_Pos delta;
if ( edge->flags & AF_EDGE_DONE )
continue;
delta = 1000;
if ( edge->serif )
{
delta = edge->serif->opos - edge->opos;
if ( delta < 0 )
delta = -delta;
}
if ( delta < 64 + 16 )
{
af_latin_align_serif_edge( hints, edge->serif, edge );
FT_TRACE5(( " SERIF: edge %td (opos=%.2f) serif to %td (opos=%.2f)"
" aligned to %.2f\n",
edge - edges, (double)edge->opos / 64,
edge->serif - edges, (double)edge->serif->opos / 64,
(double)edge->pos / 64 ));
}
else if ( !anchor )
{
edge->pos = FT_PIX_ROUND( edge->opos );
anchor = edge;
FT_TRACE5(( " SERIF_ANCHOR: edge %td (opos=%.2f)"
" snapped to %.2f\n",
edge - edges,
(double)edge->opos / 64, (double)edge->pos / 64 ));
}
else
{
AF_Edge before, after;
for ( before = edge - 1; before >= edges; before-- )
if ( before->flags & AF_EDGE_DONE )
break;
for ( after = edge + 1; after < edge_limit; after++ )
if ( after->flags & AF_EDGE_DONE )
break;
if ( before >= edges && before < edge &&
after < edge_limit && after > edge )
{
if ( after->opos == before->opos )
edge->pos = before->pos;
else
edge->pos = before->pos +
FT_MulDiv( edge->opos - before->opos,
after->pos - before->pos,
after->opos - before->opos );
FT_TRACE5(( " SERIF_LINK1: edge %td (opos=%.2f) snapped to %.2f"
" from %td (opos=%.2f)\n",
edge - edges, (double)edge->opos / 64,
(double)edge->pos / 64,
before - edges, (double)before->opos / 64 ));
}
else
{
edge->pos = anchor->pos +
( ( edge->opos - anchor->opos + 16 ) & ~31 );
FT_TRACE5(( " SERIF_LINK2: edge %td (opos=%.2f)"
" snapped to %.2f\n",
edge - edges,
(double)edge->opos / 64, (double)edge->pos / 64 ));
}
}
#ifdef FT_DEBUG_LEVEL_TRACE
num_actions++;
#endif
edge->flags |= AF_EDGE_DONE;
if ( edge > edges &&
( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos )
: ( edge->pos < edge[-1].pos ) ) )
{
/* don't move if stem would (almost) disappear otherwise; */
/* the ad-hoc value 16 corresponds to 1/4px */
if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
edge - edges,
(double)edge->pos / 64,
(double)edge[-1].pos / 64 ));
num_actions++;
#endif
edge->pos = edge[-1].pos;
}
}
if ( edge + 1 < edge_limit &&
edge[1].flags & AF_EDGE_DONE &&
( top_to_bottom_hinting ? ( edge->pos < edge[1].pos )
: ( edge->pos > edge[1].pos ) ) )
{
/* don't move if stem would (almost) disappear otherwise; */
/* the ad-hoc value 16 corresponds to 1/4px */
if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 )
{
#ifdef FT_DEBUG_LEVEL_TRACE
FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n",
edge - edges,
(double)edge->pos / 64,
(double)edge[1].pos / 64 ));
num_actions++;
#endif
edge->pos = edge[1].pos;
}
}
}
}
#ifdef FT_DEBUG_LEVEL_TRACE
if ( !num_actions )
FT_TRACE5(( " (none)\n" ));
FT_TRACE5(( "\n" ));
#endif
}
/*Print the height of the topmost contour for debugging purposes.
TODO: remove this once the tilde unflattening works.*/
static void traceheight(FT_UInt num, AF_GlyphHints hints) {
AF_Point p = hints->contours[af_find_highest_contour(hints)];
AF_Point first_point = p;
FT_Pos min_y, max_y;
min_y = max_y = p->y;
do {
p = p->next;
if ( !(p->flags & AF_FLAG_CONTROL) ) {
if ( p->y < min_y ) {
min_y = p->y;
}
if ( p->y > max_y ) {
max_y = p->y;
}
}
} while ( p != first_point );
FT_Pos height = max_y - min_y;
FT_TRACE4(( "height %d: %d\n", num, height ));
}
/* Apply the complete hinting algorithm to a latin glyph. */
static FT_Error
af_latin_hints_apply( FT_UInt glyph_index,
AF_GlyphHints hints,
FT_Outline* outline,
AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */
{
AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_;
FT_Error error;
int dim;
AF_LatinAxis axis;
error = af_glyph_hints_reload( hints, outline );
if ( error )
goto Exit;
/* analyze glyph outline */
if ( AF_HINTS_DO_HORIZONTAL( hints ) )
{
axis = &metrics->axis[AF_DIMENSION_HORZ];
error = af_latin_hints_detect_features( hints,
axis->width_count,
axis->widths,
AF_DIMENSION_HORZ );
if ( error )
goto Exit;
}
if ( AF_HINTS_DO_VERTICAL( hints ) )
{
FT_Bool is_tilde = af_lookup_tilde_correction_type( metrics->root.reverse_charmap, glyph_index );
if ( is_tilde ) {
traceheight(0, hints);
af_latin_stretch_tildes( hints );
af_latin_align_tildes( hints );
traceheight(1, hints);
}
axis = &metrics->axis[AF_DIMENSION_VERT];
error = af_latin_hints_detect_features( hints,
axis->width_count,
axis->widths,
AF_DIMENSION_VERT );
if ( is_tilde ) {
af_latin_remove_tilde_points_from_edges( hints, glyph_index );
}
if ( error )
goto Exit;
/* apply blue zones to base characters only */
if ( !( metrics->root.globals->glyph_styles[glyph_index] & AF_NONBASE ) )
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 ) ) ||
( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) ) )
{
af_latin_hint_edges( hints, (AF_Dimension)dim );
traceheight(2, hints);
af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim );
traceheight(3, hints);
af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim );
traceheight(4, hints);
af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim );
traceheight(5, hints);
af_glyph_hints_apply_vertical_separation_adjustments(hints, (AF_Dimension) dim, glyph_index, metrics->root.reverse_charmap);
traceheight(6, hints);
}
}
af_glyph_hints_save( hints, outline );
Exit:
return error;
}
/*************************************************************************/
/*************************************************************************/
/***** *****/
/***** L A T I N S C R I P T C L A S S *****/
/***** *****/
/*************************************************************************/
/*************************************************************************/
AF_DEFINE_WRITING_SYSTEM_CLASS(
af_latin_writing_system_class,
AF_WRITING_SYSTEM_LATIN,
sizeof ( AF_LatinMetricsRec ),
(AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init, /* style_metrics_init */
(AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale, /* style_metrics_scale */
(AF_WritingSystem_DoneMetricsFunc) af_latin_metrics_done, /* style_metrics_done */
(AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */
(AF_WritingSystem_InitHintsFunc) af_latin_hints_init, /* style_hints_init */
(AF_WritingSystem_ApplyHintsFunc) af_latin_hints_apply /* style_hints_apply */
)
/* END */