| 1 | /***************************************************************************/ |
| 2 | /* */ |
| 3 | /* afloader.c */ |
| 4 | /* */ |
| 5 | /* Auto-fitter glyph loading routines (body). */ |
| 6 | /* */ |
| 7 | /* Copyright 2003-2018 by */ |
| 8 | /* David Turner, Robert Wilhelm, and Werner Lemberg. */ |
| 9 | /* */ |
| 10 | /* This file is part of the FreeType project, and may only be used, */ |
| 11 | /* modified, and distributed under the terms of the FreeType project */ |
| 12 | /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ |
| 13 | /* this file you indicate that you have read the license and */ |
| 14 | /* understand and accept it fully. */ |
| 15 | /* */ |
| 16 | /***************************************************************************/ |
| 17 | |
| 18 | |
| 19 | #include "afglobal.h" |
| 20 | #include "afloader.h" |
| 21 | #include "afhints.h" |
| 22 | #include "aferrors.h" |
| 23 | #include "afmodule.h" |
| 24 | #include "afpic.h" |
| 25 | |
| 26 | #include FT_INTERNAL_CALC_H |
| 27 | |
| 28 | |
| 29 | /* Initialize glyph loader. */ |
| 30 | |
| 31 | FT_LOCAL_DEF( void ) |
| 32 | af_loader_init( AF_Loader loader, |
| 33 | AF_GlyphHints hints ) |
| 34 | { |
| 35 | FT_ZERO( loader ); |
| 36 | |
| 37 | loader->hints = hints; |
| 38 | } |
| 39 | |
| 40 | |
| 41 | /* Reset glyph loader and compute globals if necessary. */ |
| 42 | |
| 43 | FT_LOCAL_DEF( FT_Error ) |
| 44 | af_loader_reset( AF_Loader loader, |
| 45 | AF_Module module, |
| 46 | FT_Face face ) |
| 47 | { |
| 48 | FT_Error error = FT_Err_Ok; |
| 49 | |
| 50 | |
| 51 | loader->face = face; |
| 52 | loader->globals = (AF_FaceGlobals)face->autohint.data; |
| 53 | |
| 54 | if ( !loader->globals ) |
| 55 | { |
| 56 | error = af_face_globals_new( face, &loader->globals, module ); |
| 57 | if ( !error ) |
| 58 | { |
| 59 | face->autohint.data = |
| 60 | (FT_Pointer)loader->globals; |
| 61 | face->autohint.finalizer = |
| 62 | (FT_Generic_Finalizer)af_face_globals_free; |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | return error; |
| 67 | } |
| 68 | |
| 69 | |
| 70 | /* Finalize glyph loader. */ |
| 71 | |
| 72 | FT_LOCAL_DEF( void ) |
| 73 | af_loader_done( AF_Loader loader ) |
| 74 | { |
| 75 | loader->face = NULL; |
| 76 | loader->globals = NULL; |
| 77 | loader->hints = NULL; |
| 78 | } |
| 79 | |
| 80 | |
| 81 | #define af_intToFixed( i ) \ |
| 82 | ( (FT_Fixed)( (FT_UInt32)(i) << 16 ) ) |
| 83 | #define af_fixedToInt( x ) \ |
| 84 | ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) ) |
| 85 | #define af_floatToFixed( f ) \ |
| 86 | ( (FT_Fixed)( (f) * 65536.0 + 0.5 ) ) |
| 87 | |
| 88 | |
| 89 | static FT_Error |
| 90 | af_loader_embolden_glyph_in_slot( AF_Loader loader, |
| 91 | FT_Face face, |
| 92 | AF_StyleMetrics style_metrics ) |
| 93 | { |
| 94 | FT_Error error = FT_Err_Ok; |
| 95 | |
| 96 | FT_GlyphSlot slot = face->glyph; |
| 97 | AF_FaceGlobals globals = loader->globals; |
| 98 | AF_WritingSystemClass writing_system_class; |
| 99 | |
| 100 | FT_Size_Metrics* size_metrics = &face->size->internal->autohint_metrics; |
| 101 | |
| 102 | FT_Pos stdVW = 0; |
| 103 | FT_Pos stdHW = 0; |
| 104 | |
| 105 | FT_Bool size_changed = size_metrics->x_ppem != |
| 106 | globals->stem_darkening_for_ppem; |
| 107 | |
| 108 | FT_Fixed em_size = af_intToFixed( face->units_per_EM ); |
| 109 | FT_Fixed em_ratio = FT_DivFix( af_intToFixed( 1000 ), em_size ); |
| 110 | |
| 111 | FT_Matrix scale_down_matrix = { 0x10000L, 0, 0, 0x10000L }; |
| 112 | |
| 113 | |
| 114 | /* Skip stem darkening for broken fonts. */ |
| 115 | if ( !face->units_per_EM ) |
| 116 | { |
| 117 | error = FT_ERR( Corrupted_Font_Header ); |
| 118 | goto Exit; |
| 119 | } |
| 120 | |
| 121 | /* |
| 122 | * We depend on the writing system (script analyzers) to supply |
| 123 | * standard widths for the script of the glyph we are looking at. If |
| 124 | * it can't deliver, stem darkening is disabled. |
| 125 | */ |
| 126 | writing_system_class = |
| 127 | AF_WRITING_SYSTEM_CLASSES_GET[style_metrics->style_class->writing_system]; |
| 128 | |
| 129 | if ( writing_system_class->style_metrics_getstdw ) |
| 130 | writing_system_class->style_metrics_getstdw( style_metrics, |
| 131 | &stdHW, |
| 132 | &stdVW ); |
| 133 | else |
| 134 | { |
| 135 | error = FT_ERR( Unimplemented_Feature ); |
| 136 | goto Exit; |
| 137 | } |
| 138 | |
| 139 | if ( size_changed || |
| 140 | ( stdVW > 0 && stdVW != globals->standard_vertical_width ) ) |
| 141 | { |
| 142 | FT_Fixed darken_by_font_units_x, darken_x; |
| 143 | |
| 144 | |
| 145 | darken_by_font_units_x = |
| 146 | af_intToFixed( af_loader_compute_darkening( loader, |
| 147 | face, |
| 148 | stdVW ) ); |
| 149 | darken_x = FT_DivFix( FT_MulFix( darken_by_font_units_x, |
| 150 | size_metrics->x_scale ), |
| 151 | em_ratio ); |
| 152 | |
| 153 | globals->standard_vertical_width = stdVW; |
| 154 | globals->stem_darkening_for_ppem = size_metrics->x_ppem; |
| 155 | globals->darken_x = af_fixedToInt( darken_x ); |
| 156 | } |
| 157 | |
| 158 | if ( size_changed || |
| 159 | ( stdHW > 0 && stdHW != globals->standard_horizontal_width ) ) |
| 160 | { |
| 161 | FT_Fixed darken_by_font_units_y, darken_y; |
| 162 | |
| 163 | |
| 164 | darken_by_font_units_y = |
| 165 | af_intToFixed( af_loader_compute_darkening( loader, |
| 166 | face, |
| 167 | stdHW ) ); |
| 168 | darken_y = FT_DivFix( FT_MulFix( darken_by_font_units_y, |
| 169 | size_metrics->y_scale ), |
| 170 | em_ratio ); |
| 171 | |
| 172 | globals->standard_horizontal_width = stdHW; |
| 173 | globals->stem_darkening_for_ppem = size_metrics->x_ppem; |
| 174 | globals->darken_y = af_fixedToInt( darken_y ); |
| 175 | |
| 176 | /* |
| 177 | * Scale outlines down on the Y-axis to keep them inside their blue |
| 178 | * zones. The stronger the emboldening, the stronger the downscaling |
| 179 | * (plus heuristical padding to prevent outlines still falling out |
| 180 | * their zones due to rounding). |
| 181 | * |
| 182 | * Reason: `FT_Outline_Embolden' works by shifting the rightmost |
| 183 | * points of stems farther to the right, and topmost points farther |
| 184 | * up. This positions points on the Y-axis outside their |
| 185 | * pre-computed blue zones and leads to distortion when applying the |
| 186 | * hints in the code further below. Code outside this emboldening |
| 187 | * block doesn't know we are presenting it with modified outlines the |
| 188 | * analyzer didn't see! |
| 189 | * |
| 190 | * An unfortunate side effect of downscaling is that the emboldening |
| 191 | * effect is slightly decreased. The loss becomes more pronounced |
| 192 | * versus the CFF driver at smaller sizes, e.g., at 9ppem and below. |
| 193 | */ |
| 194 | globals->scale_down_factor = |
| 195 | FT_DivFix( em_size - ( darken_by_font_units_y + af_intToFixed( 8 ) ), |
| 196 | em_size ); |
| 197 | } |
| 198 | |
| 199 | FT_Outline_EmboldenXY( &slot->outline, |
| 200 | globals->darken_x, |
| 201 | globals->darken_y ); |
| 202 | |
| 203 | scale_down_matrix.yy = globals->scale_down_factor; |
| 204 | FT_Outline_Transform( &slot->outline, &scale_down_matrix ); |
| 205 | |
| 206 | Exit: |
| 207 | return error; |
| 208 | } |
| 209 | |
| 210 | |
| 211 | /* Load the glyph at index into the current slot of a face and hint it. */ |
| 212 | |
| 213 | FT_LOCAL_DEF( FT_Error ) |
| 214 | af_loader_load_glyph( AF_Loader loader, |
| 215 | AF_Module module, |
| 216 | FT_Face face, |
| 217 | FT_UInt glyph_index, |
| 218 | FT_Int32 load_flags ) |
| 219 | { |
| 220 | FT_Error error; |
| 221 | |
| 222 | FT_Size size = face->size; |
| 223 | FT_Size_Internal size_internal = size->internal; |
| 224 | FT_GlyphSlot slot = face->glyph; |
| 225 | FT_Slot_Internal slot_internal = slot->internal; |
| 226 | FT_GlyphLoader gloader = slot_internal->loader; |
| 227 | |
| 228 | AF_GlyphHints hints = loader->hints; |
| 229 | AF_ScalerRec scaler; |
| 230 | AF_StyleMetrics style_metrics; |
| 231 | FT_UInt style_options = AF_STYLE_NONE_DFLT; |
| 232 | AF_StyleClass style_class; |
| 233 | AF_WritingSystemClass writing_system_class; |
| 234 | |
| 235 | #ifdef FT_CONFIG_OPTION_PIC |
| 236 | AF_FaceGlobals globals = loader->globals; |
| 237 | #endif |
| 238 | |
| 239 | |
| 240 | if ( !size ) |
| 241 | return FT_THROW( Invalid_Size_Handle ); |
| 242 | |
| 243 | FT_ZERO( &scaler ); |
| 244 | |
| 245 | if ( !size_internal->autohint_metrics.x_scale || |
| 246 | size_internal->autohint_mode != FT_LOAD_TARGET_MODE( load_flags ) ) |
| 247 | { |
| 248 | /* switching between hinting modes usually means different scaling */ |
| 249 | /* values; this later on enforces recomputation of everything */ |
| 250 | /* related to the current size */ |
| 251 | |
| 252 | size_internal->autohint_mode = FT_LOAD_TARGET_MODE( load_flags ); |
| 253 | size_internal->autohint_metrics = size->metrics; |
| 254 | |
| 255 | #ifdef AF_CONFIG_OPTION_TT_SIZE_METRICS |
| 256 | { |
| 257 | FT_Size_Metrics* size_metrics = &size_internal->autohint_metrics; |
| 258 | |
| 259 | |
| 260 | /* set metrics to integer values and adjust scaling accordingly; */ |
| 261 | /* this is the same setup as with TrueType fonts, cf. function */ |
| 262 | /* `tt_size_reset' in file `ttobjs.c' */ |
| 263 | size_metrics->ascender = FT_PIX_ROUND( |
| 264 | FT_MulFix( face->ascender, |
| 265 | size_metrics->y_scale ) ); |
| 266 | size_metrics->descender = FT_PIX_ROUND( |
| 267 | FT_MulFix( face->descender, |
| 268 | size_metrics->y_scale ) ); |
| 269 | size_metrics->height = FT_PIX_ROUND( |
| 270 | FT_MulFix( face->height, |
| 271 | size_metrics->y_scale ) ); |
| 272 | |
| 273 | size_metrics->x_scale = FT_DivFix( size_metrics->x_ppem << 6, |
| 274 | face->units_per_EM ); |
| 275 | size_metrics->y_scale = FT_DivFix( size_metrics->y_ppem << 6, |
| 276 | face->units_per_EM ); |
| 277 | size_metrics->max_advance = FT_PIX_ROUND( |
| 278 | FT_MulFix( face->max_advance_width, |
| 279 | size_metrics->x_scale ) ); |
| 280 | } |
| 281 | #endif /* AF_CONFIG_OPTION_TT_SIZE_METRICS */ |
| 282 | } |
| 283 | |
| 284 | /* |
| 285 | * TODO: This code currently doesn't support fractional advance widths, |
| 286 | * i.e., placing hinted glyphs at anything other than integer |
| 287 | * x-positions. This is only relevant for the warper code, which |
| 288 | * scales and shifts glyphs to optimize blackness of stems (hinting on |
| 289 | * the x-axis by nature places things on pixel integers, hinting on the |
| 290 | * y-axis only, i.e., LIGHT mode, doesn't touch the x-axis). The delta |
| 291 | * values of the scaler would need to be adjusted. |
| 292 | */ |
| 293 | scaler.face = face; |
| 294 | scaler.x_scale = size_internal->autohint_metrics.x_scale; |
| 295 | scaler.x_delta = 0; |
| 296 | scaler.y_scale = size_internal->autohint_metrics.y_scale; |
| 297 | scaler.y_delta = 0; |
| 298 | |
| 299 | scaler.render_mode = FT_LOAD_TARGET_MODE( load_flags ); |
| 300 | scaler.flags = 0; |
| 301 | |
| 302 | /* note that the fallback style can't be changed anymore */ |
| 303 | /* after the first call of `af_loader_load_glyph' */ |
| 304 | error = af_loader_reset( loader, module, face ); |
| 305 | if ( error ) |
| 306 | goto Exit; |
| 307 | |
| 308 | #ifdef FT_OPTION_AUTOFIT2 |
| 309 | /* XXX: undocumented hook to activate the latin2 writing system. */ |
| 310 | if ( load_flags & ( 1UL << 20 ) ) |
| 311 | style_options = AF_STYLE_LTN2_DFLT; |
| 312 | #endif |
| 313 | |
| 314 | /* |
| 315 | * Glyphs (really code points) are assigned to scripts. Script |
| 316 | * analysis is done lazily: For each glyph that passes through here, |
| 317 | * the corresponding script analyzer is called, but returns immediately |
| 318 | * if it has been run already. |
| 319 | */ |
| 320 | error = af_face_globals_get_metrics( loader->globals, glyph_index, |
| 321 | style_options, &style_metrics ); |
| 322 | if ( error ) |
| 323 | goto Exit; |
| 324 | |
| 325 | style_class = style_metrics->style_class; |
| 326 | writing_system_class = |
| 327 | AF_WRITING_SYSTEM_CLASSES_GET[style_class->writing_system]; |
| 328 | |
| 329 | loader->metrics = style_metrics; |
| 330 | |
| 331 | if ( writing_system_class->style_metrics_scale ) |
| 332 | writing_system_class->style_metrics_scale( style_metrics, &scaler ); |
| 333 | else |
| 334 | style_metrics->scaler = scaler; |
| 335 | |
| 336 | if ( writing_system_class->style_hints_init ) |
| 337 | { |
| 338 | error = writing_system_class->style_hints_init( hints, |
| 339 | style_metrics ); |
| 340 | if ( error ) |
| 341 | goto Exit; |
| 342 | } |
| 343 | |
| 344 | /* |
| 345 | * Do the main work of `af_loader_load_glyph'. Note that we never have |
| 346 | * to deal with composite glyphs as those get loaded into |
| 347 | * FT_GLYPH_FORMAT_OUTLINE by the recursed `FT_Load_Glyph' function. |
| 348 | * In the rare cases where FT_LOAD_NO_RECURSE is set, it implies |
| 349 | * FT_LOAD_NO_SCALE and as such the auto-hinter is never called. |
| 350 | */ |
| 351 | load_flags |= FT_LOAD_NO_SCALE | |
| 352 | FT_LOAD_IGNORE_TRANSFORM | |
| 353 | FT_LOAD_LINEAR_DESIGN; |
| 354 | load_flags &= ~FT_LOAD_RENDER; |
| 355 | |
| 356 | error = FT_Load_Glyph( face, glyph_index, load_flags ); |
| 357 | if ( error ) |
| 358 | goto Exit; |
| 359 | |
| 360 | /* |
| 361 | * Apply stem darkening (emboldening) here before hints are applied to |
| 362 | * the outline. Glyphs are scaled down proportionally to the |
| 363 | * emboldening so that curve points don't fall outside their |
| 364 | * precomputed blue zones. |
| 365 | * |
| 366 | * Any emboldening done by the font driver (e.g., the CFF driver) |
| 367 | * doesn't reach here because the autohinter loads the unprocessed |
| 368 | * glyphs in font units for analysis (functions `af_*_metrics_init_*') |
| 369 | * and then above to prepare it for the rasterizers by itself, |
| 370 | * independently of the font driver. So emboldening must be done here, |
| 371 | * within the autohinter. |
| 372 | * |
| 373 | * All glyphs to be autohinted pass through here one by one. The |
| 374 | * standard widths can therefore change from one glyph to the next, |
| 375 | * depending on what script a glyph is assigned to (each script has its |
| 376 | * own set of standard widths and other metrics). The darkening amount |
| 377 | * must therefore be recomputed for each size and |
| 378 | * `standard_{vertical,horizontal}_width' change. |
| 379 | * |
| 380 | * Ignore errors and carry on without emboldening. |
| 381 | * |
| 382 | */ |
| 383 | |
| 384 | /* stem darkening only works well in `light' mode */ |
| 385 | if ( scaler.render_mode == FT_RENDER_MODE_LIGHT && |
| 386 | ( !face->internal->no_stem_darkening || |
| 387 | ( face->internal->no_stem_darkening < 0 && |
| 388 | !module->no_stem_darkening ) ) ) |
| 389 | af_loader_embolden_glyph_in_slot( loader, face, style_metrics ); |
| 390 | |
| 391 | loader->transformed = slot_internal->glyph_transformed; |
| 392 | if ( loader->transformed ) |
| 393 | { |
| 394 | FT_Matrix inverse; |
| 395 | |
| 396 | |
| 397 | loader->trans_matrix = slot_internal->glyph_matrix; |
| 398 | loader->trans_delta = slot_internal->glyph_delta; |
| 399 | |
| 400 | inverse = loader->trans_matrix; |
| 401 | if ( !FT_Matrix_Invert( &inverse ) ) |
| 402 | FT_Vector_Transform( &loader->trans_delta, &inverse ); |
| 403 | } |
| 404 | |
| 405 | switch ( slot->format ) |
| 406 | { |
| 407 | case FT_GLYPH_FORMAT_OUTLINE: |
| 408 | /* translate the loaded glyph when an internal transform is needed */ |
| 409 | if ( loader->transformed ) |
| 410 | FT_Outline_Translate( &slot->outline, |
| 411 | loader->trans_delta.x, |
| 412 | loader->trans_delta.y ); |
| 413 | |
| 414 | /* compute original horizontal phantom points */ |
| 415 | /* (and ignore vertical ones) */ |
| 416 | loader->pp1.x = hints->x_delta; |
| 417 | loader->pp1.y = hints->y_delta; |
| 418 | loader->pp2.x = FT_MulFix( slot->metrics.horiAdvance, |
| 419 | hints->x_scale ) + hints->x_delta; |
| 420 | loader->pp2.y = hints->y_delta; |
| 421 | |
| 422 | /* be sure to check for spacing glyphs */ |
| 423 | if ( slot->outline.n_points == 0 ) |
| 424 | goto Hint_Metrics; |
| 425 | |
| 426 | /* now load the slot image into the auto-outline */ |
| 427 | /* and run the automatic hinting process */ |
| 428 | if ( writing_system_class->style_hints_apply ) |
| 429 | writing_system_class->style_hints_apply( glyph_index, |
| 430 | hints, |
| 431 | &gloader->base.outline, |
| 432 | style_metrics ); |
| 433 | |
| 434 | /* we now need to adjust the metrics according to the change in */ |
| 435 | /* width/positioning that occurred during the hinting process */ |
| 436 | if ( scaler.render_mode != FT_RENDER_MODE_LIGHT ) |
| 437 | { |
| 438 | FT_Pos old_rsb, old_lsb, new_lsb; |
| 439 | FT_Pos pp1x_uh, pp2x_uh; |
| 440 | |
| 441 | AF_AxisHints axis = &hints->axis[AF_DIMENSION_HORZ]; |
| 442 | AF_Edge edge1 = axis->edges; /* leftmost edge */ |
| 443 | AF_Edge edge2 = edge1 + |
| 444 | axis->num_edges - 1; /* rightmost edge */ |
| 445 | |
| 446 | |
| 447 | if ( axis->num_edges > 1 && AF_HINTS_DO_ADVANCE( hints ) ) |
| 448 | { |
| 449 | old_rsb = loader->pp2.x - edge2->opos; |
| 450 | /* loader->pp1.x is always zero at this point of time */ |
| 451 | old_lsb = edge1->opos /* - loader->pp1.x */; |
| 452 | new_lsb = edge1->pos; |
| 453 | |
| 454 | /* remember unhinted values to later account */ |
| 455 | /* for rounding errors */ |
| 456 | pp1x_uh = new_lsb - old_lsb; |
| 457 | pp2x_uh = edge2->pos + old_rsb; |
| 458 | |
| 459 | /* prefer too much space over too little space */ |
| 460 | /* for very small sizes */ |
| 461 | |
| 462 | if ( old_lsb < 24 ) |
| 463 | pp1x_uh -= 8; |
| 464 | |
| 465 | if ( old_rsb < 24 ) |
| 466 | pp2x_uh += 8; |
| 467 | |
| 468 | loader->pp1.x = FT_PIX_ROUND( pp1x_uh ); |
| 469 | loader->pp2.x = FT_PIX_ROUND( pp2x_uh ); |
| 470 | |
| 471 | if ( loader->pp1.x >= new_lsb && old_lsb > 0 ) |
| 472 | loader->pp1.x -= 64; |
| 473 | |
| 474 | if ( loader->pp2.x <= edge2->pos && old_rsb > 0 ) |
| 475 | loader->pp2.x += 64; |
| 476 | |
| 477 | slot->lsb_delta = loader->pp1.x - pp1x_uh; |
| 478 | slot->rsb_delta = loader->pp2.x - pp2x_uh; |
| 479 | } |
| 480 | else |
| 481 | { |
| 482 | FT_Pos pp1x = loader->pp1.x; |
| 483 | FT_Pos pp2x = loader->pp2.x; |
| 484 | |
| 485 | |
| 486 | loader->pp1.x = FT_PIX_ROUND( pp1x + hints->xmin_delta ); |
| 487 | loader->pp2.x = FT_PIX_ROUND( pp2x + hints->xmax_delta ); |
| 488 | |
| 489 | slot->lsb_delta = loader->pp1.x - pp1x; |
| 490 | slot->rsb_delta = loader->pp2.x - pp2x; |
| 491 | } |
| 492 | } |
| 493 | /* `light' mode uses integer advance widths */ |
| 494 | /* but sets `lsb_delta' and `rsb_delta' */ |
| 495 | else |
| 496 | { |
| 497 | FT_Pos pp1x = loader->pp1.x; |
| 498 | FT_Pos pp2x = loader->pp2.x; |
| 499 | |
| 500 | |
| 501 | loader->pp1.x = FT_PIX_ROUND( pp1x ); |
| 502 | loader->pp2.x = FT_PIX_ROUND( pp2x ); |
| 503 | |
| 504 | slot->lsb_delta = loader->pp1.x - pp1x; |
| 505 | slot->rsb_delta = loader->pp2.x - pp2x; |
| 506 | } |
| 507 | |
| 508 | break; |
| 509 | |
| 510 | default: |
| 511 | /* we don't support other formats (yet?) */ |
| 512 | error = FT_THROW( Unimplemented_Feature ); |
| 513 | } |
| 514 | |
| 515 | Hint_Metrics: |
| 516 | { |
| 517 | FT_BBox bbox; |
| 518 | FT_Vector vvector; |
| 519 | |
| 520 | |
| 521 | vvector.x = slot->metrics.vertBearingX - slot->metrics.horiBearingX; |
| 522 | vvector.y = slot->metrics.vertBearingY - slot->metrics.horiBearingY; |
| 523 | vvector.x = FT_MulFix( vvector.x, style_metrics->scaler.x_scale ); |
| 524 | vvector.y = FT_MulFix( vvector.y, style_metrics->scaler.y_scale ); |
| 525 | |
| 526 | /* transform the hinted outline if needed */ |
| 527 | if ( loader->transformed ) |
| 528 | { |
| 529 | FT_Outline_Transform( &gloader->base.outline, &loader->trans_matrix ); |
| 530 | FT_Vector_Transform( &vvector, &loader->trans_matrix ); |
| 531 | } |
| 532 | |
| 533 | /* we must translate our final outline by -pp1.x and compute */ |
| 534 | /* the new metrics */ |
| 535 | if ( loader->pp1.x ) |
| 536 | FT_Outline_Translate( &gloader->base.outline, -loader->pp1.x, 0 ); |
| 537 | |
| 538 | FT_Outline_Get_CBox( &gloader->base.outline, &bbox ); |
| 539 | |
| 540 | bbox.xMin = FT_PIX_FLOOR( bbox.xMin ); |
| 541 | bbox.yMin = FT_PIX_FLOOR( bbox.yMin ); |
| 542 | bbox.xMax = FT_PIX_CEIL( bbox.xMax ); |
| 543 | bbox.yMax = FT_PIX_CEIL( bbox.yMax ); |
| 544 | |
| 545 | slot->metrics.width = bbox.xMax - bbox.xMin; |
| 546 | slot->metrics.height = bbox.yMax - bbox.yMin; |
| 547 | slot->metrics.horiBearingX = bbox.xMin; |
| 548 | slot->metrics.horiBearingY = bbox.yMax; |
| 549 | |
| 550 | slot->metrics.vertBearingX = FT_PIX_FLOOR( bbox.xMin + vvector.x ); |
| 551 | slot->metrics.vertBearingY = FT_PIX_FLOOR( bbox.yMax + vvector.y ); |
| 552 | |
| 553 | /* for mono-width fonts (like Andale, Courier, etc.) we need */ |
| 554 | /* to keep the original rounded advance width; ditto for */ |
| 555 | /* digits if all have the same advance width */ |
| 556 | if ( scaler.render_mode != FT_RENDER_MODE_LIGHT && |
| 557 | ( FT_IS_FIXED_WIDTH( slot->face ) || |
| 558 | ( af_face_globals_is_digit( loader->globals, glyph_index ) && |
| 559 | style_metrics->digits_have_same_width ) ) ) |
| 560 | { |
| 561 | slot->metrics.horiAdvance = |
| 562 | FT_MulFix( slot->metrics.horiAdvance, |
| 563 | style_metrics->scaler.x_scale ); |
| 564 | |
| 565 | /* Set delta values to 0. Otherwise code that uses them is */ |
| 566 | /* going to ruin the fixed advance width. */ |
| 567 | slot->lsb_delta = 0; |
| 568 | slot->rsb_delta = 0; |
| 569 | } |
| 570 | else |
| 571 | { |
| 572 | /* non-spacing glyphs must stay as-is */ |
| 573 | if ( slot->metrics.horiAdvance ) |
| 574 | slot->metrics.horiAdvance = loader->pp2.x - loader->pp1.x; |
| 575 | } |
| 576 | |
| 577 | slot->metrics.vertAdvance = FT_MulFix( slot->metrics.vertAdvance, |
| 578 | style_metrics->scaler.y_scale ); |
| 579 | |
| 580 | slot->metrics.horiAdvance = FT_PIX_ROUND( slot->metrics.horiAdvance ); |
| 581 | slot->metrics.vertAdvance = FT_PIX_ROUND( slot->metrics.vertAdvance ); |
| 582 | |
| 583 | slot->format = FT_GLYPH_FORMAT_OUTLINE; |
| 584 | } |
| 585 | |
| 586 | Exit: |
| 587 | return error; |
| 588 | } |
| 589 | |
| 590 | |
| 591 | /* |
| 592 | * Compute amount of font units the face should be emboldened by, in |
| 593 | * analogy to the CFF driver's `cf2_computeDarkening' function. See there |
| 594 | * for details of the algorithm. |
| 595 | * |
| 596 | * XXX: Currently a crude adaption of the original algorithm. Do better? |
| 597 | */ |
| 598 | FT_LOCAL_DEF( FT_Int32 ) |
| 599 | af_loader_compute_darkening( AF_Loader loader, |
| 600 | FT_Face face, |
| 601 | FT_Pos standard_width ) |
| 602 | { |
| 603 | AF_Module module = loader->globals->module; |
| 604 | |
| 605 | FT_UShort units_per_EM; |
| 606 | FT_Fixed ppem, em_ratio; |
| 607 | FT_Fixed stem_width, stem_width_per_1000, scaled_stem, darken_amount; |
| 608 | FT_Int log_base_2; |
| 609 | FT_Int x1, y1, x2, y2, x3, y3, x4, y4; |
| 610 | |
| 611 | |
| 612 | ppem = FT_MAX( af_intToFixed( 4 ), |
| 613 | af_intToFixed( face->size->metrics.x_ppem ) ); |
| 614 | units_per_EM = face->units_per_EM; |
| 615 | |
| 616 | em_ratio = FT_DivFix( af_intToFixed( 1000 ), |
| 617 | af_intToFixed ( units_per_EM ) ); |
| 618 | if ( em_ratio < af_floatToFixed( .01 ) ) |
| 619 | { |
| 620 | /* If something goes wrong, don't embolden. */ |
| 621 | return 0; |
| 622 | } |
| 623 | |
| 624 | x1 = module->darken_params[0]; |
| 625 | y1 = module->darken_params[1]; |
| 626 | x2 = module->darken_params[2]; |
| 627 | y2 = module->darken_params[3]; |
| 628 | x3 = module->darken_params[4]; |
| 629 | y3 = module->darken_params[5]; |
| 630 | x4 = module->darken_params[6]; |
| 631 | y4 = module->darken_params[7]; |
| 632 | |
| 633 | if ( standard_width <= 0 ) |
| 634 | { |
| 635 | stem_width = af_intToFixed( 75 ); /* taken from cf2font.c */ |
| 636 | stem_width_per_1000 = stem_width; |
| 637 | } |
| 638 | else |
| 639 | { |
| 640 | stem_width = af_intToFixed( standard_width ); |
| 641 | stem_width_per_1000 = FT_MulFix( stem_width, em_ratio ); |
| 642 | } |
| 643 | |
| 644 | log_base_2 = FT_MSB( (FT_UInt32)stem_width_per_1000 ) + |
| 645 | FT_MSB( (FT_UInt32)ppem ); |
| 646 | |
| 647 | if ( log_base_2 >= 46 ) |
| 648 | { |
| 649 | /* possible overflow */ |
| 650 | scaled_stem = af_intToFixed( x4 ); |
| 651 | } |
| 652 | else |
| 653 | scaled_stem = FT_MulFix( stem_width_per_1000, ppem ); |
| 654 | |
| 655 | /* now apply the darkening parameters */ |
| 656 | if ( scaled_stem < af_intToFixed( x1 ) ) |
| 657 | darken_amount = FT_DivFix( af_intToFixed( y1 ), ppem ); |
| 658 | |
| 659 | else if ( scaled_stem < af_intToFixed( x2 ) ) |
| 660 | { |
| 661 | FT_Int xdelta = x2 - x1; |
| 662 | FT_Int ydelta = y2 - y1; |
| 663 | FT_Int x = stem_width_per_1000 - |
| 664 | FT_DivFix( af_intToFixed( x1 ), ppem ); |
| 665 | |
| 666 | |
| 667 | if ( !xdelta ) |
| 668 | goto Try_x3; |
| 669 | |
| 670 | darken_amount = FT_MulDiv( x, ydelta, xdelta ) + |
| 671 | FT_DivFix( af_intToFixed( y1 ), ppem ); |
| 672 | } |
| 673 | |
| 674 | else if ( scaled_stem < af_intToFixed( x3 ) ) |
| 675 | { |
| 676 | Try_x3: |
| 677 | { |
| 678 | FT_Int xdelta = x3 - x2; |
| 679 | FT_Int ydelta = y3 - y2; |
| 680 | FT_Int x = stem_width_per_1000 - |
| 681 | FT_DivFix( af_intToFixed( x2 ), ppem ); |
| 682 | |
| 683 | |
| 684 | if ( !xdelta ) |
| 685 | goto Try_x4; |
| 686 | |
| 687 | darken_amount = FT_MulDiv( x, ydelta, xdelta ) + |
| 688 | FT_DivFix( af_intToFixed( y2 ), ppem ); |
| 689 | } |
| 690 | } |
| 691 | |
| 692 | else if ( scaled_stem < af_intToFixed( x4 ) ) |
| 693 | { |
| 694 | Try_x4: |
| 695 | { |
| 696 | FT_Int xdelta = x4 - x3; |
| 697 | FT_Int ydelta = y4 - y3; |
| 698 | FT_Int x = stem_width_per_1000 - |
| 699 | FT_DivFix( af_intToFixed( x3 ), ppem ); |
| 700 | |
| 701 | |
| 702 | if ( !xdelta ) |
| 703 | goto Use_y4; |
| 704 | |
| 705 | darken_amount = FT_MulDiv( x, ydelta, xdelta ) + |
| 706 | FT_DivFix( af_intToFixed( y3 ), ppem ); |
| 707 | } |
| 708 | } |
| 709 | |
| 710 | else |
| 711 | { |
| 712 | Use_y4: |
| 713 | darken_amount = FT_DivFix( af_intToFixed( y4 ), ppem ); |
| 714 | } |
| 715 | |
| 716 | /* Convert darken_amount from per 1000 em to true character space. */ |
| 717 | return af_fixedToInt( FT_DivFix( darken_amount, em_ratio ) ); |
| 718 | } |
| 719 | |
| 720 | |
| 721 | /* END */ |
| 722 | |