/***************************************************************************/ | |
/* */ | |
/* afloader.c */ | |
/* */ | |
/* Auto-fitter glyph loading routines (body). */ | |
/* */ | |
/* Copyright 2003-2015 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 "afglobal.h" | |
#include "afloader.h" | |
#include "afhints.h" | |
#include "aferrors.h" | |
#include "afmodule.h" | |
#include "afpic.h" | |
#include FT_INTERNAL_CALC_H | |
/* Initialize glyph loader. */ | |
FT_LOCAL_DEF( void ) | |
af_loader_init( AF_Loader loader, | |
AF_GlyphHints hints ) | |
{ | |
FT_ZERO( loader ); | |
loader->hints = hints; | |
} | |
/* Reset glyph loader and compute globals if necessary. */ | |
FT_LOCAL_DEF( FT_Error ) | |
af_loader_reset( AF_Loader loader, | |
AF_Module module, | |
FT_Face face ) | |
{ | |
FT_Error error = FT_Err_Ok; | |
loader->face = face; | |
loader->globals = (AF_FaceGlobals)face->autohint.data; | |
if ( loader->globals == NULL ) | |
{ | |
error = af_face_globals_new( face, &loader->globals, module ); | |
if ( !error ) | |
{ | |
face->autohint.data = | |
(FT_Pointer)loader->globals; | |
face->autohint.finalizer = | |
(FT_Generic_Finalizer)af_face_globals_free; | |
} | |
} | |
return error; | |
} | |
/* Finalize glyph loader. */ | |
FT_LOCAL_DEF( void ) | |
af_loader_done( AF_Loader loader ) | |
{ | |
loader->face = NULL; | |
loader->globals = NULL; | |
loader->hints = NULL; | |
} | |
#define af_intToFixed( i ) \ | |
( (FT_Fixed)( (FT_UInt32)(i) << 16 ) ) | |
#define af_fixedToInt( x ) \ | |
( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) ) | |
#define af_floatToFixed( f ) \ | |
( (FT_Fixed)( (f) * 65536.0 + 0.5 ) ) | |
/* Do the main work of `af_loader_load_glyph'. Note that we never */ | |
/* have to deal with composite glyphs as those get loaded into */ | |
/* FT_GLYPH_FORMAT_OUTLINE by the recursed `FT_Load_Glyph' function. */ | |
/* In the rare cases where FT_LOAD_NO_RECURSE is set, it implies */ | |
/* FT_LOAD_NO_SCALE and as such the auto-hinter is never called. */ | |
static FT_Error | |
af_loader_load_g( AF_Loader loader, | |
AF_Scaler scaler, | |
FT_UInt glyph_index, | |
FT_Int32 load_flags ) | |
{ | |
AF_Module module = loader->globals->module; | |
FT_Error error; | |
FT_Face face = loader->face; | |
AF_StyleMetrics metrics = loader->metrics; | |
AF_GlyphHints hints = loader->hints; | |
FT_GlyphSlot slot = face->glyph; | |
FT_Slot_Internal internal = slot->internal; | |
FT_GlyphLoader gloader = internal->loader; | |
FT_Int32 flags; | |
flags = load_flags | FT_LOAD_LINEAR_DESIGN; | |
error = FT_Load_Glyph( face, glyph_index, flags ); | |
if ( error ) | |
goto Exit; | |
/* | |
* Apply stem darkening (emboldening) here before hints are applied to | |
* the outline. Glyphs are scaled down proportionally to the | |
* emboldening so that curve points don't fall outside their precomputed | |
* blue zones. | |
* | |
* Any emboldening done by the font driver (e.g., the CFF driver) | |
* doesn't reach here because the autohinter loads the unprocessed | |
* glyphs in font units for analysis (functions `af_*_metrics_init_*') | |
* and then above to prepare it for the rasterizers by itself, | |
* independently of the font driver. So emboldening must be done here, | |
* within the autohinter. | |
* | |
* All glyphs to be autohinted pass through here one by one. The | |
* standard widths can therefore change from one glyph to the next, | |
* depending on what script a glyph is assigned to (each script has its | |
* own set of standard widths and other metrics). The darkening amount | |
* must therefore be recomputed for each size and | |
* `standard_{vertical,horizontal}_width' change. | |
*/ | |
if ( !module->no_stem_darkening ) | |
{ | |
AF_FaceGlobals globals = loader->globals; | |
AF_WritingSystemClass writing_system_class; | |
FT_Pos stdVW = 0; | |
FT_Pos stdHW = 0; | |
FT_Bool size_changed = face->size->metrics.x_ppem | |
!= globals->stem_darkening_for_ppem; | |
FT_Fixed em_size = af_intToFixed( face->units_per_EM ); | |
FT_Fixed em_ratio = FT_DivFix( af_intToFixed( 1000 ), em_size ); | |
FT_Matrix scale_down_matrix = { 0x10000L, 0, 0, 0x10000L }; | |
/* Skip stem darkening for broken fonts. */ | |
if ( !face->units_per_EM ) | |
goto After_Emboldening; | |
/* | |
* We depend on the writing system (script analyzers) to supply | |
* standard widths for the script of the glyph we are looking at. If | |
* it can't deliver, stem darkening is effectively disabled. | |
*/ | |
writing_system_class = | |
AF_WRITING_SYSTEM_CLASSES_GET[metrics->style_class->writing_system]; | |
if ( writing_system_class->style_metrics_getstdw ) | |
writing_system_class->style_metrics_getstdw( metrics, | |
&stdHW, | |
&stdVW ); | |
else | |
goto After_Emboldening; | |
if ( size_changed || | |
( stdVW > 0 && stdVW != globals->standard_vertical_width ) ) | |
{ | |
FT_Fixed darken_by_font_units_x, darken_x; | |
darken_by_font_units_x = | |
af_intToFixed( af_loader_compute_darkening( loader, | |
face, | |
stdVW ) ); | |
darken_x = FT_DivFix( FT_MulFix( darken_by_font_units_x, | |
face->size->metrics.x_scale ), | |
em_ratio ); | |
globals->standard_vertical_width = stdVW; | |
globals->stem_darkening_for_ppem = face->size->metrics.x_ppem; | |
globals->darken_x = af_fixedToInt( darken_x ); | |
} | |
if ( size_changed || | |
( stdHW > 0 && stdHW != globals->standard_horizontal_width ) ) | |
{ | |
FT_Fixed darken_by_font_units_y, darken_y; | |
darken_by_font_units_y = | |
af_intToFixed( af_loader_compute_darkening( loader, | |
face, | |
stdHW ) ); | |
darken_y = FT_DivFix( FT_MulFix( darken_by_font_units_y, | |
face->size->metrics.y_scale ), | |
em_ratio ); | |
globals->standard_horizontal_width = stdHW; | |
globals->stem_darkening_for_ppem = face->size->metrics.x_ppem; | |
globals->darken_y = af_fixedToInt( darken_y ); | |
/* | |
* Scale outlines down on the Y-axis to keep them inside their blue | |
* zones. The stronger the emboldening, the stronger the | |
* downscaling (plus heuristical padding to prevent outlines still | |
* falling out their zones due to rounding). | |
* | |
* Reason: `FT_Outline_Embolden' works by shifting the rightmost | |
* points of stems farther to the right, and topmost points farther | |
* up. This positions points on the Y-axis outside their | |
* pre-computed blue zones and leads to distortion when applying the | |
* hints in the code further below. Code outside this emboldening | |
* block doesn't know we are presenting it with modified outlines | |
* the analyzer didn't see! | |
* | |
* An unfortunate side effect of downscaling is that the emboldening | |
* effect is slightly decreased. The loss becomes more pronounced | |
* versus the CFF driver at smaller sizes, e.g., at 9ppem and below. | |
*/ | |
globals->scale_down_factor = | |
FT_DivFix( em_size - ( darken_by_font_units_y + af_intToFixed( 8 ) ), | |
em_size ); | |
} | |
FT_Outline_EmboldenXY( &slot->outline, | |
globals->darken_x, | |
globals->darken_y ); | |
scale_down_matrix.yy = globals->scale_down_factor; | |
FT_Outline_Transform( &slot->outline, &scale_down_matrix ); | |
} | |
After_Emboldening: | |
loader->transformed = internal->glyph_transformed; | |
if ( loader->transformed ) | |
{ | |
FT_Matrix inverse; | |
loader->trans_matrix = internal->glyph_matrix; | |
loader->trans_delta = internal->glyph_delta; | |
inverse = loader->trans_matrix; | |
if ( !FT_Matrix_Invert( &inverse ) ) | |
FT_Vector_Transform( &loader->trans_delta, &inverse ); | |
} | |
switch ( slot->format ) | |
{ | |
case FT_GLYPH_FORMAT_OUTLINE: | |
/* translate the loaded glyph when an internal transform is needed */ | |
if ( loader->transformed ) | |
FT_Outline_Translate( &slot->outline, | |
loader->trans_delta.x, | |
loader->trans_delta.y ); | |
/* compute original horizontal phantom points (and ignore */ | |
/* vertical ones) */ | |
loader->pp1.x = hints->x_delta; | |
loader->pp1.y = hints->y_delta; | |
loader->pp2.x = FT_MulFix( slot->metrics.horiAdvance, | |
hints->x_scale ) + hints->x_delta; | |
loader->pp2.y = hints->y_delta; | |
/* be sure to check for spacing glyphs */ | |
if ( slot->outline.n_points == 0 ) | |
goto Hint_Metrics; | |
/* now load the slot image into the auto-outline and run the */ | |
/* automatic hinting process */ | |
{ | |
#ifdef FT_CONFIG_OPTION_PIC | |
AF_FaceGlobals globals = loader->globals; | |
#endif | |
AF_StyleClass style_class = metrics->style_class; | |
AF_WritingSystemClass writing_system_class = | |
AF_WRITING_SYSTEM_CLASSES_GET[style_class->writing_system]; | |
if ( writing_system_class->style_hints_apply ) | |
writing_system_class->style_hints_apply( glyph_index, | |
hints, | |
&gloader->base.outline, | |
metrics ); | |
} | |
/* we now need to adjust the metrics according to the change in */ | |
/* width/positioning that occurred during the hinting process */ | |
if ( scaler->render_mode != FT_RENDER_MODE_LIGHT ) | |
{ | |
FT_Pos old_rsb, old_lsb, new_lsb; | |
FT_Pos pp1x_uh, pp2x_uh; | |
AF_AxisHints axis = &hints->axis[AF_DIMENSION_HORZ]; | |
AF_Edge edge1 = axis->edges; /* leftmost edge */ | |
AF_Edge edge2 = edge1 + | |
axis->num_edges - 1; /* rightmost edge */ | |
if ( axis->num_edges > 1 && AF_HINTS_DO_ADVANCE( hints ) ) | |
{ | |
old_rsb = loader->pp2.x - edge2->opos; | |
old_lsb = edge1->opos; | |
new_lsb = edge1->pos; | |
/* remember unhinted values to later account */ | |
/* for rounding errors */ | |
pp1x_uh = new_lsb - old_lsb; | |
pp2x_uh = edge2->pos + old_rsb; | |
/* prefer too much space over too little space */ | |
/* for very small sizes */ | |
if ( old_lsb < 24 ) | |
pp1x_uh -= 8; | |
if ( old_rsb < 24 ) | |
pp2x_uh += 8; | |
loader->pp1.x = FT_PIX_ROUND( pp1x_uh ); | |
loader->pp2.x = FT_PIX_ROUND( pp2x_uh ); | |
if ( loader->pp1.x >= new_lsb && old_lsb > 0 ) | |
loader->pp1.x -= 64; | |
if ( loader->pp2.x <= edge2->pos && old_rsb > 0 ) | |
loader->pp2.x += 64; | |
slot->lsb_delta = loader->pp1.x - pp1x_uh; | |
slot->rsb_delta = loader->pp2.x - pp2x_uh; | |
} | |
else | |
{ | |
FT_Pos pp1x = loader->pp1.x; | |
FT_Pos pp2x = loader->pp2.x; | |
loader->pp1.x = FT_PIX_ROUND( pp1x ); | |
loader->pp2.x = FT_PIX_ROUND( pp2x ); | |
slot->lsb_delta = loader->pp1.x - pp1x; | |
slot->rsb_delta = loader->pp2.x - pp2x; | |
} | |
} | |
else | |
{ | |
FT_Pos pp1x = loader->pp1.x; | |
FT_Pos pp2x = loader->pp2.x; | |
loader->pp1.x = FT_PIX_ROUND( pp1x + hints->xmin_delta ); | |
loader->pp2.x = FT_PIX_ROUND( pp2x + hints->xmax_delta ); | |
slot->lsb_delta = loader->pp1.x - pp1x; | |
slot->rsb_delta = loader->pp2.x - pp2x; | |
} | |
break; | |
default: | |
/* we don't support other formats (yet?) */ | |
error = FT_THROW( Unimplemented_Feature ); | |
} | |
Hint_Metrics: | |
{ | |
FT_BBox bbox; | |
FT_Vector vvector; | |
vvector.x = slot->metrics.vertBearingX - slot->metrics.horiBearingX; | |
vvector.y = slot->metrics.vertBearingY - slot->metrics.horiBearingY; | |
vvector.x = FT_MulFix( vvector.x, metrics->scaler.x_scale ); | |
vvector.y = FT_MulFix( vvector.y, metrics->scaler.y_scale ); | |
/* transform the hinted outline if needed */ | |
if ( loader->transformed ) | |
{ | |
FT_Outline_Transform( &gloader->base.outline, &loader->trans_matrix ); | |
FT_Vector_Transform( &vvector, &loader->trans_matrix ); | |
} | |
#if 1 | |
/* we must translate our final outline by -pp1.x and compute */ | |
/* the new metrics */ | |
if ( loader->pp1.x ) | |
FT_Outline_Translate( &gloader->base.outline, -loader->pp1.x, 0 ); | |
#endif | |
FT_Outline_Get_CBox( &gloader->base.outline, &bbox ); | |
bbox.xMin = FT_PIX_FLOOR( bbox.xMin ); | |
bbox.yMin = FT_PIX_FLOOR( bbox.yMin ); | |
bbox.xMax = FT_PIX_CEIL( bbox.xMax ); | |
bbox.yMax = FT_PIX_CEIL( bbox.yMax ); | |
slot->metrics.width = bbox.xMax - bbox.xMin; | |
slot->metrics.height = bbox.yMax - bbox.yMin; | |
slot->metrics.horiBearingX = bbox.xMin; | |
slot->metrics.horiBearingY = bbox.yMax; | |
slot->metrics.vertBearingX = FT_PIX_FLOOR( bbox.xMin + vvector.x ); | |
slot->metrics.vertBearingY = FT_PIX_FLOOR( bbox.yMax + vvector.y ); | |
/* for mono-width fonts (like Andale, Courier, etc.) we need */ | |
/* to keep the original rounded advance width; ditto for */ | |
/* digits if all have the same advance width */ | |
#if 0 | |
if ( !FT_IS_FIXED_WIDTH( slot->face ) ) | |
slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x; | |
else | |
slot->metrics.horiAdvance = FT_MulFix( slot->metrics.horiAdvance, | |
x_scale ); | |
#else | |
if ( scaler->render_mode != FT_RENDER_MODE_LIGHT && | |
( FT_IS_FIXED_WIDTH( slot->face ) || | |
( af_face_globals_is_digit( loader->globals, glyph_index ) && | |
metrics->digits_have_same_width ) ) ) | |
{ | |
slot->metrics.horiAdvance = FT_MulFix( slot->metrics.horiAdvance, | |
metrics->scaler.x_scale ); | |
/* Set delta values to 0. Otherwise code that uses them is */ | |
/* going to ruin the fixed advance width. */ | |
slot->lsb_delta = 0; | |
slot->rsb_delta = 0; | |
} | |
else | |
{ | |
/* non-spacing glyphs must stay as-is */ | |
if ( slot->metrics.horiAdvance ) | |
slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x; | |
} | |
#endif | |
slot->metrics.vertAdvance = FT_MulFix( slot->metrics.vertAdvance, | |
metrics->scaler.y_scale ); | |
slot->metrics.horiAdvance = FT_PIX_ROUND( slot->metrics.horiAdvance ); | |
slot->metrics.vertAdvance = FT_PIX_ROUND( slot->metrics.vertAdvance ); | |
#if 0 | |
/* reassign all outline fields except flags to protect them */ | |
slot->outline.n_contours = internal->loader->base.outline.n_contours; | |
slot->outline.n_points = internal->loader->base.outline.n_points; | |
slot->outline.points = internal->loader->base.outline.points; | |
slot->outline.tags = internal->loader->base.outline.tags; | |
slot->outline.contours = internal->loader->base.outline.contours; | |
#endif | |
slot->format = FT_GLYPH_FORMAT_OUTLINE; | |
} | |
Exit: | |
return error; | |
} | |
/* Load a glyph. */ | |
FT_LOCAL_DEF( FT_Error ) | |
af_loader_load_glyph( AF_Loader loader, | |
AF_Module module, | |
FT_Face face, | |
FT_UInt gindex, | |
FT_Int32 load_flags ) | |
{ | |
FT_Error error; | |
FT_Size size = face->size; | |
AF_ScalerRec scaler; | |
if ( !size ) | |
return FT_THROW( Invalid_Size_Handle ); | |
FT_ZERO( &scaler ); | |
scaler.face = face; | |
scaler.x_scale = size->metrics.x_scale; | |
scaler.x_delta = 0; /* XXX: TODO: add support for sub-pixel hinting */ | |
scaler.y_scale = size->metrics.y_scale; | |
scaler.y_delta = 0; /* XXX: TODO: add support for sub-pixel hinting */ | |
scaler.render_mode = FT_LOAD_TARGET_MODE( load_flags ); | |
scaler.flags = 0; /* XXX: fix this */ | |
error = af_loader_reset( loader, module, face ); | |
if ( !error ) | |
{ | |
AF_StyleMetrics metrics; | |
FT_UInt options = AF_STYLE_NONE_DFLT; | |
#ifdef FT_OPTION_AUTOFIT2 | |
/* XXX: undocumented hook to activate the latin2 writing system */ | |
if ( load_flags & ( 1UL << 20 ) ) | |
options = AF_STYLE_LTN2_DFLT; | |
#endif | |
error = af_face_globals_get_metrics( loader->globals, gindex, | |
options, &metrics ); | |
if ( !error ) | |
{ | |
#ifdef FT_CONFIG_OPTION_PIC | |
AF_FaceGlobals globals = loader->globals; | |
#endif | |
AF_StyleClass style_class = metrics->style_class; | |
AF_WritingSystemClass writing_system_class = | |
AF_WRITING_SYSTEM_CLASSES_GET[style_class->writing_system]; | |
loader->metrics = metrics; | |
if ( writing_system_class->style_metrics_scale ) | |
writing_system_class->style_metrics_scale( metrics, &scaler ); | |
else | |
metrics->scaler = scaler; | |
load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_IGNORE_TRANSFORM; | |
load_flags &= ~FT_LOAD_RENDER; | |
if ( writing_system_class->style_hints_init ) | |
{ | |
error = writing_system_class->style_hints_init( loader->hints, | |
metrics ); | |
if ( error ) | |
goto Exit; | |
} | |
error = af_loader_load_g( loader, &scaler, gindex, load_flags ); | |
} | |
} | |
Exit: | |
return error; | |
} | |
/* | |
* Compute amount of font units the face should be emboldened by, in | |
* analogy to the CFF driver's `cf2_computeDarkening' function. See there | |
* for details of the algorithm. | |
* | |
* XXX: Currently a crude adaption of the original algorithm. Do better? | |
*/ | |
FT_LOCAL_DEF( FT_Int32 ) | |
af_loader_compute_darkening( AF_Loader loader, | |
FT_Face face, | |
FT_Pos standard_width ) | |
{ | |
AF_Module module = loader->globals->module; | |
FT_UShort units_per_EM; | |
FT_Fixed ppem, em_ratio; | |
FT_Fixed stem_width, stem_width_per_1000, scaled_stem, darken_amount; | |
FT_Int log_base_2; | |
FT_Int x1, y1, x2, y2, x3, y3, x4, y4; | |
ppem = FT_MAX( af_intToFixed( 4 ), | |
af_intToFixed( face->size->metrics.x_ppem ) ); | |
units_per_EM = face->units_per_EM; | |
em_ratio = FT_DivFix( af_intToFixed( 1000 ), | |
af_intToFixed ( units_per_EM ) ); | |
if ( em_ratio < af_floatToFixed( .01 ) ) | |
{ | |
/* If something goes wrong, don't embolden. */ | |
return 0; | |
} | |
x1 = module->darken_params[0]; | |
y1 = module->darken_params[1]; | |
x2 = module->darken_params[2]; | |
y2 = module->darken_params[3]; | |
x3 = module->darken_params[4]; | |
y3 = module->darken_params[5]; | |
x4 = module->darken_params[6]; | |
y4 = module->darken_params[7]; | |
if ( standard_width <= 0 ) | |
{ | |
stem_width = af_intToFixed( 75 ); /* taken from cf2font.c */ | |
stem_width_per_1000 = stem_width; | |
} | |
else | |
{ | |
stem_width = af_intToFixed( standard_width ); | |
stem_width_per_1000 = FT_MulFix( stem_width, em_ratio ); | |
} | |
log_base_2 = FT_MSB( (FT_UInt32)stem_width_per_1000 ) + | |
FT_MSB( (FT_UInt32)ppem ); | |
if ( log_base_2 >= 46 ) | |
{ | |
/* possible overflow */ | |
scaled_stem = af_intToFixed( x4 ); | |
} | |
else | |
scaled_stem = FT_MulFix( stem_width_per_1000, ppem ); | |
/* now apply the darkening parameters */ | |
if ( scaled_stem < af_intToFixed( x1 ) ) | |
darken_amount = FT_DivFix( af_intToFixed( y1 ), ppem ); | |
else if ( scaled_stem < af_intToFixed( x2 ) ) | |
{ | |
FT_Int xdelta = x2 - x1; | |
FT_Int ydelta = y2 - y1; | |
FT_Int x = stem_width_per_1000 - | |
FT_DivFix( af_intToFixed( x1 ), ppem ); | |
if ( !xdelta ) | |
goto Try_x3; | |
darken_amount = FT_MulDiv( x, ydelta, xdelta ) + | |
FT_DivFix( af_intToFixed( y1 ), ppem ); | |
} | |
else if ( scaled_stem < af_intToFixed( x3 ) ) | |
{ | |
Try_x3: | |
{ | |
FT_Int xdelta = x3 - x2; | |
FT_Int ydelta = y3 - y2; | |
FT_Int x = stem_width_per_1000 - | |
FT_DivFix( af_intToFixed( x2 ), ppem ); | |
if ( !xdelta ) | |
goto Try_x4; | |
darken_amount = FT_MulDiv( x, ydelta, xdelta ) + | |
FT_DivFix( af_intToFixed( y2 ), ppem ); | |
} | |
} | |
else if ( scaled_stem < af_intToFixed( x4 ) ) | |
{ | |
Try_x4: | |
{ | |
FT_Int xdelta = x4 - x3; | |
FT_Int ydelta = y4 - y3; | |
FT_Int x = stem_width_per_1000 - | |
FT_DivFix( af_intToFixed( x3 ), ppem ); | |
if ( !xdelta ) | |
goto Use_y4; | |
darken_amount = FT_MulDiv( x, ydelta, xdelta ) + | |
FT_DivFix( af_intToFixed( y3 ), ppem ); | |
} | |
} | |
else | |
{ | |
Use_y4: | |
darken_amount = FT_DivFix( af_intToFixed( y4 ), ppem ); | |
} | |
/* Convert darken_amount from per 1000 em to true character space. */ | |
return af_fixedToInt( FT_DivFix( darken_amount, em_ratio ) ); | |
} | |
/* END */ |