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