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