aflatin.c (160239B)
1 /**************************************************************************** 2 * 3 * aflatin.c 4 * 5 * Auto-fitter hinting routines for latin writing system (body). 6 * 7 * Copyright (C) 2003-2025 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 <freetype/ftadvanc.h> 20 #include <freetype/internal/ftdebug.h> 21 22 #include "afglobal.h" 23 #include "aflatin.h" 24 #include "aferrors.h" 25 #include "afadjust.h" 26 27 28 /************************************************************************** 29 * 30 * The macro FT_COMPONENT is used in trace mode. It is an implicit 31 * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log 32 * messages during execution. 33 */ 34 #undef FT_COMPONENT 35 #define FT_COMPONENT aflatin 36 37 38 /* needed for computation of round vs. flat segments */ 39 #define FLAT_THRESHOLD( x ) ( x / 14 ) 40 41 42 /*************************************************************************/ 43 /*************************************************************************/ 44 /***** *****/ 45 /***** L A T I N G L O B A L M E T R I C S *****/ 46 /***** *****/ 47 /*************************************************************************/ 48 /*************************************************************************/ 49 50 51 /* Find segments and links, compute all stem widths, and initialize */ 52 /* standard width and height for the glyph with given charcode. */ 53 54 FT_LOCAL_DEF( void ) 55 af_latin_metrics_init_widths( AF_LatinMetrics metrics, 56 FT_Face face ) 57 { 58 /* scan the array of segments in each direction */ 59 AF_GlyphHintsRec hints[1]; 60 61 62 FT_TRACE5(( "\n" )); 63 FT_TRACE5(( "latin standard widths computation (style `%s')\n", 64 af_style_names[metrics->root.style_class->style] )); 65 FT_TRACE5(( "=====================================================\n" )); 66 FT_TRACE5(( "\n" )); 67 68 af_glyph_hints_init( hints, face->memory ); 69 70 metrics->axis[AF_DIMENSION_HORZ].width_count = 0; 71 metrics->axis[AF_DIMENSION_VERT].width_count = 0; 72 73 { 74 FT_Error error; 75 FT_ULong glyph_index; 76 int dim; 77 AF_LatinMetricsRec dummy[1]; 78 AF_Scaler scaler = &dummy->root.scaler; 79 80 AF_StyleClass style_class = metrics->root.style_class; 81 AF_ScriptClass script_class = af_script_classes[style_class->script]; 82 83 /* If HarfBuzz is not available, we need a pointer to a single */ 84 /* unsigned long value. */ 85 FT_ULong shaper_buf_; 86 void* shaper_buf = &shaper_buf_; 87 88 const char* p; 89 90 #ifdef FT_DEBUG_LEVEL_TRACE 91 FT_ULong ch = 0; 92 #endif 93 94 95 p = script_class->standard_charstring; 96 97 if ( ft_hb_enabled ( metrics->root.globals ) ) 98 shaper_buf = af_shaper_buf_create( metrics->root.globals ); 99 100 /* 101 * We check a list of standard characters to catch features like 102 * `c2sc' (small caps from caps) that don't contain lowercase letters 103 * by definition, or other features that mainly operate on numerals. 104 * The first match wins. 105 */ 106 107 glyph_index = 0; 108 while ( *p ) 109 { 110 unsigned int num_idx; 111 112 #ifdef FT_DEBUG_LEVEL_TRACE 113 const char* p_old; 114 #endif 115 116 117 while ( *p == ' ' ) 118 p++; 119 120 #ifdef FT_DEBUG_LEVEL_TRACE 121 p_old = p; 122 GET_UTF8_CHAR( ch, p_old ); 123 #endif 124 125 /* reject input that maps to more than a single glyph */ 126 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx ); 127 if ( num_idx > 1 ) 128 continue; 129 130 /* otherwise exit loop if we have a result */ 131 glyph_index = af_shaper_get_elem( &metrics->root, 132 shaper_buf, 133 0, 134 NULL, 135 NULL ); 136 if ( glyph_index ) 137 break; 138 } 139 140 af_shaper_buf_destroy( metrics->root.globals, shaper_buf ); 141 142 if ( !glyph_index ) 143 { 144 FT_TRACE5(( "standard character missing;" 145 " using fallback stem widths\n" )); 146 goto Exit; 147 } 148 149 FT_TRACE5(( "standard character: U+%04lX (glyph index %lu)\n", 150 ch, glyph_index )); 151 152 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE ); 153 if ( error || face->glyph->outline.n_points <= 0 ) 154 goto Exit; 155 156 FT_ZERO( dummy ); 157 158 dummy->units_per_em = metrics->units_per_em; 159 160 scaler->x_scale = 0x10000L; 161 scaler->y_scale = 0x10000L; 162 scaler->x_delta = 0; 163 scaler->y_delta = 0; 164 165 scaler->face = face; 166 scaler->render_mode = FT_RENDER_MODE_NORMAL; 167 scaler->flags = 0; 168 169 af_glyph_hints_rescale( hints, (AF_StyleMetrics)dummy ); 170 171 error = af_glyph_hints_reload( hints, &face->glyph->outline ); 172 if ( error ) 173 goto Exit; 174 175 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ ) 176 { 177 AF_LatinAxis axis = &metrics->axis[dim]; 178 AF_AxisHints axhints = &hints->axis[dim]; 179 AF_Segment seg, limit, link; 180 FT_UInt num_widths = 0; 181 182 183 error = af_latin_hints_compute_segments( hints, 184 (AF_Dimension)dim ); 185 if ( error ) 186 goto Exit; 187 188 /* 189 * We assume that the glyphs selected for the stem width 190 * computation are `featureless' enough so that the linking 191 * algorithm works fine without adjustments of its scoring 192 * function. 193 */ 194 af_latin_hints_link_segments( hints, 195 0, 196 NULL, 197 (AF_Dimension)dim ); 198 199 seg = axhints->segments; 200 limit = FT_OFFSET( seg, axhints->num_segments ); 201 202 for ( ; seg < limit; seg++ ) 203 { 204 link = seg->link; 205 206 /* we only consider stem segments there! */ 207 if ( link && link->link == seg && link > seg ) 208 { 209 FT_Pos dist; 210 211 212 dist = seg->pos - link->pos; 213 if ( dist < 0 ) 214 dist = -dist; 215 216 if ( num_widths < AF_LATIN_MAX_WIDTHS ) 217 axis->widths[num_widths++].org = dist; 218 } 219 } 220 221 /* this also replaces multiple almost identical stem widths */ 222 /* with a single one (the value 100 is heuristic) */ 223 af_sort_and_quantize_widths( &num_widths, axis->widths, 224 dummy->units_per_em / 100 ); 225 axis->width_count = num_widths; 226 } 227 228 Exit: 229 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ ) 230 { 231 AF_LatinAxis axis = &metrics->axis[dim]; 232 FT_Pos stdw; 233 234 235 stdw = ( axis->width_count > 0 ) ? axis->widths[0].org 236 : AF_LATIN_CONSTANT( metrics, 50 ); 237 238 /* let's try 20% of the smallest width */ 239 axis->edge_distance_threshold = stdw / 5; 240 axis->standard_width = stdw; 241 axis->extra_light = 0; 242 243 #ifdef FT_DEBUG_LEVEL_TRACE 244 { 245 FT_UInt i; 246 247 248 FT_TRACE5(( "%s widths:\n", 249 dim == AF_DIMENSION_VERT ? "horizontal" 250 : "vertical" )); 251 252 FT_TRACE5(( " %ld (standard)", axis->standard_width )); 253 for ( i = 1; i < axis->width_count; i++ ) 254 FT_TRACE5(( " %ld", axis->widths[i].org )); 255 256 FT_TRACE5(( "\n" )); 257 } 258 #endif 259 } 260 } 261 262 FT_TRACE5(( "\n" )); 263 264 af_glyph_hints_done( hints ); 265 } 266 267 268 static void 269 af_latin_sort_blue( FT_UInt count, 270 AF_LatinBlue* table ) 271 { 272 FT_UInt i, j; 273 AF_LatinBlue swap; 274 275 276 /* we sort from bottom to top */ 277 for ( i = 1; i < count; i++ ) 278 { 279 for ( j = i; j > 0; j-- ) 280 { 281 FT_Pos a, b; 282 283 284 if ( table[j - 1]->flags & ( AF_LATIN_BLUE_TOP | 285 AF_LATIN_BLUE_SUB_TOP ) ) 286 a = table[j - 1]->ref.org; 287 else 288 a = table[j - 1]->shoot.org; 289 290 if ( table[j]->flags & ( AF_LATIN_BLUE_TOP | 291 AF_LATIN_BLUE_SUB_TOP ) ) 292 b = table[j]->ref.org; 293 else 294 b = table[j]->shoot.org; 295 296 if ( b >= a ) 297 break; 298 299 swap = table[j]; 300 table[j] = table[j - 1]; 301 table[j - 1] = swap; 302 } 303 } 304 } 305 306 307 /* Find all blue zones. Flat segments give the reference points, */ 308 /* round segments the overshoot positions. */ 309 310 static int 311 af_latin_metrics_init_blues( AF_LatinMetrics metrics, 312 FT_Face face ) 313 { 314 FT_Pos flats [AF_BLUE_STRING_MAX_LEN]; 315 FT_Pos rounds[AF_BLUE_STRING_MAX_LEN]; 316 317 FT_UInt num_flats; 318 FT_UInt num_rounds; 319 320 AF_LatinBlue blue; 321 FT_Error error; 322 AF_LatinAxis axis = &metrics->axis[AF_DIMENSION_VERT]; 323 FT_Outline outline; 324 325 AF_StyleClass sc = metrics->root.style_class; 326 327 AF_Blue_Stringset bss = sc->blue_stringset; 328 const AF_Blue_StringRec* bs = &af_blue_stringsets[bss]; 329 330 FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em ); 331 332 /* If HarfBuzz is not available, we need a pointer to a single */ 333 /* unsigned long value. */ 334 FT_ULong shaper_buf_; 335 void* shaper_buf = &shaper_buf_; 336 337 338 /* we walk over the blue character strings as specified in the */ 339 /* style's entry in the `af_blue_stringset' array */ 340 341 FT_TRACE5(( "latin blue zones computation\n" )); 342 FT_TRACE5(( "============================\n" )); 343 FT_TRACE5(( "\n" )); 344 345 if ( ft_hb_enabled ( metrics->root.globals ) ) 346 shaper_buf = af_shaper_buf_create( metrics->root.globals ); 347 348 for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ ) 349 { 350 const char* p = &af_blue_strings[bs->string]; 351 FT_Pos* blue_ref; 352 FT_Pos* blue_shoot; 353 FT_Pos ascender; 354 FT_Pos descender; 355 356 357 #ifdef FT_DEBUG_LEVEL_TRACE 358 { 359 FT_Bool have_flag = 0; 360 361 362 FT_TRACE5(( "blue zone %u", axis->blue_count )); 363 364 if ( bs->properties ) 365 { 366 FT_TRACE5(( " (" )); 367 368 if ( AF_LATIN_IS_TOP_BLUE( bs ) ) 369 { 370 FT_TRACE5(( "top" )); 371 have_flag = 1; 372 } 373 else if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) ) 374 { 375 FT_TRACE5(( "sub top" )); 376 have_flag = 1; 377 } 378 379 if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) ) 380 { 381 if ( have_flag ) 382 FT_TRACE5(( ", " )); 383 FT_TRACE5(( "neutral" )); 384 have_flag = 1; 385 } 386 387 if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) ) 388 { 389 if ( have_flag ) 390 FT_TRACE5(( ", " )); 391 FT_TRACE5(( "small top" )); 392 have_flag = 1; 393 } 394 395 if ( AF_LATIN_IS_LONG_BLUE( bs ) ) 396 { 397 if ( have_flag ) 398 FT_TRACE5(( ", " )); 399 FT_TRACE5(( "long" )); 400 } 401 402 if ( AF_LATIN_IS_CAPITAL_BOTTOM_BLUE( bs ) ) 403 { 404 if ( have_flag ) 405 FT_TRACE5(( ", " )); 406 FT_TRACE5(( "capital bottom" )); 407 } 408 409 if ( AF_LATIN_IS_SMALL_BOTTOM_BLUE( bs ) ) 410 { 411 if ( have_flag ) 412 FT_TRACE5(( ", " )); 413 FT_TRACE5(( "small bottom" )); 414 } 415 416 FT_TRACE5(( ")" )); 417 } 418 419 FT_TRACE5(( ":\n" )); 420 } 421 #endif /* FT_DEBUG_LEVEL_TRACE */ 422 423 num_flats = 0; 424 num_rounds = 0; 425 ascender = 0; 426 descender = 0; 427 428 while ( *p ) 429 { 430 FT_ULong glyph_index; 431 FT_Long y_offset; 432 FT_Int best_point, best_contour_first, best_contour_last; 433 FT_Vector* points; 434 435 FT_Pos best_y_extremum; /* same as points.y */ 436 FT_Bool best_round = 0; 437 438 unsigned int i, num_idx; 439 440 #ifdef FT_DEBUG_LEVEL_TRACE 441 const char* p_old; 442 FT_ULong ch; 443 #endif 444 445 446 while ( *p == ' ' ) 447 p++; 448 449 #ifdef FT_DEBUG_LEVEL_TRACE 450 p_old = p; 451 GET_UTF8_CHAR( ch, p_old ); 452 #endif 453 454 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx ); 455 456 if ( !num_idx ) 457 { 458 FT_TRACE5(( " U+%04lX unavailable\n", ch )); 459 continue; 460 } 461 462 if ( AF_LATIN_IS_TOP_BLUE( bs ) ) 463 best_y_extremum = FT_LONG_MIN; 464 else 465 best_y_extremum = FT_LONG_MAX; 466 467 /* iterate over all glyph elements of the character cluster */ 468 /* and get the data of the `biggest' one */ 469 for ( i = 0; i < num_idx; i++ ) 470 { 471 FT_Pos best_y; 472 FT_Bool round = 0; 473 474 475 /* load the character in the face -- skip unknown or empty ones */ 476 glyph_index = af_shaper_get_elem( &metrics->root, 477 shaper_buf, 478 i, 479 NULL, 480 &y_offset ); 481 if ( glyph_index == 0 ) 482 { 483 FT_TRACE5(( " U+%04lX unavailable\n", ch )); 484 continue; 485 } 486 487 error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NO_SCALE ); 488 outline = face->glyph->outline; 489 /* reject glyphs that don't produce any rendering */ 490 if ( error || outline.n_points <= 2 ) 491 { 492 #ifdef FT_DEBUG_LEVEL_TRACE 493 if ( num_idx == 1 ) 494 FT_TRACE5(( " U+%04lX contains no (usable) outlines\n", ch )); 495 else 496 FT_TRACE5(( " component %u of cluster starting with U+%04lX" 497 " contains no (usable) outlines\n", i, ch )); 498 #endif 499 continue; 500 } 501 502 /* now compute min or max point indices and coordinates */ 503 points = outline.points; 504 best_point = -1; 505 best_contour_first = -1; 506 best_contour_last = -1; 507 best_y = 0; /* make compiler happy */ 508 509 { 510 FT_Int nn; 511 FT_Int pp, first, last; 512 513 514 last = -1; 515 for ( nn = 0; nn < outline.n_contours; nn++ ) 516 { 517 first = last + 1; 518 last = outline.contours[nn]; 519 520 /* Avoid single-point contours since they are never */ 521 /* rasterized. In some fonts, they correspond to mark */ 522 /* attachment points that are way outside of the glyph's */ 523 /* real outline. */ 524 if ( last <= first ) 525 continue; 526 527 if ( AF_LATIN_IS_TOP_BLUE( bs ) || 528 AF_LATIN_IS_SUB_TOP_BLUE( bs ) ) 529 { 530 for ( pp = first; pp <= last; pp++ ) 531 { 532 if ( best_point < 0 || points[pp].y > best_y ) 533 { 534 best_point = pp; 535 best_y = points[pp].y; 536 ascender = FT_MAX( ascender, best_y + y_offset ); 537 } 538 else 539 descender = FT_MIN( descender, points[pp].y + y_offset ); 540 } 541 } 542 else 543 { 544 for ( pp = first; pp <= last; pp++ ) 545 { 546 if ( best_point < 0 || points[pp].y < best_y ) 547 { 548 best_point = pp; 549 best_y = points[pp].y; 550 descender = FT_MIN( descender, best_y + y_offset ); 551 } 552 else 553 ascender = FT_MAX( ascender, points[pp].y + y_offset ); 554 } 555 } 556 557 if ( best_point > best_contour_last ) 558 { 559 best_contour_first = first; 560 best_contour_last = last; 561 } 562 } 563 } 564 565 /* now check whether the point belongs to a straight or round */ 566 /* segment; we first need to find in which contour the extremum */ 567 /* lies, then inspect its previous and next points */ 568 if ( best_point >= 0 ) 569 { 570 FT_Pos best_x = points[best_point].x; 571 FT_Int prev, next; 572 FT_Int best_segment_first, best_segment_last; 573 FT_Int best_on_point_first, best_on_point_last; 574 FT_Pos dist; 575 576 577 best_segment_first = best_point; 578 best_segment_last = best_point; 579 580 if ( FT_CURVE_TAG( outline.tags[best_point] ) == FT_CURVE_TAG_ON ) 581 { 582 best_on_point_first = best_point; 583 best_on_point_last = best_point; 584 } 585 else 586 { 587 best_on_point_first = -1; 588 best_on_point_last = -1; 589 } 590 591 /* look for the previous and next points on the contour */ 592 /* that are not on the same Y coordinate, then threshold */ 593 /* the `closeness'... */ 594 prev = best_point; 595 next = prev; 596 597 do 598 { 599 if ( prev > best_contour_first ) 600 prev--; 601 else 602 prev = best_contour_last; 603 604 dist = FT_ABS( points[prev].y - best_y ); 605 /* accept a small distance or a small angle (both values are */ 606 /* heuristic; value 20 corresponds to approx. 2.9 degrees) */ 607 if ( dist > 5 ) 608 if ( FT_ABS( points[prev].x - best_x ) <= 20 * dist ) 609 break; 610 611 best_segment_first = prev; 612 613 if ( FT_CURVE_TAG( outline.tags[prev] ) == FT_CURVE_TAG_ON ) 614 { 615 best_on_point_first = prev; 616 if ( best_on_point_last < 0 ) 617 best_on_point_last = prev; 618 } 619 620 } while ( prev != best_point ); 621 622 do 623 { 624 if ( next < best_contour_last ) 625 next++; 626 else 627 next = best_contour_first; 628 629 dist = FT_ABS( points[next].y - best_y ); 630 if ( dist > 5 ) 631 if ( FT_ABS( points[next].x - best_x ) <= 20 * dist ) 632 break; 633 634 best_segment_last = next; 635 636 if ( FT_CURVE_TAG( outline.tags[next] ) == FT_CURVE_TAG_ON ) 637 { 638 best_on_point_last = next; 639 if ( best_on_point_first < 0 ) 640 best_on_point_first = next; 641 } 642 643 } while ( next != best_point ); 644 645 if ( AF_LATIN_IS_LONG_BLUE( bs ) ) 646 { 647 /* If this flag is set, we have an additional constraint to */ 648 /* get the blue zone distance: Find a segment of the topmost */ 649 /* (or bottommost) contour that is longer than a heuristic */ 650 /* threshold. This ensures that small bumps in the outline */ 651 /* are ignored (for example, the `vertical serifs' found in */ 652 /* many Hebrew glyph designs). */ 653 654 /* If this segment is long enough, we are done. Otherwise, */ 655 /* search the segment next to the extremum that is long */ 656 /* enough, has the same direction, and a not too large */ 657 /* vertical distance from the extremum. Note that the */ 658 /* algorithm doesn't check whether the found segment is */ 659 /* actually the one (vertically) nearest to the extremum. */ 660 661 /* heuristic threshold value */ 662 FT_Pos length_threshold = metrics->units_per_em / 25; 663 664 665 dist = FT_ABS( points[best_segment_last].x - 666 points[best_segment_first].x ); 667 668 if ( dist < length_threshold && 669 best_segment_last - best_segment_first + 2 <= 670 best_contour_last - best_contour_first ) 671 { 672 /* heuristic threshold value */ 673 FT_Pos height_threshold = metrics->units_per_em / 4; 674 675 FT_Int first; 676 FT_Int last; 677 FT_Bool hit; 678 679 /* we intentionally declare these two variables */ 680 /* outside of the loop since various compilers emit */ 681 /* incorrect warning messages otherwise, talking about */ 682 /* `possibly uninitialized variables' */ 683 FT_Int p_first = 0; /* make compiler happy */ 684 FT_Int p_last = 0; 685 686 FT_Bool left2right; 687 688 689 /* compute direction */ 690 prev = best_point; 691 692 do 693 { 694 if ( prev > best_contour_first ) 695 prev--; 696 else 697 prev = best_contour_last; 698 699 if ( points[prev].x != best_x ) 700 break; 701 702 } while ( prev != best_point ); 703 704 /* skip glyph for the degenerate case */ 705 if ( prev == best_point ) 706 continue; 707 708 left2right = FT_BOOL( points[prev].x < points[best_point].x ); 709 710 first = best_segment_last; 711 last = first; 712 hit = 0; 713 714 do 715 { 716 FT_Bool l2r; 717 FT_Pos d; 718 719 720 if ( !hit ) 721 { 722 /* no hit; adjust first point */ 723 first = last; 724 725 /* also adjust first and last on point */ 726 if ( FT_CURVE_TAG( outline.tags[first] ) == 727 FT_CURVE_TAG_ON ) 728 { 729 p_first = first; 730 p_last = first; 731 } 732 else 733 { 734 p_first = -1; 735 p_last = -1; 736 } 737 738 hit = 1; 739 } 740 741 if ( last < best_contour_last ) 742 last++; 743 else 744 last = best_contour_first; 745 746 if ( FT_ABS( best_y - points[first].y ) > height_threshold ) 747 { 748 /* vertical distance too large */ 749 hit = 0; 750 continue; 751 } 752 753 /* same test as above */ 754 dist = FT_ABS( points[last].y - points[first].y ); 755 if ( dist > 5 ) 756 if ( FT_ABS( points[last].x - points[first].x ) <= 757 20 * dist ) 758 { 759 hit = 0; 760 continue; 761 } 762 763 if ( FT_CURVE_TAG( outline.tags[last] ) == FT_CURVE_TAG_ON ) 764 { 765 p_last = last; 766 if ( p_first < 0 ) 767 p_first = last; 768 } 769 770 l2r = FT_BOOL( points[first].x < points[last].x ); 771 d = FT_ABS( points[last].x - points[first].x ); 772 773 if ( l2r == left2right && 774 d >= length_threshold ) 775 { 776 /* all constraints are met; update segment after */ 777 /* finding its end */ 778 do 779 { 780 if ( last < best_contour_last ) 781 last++; 782 else 783 last = best_contour_first; 784 785 d = FT_ABS( points[last].y - points[first].y ); 786 if ( d > 5 ) 787 if ( FT_ABS( points[next].x - points[first].x ) <= 788 20 * dist ) 789 { 790 if ( last > best_contour_first ) 791 last--; 792 else 793 last = best_contour_last; 794 break; 795 } 796 797 p_last = last; 798 799 if ( FT_CURVE_TAG( outline.tags[last] ) == 800 FT_CURVE_TAG_ON ) 801 { 802 p_last = last; 803 if ( p_first < 0 ) 804 p_first = last; 805 } 806 807 } while ( last != best_segment_first ); 808 809 best_y = points[first].y; 810 811 best_segment_first = first; 812 best_segment_last = last; 813 814 best_on_point_first = p_first; 815 best_on_point_last = p_last; 816 817 break; 818 } 819 820 } while ( last != best_segment_first ); 821 } 822 } 823 824 /* for computing blue zones, we add the y offset as returned */ 825 /* by the currently used OpenType feature -- for example, */ 826 /* superscript glyphs might be identical to subscript glyphs */ 827 /* with a vertical shift */ 828 best_y += y_offset; 829 830 #ifdef FT_DEBUG_LEVEL_TRACE 831 if ( num_idx == 1 ) 832 FT_TRACE5(( " U+%04lX: best_y = %5ld", ch, best_y )); 833 else 834 FT_TRACE5(( " component %u of cluster starting with U+%04lX:" 835 " best_y = %5ld", i, ch, best_y )); 836 #endif 837 838 /* now set the `round' flag depending on the segment's kind: */ 839 /* */ 840 /* - if the horizontal distance between the first and last */ 841 /* `on' point is larger than a heuristic threshold */ 842 /* we have a flat segment */ 843 /* - if either the first or the last point of the segment is */ 844 /* an `off' point, the segment is round, otherwise it is */ 845 /* flat */ 846 if ( best_on_point_first >= 0 && 847 best_on_point_last >= 0 && 848 ( FT_ABS( points[best_on_point_last].x - 849 points[best_on_point_first].x ) ) > 850 flat_threshold ) 851 round = 0; 852 else 853 round = FT_BOOL( 854 FT_CURVE_TAG( outline.tags[best_segment_first] ) != 855 FT_CURVE_TAG_ON || 856 FT_CURVE_TAG( outline.tags[best_segment_last] ) != 857 FT_CURVE_TAG_ON ); 858 859 if ( round && AF_LATIN_IS_NEUTRAL_BLUE( bs ) ) 860 { 861 /* only use flat segments for a neutral blue zone */ 862 FT_TRACE5(( " (round, skipped)\n" )); 863 continue; 864 } 865 866 FT_TRACE5(( " (%s)\n", round ? "round" : "flat" )); 867 } 868 869 if ( AF_LATIN_IS_TOP_BLUE( bs ) ) 870 { 871 if ( best_y > best_y_extremum ) 872 { 873 best_y_extremum = best_y; 874 best_round = round; 875 } 876 } 877 else 878 { 879 if ( best_y < best_y_extremum ) 880 { 881 best_y_extremum = best_y; 882 best_round = round; 883 } 884 } 885 886 } /* end for loop */ 887 888 if ( !( best_y_extremum == FT_LONG_MIN || 889 best_y_extremum == FT_LONG_MAX ) ) 890 { 891 if ( best_round ) 892 rounds[num_rounds++] = best_y_extremum; 893 else 894 flats[num_flats++] = best_y_extremum; 895 } 896 897 } /* end while loop */ 898 899 if ( num_flats == 0 && num_rounds == 0 ) 900 { 901 /* 902 * we couldn't find a single glyph to compute this blue zone, 903 * we will simply ignore it then 904 */ 905 FT_TRACE5(( " empty\n" )); 906 continue; 907 } 908 909 /* we have computed the contents of the `rounds' and `flats' tables, */ 910 /* now determine the reference and overshoot position of the blue -- */ 911 /* we simply take the median value after a simple sort */ 912 af_sort_pos( num_rounds, rounds ); 913 af_sort_pos( num_flats, flats ); 914 915 blue = &axis->blues[axis->blue_count]; 916 blue_ref = &blue->ref.org; 917 blue_shoot = &blue->shoot.org; 918 919 axis->blue_count++; 920 921 if ( num_flats == 0 ) 922 { 923 *blue_ref = 924 *blue_shoot = rounds[num_rounds / 2]; 925 } 926 else if ( num_rounds == 0 ) 927 { 928 *blue_ref = 929 *blue_shoot = flats[num_flats / 2]; 930 } 931 else 932 { 933 *blue_ref = flats [num_flats / 2]; 934 *blue_shoot = rounds[num_rounds / 2]; 935 } 936 937 /* there are sometimes problems: if the overshoot position of top */ 938 /* zones is under its reference position, or the opposite for bottom */ 939 /* zones. We must thus check everything there and correct the errors */ 940 if ( *blue_shoot != *blue_ref ) 941 { 942 FT_Pos ref = *blue_ref; 943 FT_Pos shoot = *blue_shoot; 944 FT_Bool over_ref = FT_BOOL( shoot > ref ); 945 946 947 if ( ( AF_LATIN_IS_TOP_BLUE( bs ) || 948 AF_LATIN_IS_SUB_TOP_BLUE( bs) ) ^ over_ref ) 949 { 950 *blue_ref = 951 *blue_shoot = ( shoot + ref ) / 2; 952 953 FT_TRACE5(( " [overshoot smaller than reference," 954 " taking mean value]\n" )); 955 } 956 } 957 958 blue->ascender = ascender; 959 blue->descender = descender; 960 961 blue->flags = 0; 962 if ( AF_LATIN_IS_TOP_BLUE( bs ) ) 963 blue->flags |= AF_LATIN_BLUE_TOP; 964 if ( AF_LATIN_IS_SUB_TOP_BLUE( bs ) ) 965 blue->flags |= AF_LATIN_BLUE_SUB_TOP; 966 if ( AF_LATIN_IS_NEUTRAL_BLUE( bs ) ) 967 blue->flags |= AF_LATIN_BLUE_NEUTRAL; 968 if ( AF_LATIN_IS_CAPITAL_BOTTOM_BLUE( bs ) ) 969 blue->flags |= AF_LATIN_BLUE_BOTTOM; 970 if ( AF_LATIN_IS_SMALL_BOTTOM_BLUE( bs ) ) 971 blue->flags |= AF_LATIN_BLUE_BOTTOM_SMALL; 972 973 /* 974 * The following flag is used later to adjust the y and x scales 975 * in order to optimize the pixel grid alignment of the top of small 976 * letters. 977 */ 978 if ( AF_LATIN_IS_X_HEIGHT_BLUE( bs ) ) 979 blue->flags |= AF_LATIN_BLUE_ADJUSTMENT; 980 981 FT_TRACE5(( " -> reference = %ld\n", *blue_ref )); 982 FT_TRACE5(( " overshoot = %ld\n", *blue_shoot )); 983 984 } /* end for loop */ 985 986 af_shaper_buf_destroy( metrics->root.globals, shaper_buf ); 987 988 if ( axis->blue_count ) 989 { 990 /* we finally check whether blue zones are ordered; */ 991 /* `ref' and `shoot' values of two blue zones must not overlap */ 992 993 FT_UInt i; 994 AF_LatinBlue blue_sorted[AF_BLUE_STRINGSET_MAX_LEN]; 995 996 997 for ( i = 0; i < axis->blue_count; i++ ) 998 blue_sorted[i] = &axis->blues[i]; 999 1000 /* sort bottoms of blue zones... */ 1001 af_latin_sort_blue( axis->blue_count, blue_sorted ); 1002 1003 /* ...and adjust top values if necessary */ 1004 for ( i = 0; i < axis->blue_count - 1; i++ ) 1005 { 1006 FT_Pos* a; 1007 FT_Pos* b; 1008 1009 #ifdef FT_DEBUG_LEVEL_TRACE 1010 FT_Bool a_is_top = 0; 1011 #endif 1012 1013 1014 if ( blue_sorted[i]->flags & ( AF_LATIN_BLUE_TOP | 1015 AF_LATIN_BLUE_SUB_TOP ) ) 1016 { 1017 a = &blue_sorted[i]->shoot.org; 1018 #ifdef FT_DEBUG_LEVEL_TRACE 1019 a_is_top = 1; 1020 #endif 1021 } 1022 else 1023 a = &blue_sorted[i]->ref.org; 1024 1025 if ( blue_sorted[i + 1]->flags & ( AF_LATIN_BLUE_TOP | 1026 AF_LATIN_BLUE_SUB_TOP ) ) 1027 b = &blue_sorted[i + 1]->shoot.org; 1028 else 1029 b = &blue_sorted[i + 1]->ref.org; 1030 1031 if ( *a > *b ) 1032 { 1033 *a = *b; 1034 FT_TRACE5(( "blue zone overlap:" 1035 " adjusting %s %td to %ld\n", 1036 a_is_top ? "overshoot" : "reference", 1037 blue_sorted[i] - axis->blues, 1038 *a )); 1039 } 1040 } 1041 1042 FT_TRACE5(( "\n" )); 1043 1044 return 0; 1045 } 1046 else 1047 { 1048 /* disable hinting for the current style if there are no blue zones */ 1049 1050 AF_FaceGlobals globals = metrics->root.globals; 1051 FT_UShort* gstyles = globals->glyph_styles; 1052 1053 FT_UInt i; 1054 1055 1056 FT_TRACE5(( "no blue zones found:" 1057 " hinting disabled for this style\n" )); 1058 1059 for ( i = 0; i < globals->glyph_count; i++ ) 1060 { 1061 if ( ( gstyles[i] & AF_STYLE_MASK ) == sc->style ) 1062 gstyles[i] = AF_STYLE_NONE_DFLT; 1063 } 1064 1065 FT_TRACE5(( "\n" )); 1066 1067 return 1; 1068 } 1069 } 1070 1071 1072 /* Check whether all ASCII digits have the same advance width. */ 1073 1074 FT_LOCAL_DEF( void ) 1075 af_latin_metrics_check_digits( AF_LatinMetrics metrics, 1076 FT_Face face ) 1077 { 1078 FT_Bool started = 0, same_width = 1; 1079 FT_Long advance = 0, old_advance = 0; 1080 1081 /* If HarfBuzz is not available, we need a pointer to a single */ 1082 /* unsigned long value. */ 1083 FT_ULong shaper_buf_; 1084 void* shaper_buf = &shaper_buf_; 1085 1086 /* in all supported charmaps, digits have character codes 0x30-0x39 */ 1087 const char digits[] = "0 1 2 3 4 5 6 7 8 9"; 1088 const char* p; 1089 1090 FT_UNUSED( face ); 1091 1092 1093 p = digits; 1094 1095 if ( ft_hb_enabled ( metrics->root.globals ) ) 1096 shaper_buf = af_shaper_buf_create( metrics->root.globals ); 1097 1098 while ( *p ) 1099 { 1100 FT_ULong glyph_index; 1101 unsigned int num_idx; 1102 1103 1104 /* reject input that maps to more than a single glyph */ 1105 p = af_shaper_get_cluster( p, &metrics->root, shaper_buf, &num_idx ); 1106 if ( num_idx > 1 ) 1107 continue; 1108 1109 glyph_index = af_shaper_get_elem( &metrics->root, 1110 shaper_buf, 1111 0, 1112 &advance, 1113 NULL ); 1114 if ( !glyph_index ) 1115 continue; 1116 1117 if ( started ) 1118 { 1119 if ( advance != old_advance ) 1120 { 1121 same_width = 0; 1122 break; 1123 } 1124 } 1125 else 1126 { 1127 old_advance = advance; 1128 started = 1; 1129 } 1130 } 1131 1132 af_shaper_buf_destroy( metrics->root.globals, shaper_buf ); 1133 1134 metrics->root.digits_have_same_width = same_width; 1135 } 1136 1137 1138 /* Initialize global metrics. */ 1139 1140 FT_LOCAL_DEF( FT_Error ) 1141 af_latin_metrics_init( AF_StyleMetrics metrics_, /* AF_LatinMetrics */ 1142 FT_Face face ) 1143 { 1144 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 1145 1146 FT_Error error = FT_Err_Ok; 1147 1148 FT_CharMap oldmap = face->charmap; 1149 1150 1151 metrics->units_per_em = face->units_per_EM; 1152 1153 if ( !FT_Select_Charmap( face, FT_ENCODING_UNICODE ) ) 1154 { 1155 af_latin_metrics_init_widths( metrics, face ); 1156 if ( af_latin_metrics_init_blues( metrics, face ) ) 1157 { 1158 /* use internal error code to indicate missing blue zones */ 1159 error = -1; 1160 goto Exit; 1161 } 1162 af_latin_metrics_check_digits( metrics, face ); 1163 } 1164 1165 af_reverse_character_map_new( &metrics->root.reverse_charmap, 1166 &metrics->root ); 1167 1168 Exit: 1169 face->charmap = oldmap; 1170 return error; 1171 } 1172 1173 1174 /* Adjust scaling value, then scale and shift widths */ 1175 /* and blue zones (if applicable) for given dimension. */ 1176 1177 static void 1178 af_latin_metrics_scale_dim( AF_LatinMetrics metrics, 1179 AF_Scaler scaler, 1180 AF_Dimension dim ) 1181 { 1182 FT_Fixed scale; 1183 FT_Pos delta; 1184 AF_LatinAxis axis; 1185 FT_UInt nn; 1186 1187 1188 if ( dim == AF_DIMENSION_HORZ ) 1189 { 1190 scale = scaler->x_scale; 1191 delta = scaler->x_delta; 1192 } 1193 else 1194 { 1195 scale = scaler->y_scale; 1196 delta = scaler->y_delta; 1197 } 1198 1199 axis = &metrics->axis[dim]; 1200 1201 if ( axis->org_scale == scale && axis->org_delta == delta ) 1202 return; 1203 1204 axis->org_scale = scale; 1205 axis->org_delta = delta; 1206 1207 /* 1208 * correct X and Y scale to optimize the alignment of the top of small 1209 * letters to the pixel grid 1210 */ 1211 { 1212 AF_LatinAxis Axis = &metrics->axis[AF_DIMENSION_VERT]; 1213 AF_LatinBlue blue = NULL; 1214 1215 1216 for ( nn = 0; nn < Axis->blue_count; nn++ ) 1217 { 1218 if ( Axis->blues[nn].flags & AF_LATIN_BLUE_ADJUSTMENT ) 1219 { 1220 blue = &Axis->blues[nn]; 1221 break; 1222 } 1223 } 1224 1225 if ( blue ) 1226 { 1227 FT_Pos scaled; 1228 FT_Pos threshold; 1229 FT_Pos fitted; 1230 FT_UInt limit; 1231 FT_UInt ppem; 1232 1233 1234 scaled = FT_MulFix( blue->shoot.org, scale ); 1235 ppem = metrics->root.scaler.face->size->metrics.x_ppem; 1236 limit = metrics->root.globals->increase_x_height; 1237 threshold = 40; 1238 1239 /* if the `increase-x-height' property is active, */ 1240 /* we round up much more often */ 1241 if ( limit && 1242 ppem <= limit && 1243 ppem >= AF_PROP_INCREASE_X_HEIGHT_MIN ) 1244 threshold = 52; 1245 1246 fitted = ( scaled + threshold ) & ~63; 1247 1248 if ( scaled != fitted ) 1249 { 1250 #if 0 1251 if ( dim == AF_DIMENSION_HORZ ) 1252 { 1253 if ( fitted < scaled ) 1254 scale -= scale / 50; /* scale *= 0.98 */ 1255 } 1256 else 1257 #endif 1258 if ( dim == AF_DIMENSION_VERT ) 1259 { 1260 FT_Pos max_height; 1261 FT_Pos dist; 1262 FT_Fixed new_scale; 1263 1264 1265 new_scale = FT_MulDiv( scale, fitted, scaled ); 1266 1267 /* the scaling should not change the result by more than two pixels */ 1268 max_height = metrics->units_per_em; 1269 1270 for ( nn = 0; nn < Axis->blue_count; nn++ ) 1271 { 1272 max_height = FT_MAX( max_height, Axis->blues[nn].ascender ); 1273 max_height = FT_MAX( max_height, -Axis->blues[nn].descender ); 1274 } 1275 1276 dist = FT_MulFix( max_height, new_scale - scale ); 1277 1278 if ( -128 < dist && dist < 128 ) 1279 { 1280 FT_TRACE5(( "af_latin_metrics_scale_dim:" 1281 " x height alignment (style `%s'):\n", 1282 af_style_names[metrics->root.style_class->style] )); 1283 FT_TRACE5(( " " 1284 " vertical scaling changed" 1285 " from %.5f to %.5f (by %ld%%)\n", 1286 (double)scale / 65536, 1287 (double)new_scale / 65536, 1288 ( fitted - scaled ) * 100 / scaled )); 1289 FT_TRACE5(( "\n" )); 1290 1291 scale = new_scale; 1292 } 1293 #ifdef FT_DEBUG_LEVEL_TRACE 1294 else 1295 { 1296 FT_TRACE5(( "af_latin_metrics_scale_dim:" 1297 " x height alignment (style `%s'):\n", 1298 af_style_names[metrics->root.style_class->style] )); 1299 FT_TRACE5(( " " 1300 " excessive vertical scaling abandoned\n" )); 1301 FT_TRACE5(( "\n" )); 1302 } 1303 #endif 1304 } 1305 } 1306 } 1307 } 1308 1309 axis->scale = scale; 1310 axis->delta = delta; 1311 1312 if ( dim == AF_DIMENSION_HORZ ) 1313 { 1314 metrics->root.scaler.x_scale = scale; 1315 metrics->root.scaler.x_delta = delta; 1316 } 1317 else 1318 { 1319 metrics->root.scaler.y_scale = scale; 1320 metrics->root.scaler.y_delta = delta; 1321 } 1322 1323 FT_TRACE5(( "%s widths (style `%s')\n", 1324 dim == AF_DIMENSION_HORZ ? "horizontal" : "vertical", 1325 af_style_names[metrics->root.style_class->style] )); 1326 1327 /* scale the widths */ 1328 for ( nn = 0; nn < axis->width_count; nn++ ) 1329 { 1330 AF_Width width = axis->widths + nn; 1331 1332 1333 width->cur = FT_MulFix( width->org, scale ); 1334 width->fit = width->cur; 1335 1336 FT_TRACE5(( " %ld scaled to %.2f\n", 1337 width->org, 1338 (double)width->cur / 64 )); 1339 } 1340 1341 FT_TRACE5(( "\n" )); 1342 1343 /* an extra-light axis corresponds to a standard width that is */ 1344 /* smaller than 5/8 pixels */ 1345 axis->extra_light = 1346 FT_BOOL( FT_MulFix( axis->standard_width, scale ) < 32 + 8 ); 1347 1348 #ifdef FT_DEBUG_LEVEL_TRACE 1349 if ( axis->extra_light ) 1350 { 1351 FT_TRACE5(( "`%s' style is extra light (at current resolution)\n", 1352 af_style_names[metrics->root.style_class->style] )); 1353 FT_TRACE5(( "\n" )); 1354 } 1355 #endif 1356 1357 if ( dim == AF_DIMENSION_VERT ) 1358 { 1359 #ifdef FT_DEBUG_LEVEL_TRACE 1360 if ( axis->blue_count ) 1361 FT_TRACE5(( "blue zones (style `%s')\n", 1362 af_style_names[metrics->root.style_class->style] )); 1363 #endif 1364 1365 /* scale the blue zones */ 1366 for ( nn = 0; nn < axis->blue_count; nn++ ) 1367 { 1368 AF_LatinBlue blue = &axis->blues[nn]; 1369 FT_Pos dist; 1370 1371 1372 blue->ref.cur = FT_MulFix( blue->ref.org, scale ) + delta; 1373 blue->ref.fit = blue->ref.cur; 1374 blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta; 1375 blue->shoot.fit = blue->shoot.cur; 1376 blue->flags &= ~AF_LATIN_BLUE_ACTIVE; 1377 1378 /* a blue zone is only active if it is less than 3/4 pixels tall */ 1379 dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale ); 1380 if ( dist <= 48 && dist >= -48 ) 1381 { 1382 #if 0 1383 FT_Pos delta1; 1384 #endif 1385 FT_Pos delta2; 1386 1387 1388 /* use discrete values for blue zone widths */ 1389 1390 #if 0 1391 1392 /* generic, original code */ 1393 delta1 = blue->shoot.org - blue->ref.org; 1394 delta2 = delta1; 1395 if ( delta1 < 0 ) 1396 delta2 = -delta2; 1397 1398 delta2 = FT_MulFix( delta2, scale ); 1399 1400 if ( delta2 < 32 ) 1401 delta2 = 0; 1402 else if ( delta2 < 64 ) 1403 delta2 = 32 + ( ( ( delta2 - 32 ) + 16 ) & ~31 ); 1404 else 1405 delta2 = FT_PIX_ROUND( delta2 ); 1406 1407 if ( delta1 < 0 ) 1408 delta2 = -delta2; 1409 1410 blue->ref.fit = FT_PIX_ROUND( blue->ref.cur ); 1411 blue->shoot.fit = blue->ref.fit + delta2; 1412 1413 #else 1414 1415 /* simplified version due to abs(dist) <= 48 */ 1416 delta2 = dist; 1417 if ( dist < 0 ) 1418 delta2 = -delta2; 1419 1420 if ( delta2 < 32 ) 1421 delta2 = 0; 1422 else if ( delta2 < 48 ) 1423 delta2 = 32; 1424 else 1425 delta2 = 64; 1426 1427 if ( dist < 0 ) 1428 delta2 = -delta2; 1429 1430 blue->ref.fit = FT_PIX_ROUND( blue->ref.cur ); 1431 blue->shoot.fit = blue->ref.fit - delta2; 1432 1433 #endif 1434 1435 blue->flags |= AF_LATIN_BLUE_ACTIVE; 1436 } 1437 } 1438 1439 /* use sub-top blue zone only if it doesn't overlap with */ 1440 /* another (non-sup-top) blue zone; otherwise, the */ 1441 /* effect would be similar to a neutral blue zone, which */ 1442 /* is not desired here */ 1443 for ( nn = 0; nn < axis->blue_count; nn++ ) 1444 { 1445 AF_LatinBlue blue = &axis->blues[nn]; 1446 FT_UInt i; 1447 1448 1449 if ( !( blue->flags & AF_LATIN_BLUE_SUB_TOP ) ) 1450 continue; 1451 if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) ) 1452 continue; 1453 1454 for ( i = 0; i < axis->blue_count; i++ ) 1455 { 1456 AF_LatinBlue b = &axis->blues[i]; 1457 1458 1459 if ( b->flags & AF_LATIN_BLUE_SUB_TOP ) 1460 continue; 1461 if ( !( b->flags & AF_LATIN_BLUE_ACTIVE ) ) 1462 continue; 1463 1464 if ( b->ref.fit <= blue->shoot.fit && 1465 b->shoot.fit >= blue->ref.fit ) 1466 { 1467 blue->flags &= ~AF_LATIN_BLUE_ACTIVE; 1468 break; 1469 } 1470 } 1471 } 1472 1473 #ifdef FT_DEBUG_LEVEL_TRACE 1474 for ( nn = 0; nn < axis->blue_count; nn++ ) 1475 { 1476 AF_LatinBlue blue = &axis->blues[nn]; 1477 1478 1479 FT_TRACE5(( " reference %u: %ld scaled to %.2f%s\n", 1480 nn, 1481 blue->ref.org, 1482 (double)blue->ref.fit / 64, 1483 ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? "" 1484 : " (inactive)" )); 1485 FT_TRACE5(( " overshoot %u: %ld scaled to %.2f%s\n", 1486 nn, 1487 blue->shoot.org, 1488 (double)blue->shoot.fit / 64, 1489 ( blue->flags & AF_LATIN_BLUE_ACTIVE ) ? "" 1490 : " (inactive)" )); 1491 } 1492 #endif 1493 } 1494 } 1495 1496 1497 FT_CALLBACK_DEF( void ) 1498 af_latin_metrics_done( AF_StyleMetrics metrics_ ) 1499 { 1500 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 1501 1502 1503 af_reverse_character_map_done( metrics->root.reverse_charmap, 1504 metrics->root.globals->face->memory ); 1505 } 1506 1507 1508 /* Scale global values in both directions. */ 1509 1510 FT_LOCAL_DEF( void ) 1511 af_latin_metrics_scale( AF_StyleMetrics metrics_, /* AF_LatinMetrics */ 1512 AF_Scaler scaler ) 1513 { 1514 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 1515 1516 1517 metrics->root.scaler.render_mode = scaler->render_mode; 1518 metrics->root.scaler.face = scaler->face; 1519 metrics->root.scaler.flags = scaler->flags; 1520 1521 af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_HORZ ); 1522 af_latin_metrics_scale_dim( metrics, scaler, AF_DIMENSION_VERT ); 1523 } 1524 1525 1526 /* Extract standard_width from writing system/script specific */ 1527 /* metrics class. */ 1528 1529 FT_CALLBACK_DEF( void ) 1530 af_latin_get_standard_widths( AF_StyleMetrics metrics_, /* AF_LatinMetrics */ 1531 FT_Pos* stdHW, 1532 FT_Pos* stdVW ) 1533 { 1534 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 1535 1536 1537 if ( stdHW ) 1538 *stdHW = metrics->axis[AF_DIMENSION_VERT].standard_width; 1539 1540 if ( stdVW ) 1541 *stdVW = metrics->axis[AF_DIMENSION_HORZ].standard_width; 1542 } 1543 1544 1545 /*************************************************************************/ 1546 /*************************************************************************/ 1547 /***** *****/ 1548 /***** L A T I N G L Y P H A N A L Y S I S *****/ 1549 /***** *****/ 1550 /*************************************************************************/ 1551 /*************************************************************************/ 1552 1553 1554 /* Walk over all contours and compute its segments. */ 1555 1556 FT_LOCAL_DEF( FT_Error ) 1557 af_latin_hints_compute_segments( AF_GlyphHints hints, 1558 AF_Dimension dim ) 1559 { 1560 AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics; 1561 AF_AxisHints axis = &hints->axis[dim]; 1562 FT_Memory memory = hints->memory; 1563 FT_Error error = FT_Err_Ok; 1564 AF_Segment segment = NULL; 1565 AF_SegmentRec seg0; 1566 AF_Point* contour = hints->contours; 1567 AF_Point* contour_limit = contour + hints->num_contours; 1568 AF_Direction major_dir, segment_dir; 1569 1570 FT_Pos flat_threshold = FLAT_THRESHOLD( metrics->units_per_em ); 1571 1572 1573 FT_ZERO( &seg0 ); 1574 seg0.score = 32000; 1575 seg0.flags = AF_EDGE_NORMAL; 1576 1577 major_dir = (AF_Direction)FT_ABS( axis->major_dir ); 1578 segment_dir = major_dir; 1579 1580 axis->num_segments = 0; 1581 1582 /* set up (u,v) in each point */ 1583 if ( dim == AF_DIMENSION_HORZ ) 1584 { 1585 AF_Point point = hints->points; 1586 AF_Point limit = point + hints->num_points; 1587 1588 1589 for ( ; point < limit; point++ ) 1590 { 1591 point->u = point->fx; 1592 point->v = point->fy; 1593 } 1594 } 1595 else 1596 { 1597 AF_Point point = hints->points; 1598 AF_Point limit = point + hints->num_points; 1599 1600 1601 for ( ; point < limit; point++ ) 1602 { 1603 point->u = point->fy; 1604 point->v = point->fx; 1605 } 1606 } 1607 1608 /* do each contour separately */ 1609 for ( ; contour < contour_limit; contour++ ) 1610 { 1611 AF_Point point = contour[0]; 1612 AF_Point last = point->prev; 1613 int on_edge = 0; 1614 1615 /* we call values measured along a segment (point->v) */ 1616 /* `coordinates', and values orthogonal to it (point->u) */ 1617 /* `positions' */ 1618 FT_Pos min_pos = 32000; 1619 FT_Pos max_pos = -32000; 1620 FT_Pos min_coord = 32000; 1621 FT_Pos max_coord = -32000; 1622 FT_UShort min_flags = AF_FLAG_NONE; 1623 FT_UShort max_flags = AF_FLAG_NONE; 1624 FT_Pos min_on_coord = 32000; 1625 FT_Pos max_on_coord = -32000; 1626 1627 FT_Bool passed; 1628 1629 AF_Segment prev_segment = NULL; 1630 1631 FT_Pos prev_min_pos = min_pos; 1632 FT_Pos prev_max_pos = max_pos; 1633 FT_Pos prev_min_coord = min_coord; 1634 FT_Pos prev_max_coord = max_coord; 1635 FT_UShort prev_min_flags = min_flags; 1636 FT_UShort prev_max_flags = max_flags; 1637 FT_Pos prev_min_on_coord = min_on_coord; 1638 FT_Pos prev_max_on_coord = max_on_coord; 1639 1640 1641 if ( !( point->flags & AF_FLAG_IGNORE ) && 1642 FT_ABS( last->out_dir ) == major_dir && 1643 FT_ABS( point->out_dir ) == major_dir ) 1644 { 1645 /* we are already on an edge, try to locate its start */ 1646 last = point; 1647 1648 for (;;) 1649 { 1650 point = point->prev; 1651 if ( FT_ABS( point->out_dir ) != major_dir ) 1652 { 1653 point = point->next; 1654 break; 1655 } 1656 if ( point == last ) 1657 break; 1658 } 1659 } 1660 1661 last = point; 1662 passed = 0; 1663 1664 for (;;) 1665 { 1666 FT_Pos u, v; 1667 1668 1669 if ( on_edge ) 1670 { 1671 /* get minimum and maximum position */ 1672 u = point->u; 1673 if ( u < min_pos ) 1674 min_pos = u; 1675 if ( u > max_pos ) 1676 max_pos = u; 1677 1678 /* get minimum and maximum coordinate together with flags */ 1679 v = point->v; 1680 if ( v < min_coord ) 1681 { 1682 min_coord = v; 1683 min_flags = point->flags; 1684 } 1685 if ( v > max_coord ) 1686 { 1687 max_coord = v; 1688 max_flags = point->flags; 1689 } 1690 1691 /* get minimum and maximum coordinate of `on' points */ 1692 if ( !( point->flags & AF_FLAG_CONTROL ) ) 1693 { 1694 v = point->v; 1695 if ( v < min_on_coord ) 1696 min_on_coord = v; 1697 if ( v > max_on_coord ) 1698 max_on_coord = v; 1699 } 1700 1701 if ( point->flags & AF_FLAG_IGNORE || 1702 point->out_dir != segment_dir || 1703 point == last ) 1704 { 1705 /* check whether the new segment's start point is identical to */ 1706 /* the previous segment's end point; for example, this might */ 1707 /* happen for spikes */ 1708 1709 if ( point->flags & AF_FLAG_IGNORE || 1710 !prev_segment || 1711 segment->first != prev_segment->last ) 1712 { 1713 /* points are different: we are just leaving an edge, thus */ 1714 /* record a new segment */ 1715 1716 segment->last = point; 1717 segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 ); 1718 segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 ); 1719 1720 /* a segment is round if either its first or last point */ 1721 /* is a control point, and the length of the on points */ 1722 /* inbetween doesn't exceed a heuristic limit */ 1723 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL && 1724 ( max_on_coord - min_on_coord ) < flat_threshold ) 1725 segment->flags |= AF_EDGE_ROUND; 1726 1727 segment->min_coord = (FT_Short)min_coord; 1728 segment->max_coord = (FT_Short)max_coord; 1729 segment->height = segment->max_coord - segment->min_coord; 1730 1731 prev_segment = segment; 1732 prev_min_pos = min_pos; 1733 prev_max_pos = max_pos; 1734 prev_min_coord = min_coord; 1735 prev_max_coord = max_coord; 1736 prev_min_flags = min_flags; 1737 prev_max_flags = max_flags; 1738 prev_min_on_coord = min_on_coord; 1739 prev_max_on_coord = max_on_coord; 1740 } 1741 else 1742 { 1743 /* points are the same: we don't create a new segment but */ 1744 /* merge the current segment with the previous one */ 1745 1746 if ( prev_segment->last->in_dir == point->in_dir ) 1747 { 1748 /* we have identical directions (this can happen for */ 1749 /* degenerate outlines that move zig-zag along the main */ 1750 /* axis without changing the coordinate value of the other */ 1751 /* axis, and where the segments have just been merged): */ 1752 /* unify segments */ 1753 1754 /* update constraints */ 1755 1756 if ( prev_min_pos < min_pos ) 1757 min_pos = prev_min_pos; 1758 if ( prev_max_pos > max_pos ) 1759 max_pos = prev_max_pos; 1760 1761 if ( prev_min_coord < min_coord ) 1762 { 1763 min_coord = prev_min_coord; 1764 min_flags = prev_min_flags; 1765 } 1766 if ( prev_max_coord > max_coord ) 1767 { 1768 max_coord = prev_max_coord; 1769 max_flags = prev_max_flags; 1770 } 1771 1772 if ( prev_min_on_coord < min_on_coord ) 1773 min_on_coord = prev_min_on_coord; 1774 if ( prev_max_on_coord > max_on_coord ) 1775 max_on_coord = prev_max_on_coord; 1776 1777 prev_segment->last = point; 1778 prev_segment->pos = (FT_Short)( ( min_pos + 1779 max_pos ) >> 1 ); 1780 prev_segment->delta = (FT_Short)( ( max_pos - 1781 min_pos ) >> 1 ); 1782 1783 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL && 1784 ( max_on_coord - min_on_coord ) < flat_threshold ) 1785 prev_segment->flags |= AF_EDGE_ROUND; 1786 else 1787 prev_segment->flags &= ~AF_EDGE_ROUND; 1788 1789 prev_segment->min_coord = (FT_Short)min_coord; 1790 prev_segment->max_coord = (FT_Short)max_coord; 1791 prev_segment->height = prev_segment->max_coord - 1792 prev_segment->min_coord; 1793 } 1794 else 1795 { 1796 /* we have different directions; use the properties of the */ 1797 /* longer segment and discard the other one */ 1798 1799 if ( FT_ABS( prev_max_coord - prev_min_coord ) > 1800 FT_ABS( max_coord - min_coord ) ) 1801 { 1802 /* discard current segment */ 1803 1804 if ( min_pos < prev_min_pos ) 1805 prev_min_pos = min_pos; 1806 if ( max_pos > prev_max_pos ) 1807 prev_max_pos = max_pos; 1808 1809 prev_segment->last = point; 1810 prev_segment->pos = (FT_Short)( ( prev_min_pos + 1811 prev_max_pos ) >> 1 ); 1812 prev_segment->delta = (FT_Short)( ( prev_max_pos - 1813 prev_min_pos ) >> 1 ); 1814 } 1815 else 1816 { 1817 /* discard previous segment */ 1818 1819 if ( prev_min_pos < min_pos ) 1820 min_pos = prev_min_pos; 1821 if ( prev_max_pos > max_pos ) 1822 max_pos = prev_max_pos; 1823 1824 segment->last = point; 1825 segment->pos = (FT_Short)( ( min_pos + max_pos ) >> 1 ); 1826 segment->delta = (FT_Short)( ( max_pos - min_pos ) >> 1 ); 1827 1828 if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL && 1829 ( max_on_coord - min_on_coord ) < flat_threshold ) 1830 segment->flags |= AF_EDGE_ROUND; 1831 1832 segment->min_coord = (FT_Short)min_coord; 1833 segment->max_coord = (FT_Short)max_coord; 1834 segment->height = segment->max_coord - 1835 segment->min_coord; 1836 1837 *prev_segment = *segment; 1838 1839 prev_min_pos = min_pos; 1840 prev_max_pos = max_pos; 1841 prev_min_coord = min_coord; 1842 prev_max_coord = max_coord; 1843 prev_min_flags = min_flags; 1844 prev_max_flags = max_flags; 1845 prev_min_on_coord = min_on_coord; 1846 prev_max_on_coord = max_on_coord; 1847 } 1848 } 1849 1850 axis->num_segments--; 1851 } 1852 1853 on_edge = 0; 1854 segment = NULL; 1855 1856 /* fall through */ 1857 } 1858 } 1859 1860 /* now exit if we are at the start/end point */ 1861 if ( point == last ) 1862 { 1863 if ( passed ) 1864 break; 1865 passed = 1; 1866 } 1867 1868 /* if we are not on an edge, check whether the major direction */ 1869 /* coincides with the current point's `out' direction, or */ 1870 /* whether we have a single-point contour */ 1871 if ( !( point->flags & AF_FLAG_IGNORE ) && 1872 !on_edge && 1873 ( FT_ABS( point->out_dir ) == major_dir || 1874 point == point->prev ) ) 1875 { 1876 /* 1877 * For efficiency, we restrict the number of segments to 1000, 1878 * which is a heuristic value: it is very unlikely that a glyph 1879 * with so many segments can be hinted in a sensible way. 1880 * Reasons: 1881 * 1882 * - The glyph has really 1000 segments; this implies that it has 1883 * at least 2000 outline points. Assuming 'normal' fonts that 1884 * have superfluous points optimized away, viewing such a glyph 1885 * only makes sense at large magnifications where hinting 1886 * isn't applied anyway. 1887 * 1888 * - We have a broken glyph. Hinting doesn't make sense in this 1889 * case either. 1890 */ 1891 if ( axis->num_segments > 1000 ) 1892 { 1893 FT_TRACE0(( "af_latin_hints_compute_segments:" 1894 " more than 1000 segments in this glyph;\n" )); 1895 FT_TRACE0(( " " 1896 " hinting is suppressed\n" )); 1897 axis->num_segments = 0; 1898 return FT_Err_Ok; 1899 } 1900 1901 /* this is the start of a new segment! */ 1902 segment_dir = (AF_Direction)point->out_dir; 1903 1904 error = af_axis_hints_new_segment( axis, memory, &segment ); 1905 if ( error ) 1906 goto Exit; 1907 1908 /* clear all segment fields */ 1909 segment[0] = seg0; 1910 1911 segment->dir = (FT_Char)segment_dir; 1912 segment->first = point; 1913 segment->last = point; 1914 1915 /* `af_axis_hints_new_segment' reallocates memory, */ 1916 /* thus we have to refresh the `prev_segment' pointer */ 1917 if ( prev_segment ) 1918 prev_segment = segment - 1; 1919 1920 min_pos = max_pos = point->u; 1921 min_coord = max_coord = point->v; 1922 min_flags = max_flags = point->flags; 1923 1924 if ( point->flags & AF_FLAG_CONTROL ) 1925 { 1926 min_on_coord = 32000; 1927 max_on_coord = -32000; 1928 } 1929 else 1930 min_on_coord = max_on_coord = point->v; 1931 1932 on_edge = 1; 1933 1934 if ( point == point->prev ) 1935 { 1936 /* we have a one-point segment: this is a one-point */ 1937 /* contour with `in' and `out' direction set to */ 1938 /* AF_DIR_NONE */ 1939 segment->pos = (FT_Short)min_pos; 1940 1941 if (point->flags & AF_FLAG_CONTROL) 1942 segment->flags |= AF_EDGE_ROUND; 1943 1944 segment->min_coord = (FT_Short)point->v; 1945 segment->max_coord = (FT_Short)point->v; 1946 segment->height = 0; 1947 1948 on_edge = 0; 1949 segment = NULL; 1950 } 1951 } 1952 1953 point = point->next; 1954 } 1955 1956 } /* contours */ 1957 1958 1959 /* now slightly increase the height of segments if this makes */ 1960 /* sense -- this is used to better detect and ignore serifs */ 1961 { 1962 AF_Segment segments = axis->segments; 1963 AF_Segment segments_end = FT_OFFSET( segments, axis->num_segments ); 1964 1965 1966 for ( segment = segments; segment < segments_end; segment++ ) 1967 { 1968 AF_Point first = segment->first; 1969 AF_Point last = segment->last; 1970 FT_Pos first_v = first->v; 1971 FT_Pos last_v = last->v; 1972 1973 1974 if ( first_v < last_v ) 1975 { 1976 AF_Point p; 1977 1978 1979 p = first->prev; 1980 if ( p->v < first_v ) 1981 segment->height = (FT_Short)( segment->height + 1982 ( ( first_v - p->v ) >> 1 ) ); 1983 1984 p = last->next; 1985 if ( p->v > last_v ) 1986 segment->height = (FT_Short)( segment->height + 1987 ( ( p->v - last_v ) >> 1 ) ); 1988 } 1989 else 1990 { 1991 AF_Point p; 1992 1993 1994 p = first->prev; 1995 if ( p->v > first_v ) 1996 segment->height = (FT_Short)( segment->height + 1997 ( ( p->v - first_v ) >> 1 ) ); 1998 1999 p = last->next; 2000 if ( p->v < last_v ) 2001 segment->height = (FT_Short)( segment->height + 2002 ( ( last_v - p->v ) >> 1 ) ); 2003 } 2004 } 2005 } 2006 2007 Exit: 2008 return error; 2009 } 2010 2011 2012 /* Link segments to form stems and serifs. If `width_count' and */ 2013 /* `widths' are non-zero, use them to fine-tune the scoring function. */ 2014 2015 FT_LOCAL_DEF( void ) 2016 af_latin_hints_link_segments( AF_GlyphHints hints, 2017 FT_UInt width_count, 2018 AF_WidthRec* widths, 2019 AF_Dimension dim ) 2020 { 2021 AF_AxisHints axis = &hints->axis[dim]; 2022 AF_Segment segments = axis->segments; 2023 AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments ); 2024 FT_Pos len_threshold, len_score, dist_score, max_width; 2025 AF_Segment seg1, seg2; 2026 2027 2028 if ( width_count ) 2029 max_width = widths[width_count - 1].org; 2030 else 2031 max_width = 0; 2032 2033 /* a heuristic value to set up a minimum value for overlapping */ 2034 len_threshold = AF_LATIN_CONSTANT( hints->metrics, 8 ); 2035 if ( len_threshold == 0 ) 2036 len_threshold = 1; 2037 2038 /* a heuristic value to weight lengths */ 2039 len_score = AF_LATIN_CONSTANT( hints->metrics, 6000 ); 2040 2041 /* a heuristic value to weight distances (no call to */ 2042 /* AF_LATIN_CONSTANT needed, since we work on multiples */ 2043 /* of the stem width) */ 2044 dist_score = 3000; 2045 2046 /* now compare each segment to the others */ 2047 for ( seg1 = segments; seg1 < segment_limit; seg1++ ) 2048 { 2049 if ( seg1->dir != axis->major_dir ) 2050 continue; 2051 2052 /* search for stems having opposite directions, */ 2053 /* with seg1 to the `left' of seg2 */ 2054 for ( seg2 = segments; seg2 < segment_limit; seg2++ ) 2055 { 2056 FT_Pos pos1 = seg1->pos; 2057 FT_Pos pos2 = seg2->pos; 2058 2059 2060 if ( seg1->dir + seg2->dir == 0 && pos2 > pos1 ) 2061 { 2062 /* compute distance between the two segments */ 2063 FT_Pos min = seg1->min_coord; 2064 FT_Pos max = seg1->max_coord; 2065 FT_Pos len; 2066 2067 2068 if ( min < seg2->min_coord ) 2069 min = seg2->min_coord; 2070 2071 if ( max > seg2->max_coord ) 2072 max = seg2->max_coord; 2073 2074 /* compute maximum coordinate difference of the two segments */ 2075 /* (that is, how much they overlap) */ 2076 len = max - min; 2077 if ( len >= len_threshold ) 2078 { 2079 /* 2080 * The score is the sum of two demerits indicating the 2081 * `badness' of a fit, measured along the segments' main axis 2082 * and orthogonal to it, respectively. 2083 * 2084 * - The less overlapping along the main axis, the worse it 2085 * is, causing a larger demerit. 2086 * 2087 * - The nearer the orthogonal distance to a stem width, the 2088 * better it is, causing a smaller demerit. For simplicity, 2089 * however, we only increase the demerit for values that 2090 * exceed the largest stem width. 2091 */ 2092 2093 FT_Pos dist = pos2 - pos1; 2094 2095 FT_Pos dist_demerit, score; 2096 2097 2098 if ( max_width ) 2099 { 2100 /* distance demerits are based on multiples of `max_width'; */ 2101 /* we scale by 1024 for getting more precision */ 2102 FT_Pos delta = ( dist << 10 ) / max_width - ( 1 << 10 ); 2103 2104 2105 if ( delta > 10000 ) 2106 dist_demerit = 32000; 2107 else if ( delta > 0 ) 2108 dist_demerit = delta * delta / dist_score; 2109 else 2110 dist_demerit = 0; 2111 } 2112 else 2113 dist_demerit = dist; /* default if no widths available */ 2114 2115 score = dist_demerit + len_score / len; 2116 2117 /* and we search for the smallest score */ 2118 if ( score < seg1->score ) 2119 { 2120 seg1->score = score; 2121 seg1->link = seg2; 2122 } 2123 2124 if ( score < seg2->score ) 2125 { 2126 seg2->score = score; 2127 seg2->link = seg1; 2128 } 2129 } 2130 } 2131 } 2132 } 2133 2134 /* now compute the `serif' segments, cf. explanations in `afhints.h' */ 2135 for ( seg1 = segments; seg1 < segment_limit; seg1++ ) 2136 { 2137 seg2 = seg1->link; 2138 2139 if ( seg2 ) 2140 { 2141 if ( seg2->link != seg1 ) 2142 { 2143 seg1->link = NULL; 2144 seg1->serif = seg2->link; 2145 } 2146 } 2147 } 2148 } 2149 2150 2151 /* Link segments to edges, using feature analysis for selection. */ 2152 2153 FT_LOCAL_DEF( FT_Error ) 2154 af_latin_hints_compute_edges( AF_GlyphHints hints, 2155 AF_Dimension dim ) 2156 { 2157 AF_AxisHints axis = &hints->axis[dim]; 2158 FT_Error error = FT_Err_Ok; 2159 FT_Memory memory = hints->memory; 2160 AF_LatinAxis laxis = &((AF_LatinMetrics)hints->metrics)->axis[dim]; 2161 2162 AF_StyleClass style_class = hints->metrics->style_class; 2163 AF_ScriptClass script_class = af_script_classes[style_class->script]; 2164 2165 FT_Bool top_to_bottom_hinting = 0; 2166 2167 AF_Segment segments = axis->segments; 2168 AF_Segment segment_limit = FT_OFFSET( segments, axis->num_segments ); 2169 AF_Segment seg; 2170 2171 #if 0 2172 AF_Direction up_dir; 2173 #endif 2174 FT_Fixed scale; 2175 FT_Pos edge_distance_threshold; 2176 FT_Pos segment_length_threshold; 2177 FT_Pos segment_width_threshold; 2178 2179 2180 axis->num_edges = 0; 2181 2182 scale = ( dim == AF_DIMENSION_HORZ ) ? hints->x_scale 2183 : hints->y_scale; 2184 2185 #if 0 2186 up_dir = ( dim == AF_DIMENSION_HORZ ) ? AF_DIR_UP 2187 : AF_DIR_RIGHT; 2188 #endif 2189 2190 if ( dim == AF_DIMENSION_VERT ) 2191 top_to_bottom_hinting = script_class->top_to_bottom_hinting; 2192 2193 /* 2194 * We ignore all segments that are less than 1 pixel in length 2195 * to avoid many problems with serif fonts. We compute the 2196 * corresponding threshold in font units. 2197 */ 2198 if ( dim == AF_DIMENSION_HORZ ) 2199 segment_length_threshold = FT_DivFix( 64, hints->y_scale ); 2200 else 2201 segment_length_threshold = 0; 2202 2203 /* 2204 * Similarly, we ignore segments that have a width delta 2205 * larger than 0.5px (i.e., a width larger than 1px). 2206 */ 2207 segment_width_threshold = FT_DivFix( 32, scale ); 2208 2209 /********************************************************************** 2210 * 2211 * We begin by generating a sorted table of edges for the current 2212 * direction. To do so, we simply scan each segment and try to find 2213 * an edge in our table that corresponds to its position. 2214 * 2215 * If no edge is found, we create and insert a new edge in the 2216 * sorted table. Otherwise, we simply add the segment to the edge's 2217 * list which gets processed in the second step to compute the 2218 * edge's properties. 2219 * 2220 * Note that the table of edges is sorted along the segment/edge 2221 * position. 2222 * 2223 */ 2224 2225 /* assure that edge distance threshold is at most 0.25px */ 2226 edge_distance_threshold = FT_MulFix( laxis->edge_distance_threshold, 2227 scale ); 2228 if ( edge_distance_threshold > 64 / 4 ) 2229 edge_distance_threshold = 64 / 4; 2230 2231 edge_distance_threshold = FT_DivFix( edge_distance_threshold, 2232 scale ); 2233 2234 for ( seg = segments; seg < segment_limit; seg++ ) 2235 { 2236 AF_Edge found = NULL; 2237 FT_UInt ee; 2238 2239 2240 /* ignore too short segments, too wide ones, and, in this loop, */ 2241 /* one-point segments without a direction */ 2242 if ( seg->height < segment_length_threshold || 2243 seg->delta > segment_width_threshold || 2244 seg->dir == AF_DIR_NONE ) 2245 continue; 2246 2247 /* A special case for serif edges: If they are smaller than */ 2248 /* 1.5 pixels we ignore them. */ 2249 if ( seg->serif && 2250 2 * seg->height < 3 * segment_length_threshold ) 2251 continue; 2252 2253 /* look for an edge corresponding to the segment */ 2254 for ( ee = 0; ee < axis->num_edges; ee++ ) 2255 { 2256 AF_Edge edge = axis->edges + ee; 2257 FT_Pos dist; 2258 2259 2260 dist = seg->pos - edge->fpos; 2261 if ( dist < 0 ) 2262 dist = -dist; 2263 2264 if ( dist < edge_distance_threshold && edge->dir == seg->dir ) 2265 { 2266 found = edge; 2267 break; 2268 } 2269 } 2270 2271 if ( !found ) 2272 { 2273 AF_Edge edge; 2274 2275 2276 /* insert a new edge in the list and */ 2277 /* sort according to the position */ 2278 error = af_axis_hints_new_edge( axis, seg->pos, 2279 (AF_Direction)seg->dir, 2280 top_to_bottom_hinting, 2281 memory, &edge ); 2282 if ( error ) 2283 goto Exit; 2284 2285 /* add the segment to the new edge's list */ 2286 FT_ZERO( edge ); 2287 2288 edge->first = seg; 2289 edge->last = seg; 2290 edge->dir = seg->dir; 2291 edge->fpos = seg->pos; 2292 edge->opos = FT_MulFix( seg->pos, scale ); 2293 edge->pos = edge->opos; 2294 seg->edge_next = seg; 2295 } 2296 else 2297 { 2298 /* if an edge was found, simply add the segment to the edge's */ 2299 /* list */ 2300 seg->edge_next = found->first; 2301 found->last->edge_next = seg; 2302 found->last = seg; 2303 } 2304 } 2305 2306 /* we loop again over all segments to catch one-point segments */ 2307 /* without a direction: if possible, link them to existing edges */ 2308 for ( seg = segments; seg < segment_limit; seg++ ) 2309 { 2310 AF_Edge found = NULL; 2311 FT_UInt ee; 2312 2313 2314 if ( seg->dir != AF_DIR_NONE ) 2315 continue; 2316 2317 /* look for an edge corresponding to the segment */ 2318 for ( ee = 0; ee < axis->num_edges; ee++ ) 2319 { 2320 AF_Edge edge = axis->edges + ee; 2321 FT_Pos dist; 2322 2323 2324 dist = seg->pos - edge->fpos; 2325 if ( dist < 0 ) 2326 dist = -dist; 2327 2328 if ( dist < edge_distance_threshold ) 2329 { 2330 found = edge; 2331 break; 2332 } 2333 } 2334 2335 /* one-point segments without a match are ignored */ 2336 if ( found ) 2337 { 2338 seg->edge_next = found->first; 2339 found->last->edge_next = seg; 2340 found->last = seg; 2341 } 2342 } 2343 2344 2345 /******************************************************************* 2346 * 2347 * Good, we now compute each edge's properties according to the 2348 * segments found on its position. Basically, these are 2349 * 2350 * - the edge's main direction 2351 * - stem edge, serif edge or both (which defaults to stem then) 2352 * - rounded edge, straight or both (which defaults to straight) 2353 * - link for edge 2354 * 2355 */ 2356 2357 /* first of all, set the `edge' field in each segment -- this is */ 2358 /* required in order to compute edge links */ 2359 2360 /* 2361 * Note that removing this loop and setting the `edge' field of each 2362 * segment directly in the code above slows down execution speed for 2363 * some reasons on platforms like the Sun. 2364 */ 2365 { 2366 AF_Edge edges = axis->edges; 2367 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges ); 2368 AF_Edge edge; 2369 2370 2371 for ( edge = edges; edge < edge_limit; edge++ ) 2372 { 2373 seg = edge->first; 2374 if ( seg ) 2375 do 2376 { 2377 seg->edge = edge; 2378 seg = seg->edge_next; 2379 2380 } while ( seg != edge->first ); 2381 } 2382 2383 /* now compute each edge properties */ 2384 for ( edge = edges; edge < edge_limit; edge++ ) 2385 { 2386 FT_Int is_round = 0; /* does it contain round segments? */ 2387 FT_Int is_straight = 0; /* does it contain straight segments? */ 2388 #if 0 2389 FT_Pos ups = 0; /* number of upwards segments */ 2390 FT_Pos downs = 0; /* number of downwards segments */ 2391 #endif 2392 2393 2394 seg = edge->first; 2395 2396 do 2397 { 2398 FT_Bool is_serif; 2399 2400 2401 /* check for roundness of segment */ 2402 if ( seg->flags & AF_EDGE_ROUND ) 2403 is_round++; 2404 else 2405 is_straight++; 2406 2407 #if 0 2408 /* check for segment direction */ 2409 if ( seg->dir == up_dir ) 2410 ups += seg->max_coord - seg->min_coord; 2411 else 2412 downs += seg->max_coord - seg->min_coord; 2413 #endif 2414 2415 /* check for links -- if seg->serif is set, then seg->link must */ 2416 /* be ignored */ 2417 is_serif = FT_BOOL( seg->serif && 2418 seg->serif->edge && 2419 seg->serif->edge != edge ); 2420 2421 if ( ( seg->link && seg->link->edge ) || is_serif ) 2422 { 2423 AF_Edge edge2; 2424 AF_Segment seg2; 2425 2426 2427 edge2 = edge->link; 2428 seg2 = seg->link; 2429 2430 if ( is_serif ) 2431 { 2432 seg2 = seg->serif; 2433 edge2 = edge->serif; 2434 } 2435 2436 if ( edge2 ) 2437 { 2438 FT_Pos edge_delta; 2439 FT_Pos seg_delta; 2440 2441 2442 edge_delta = edge->fpos - edge2->fpos; 2443 if ( edge_delta < 0 ) 2444 edge_delta = -edge_delta; 2445 2446 seg_delta = seg->pos - seg2->pos; 2447 if ( seg_delta < 0 ) 2448 seg_delta = -seg_delta; 2449 2450 if ( seg_delta < edge_delta ) 2451 edge2 = seg2->edge; 2452 } 2453 else 2454 edge2 = seg2->edge; 2455 2456 if ( is_serif ) 2457 { 2458 edge->serif = edge2; 2459 edge2->flags |= AF_EDGE_SERIF; 2460 } 2461 else 2462 edge->link = edge2; 2463 } 2464 2465 seg = seg->edge_next; 2466 2467 } while ( seg != edge->first ); 2468 2469 /* set the round/straight flags */ 2470 edge->flags = AF_EDGE_NORMAL; 2471 2472 if ( is_round > 0 && is_round >= is_straight ) 2473 edge->flags |= AF_EDGE_ROUND; 2474 2475 #if 0 2476 /* set the edge's main direction */ 2477 edge->dir = AF_DIR_NONE; 2478 2479 if ( ups > downs ) 2480 edge->dir = (FT_Char)up_dir; 2481 2482 else if ( ups < downs ) 2483 edge->dir = (FT_Char)-up_dir; 2484 2485 else if ( ups == downs ) 2486 edge->dir = 0; /* both up and down! */ 2487 #endif 2488 2489 /* get rid of serifs if link is set */ 2490 /* XXX: This gets rid of many unpleasant artefacts! */ 2491 /* Example: the `c' in cour.pfa at size 13 */ 2492 2493 if ( edge->serif && edge->link ) 2494 edge->serif = NULL; 2495 } 2496 } 2497 2498 Exit: 2499 return error; 2500 } 2501 2502 2503 /* Detect segments and edges for given dimension. */ 2504 2505 FT_LOCAL_DEF( FT_Error ) 2506 af_latin_hints_detect_features( AF_GlyphHints hints, 2507 FT_UInt width_count, 2508 AF_WidthRec* widths, 2509 AF_Dimension dim ) 2510 { 2511 FT_Error error; 2512 2513 2514 error = af_latin_hints_compute_segments( hints, dim ); 2515 if ( !error ) 2516 { 2517 af_latin_hints_link_segments( hints, width_count, widths, dim ); 2518 2519 error = af_latin_hints_compute_edges( hints, dim ); 2520 } 2521 2522 return error; 2523 } 2524 2525 2526 /* Compute all edges which lie within blue zones. */ 2527 2528 static void 2529 af_latin_hints_compute_blue_edges( AF_GlyphHints hints, 2530 AF_LatinMetrics metrics ) 2531 { 2532 AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT]; 2533 AF_Edge edge = axis->edges; 2534 AF_Edge edge_limit = FT_OFFSET( edge, axis->num_edges ); 2535 AF_LatinAxis latin = &metrics->axis[AF_DIMENSION_VERT]; 2536 FT_Fixed scale = latin->scale; 2537 2538 2539 /* compute which blue zones are active, i.e. have their scaled */ 2540 /* size < 3/4 pixels */ 2541 2542 /* for each horizontal edge search the blue zone which is closest */ 2543 for ( ; edge < edge_limit; edge++ ) 2544 { 2545 FT_UInt bb; 2546 AF_Width best_blue = NULL; 2547 FT_Bool best_blue_is_neutral = 0; 2548 FT_Pos best_dist; /* initial threshold */ 2549 2550 2551 if ( edge->flags & AF_EDGE_NO_BLUE ) 2552 continue; 2553 2554 /* compute the initial threshold as a fraction of the EM size */ 2555 /* (the value 40 is heuristic) */ 2556 best_dist = FT_MulFix( metrics->units_per_em / 40, scale ); 2557 2558 /* assure a minimum distance of 0.5px */ 2559 if ( best_dist > 64 / 2 ) 2560 best_dist = 64 / 2; 2561 2562 for ( bb = 0; bb < latin->blue_count; bb++ ) 2563 { 2564 AF_LatinBlue blue = latin->blues + bb; 2565 FT_Bool is_top_blue, is_neutral_blue, is_major_dir; 2566 2567 2568 /* skip inactive blue zones (i.e., those that are too large) */ 2569 if ( !( blue->flags & AF_LATIN_BLUE_ACTIVE ) ) 2570 continue; 2571 2572 /* if it is a top zone, check for right edges (against the major */ 2573 /* direction); if it is a bottom zone, check for left edges (in */ 2574 /* the major direction) -- this assumes the TrueType convention */ 2575 /* for the orientation of contours */ 2576 is_top_blue = 2577 (FT_Byte)( ( blue->flags & ( AF_LATIN_BLUE_TOP | 2578 AF_LATIN_BLUE_SUB_TOP ) ) != 0 ); 2579 is_neutral_blue = 2580 (FT_Byte)( ( blue->flags & AF_LATIN_BLUE_NEUTRAL ) != 0); 2581 is_major_dir = 2582 FT_BOOL( edge->dir == axis->major_dir ); 2583 2584 /* neutral blue zones are handled for both directions */ 2585 if ( is_top_blue ^ is_major_dir || is_neutral_blue ) 2586 { 2587 FT_Pos dist; 2588 2589 2590 /* first of all, compare it to the reference position */ 2591 dist = edge->fpos - blue->ref.org; 2592 if ( dist < 0 ) 2593 dist = -dist; 2594 2595 dist = FT_MulFix( dist, scale ); 2596 if ( dist < best_dist ) 2597 { 2598 best_dist = dist; 2599 best_blue = &blue->ref; 2600 best_blue_is_neutral = is_neutral_blue; 2601 } 2602 2603 /* now compare it to the overshoot position and check whether */ 2604 /* the edge is rounded, and whether the edge is over the */ 2605 /* reference position of a top zone, or under the reference */ 2606 /* position of a bottom zone (provided we don't have a */ 2607 /* neutral blue zone) */ 2608 if ( edge->flags & AF_EDGE_ROUND && 2609 dist != 0 && 2610 !is_neutral_blue ) 2611 { 2612 FT_Bool is_under_ref = FT_BOOL( edge->fpos < blue->ref.org ); 2613 2614 2615 if ( is_top_blue ^ is_under_ref ) 2616 { 2617 dist = edge->fpos - blue->shoot.org; 2618 if ( dist < 0 ) 2619 dist = -dist; 2620 2621 dist = FT_MulFix( dist, scale ); 2622 if ( dist < best_dist ) 2623 { 2624 best_dist = dist; 2625 best_blue = &blue->shoot; 2626 best_blue_is_neutral = is_neutral_blue; 2627 } 2628 } 2629 } 2630 } 2631 } 2632 2633 if ( best_blue ) 2634 { 2635 edge->blue_edge = best_blue; 2636 if ( best_blue_is_neutral ) 2637 edge->flags |= AF_EDGE_NEUTRAL; 2638 } 2639 } 2640 } 2641 2642 2643 /* Initialize hinting engine. */ 2644 2645 static FT_Error 2646 af_latin_hints_init( AF_GlyphHints hints, 2647 AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */ 2648 { 2649 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 2650 2651 FT_Render_Mode mode; 2652 FT_UInt32 scaler_flags, other_flags; 2653 FT_Face face = metrics->root.scaler.face; 2654 2655 2656 af_glyph_hints_rescale( hints, (AF_StyleMetrics)metrics ); 2657 2658 /* 2659 * correct x_scale and y_scale if needed, since they may have 2660 * been modified by `af_latin_metrics_scale_dim' above 2661 */ 2662 hints->x_scale = metrics->axis[AF_DIMENSION_HORZ].scale; 2663 hints->x_delta = metrics->axis[AF_DIMENSION_HORZ].delta; 2664 hints->y_scale = metrics->axis[AF_DIMENSION_VERT].scale; 2665 hints->y_delta = metrics->axis[AF_DIMENSION_VERT].delta; 2666 2667 /* compute flags depending on render mode, etc. */ 2668 mode = metrics->root.scaler.render_mode; 2669 2670 scaler_flags = hints->scaler_flags; 2671 other_flags = 0; 2672 2673 /* 2674 * We snap the width of vertical stems for the monochrome and 2675 * horizontal LCD rendering targets only. 2676 */ 2677 if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD ) 2678 other_flags |= AF_LATIN_HINTS_HORZ_SNAP; 2679 2680 /* 2681 * We snap the width of horizontal stems for the monochrome and 2682 * vertical LCD rendering targets only. 2683 */ 2684 if ( mode == FT_RENDER_MODE_MONO || mode == FT_RENDER_MODE_LCD_V ) 2685 other_flags |= AF_LATIN_HINTS_VERT_SNAP; 2686 2687 /* 2688 * We adjust stems to full pixels unless in `light' or `lcd' mode. 2689 */ 2690 if ( mode != FT_RENDER_MODE_LIGHT && mode != FT_RENDER_MODE_LCD ) 2691 other_flags |= AF_LATIN_HINTS_STEM_ADJUST; 2692 2693 if ( mode == FT_RENDER_MODE_MONO ) 2694 other_flags |= AF_LATIN_HINTS_MONO; 2695 2696 /* 2697 * In `light' or `lcd' mode we disable horizontal hinting completely. 2698 * We also do it if the face is italic. 2699 * 2700 * However, if warping is enabled (which only works in `light' hinting 2701 * mode), advance widths get adjusted, too. 2702 */ 2703 if ( mode == FT_RENDER_MODE_LIGHT || mode == FT_RENDER_MODE_LCD || 2704 ( face->style_flags & FT_STYLE_FLAG_ITALIC ) != 0 ) 2705 scaler_flags |= AF_SCALER_FLAG_NO_HORIZONTAL; 2706 2707 hints->scaler_flags = scaler_flags; 2708 hints->other_flags = other_flags; 2709 2710 return FT_Err_Ok; 2711 } 2712 2713 2714 /*************************************************************************/ 2715 /*************************************************************************/ 2716 /***** *****/ 2717 /***** L A T I N G L Y P H G R I D - F I T T I N G *****/ 2718 /***** *****/ 2719 /*************************************************************************/ 2720 /*************************************************************************/ 2721 2722 /* Snap a given width in scaled coordinates to one of the */ 2723 /* current standard widths. */ 2724 2725 static FT_Pos 2726 af_latin_snap_width( AF_Width widths, 2727 FT_UInt count, 2728 FT_Pos width ) 2729 { 2730 FT_UInt n; 2731 FT_Pos best = 64 + 32 + 2; 2732 FT_Pos reference = width; 2733 FT_Pos scaled; 2734 2735 2736 for ( n = 0; n < count; n++ ) 2737 { 2738 FT_Pos w; 2739 FT_Pos dist; 2740 2741 2742 w = widths[n].cur; 2743 dist = width - w; 2744 if ( dist < 0 ) 2745 dist = -dist; 2746 if ( dist < best ) 2747 { 2748 best = dist; 2749 reference = w; 2750 } 2751 } 2752 2753 scaled = FT_PIX_ROUND( reference ); 2754 2755 if ( width >= reference ) 2756 { 2757 if ( width < scaled + 48 ) 2758 width = reference; 2759 } 2760 else 2761 { 2762 if ( width > scaled - 48 ) 2763 width = reference; 2764 } 2765 2766 return width; 2767 } 2768 2769 2770 #undef FT_COMPONENT 2771 #define FT_COMPONENT afadjust 2772 2773 2774 static void 2775 af_move_contour_vertically( AF_Point contour, 2776 FT_Int movement ) 2777 { 2778 AF_Point point = contour; 2779 AF_Point first_point = point; 2780 2781 2782 if ( point ) 2783 { 2784 do 2785 { 2786 point->y += movement; 2787 point = point->next; 2788 2789 } while ( point != first_point ); 2790 } 2791 } 2792 2793 2794 /* Move all contours higher than `limit` by `delta`. */ 2795 static void 2796 af_move_contours_up( AF_GlyphHints hints, 2797 FT_Pos limit, 2798 FT_Pos delta ) 2799 { 2800 FT_Int contour; 2801 2802 2803 for ( contour = 0; contour < hints->num_contours; contour++ ) 2804 { 2805 FT_Pos min_y = hints->contour_y_minima[contour]; 2806 FT_Pos max_y = hints->contour_y_maxima[contour]; 2807 2808 2809 if ( min_y < max_y && 2810 min_y > limit ) 2811 af_move_contour_vertically( hints->contours[contour], 2812 delta ); 2813 } 2814 } 2815 2816 2817 static void 2818 af_move_contours_down( AF_GlyphHints hints, 2819 FT_Pos limit, 2820 FT_Pos delta ) 2821 { 2822 FT_Int contour; 2823 2824 2825 for ( contour = 0; contour < hints->num_contours; contour++ ) 2826 { 2827 FT_Pos min_y = hints->contour_y_minima[contour]; 2828 FT_Pos max_y = hints->contour_y_maxima[contour]; 2829 2830 2831 if ( min_y < max_y && 2832 max_y < limit ) 2833 af_move_contour_vertically( hints->contours[contour], 2834 -delta ); 2835 } 2836 } 2837 2838 2839 /* Compute vertical extrema of all contours and store them in the */ 2840 /* `contour_y_minima` and `contour_y_maxima` arrays of `hints`. */ 2841 static void 2842 af_compute_vertical_extrema( AF_GlyphHints hints ) 2843 { 2844 FT_Int contour; 2845 2846 2847 for ( contour = 0; contour < hints->num_contours; contour++ ) 2848 { 2849 FT_Pos min_y = FT_LONG_MAX; 2850 FT_Pos max_y = FT_LONG_MIN; 2851 2852 AF_Point first_point = hints->contours[contour]; 2853 AF_Point point = first_point; 2854 2855 2856 if ( !first_point || first_point->next->next == first_point ) 2857 goto End_loop; 2858 2859 do 2860 { 2861 if ( point->y < min_y ) 2862 min_y = point->y; 2863 if ( point->y > max_y ) 2864 max_y = point->y; 2865 2866 point = point->next; 2867 2868 } while ( point != first_point ); 2869 2870 End_loop: 2871 hints->contour_y_minima[contour] = min_y; 2872 hints->contour_y_maxima[contour] = max_y; 2873 } 2874 } 2875 2876 2877 static FT_Int 2878 af_find_highest_contour( AF_GlyphHints hints ) 2879 { 2880 FT_Int highest_contour = 0; 2881 FT_Pos highest_min_y = FT_LONG_MAX; 2882 FT_Pos highest_max_y = FT_LONG_MIN; 2883 2884 FT_Int contour; 2885 2886 2887 /* At this point we have one 'lower' (usually the base glyph) */ 2888 /* and one 'upper' object (usually the diacritic glyph). If */ 2889 /* there are more contours, they must be enclosed within either */ 2890 /* 'lower' or 'upper'. To find this enclosing 'upper' contour */ 2891 /* it is thus sufficient to search for the contour with the */ 2892 /* highest y maximum value. */ 2893 for ( contour = 0; contour < hints->num_contours; contour++ ) 2894 { 2895 FT_Pos current_min_y = hints->contour_y_minima[contour]; 2896 FT_Pos current_max_y = hints->contour_y_maxima[contour]; 2897 2898 2899 /* If we have two contours with the same maximum value, take */ 2900 /* the one that has a smaller height. */ 2901 if ( current_max_y > highest_max_y || 2902 ( current_max_y == highest_max_y && 2903 current_min_y > highest_min_y ) ) 2904 { 2905 highest_min_y = current_min_y; 2906 highest_max_y = current_max_y; 2907 highest_contour = contour; 2908 } 2909 } 2910 2911 return highest_contour; 2912 } 2913 2914 2915 static FT_Int 2916 af_find_second_highest_contour( AF_GlyphHints hints ) 2917 { 2918 FT_Int highest_contour; 2919 FT_Pos highest_min_y; 2920 2921 FT_Int second_highest_contour = 0; 2922 FT_Pos second_highest_max_y = FT_LONG_MIN; 2923 2924 FT_Int contour; 2925 2926 2927 if ( hints->num_contours < 3 ) 2928 return 0; 2929 2930 highest_contour = af_find_highest_contour( hints ); 2931 highest_min_y = hints->contour_y_minima[highest_contour]; 2932 2933 /* Search the contour with the largest vertical maximum that has a */ 2934 /* vertical minimum lower than the vertical minimum of the topmost */ 2935 /* contour. */ 2936 for ( contour = 0; contour < hints->num_contours; contour++ ) 2937 { 2938 FT_Pos current_min_y; 2939 FT_Pos current_max_y; 2940 2941 2942 if ( contour == highest_contour ) 2943 continue; 2944 2945 current_min_y = hints->contour_y_minima[contour]; 2946 current_max_y = hints->contour_y_maxima[contour]; 2947 2948 if ( current_max_y > second_highest_max_y && 2949 current_min_y < highest_min_y ) 2950 { 2951 second_highest_max_y = current_max_y; 2952 second_highest_contour = contour; 2953 } 2954 } 2955 2956 return second_highest_contour; 2957 } 2958 2959 2960 static FT_Int 2961 af_find_lowest_contour( AF_GlyphHints hints ) 2962 { 2963 FT_Int lowest_contour = 0; 2964 FT_Pos lowest_min_y = FT_LONG_MAX; 2965 FT_Pos lowest_max_y = FT_LONG_MIN; 2966 2967 FT_Int contour; 2968 2969 2970 for ( contour = 0; contour < hints->num_contours; contour++ ) 2971 { 2972 FT_Pos current_min_y = hints->contour_y_minima[contour]; 2973 FT_Pos current_max_y = hints->contour_y_maxima[contour]; 2974 2975 2976 if ( current_min_y < lowest_min_y || 2977 ( current_min_y == lowest_min_y && 2978 current_max_y < lowest_max_y ) ) 2979 { 2980 lowest_min_y = current_min_y; 2981 lowest_max_y = current_max_y; 2982 lowest_contour = contour; 2983 } 2984 } 2985 2986 return lowest_contour; 2987 } 2988 2989 2990 static FT_Int 2991 af_find_second_lowest_contour( AF_GlyphHints hints ) 2992 { 2993 FT_Int lowest_contour; 2994 FT_Pos lowest_max_y; 2995 2996 FT_Int second_lowest_contour = 0; 2997 FT_Pos second_lowest_min_y = FT_LONG_MAX; 2998 2999 FT_Int contour; 3000 3001 3002 if ( hints->num_contours < 3 ) 3003 return 0; 3004 3005 lowest_contour = af_find_lowest_contour( hints ); 3006 lowest_max_y = hints->contour_y_maxima[lowest_contour]; 3007 3008 for ( contour = 0; contour < hints->num_contours; contour++ ) 3009 { 3010 FT_Pos current_min_y; 3011 FT_Pos current_max_y; 3012 3013 3014 if ( contour == lowest_contour ) 3015 continue; 3016 3017 current_min_y = hints->contour_y_minima[contour]; 3018 current_max_y = hints->contour_y_maxima[contour]; 3019 3020 if ( current_min_y < second_lowest_min_y && 3021 current_max_y > lowest_max_y ) 3022 { 3023 second_lowest_min_y = current_min_y; 3024 second_lowest_contour = contour; 3025 } 3026 } 3027 3028 return second_lowest_contour; 3029 } 3030 3031 3032 /* While aligning edges to blue zones, make the auto-hinter */ 3033 /* ignore the ones that are higher than `pos`. */ 3034 static void 3035 af_prevent_top_blue_alignment( AF_GlyphHints hints, 3036 FT_Pos pos ) 3037 { 3038 AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT]; 3039 3040 AF_Edge edges = axis->edges; 3041 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges ); 3042 AF_Edge edge; 3043 3044 3045 for ( edge = edges; edge < edge_limit; edge++ ) 3046 if ( edge->pos > pos ) 3047 edge->flags |= AF_EDGE_NO_BLUE; 3048 } 3049 3050 3051 static void 3052 af_prevent_bottom_blue_alignment( AF_GlyphHints hints, 3053 FT_Pos pos ) 3054 { 3055 AF_AxisHints axis = &hints->axis[AF_DIMENSION_VERT]; 3056 3057 AF_Edge edges = axis->edges; 3058 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges ); 3059 AF_Edge edge; 3060 3061 3062 for ( edge = edges; edge < edge_limit; edge++ ) 3063 if ( edge->pos < pos ) 3064 edge->flags |= AF_EDGE_NO_BLUE; 3065 } 3066 3067 3068 static void 3069 af_latin_get_base_glyph_blues( AF_GlyphHints hints, 3070 FT_Bool is_capital, 3071 AF_LatinBlue* top, 3072 AF_LatinBlue* bottom ) 3073 { 3074 AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics; 3075 AF_LatinAxis axis = &metrics->axis[AF_DIMENSION_VERT]; 3076 3077 FT_UInt top_flag; 3078 FT_UInt bottom_flag; 3079 3080 FT_UInt i; 3081 3082 3083 top_flag = is_capital ? AF_LATIN_BLUE_TOP 3084 : AF_LATIN_BLUE_ADJUSTMENT; 3085 top_flag |= AF_LATIN_BLUE_ACTIVE; 3086 3087 for ( i = 0; i < axis->blue_count; i++ ) 3088 if ( ( axis->blues[i].flags & top_flag ) == top_flag ) 3089 break; 3090 if ( i < axis->blue_count ) 3091 *top = &axis->blues[i]; 3092 3093 bottom_flag = is_capital ? AF_LATIN_BLUE_BOTTOM 3094 : AF_LATIN_BLUE_BOTTOM_SMALL; 3095 bottom_flag |= AF_LATIN_BLUE_ACTIVE; 3096 3097 for ( i = 0; i < axis->blue_count; i++ ) 3098 if ( ( axis->blues[i].flags & bottom_flag ) == bottom_flag ) 3099 break; 3100 if ( i < axis->blue_count ) 3101 *bottom = &axis->blues[i]; 3102 } 3103 3104 3105 /* Make the auto-hinter ignore top blue zones while aligning edges. */ 3106 /* This affects everything that is higher than a vertical position */ 3107 /* based on the lowercase or uppercase top and bottom blue zones */ 3108 /* (depending on `is_capital`). */ 3109 static void 3110 af_latin_ignore_top( AF_GlyphHints hints, 3111 AF_LatinBlue top_blue, 3112 AF_LatinBlue bottom_blue ) 3113 { 3114 FT_Pos base_glyph_height; 3115 FT_Pos limit; 3116 3117 3118 /* Ignore blue zones that are higher than a heuristic threshold */ 3119 /* (value 7 corresponds to approx. 14%, which should be sufficient */ 3120 /* to exceed the height of uppercase serifs. We also add a quarter */ 3121 /* of a pixel as a safety measure. */ 3122 base_glyph_height = top_blue->shoot.cur - bottom_blue->shoot.cur; 3123 limit = top_blue->shoot.cur + base_glyph_height / 7 + 16; 3124 3125 af_prevent_top_blue_alignment( hints, limit ); 3126 } 3127 3128 3129 static void 3130 af_latin_ignore_bottom( AF_GlyphHints hints, 3131 AF_LatinBlue top_blue, 3132 AF_LatinBlue bottom_blue ) 3133 { 3134 FT_Pos base_glyph_height; 3135 FT_Pos limit; 3136 3137 3138 base_glyph_height = top_blue->shoot.cur - bottom_blue->shoot.cur; 3139 limit = bottom_blue->shoot.cur - base_glyph_height / 7 - 16; 3140 3141 af_prevent_bottom_blue_alignment( hints, limit ); 3142 } 3143 3144 3145 static void 3146 af_touch_contour( AF_GlyphHints hints, 3147 FT_Int contour ) 3148 { 3149 AF_Point first_point = hints->contours[contour]; 3150 AF_Point p = first_point; 3151 3152 3153 do 3154 { 3155 p = p->next; 3156 3157 p->flags |= AF_FLAG_IGNORE; 3158 if ( !( p->flags & AF_FLAG_CONTROL ) ) 3159 p->flags |= AF_FLAG_TOUCH_Y; 3160 3161 } while ( p != first_point ); 3162 } 3163 3164 3165 static void 3166 af_touch_top_contours( AF_GlyphHints hints, 3167 FT_Int limit_contour ) 3168 { 3169 FT_Pos limit = hints->contour_y_minima[limit_contour]; 3170 3171 FT_Int contour; 3172 3173 3174 for ( contour = 0; contour < hints->num_contours; contour++ ) 3175 { 3176 FT_Pos min_y = hints->contour_y_minima[contour]; 3177 FT_Pos max_y = hints->contour_y_maxima[contour]; 3178 3179 3180 if ( min_y < max_y && 3181 min_y >= limit ) 3182 af_touch_contour( hints, contour ); 3183 } 3184 } 3185 3186 3187 static void 3188 af_touch_bottom_contours( AF_GlyphHints hints, 3189 FT_Int limit_contour ) 3190 { 3191 FT_Pos limit = hints->contour_y_minima[limit_contour]; 3192 3193 FT_Int contour; 3194 3195 3196 for ( contour = 0; contour < hints->num_contours; contour++ ) 3197 { 3198 FT_Pos min_y = hints->contour_y_minima[contour]; 3199 FT_Pos max_y = hints->contour_y_maxima[contour]; 3200 3201 3202 if ( min_y < max_y && 3203 max_y <= limit ) 3204 af_touch_contour( hints, contour ); 3205 } 3206 } 3207 3208 3209 /* Stretch tilde vertically, if necessary, and return the height */ 3210 /* difference between the original and the stretched outline. */ 3211 static FT_Pos 3212 af_latin_stretch_top_tilde( AF_GlyphHints hints, 3213 FT_Int tilde_contour ) 3214 { 3215 AF_Point p = hints->contours[tilde_contour]; 3216 AF_Point first_point = p; 3217 3218 FT_Pos min_y = hints->contour_y_minima[tilde_contour]; 3219 FT_Pos max_y = hints->contour_y_maxima[tilde_contour]; 3220 3221 FT_Pos min_measurement = FT_LONG_MAX; 3222 FT_Bool measurement_taken = FALSE; 3223 3224 FT_Pos height; 3225 FT_Pos extremum_threshold; 3226 FT_Pos target_height; 3227 3228 3229 if ( min_y == max_y ) 3230 return 0; 3231 3232 FT_TRACE4(( "af_latin_stretch_top_tilde: min y: %ld, max y: %ld\n", 3233 min_y, max_y )); 3234 3235 height = SUB_LONG( max_y, min_y ); 3236 extremum_threshold = height / 8; /* Value 8 is heuristic. */ 3237 3238 /* Find points that are local vertical round extrema, and which */ 3239 /* do not coincide with the vertical extreme values (i.e., we */ 3240 /* search for the 'other' wiggles in the tilde), then measure the */ 3241 /* distance to the vertical extreme values. Try to find the one */ 3242 /* with the smallest distance. */ 3243 /* */ 3244 /* The algorithm only works for tilde shapes that don't deviate */ 3245 /* from the standard shape too much. In particular, the wiggles */ 3246 /* must be round extrema. */ 3247 do 3248 { 3249 p = p->next; 3250 3251 if ( !( p->flags & AF_FLAG_CONTROL ) && 3252 p->prev->y == p->y && p->next->y == p->y && 3253 p->y != min_y && p->y != max_y && 3254 p->prev->flags & AF_FLAG_CONTROL && 3255 p->next->flags & AF_FLAG_CONTROL ) 3256 { 3257 /* This point could be a candidate. Find the next and previous */ 3258 /* on-curve points, and make sure they are both either above or */ 3259 /* below the point, then make the measurement. */ 3260 AF_Point prev_on = p->prev; 3261 AF_Point next_on = p->next; 3262 3263 FT_Pos measurement; 3264 3265 3266 while ( prev_on->flags & AF_FLAG_CONTROL ) 3267 prev_on = prev_on->prev; 3268 while ( next_on->flags & AF_FLAG_CONTROL ) 3269 next_on = next_on->next; 3270 3271 if ( next_on->y > p->y && prev_on->y > p->y ) 3272 measurement = p->y - min_y; 3273 else if ( next_on->y < p->y && prev_on->y < p->y ) 3274 measurement = max_y - p->y; 3275 else 3276 continue; 3277 3278 /* Ignore hits that are too near to a vertical extremum. */ 3279 if ( measurement < extremum_threshold ) 3280 continue; 3281 3282 if ( !measurement_taken || measurement < min_measurement ) 3283 { 3284 measurement_taken = TRUE; 3285 min_measurement = measurement; 3286 } 3287 } 3288 3289 } while ( p != first_point ); 3290 3291 if ( !measurement_taken ) 3292 min_measurement = 0; 3293 3294 FT_TRACE4(( "af_latin_stretch_top_tilde: min measurement %ld\n", 3295 min_measurement )); 3296 3297 /* To preserve the stretched shape we prevent that the tilde */ 3298 /* gets auto-hinted; we do this for all contours equal or */ 3299 /* above the vertical minimum of `tilde_contour`. */ 3300 af_touch_top_contours( hints, tilde_contour ); 3301 3302 /* XXX This is an important element of the algorithm; */ 3303 /* we need a description. */ 3304 target_height = min_measurement + 64; 3305 if ( height >= target_height ) 3306 return 0; 3307 3308 /* Do the scaling. */ 3309 p = first_point; 3310 do 3311 { 3312 p = p->next; 3313 /* We adjust the height of the diacritic only, which means */ 3314 /* we are never dealing with large numbers and can thus avoid */ 3315 /* `FT_MulFix`. */ 3316 p->y = ( ( p->y - min_y ) * target_height / height ) + min_y; 3317 3318 } while ( p != first_point ); 3319 3320 return target_height - height; 3321 } 3322 3323 3324 static FT_Pos 3325 af_latin_stretch_bottom_tilde( AF_GlyphHints hints, 3326 FT_Int tilde_contour ) 3327 { 3328 AF_Point p = hints->contours[tilde_contour]; 3329 AF_Point first_point = p; 3330 3331 FT_Pos min_y = hints->contour_y_minima[tilde_contour]; 3332 FT_Pos max_y = hints->contour_y_maxima[tilde_contour]; 3333 3334 FT_Pos min_measurement = FT_LONG_MAX; 3335 FT_Bool measurement_taken = FALSE; 3336 3337 FT_Pos height; 3338 FT_Pos extremum_threshold; 3339 FT_Pos target_height; 3340 3341 3342 if ( min_y == max_y ) 3343 return 0; 3344 3345 FT_TRACE4(( "af_latin_stretch_bottom_tilde: min y: %ld, max y: %ld\n", 3346 min_y, max_y )); 3347 3348 height = SUB_LONG( max_y, min_y ); 3349 extremum_threshold = height / 8; 3350 3351 do 3352 { 3353 p = p->next; 3354 3355 if ( !( p->flags & AF_FLAG_CONTROL ) && 3356 p->prev->y == p->y && p->next->y == p->y && 3357 p->y != min_y && p->y != max_y && 3358 p->prev->flags & AF_FLAG_CONTROL && 3359 p->next->flags & AF_FLAG_CONTROL ) 3360 { 3361 AF_Point prev_on = p->prev; 3362 AF_Point next_on = p->next; 3363 3364 FT_Pos measurement; 3365 3366 3367 while ( prev_on->flags & AF_FLAG_CONTROL ) 3368 prev_on = prev_on->prev; 3369 while ( next_on->flags & AF_FLAG_CONTROL ) 3370 next_on = next_on->next; 3371 3372 if ( next_on->y > p->y && prev_on->y > p->y ) 3373 measurement = p->y - min_y; 3374 else if ( next_on->y < p->y && prev_on->y < p->y ) 3375 measurement = max_y - p->y; 3376 else 3377 continue; 3378 3379 if ( measurement < extremum_threshold ) 3380 continue; 3381 3382 if ( !measurement_taken || measurement < min_measurement ) 3383 { 3384 measurement_taken = TRUE; 3385 min_measurement = measurement; 3386 } 3387 } 3388 3389 } while ( p != first_point ); 3390 3391 if ( !measurement_taken ) 3392 min_measurement = 0; 3393 3394 FT_TRACE4(( "af_latin_stretch_bottom_tilde: min measurement %ld\n", 3395 min_measurement )); 3396 3397 af_touch_bottom_contours( hints, tilde_contour ); 3398 3399 target_height = min_measurement + 64; 3400 if ( height >= target_height ) 3401 return 0; 3402 3403 p = first_point; 3404 do 3405 { 3406 p = p->next; 3407 p->y = ( ( p->y - max_y ) * target_height / height ) + max_y; 3408 3409 } while ( p != first_point ); 3410 3411 return target_height - height; 3412 } 3413 3414 3415 /* 3416 As part of `af_latin_stretch_top_tilde`, normally all points in the 3417 tilde are marked as touched, so the existing grid fitting will leave the 3418 tilde misaligned with the grid. 3419 3420 This function moves the tilde contour down to be grid-fitted. We assume 3421 that if moving the tilde down would cause it to touch or overlap another 3422 countour, the vertical adjustment step will fix it. 3423 3424 Because the vertical adjustment step comes after all other grid-fitting 3425 steps, the top edge of the contour under the tilde is usually aligned 3426 with a horizontal grid line. The vertical gap enforced by the vertical 3427 adjustment is exactly one pixel, so if the top edge of the contour below 3428 the tilde is on a grid line, the resulting tilde contour will also be 3429 grid-aligned. 3430 3431 But in cases where the gap is already big enough so that the vertical 3432 adjustment does nothing, this function ensures that even without the 3433 intervention of the vertical adjustment step, the tilde will be 3434 grid-aligned. 3435 3436 Return the vertical alignment amount. 3437 */ 3438 static FT_Pos 3439 af_latin_align_top_tilde( AF_GlyphHints hints, 3440 FT_Int tilde_contour ) 3441 { 3442 AF_Point p = hints->contours[tilde_contour]; 3443 AF_Point first_point = p; 3444 3445 FT_Pos min_y = p->y; 3446 FT_Pos max_y = p->y; 3447 3448 FT_Pos min_y_rounded; 3449 FT_Pos delta; 3450 FT_Pos height; 3451 3452 3453 /* Find vertical extrema of the (now stretched) tilde contour. */ 3454 do 3455 { 3456 p = p->next; 3457 if ( p->y < min_y ) 3458 min_y = p->y; 3459 if ( p->y > max_y ) 3460 max_y = p->y; 3461 3462 } while ( p != first_point ); 3463 3464 /* Align bottom of the tilde to the grid. */ 3465 min_y_rounded = FT_PIX_ROUND( min_y ); 3466 delta = min_y_rounded - min_y; 3467 height = max_y - min_y; 3468 3469 /* If the tilde is less than 3 pixels tall, snap the center of it */ 3470 /* to the grid instead of the bottom to improve readability. */ 3471 if ( height < 64 * 3 ) 3472 delta += ( FT_PIX_ROUND( height ) - height ) / 2; 3473 3474 af_move_contour_vertically( first_point, delta ); 3475 3476 return delta; 3477 } 3478 3479 3480 static FT_Pos 3481 af_latin_align_bottom_tilde( AF_GlyphHints hints, 3482 FT_Int tilde_contour ) 3483 { 3484 AF_Point p = hints->contours[tilde_contour]; 3485 AF_Point first_point = p; 3486 3487 FT_Pos min_y = p->y; 3488 FT_Pos max_y = p->y; 3489 3490 FT_Pos max_y_rounded; 3491 FT_Pos delta; 3492 FT_Pos height; 3493 3494 3495 do 3496 { 3497 p = p->next; 3498 if ( p->y < min_y ) 3499 min_y = p->y; 3500 if ( p->y > max_y ) 3501 max_y = p->y; 3502 3503 } while ( p != first_point ); 3504 3505 max_y_rounded = FT_PIX_ROUND( max_y ); 3506 delta = max_y_rounded - max_y; 3507 height = max_y - min_y; 3508 3509 if ( height < 64 * 3 ) 3510 delta -= ( FT_PIX_ROUND( height ) - height ) / 2; 3511 3512 af_move_contour_vertically( first_point, delta ); 3513 3514 return delta; 3515 } 3516 3517 3518 /* Return 1 if the given contour overlaps horizontally with the bounding */ 3519 /* box of all other contours combined. This is a helper for function */ 3520 /* `af_glyph_hints_apply_vertical_separation_adjustments`. */ 3521 static FT_Bool 3522 af_check_contour_horizontal_overlap( AF_GlyphHints hints, 3523 FT_Int contour_index ) 3524 { 3525 FT_Pos contour_max_x = FT_LONG_MIN; 3526 FT_Pos contour_min_x = FT_LONG_MAX; 3527 FT_Pos others_max_x = FT_LONG_MIN; 3528 FT_Pos others_min_x = FT_LONG_MAX; 3529 3530 FT_Int contour; 3531 3532 FT_Bool horizontal_overlap; 3533 3534 3535 for ( contour = 0; contour < hints->num_contours; contour++ ) 3536 { 3537 AF_Point first_point = hints->contours[contour]; 3538 AF_Point p = first_point; 3539 3540 3541 /* Ignore dimensionless contours (i.e., contours with only one or */ 3542 /* two points). */ 3543 if ( first_point->next->next == first_point ) 3544 continue; 3545 3546 do 3547 { 3548 p = p->next; 3549 3550 if ( contour == contour_index ) 3551 { 3552 if ( p->x < contour_min_x ) 3553 contour_min_x = p->x; 3554 if ( p->x > contour_max_x ) 3555 contour_max_x = p->x; 3556 } 3557 else 3558 { 3559 if ( p->x < others_min_x ) 3560 others_min_x = p->x; 3561 if ( p->x > others_max_x ) 3562 others_max_x = p->x; 3563 } 3564 } while ( p != first_point ); 3565 } 3566 3567 horizontal_overlap = 3568 ( others_min_x <= contour_max_x && contour_max_x <= others_max_x ) || 3569 ( others_min_x <= contour_min_x && contour_min_x <= others_max_x ) || 3570 ( contour_max_x >= others_max_x && contour_min_x <= others_min_x ); 3571 3572 return horizontal_overlap; 3573 } 3574 3575 3576 static void 3577 af_glyph_hints_apply_vertical_separation_adjustments( 3578 AF_GlyphHints hints, 3579 AF_Dimension dim, 3580 FT_UInt glyph_index, 3581 FT_Pos accent_height_limit, 3582 FT_Hash reverse_charmap ) 3583 { 3584 FT_Bool adjust_top = FALSE; 3585 FT_Bool adjust_below_top = FALSE; 3586 3587 FT_Bool adjust_bottom = FALSE; 3588 FT_Bool adjust_above_bottom = FALSE; 3589 3590 size_t* val; 3591 FT_UInt32 adj_type = AF_ADJUST_NONE; 3592 3593 3594 FT_TRACE4(( "Entering" 3595 " af_glyph_hints_apply_vertical_separation_adjustments\n" )); 3596 3597 if ( dim != AF_DIMENSION_VERT ) 3598 return; 3599 3600 val = ft_hash_num_lookup( (FT_Int)glyph_index, reverse_charmap ); 3601 if ( val ) 3602 { 3603 FT_UInt codepoint = *val; 3604 3605 3606 adj_type = af_adjustment_database_lookup( codepoint ); 3607 3608 if ( adj_type ) 3609 { 3610 adjust_top = !!( adj_type & AF_ADJUST_UP ); 3611 adjust_below_top = !!( adj_type & AF_ADJUST_UP2 ); 3612 3613 adjust_bottom = !!( adj_type & AF_ADJUST_DOWN ); 3614 adjust_above_bottom = !!( adj_type & AF_ADJUST_DOWN2 ); 3615 } 3616 } 3617 3618 if ( ( ( adjust_top || adjust_bottom ) && 3619 hints->num_contours >= 2 ) || 3620 ( ( adjust_below_top || adjust_above_bottom ) && 3621 hints->num_contours >= 3 ) ) 3622 { 3623 /* Recompute vertical extrema, this time acting on already */ 3624 /* auto-hinted outlines. */ 3625 af_compute_vertical_extrema( hints ); 3626 } 3627 3628 if ( ( adjust_top && hints->num_contours >= 2 ) || 3629 ( adjust_below_top && hints->num_contours >= 3 ) ) 3630 { 3631 FT_Int high_contour; 3632 FT_Pos high_min_y; 3633 FT_Pos high_max_y; 3634 FT_Pos high_height; 3635 3636 FT_Int tilde_contour; 3637 FT_Pos tilde_min_y; 3638 FT_Pos tilde_max_y; 3639 FT_Pos tilde_height; 3640 3641 FT_Int contour; 3642 FT_Bool horizontal_overlap; 3643 3644 FT_Pos min_distance = 64; 3645 FT_Pos adjustment_amount; 3646 FT_Pos calculated_amount; 3647 FT_Pos centering_adjustment = 0; 3648 FT_Pos pos; 3649 3650 FT_Bool is_top_tilde = !!( adj_type & AF_ADJUST_TILDE_TOP ); 3651 FT_Bool is_below_top_tilde = !!( adj_type & AF_ADJUST_TILDE_TOP2 ); 3652 3653 3654 FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n" 3655 " Applying vertical adjustment: %s\n", 3656 adjust_top ? "AF_ADJUST_TOP" : "AF_ADJUST_TOP2" )); 3657 3658 high_contour = adjust_below_top 3659 ? af_find_second_highest_contour( hints ) 3660 : af_find_highest_contour( hints ); 3661 3662 /* Check for a horizontal overlap between the high contour and the */ 3663 /* rest. If there is no overlap, do not adjust. */ 3664 horizontal_overlap = 3665 af_check_contour_horizontal_overlap( hints, high_contour ); 3666 if ( !horizontal_overlap ) 3667 { 3668 FT_TRACE4(( " High contour does not horizontally overlap" 3669 " with other contours.\n" 3670 " Skipping adjustment.\n" )); 3671 return; 3672 } 3673 3674 high_min_y = hints->contour_y_minima[high_contour]; 3675 high_max_y = hints->contour_y_maxima[high_contour]; 3676 high_height = high_max_y - high_min_y; 3677 3678 if ( high_height > accent_height_limit ) 3679 { 3680 FT_TRACE4(( " High contour height (%.2f) exceeds accent height" 3681 " limit (%.2f).\n" 3682 " Skipping adjustment.\n", 3683 (double)high_height / 64, 3684 (double)accent_height_limit / 64 )); 3685 return; 3686 } 3687 3688 /* If the difference between the vertical minimum of the high */ 3689 /* contour and the vertical maximum of another contour is less */ 3690 /* than a pixel, shift up the high contour to make the distance */ 3691 /* one pixel. */ 3692 for ( contour = 0; contour < hints->num_contours; contour++ ) 3693 { 3694 FT_Pos min_y; 3695 FT_Pos max_y; 3696 FT_Pos distance; 3697 3698 3699 if ( contour == high_contour ) 3700 continue; 3701 3702 min_y = hints->contour_y_minima[contour]; 3703 max_y = hints->contour_y_maxima[contour]; 3704 3705 /* We also check that the y minimum of the 'other' contour */ 3706 /* is below the high contour to avoid potential false hits */ 3707 /* with contours enclosed in the high one. */ 3708 distance = high_min_y - max_y; 3709 if ( distance < 64 && 3710 distance < min_distance && 3711 min_y < high_min_y ) 3712 min_distance = distance; 3713 } 3714 3715 adjustment_amount = 64 - min_distance; 3716 3717 if ( is_top_tilde || is_below_top_tilde ) 3718 { 3719 tilde_contour = adjust_top 3720 ? high_contour 3721 : ( is_below_top_tilde 3722 ? high_contour 3723 : af_find_highest_contour( hints ) ); 3724 3725 tilde_min_y = hints->contour_y_minima[tilde_contour]; 3726 tilde_max_y = hints->contour_y_maxima[tilde_contour]; 3727 tilde_height = tilde_max_y - tilde_min_y; 3728 3729 /* The vertical separation adjustment potentially undoes a */ 3730 /* tilde center alignment. If it would grid-align a tilde */ 3731 /* less than 3 pixels in height, shift additionally to */ 3732 /* re-center the tilde. */ 3733 3734 pos = high_min_y + adjustment_amount; 3735 if ( adjust_below_top && is_top_tilde ) 3736 pos += high_height; 3737 3738 if ( pos % 64 == 0 && tilde_height < 3 * 64 ) 3739 { 3740 centering_adjustment = ( FT_PIX_ROUND( tilde_height ) - 3741 tilde_height ) / 2; 3742 3743 FT_TRACE4(( " Additional tilde centering adjustment: %ld\n", 3744 centering_adjustment )); 3745 } 3746 } 3747 3748 if ( ( adjust_top && is_top_tilde ) || 3749 ( adjust_below_top && is_below_top_tilde ) ) 3750 calculated_amount = adjustment_amount + centering_adjustment; 3751 else 3752 calculated_amount = adjustment_amount; 3753 3754 /* allow a delta of 2/64px to handle rounding differences */ 3755 FT_TRACE4(( " Calculated adjustment amount: %ld%s\n", 3756 calculated_amount, 3757 ( calculated_amount < -2 || 3758 ( adjustment_amount > 66 && calculated_amount > 66 ) ) 3759 ? " (out of range [-2;66], not adjusting)" : "" )); 3760 3761 if ( calculated_amount != 0 && 3762 calculated_amount >= -2 && 3763 ( calculated_amount <= 66 || adjustment_amount <= 66 ) ) 3764 { 3765 /* Value 8 is heuristic. */ 3766 FT_Pos height_delta = high_height / 8; 3767 FT_Pos min_y_limit = high_min_y - height_delta; 3768 3769 3770 FT_TRACE4(( " Pushing high contour %ld units up\n", 3771 calculated_amount )); 3772 3773 /* While we use only a single contour (the 'high' one) for */ 3774 /* computing `adjustment_amount`, we apply it to all contours */ 3775 /* that are (approximately) in the same vertical range or */ 3776 /* higher. This covers, for example, the inner contour of */ 3777 /* the Czech ring accent or the second acute accent in the */ 3778 /* Hungarian double acute accent. */ 3779 af_move_contours_up( hints, min_y_limit, adjustment_amount ); 3780 3781 if ( adjust_below_top && is_top_tilde ) 3782 { 3783 FT_TRACE4(( " Pushing top tilde %ld units up\n", 3784 centering_adjustment )); 3785 3786 af_move_contours_up( hints, 3787 min_y_limit + high_height, 3788 centering_adjustment ); 3789 } 3790 } 3791 } 3792 3793 if ( ( adjust_bottom && hints->num_contours >= 2 ) || 3794 ( adjust_above_bottom && hints->num_contours >= 3 ) ) 3795 { 3796 FT_Int low_contour; 3797 FT_Pos low_min_y; 3798 FT_Pos low_max_y; 3799 FT_Pos low_height; 3800 3801 FT_Int tilde_contour; 3802 FT_Pos tilde_min_y; 3803 FT_Pos tilde_max_y; 3804 FT_Pos tilde_height; 3805 3806 FT_Int contour; 3807 FT_Bool horizontal_overlap; 3808 3809 FT_Pos min_distance = 64; 3810 FT_Pos adjustment_amount; 3811 FT_Pos calculated_amount; 3812 FT_Pos centering_adjustment = 0; 3813 FT_Pos pos; 3814 3815 FT_Bool is_bottom_tilde = 3816 !!( adj_type & AF_ADJUST_TILDE_BOTTOM ); 3817 FT_Bool is_above_bottom_tilde = 3818 !!( adj_type & AF_ADJUST_TILDE_BOTTOM2 ); 3819 3820 3821 FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n" 3822 " Applying vertical adjustment: %s\n", 3823 adjust_bottom ? "AF_ADJUST_DOWN": "AF_ADJUST_DOWN2" )); 3824 3825 low_contour = adjust_above_bottom 3826 ? af_find_second_lowest_contour( hints ) 3827 : af_find_lowest_contour( hints ); 3828 3829 horizontal_overlap = 3830 af_check_contour_horizontal_overlap( hints, low_contour ); 3831 if ( !horizontal_overlap ) 3832 { 3833 FT_TRACE4(( " Low contour does not horizontally overlap" 3834 " with other contours.\n" 3835 " Skipping adjustment.\n" )); 3836 return; 3837 } 3838 3839 low_min_y = hints->contour_y_minima[low_contour]; 3840 low_max_y = hints->contour_y_maxima[low_contour]; 3841 low_height = low_max_y - low_min_y; 3842 3843 if ( low_height > accent_height_limit ) 3844 { 3845 FT_TRACE4(( " Low contour height (%.2f) exceeds accent height" 3846 " limit (%.2f).\n" 3847 " Skipping adjustment.\n", 3848 (double)low_height / 64, 3849 (double)accent_height_limit / 64 )); 3850 return; 3851 } 3852 3853 for ( contour = 0; contour < hints->num_contours; contour++ ) 3854 { 3855 FT_Pos min_y; 3856 FT_Pos max_y; 3857 FT_Pos distance; 3858 3859 3860 if ( contour == low_contour ) 3861 continue; 3862 3863 min_y = hints->contour_y_minima[contour]; 3864 max_y = hints->contour_y_maxima[contour]; 3865 3866 distance = min_y - low_max_y; 3867 if ( distance < 64 && 3868 distance < min_distance && 3869 max_y > low_max_y ) 3870 min_distance = distance; 3871 } 3872 3873 adjustment_amount = 64 - min_distance; 3874 3875 if ( is_bottom_tilde || is_above_bottom_tilde ) 3876 { 3877 tilde_contour = adjust_bottom 3878 ? low_contour 3879 : ( is_above_bottom_tilde 3880 ? low_contour 3881 : af_find_lowest_contour( hints ) ); 3882 3883 tilde_min_y = hints->contour_y_minima[tilde_contour]; 3884 tilde_max_y = hints->contour_y_maxima[tilde_contour]; 3885 tilde_height = tilde_max_y - tilde_min_y; 3886 3887 pos = low_max_y - adjustment_amount; 3888 if ( adjust_above_bottom && is_bottom_tilde ) 3889 pos -= low_height; 3890 3891 if ( pos % 64 == 0 && tilde_height < 3 * 64 ) 3892 { 3893 centering_adjustment = ( FT_PIX_ROUND( tilde_height ) - 3894 tilde_height ) / 2; 3895 3896 FT_TRACE4(( " Additional tilde centering adjustment: %ld\n", 3897 centering_adjustment )); 3898 } 3899 } 3900 3901 if ( ( adjust_bottom && is_bottom_tilde ) || 3902 ( adjust_above_bottom && is_above_bottom_tilde ) ) 3903 calculated_amount = adjustment_amount + centering_adjustment; 3904 else 3905 calculated_amount = adjustment_amount; 3906 3907 FT_TRACE4(( " Calculated adjustment amount: %ld%s\n", 3908 calculated_amount, 3909 ( calculated_amount < -2 || 3910 ( adjustment_amount > 66 && calculated_amount > 66 ) ) 3911 ? " (out of range [-2;66], not adjusting)" : "" )); 3912 3913 if ( calculated_amount != 0 && 3914 calculated_amount >= -2 && 3915 ( calculated_amount <= 66 || adjustment_amount <= 66 ) ) 3916 { 3917 FT_Pos height_delta = low_height / 8; 3918 FT_Pos max_y_limit = low_max_y + height_delta; 3919 3920 3921 FT_TRACE4(( " Pushing low contour %ld units down\n", 3922 calculated_amount )); 3923 3924 af_move_contours_down( hints, max_y_limit, adjustment_amount ); 3925 3926 if ( adjust_above_bottom && is_bottom_tilde ) 3927 { 3928 FT_TRACE4(( " Pushing bottom tilde %ld units down\n", 3929 centering_adjustment )); 3930 3931 af_move_contours_down( hints, 3932 max_y_limit - low_height, 3933 centering_adjustment ); 3934 } 3935 } 3936 } 3937 3938 #ifdef FT_DEBUG_LEVEL_TRACE 3939 if ( !( ( ( adjust_top || adjust_bottom ) && 3940 hints->num_contours >= 2 ) || 3941 ( ( adjust_below_top || adjust_above_bottom ) && 3942 hints->num_contours >= 3 ) ) ) 3943 FT_TRACE4(( "af_glyph_hints_apply_vertical_separation_adjustments:\n" 3944 " No vertical adjustment applied\n" )); 3945 #endif 3946 3947 FT_TRACE4(( "Exiting" 3948 " af_glyph_hints_apply_vertical_separation_adjustments\n" )); 3949 } 3950 3951 3952 #undef FT_COMPONENT 3953 #define FT_COMPONENT aflatin 3954 3955 3956 /* Compute the snapped width of a given stem, ignoring very thin ones. */ 3957 /* There is a lot of voodoo in this function; changing the hard-coded */ 3958 /* parameters influence the whole hinting process. */ 3959 3960 static FT_Pos 3961 af_latin_compute_stem_width( AF_GlyphHints hints, 3962 AF_Dimension dim, 3963 FT_Pos width, 3964 FT_Pos base_delta, 3965 FT_UInt base_flags, 3966 FT_UInt stem_flags ) 3967 { 3968 AF_LatinMetrics metrics = (AF_LatinMetrics)hints->metrics; 3969 AF_LatinAxis axis = &metrics->axis[dim]; 3970 FT_Pos dist = width; 3971 FT_Int sign = 0; 3972 FT_Int vertical = ( dim == AF_DIMENSION_VERT ); 3973 3974 3975 if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) || 3976 axis->extra_light ) 3977 return width; 3978 3979 if ( dist < 0 ) 3980 { 3981 dist = -width; 3982 sign = 1; 3983 } 3984 3985 if ( ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) || 3986 ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) ) 3987 { 3988 /* smooth hinting process: very lightly quantize the stem width */ 3989 3990 /* leave the widths of serifs alone */ 3991 if ( ( stem_flags & AF_EDGE_SERIF ) && 3992 vertical && 3993 ( dist < 3 * 64 ) ) 3994 goto Done_Width; 3995 3996 else if ( base_flags & AF_EDGE_ROUND ) 3997 { 3998 if ( dist < 80 ) 3999 dist = 64; 4000 } 4001 else if ( dist < 56 ) 4002 dist = 56; 4003 4004 if ( axis->width_count > 0 ) 4005 { 4006 FT_Pos delta; 4007 4008 4009 /* compare to standard width */ 4010 delta = dist - axis->widths[0].cur; 4011 4012 if ( delta < 0 ) 4013 delta = -delta; 4014 4015 if ( delta < 40 ) 4016 { 4017 dist = axis->widths[0].cur; 4018 if ( dist < 48 ) 4019 dist = 48; 4020 4021 goto Done_Width; 4022 } 4023 4024 if ( dist < 3 * 64 ) 4025 { 4026 delta = dist & 63; 4027 dist &= -64; 4028 4029 if ( delta < 10 ) 4030 dist += delta; 4031 4032 else if ( delta < 32 ) 4033 dist += 10; 4034 4035 else if ( delta < 54 ) 4036 dist += 54; 4037 4038 else 4039 dist += delta; 4040 } 4041 else 4042 { 4043 /* A stem's end position depends on two values: the start */ 4044 /* position and the stem length. The former gets usually */ 4045 /* rounded to the grid, while the latter gets rounded also if it */ 4046 /* exceeds a certain length (see below in this function). This */ 4047 /* `double rounding' can lead to a great difference to the */ 4048 /* original, unhinted position; this normally doesn't matter for */ 4049 /* large PPEM values, but for small sizes it can easily make */ 4050 /* outlines collide. For this reason, we adjust the stem length */ 4051 /* by a small amount depending on the PPEM value in case the */ 4052 /* former and latter rounding both point into the same */ 4053 /* direction. */ 4054 4055 FT_Pos bdelta = 0; 4056 4057 4058 if ( ( ( width > 0 ) && ( base_delta > 0 ) ) || 4059 ( ( width < 0 ) && ( base_delta < 0 ) ) ) 4060 { 4061 FT_UInt ppem = metrics->root.scaler.face->size->metrics.x_ppem; 4062 4063 4064 if ( ppem < 10 ) 4065 bdelta = base_delta; 4066 else if ( ppem < 30 ) 4067 bdelta = ( base_delta * (FT_Pos)( 30 - ppem ) ) / 20; 4068 4069 if ( bdelta < 0 ) 4070 bdelta = -bdelta; 4071 } 4072 4073 dist = ( dist - bdelta + 32 ) & ~63; 4074 } 4075 } 4076 } 4077 else 4078 { 4079 /* strong hinting process: snap the stem width to integer pixels */ 4080 4081 FT_Pos org_dist = dist; 4082 4083 4084 dist = af_latin_snap_width( axis->widths, axis->width_count, dist ); 4085 4086 if ( vertical ) 4087 { 4088 /* in the case of vertical hinting, always round */ 4089 /* the stem heights to integer pixels */ 4090 4091 if ( dist >= 64 ) 4092 dist = ( dist + 16 ) & ~63; 4093 else 4094 dist = 64; 4095 } 4096 else 4097 { 4098 if ( AF_LATIN_HINTS_DO_MONO( hints ) ) 4099 { 4100 /* monochrome horizontal hinting: snap widths to integer pixels */ 4101 /* with a different threshold */ 4102 4103 if ( dist < 64 ) 4104 dist = 64; 4105 else 4106 dist = ( dist + 32 ) & ~63; 4107 } 4108 else 4109 { 4110 /* for horizontal anti-aliased hinting, we adopt a more subtle */ 4111 /* approach: we strengthen small stems, round stems whose size */ 4112 /* is between 1 and 2 pixels to an integer, otherwise nothing */ 4113 4114 if ( dist < 48 ) 4115 dist = ( dist + 64 ) >> 1; 4116 4117 else if ( dist < 128 ) 4118 { 4119 /* We only round to an integer width if the corresponding */ 4120 /* distortion is less than 1/4 pixel. Otherwise this */ 4121 /* makes everything worse since the diagonals, which are */ 4122 /* not hinted, appear a lot bolder or thinner than the */ 4123 /* vertical stems. */ 4124 4125 FT_Pos delta; 4126 4127 4128 dist = ( dist + 22 ) & ~63; 4129 delta = dist - org_dist; 4130 if ( delta < 0 ) 4131 delta = -delta; 4132 4133 if ( delta >= 16 ) 4134 { 4135 dist = org_dist; 4136 if ( dist < 48 ) 4137 dist = ( dist + 64 ) >> 1; 4138 } 4139 } 4140 else 4141 /* round otherwise to prevent color fringes in LCD mode */ 4142 dist = ( dist + 32 ) & ~63; 4143 } 4144 } 4145 } 4146 4147 Done_Width: 4148 if ( sign ) 4149 dist = -dist; 4150 4151 return dist; 4152 } 4153 4154 4155 /* Align one stem edge relative to the previous stem edge. */ 4156 4157 static void 4158 af_latin_align_linked_edge( AF_GlyphHints hints, 4159 AF_Dimension dim, 4160 AF_Edge base_edge, 4161 AF_Edge stem_edge ) 4162 { 4163 FT_Pos dist, base_delta; 4164 FT_Pos fitted_width; 4165 4166 4167 dist = stem_edge->opos - base_edge->opos; 4168 base_delta = base_edge->pos - base_edge->opos; 4169 4170 fitted_width = af_latin_compute_stem_width( hints, dim, 4171 dist, base_delta, 4172 base_edge->flags, 4173 stem_edge->flags ); 4174 4175 4176 stem_edge->pos = base_edge->pos + fitted_width; 4177 4178 FT_TRACE5(( " LINK: edge %td (opos=%.2f) linked to %.2f," 4179 " dist was %.2f, now %.2f\n", 4180 stem_edge - hints->axis[dim].edges, 4181 (double)stem_edge->opos / 64, (double)stem_edge->pos / 64, 4182 (double)dist / 64, (double)fitted_width / 64 )); 4183 } 4184 4185 4186 /* Shift the coordinates of the `serif' edge by the same amount */ 4187 /* as the corresponding `base' edge has been moved already. */ 4188 4189 static void 4190 af_latin_align_serif_edge( AF_GlyphHints hints, 4191 AF_Edge base, 4192 AF_Edge serif ) 4193 { 4194 FT_UNUSED( hints ); 4195 4196 serif->pos = base->pos + ( serif->opos - base->opos ); 4197 } 4198 4199 4200 /*************************************************************************/ 4201 /*************************************************************************/ 4202 /*************************************************************************/ 4203 /**** ****/ 4204 /**** E D G E H I N T I N G ****/ 4205 /**** ****/ 4206 /*************************************************************************/ 4207 /*************************************************************************/ 4208 /*************************************************************************/ 4209 4210 4211 /* The main grid-fitting routine. */ 4212 4213 static void 4214 af_latin_hint_edges( AF_GlyphHints hints, 4215 AF_Dimension dim ) 4216 { 4217 AF_AxisHints axis = &hints->axis[dim]; 4218 4219 AF_Edge edges = axis->edges; 4220 AF_Edge edge_limit = FT_OFFSET( edges, axis->num_edges ); 4221 AF_Edge edge; 4222 FT_PtrDist n_edges; 4223 4224 AF_Edge anchor = NULL; 4225 FT_Bool has_non_stem_edges = 0; 4226 4227 AF_StyleClass style_class = hints->metrics->style_class; 4228 AF_ScriptClass script_class = af_script_classes[style_class->script]; 4229 4230 FT_Bool top_to_bottom_hinting = 0; 4231 4232 #ifdef FT_DEBUG_LEVEL_TRACE 4233 FT_UInt num_actions = 0; 4234 #endif 4235 4236 4237 FT_TRACE5(( "latin %s edge hinting (style `%s')\n", 4238 dim == AF_DIMENSION_VERT ? "horizontal" : "vertical", 4239 af_style_names[hints->metrics->style_class->style] )); 4240 4241 if ( dim == AF_DIMENSION_VERT ) 4242 top_to_bottom_hinting = script_class->top_to_bottom_hinting; 4243 4244 /* we begin by aligning all stems relative to the blue zone */ 4245 /* if needed -- that's only for horizontal edges */ 4246 4247 if ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_BLUES( hints ) ) 4248 { 4249 for ( edge = edges; edge < edge_limit; edge++ ) 4250 { 4251 AF_Width blue; 4252 AF_Edge edge1, edge2; /* these edges form the stem to check */ 4253 4254 4255 if ( edge->flags & AF_EDGE_DONE ) 4256 continue; 4257 4258 edge1 = NULL; 4259 edge2 = edge->link; 4260 4261 /* 4262 * If a stem contains both a neutral and a non-neutral blue zone, 4263 * skip the neutral one. Otherwise, outlines with different 4264 * directions might be incorrectly aligned at the same vertical 4265 * position. 4266 * 4267 * If we have two neutral blue zones, skip one of them. 4268 * 4269 */ 4270 if ( edge->blue_edge && edge2 && edge2->blue_edge ) 4271 { 4272 FT_Byte neutral = edge->flags & AF_EDGE_NEUTRAL; 4273 FT_Byte neutral2 = edge2->flags & AF_EDGE_NEUTRAL; 4274 4275 4276 if ( neutral2 ) 4277 { 4278 edge2->blue_edge = NULL; 4279 edge2->flags &= ~AF_EDGE_NEUTRAL; 4280 } 4281 else if ( neutral ) 4282 { 4283 edge->blue_edge = NULL; 4284 edge->flags &= ~AF_EDGE_NEUTRAL; 4285 } 4286 } 4287 4288 blue = edge->blue_edge; 4289 if ( blue ) 4290 edge1 = edge; 4291 4292 /* flip edges if the other edge is aligned to a blue zone */ 4293 else if ( edge2 && edge2->blue_edge ) 4294 { 4295 blue = edge2->blue_edge; 4296 edge1 = edge2; 4297 edge2 = edge; 4298 } 4299 4300 if ( !edge1 ) 4301 continue; 4302 4303 #ifdef FT_DEBUG_LEVEL_TRACE 4304 if ( !anchor ) 4305 FT_TRACE5(( " BLUE_ANCHOR: edge %td (opos=%.2f) snapped to %.2f," 4306 " was %.2f (anchor=edge %td)\n", 4307 edge1 - edges, 4308 (double)edge1->opos / 64, (double)blue->fit / 64, 4309 (double)edge1->pos / 64, edge - edges )); 4310 else 4311 FT_TRACE5(( " BLUE: edge %td (opos=%.2f) snapped to %.2f," 4312 " was %.2f\n", 4313 edge1 - edges, 4314 (double)edge1->opos / 64, (double)blue->fit / 64, 4315 (double)edge1->pos / 64 )); 4316 4317 num_actions++; 4318 #endif 4319 4320 edge1->pos = blue->fit; 4321 edge1->flags |= AF_EDGE_DONE; 4322 4323 if ( edge2 && !edge2->blue_edge ) 4324 { 4325 af_latin_align_linked_edge( hints, dim, edge1, edge2 ); 4326 edge2->flags |= AF_EDGE_DONE; 4327 4328 #ifdef FT_DEBUG_LEVEL_TRACE 4329 num_actions++; 4330 #endif 4331 } 4332 4333 if ( !anchor ) 4334 anchor = edge; 4335 } 4336 } 4337 4338 /* now we align all other stem edges, trying to maintain the */ 4339 /* relative order of stems in the glyph */ 4340 for ( edge = edges; edge < edge_limit; edge++ ) 4341 { 4342 AF_Edge edge2; 4343 4344 4345 if ( edge->flags & AF_EDGE_DONE ) 4346 continue; 4347 4348 /* skip all non-stem edges */ 4349 edge2 = edge->link; 4350 if ( !edge2 ) 4351 { 4352 has_non_stem_edges = TRUE; 4353 continue; 4354 } 4355 4356 /* now align the stem */ 4357 4358 /* this should not happen, but it's better to be safe */ 4359 if ( edge2->blue_edge ) 4360 { 4361 FT_TRACE5(( " ASSERTION FAILED for edge %td\n", edge2 - edges )); 4362 4363 af_latin_align_linked_edge( hints, dim, edge2, edge ); 4364 edge->flags |= AF_EDGE_DONE; 4365 4366 #ifdef FT_DEBUG_LEVEL_TRACE 4367 num_actions++; 4368 #endif 4369 continue; 4370 } 4371 4372 if ( !anchor ) 4373 { 4374 /* if we reach this if clause, no stem has been aligned yet */ 4375 4376 FT_Pos org_len, org_center, cur_len; 4377 FT_Pos cur_pos1, error1, error2, u_off, d_off; 4378 4379 4380 org_len = edge2->opos - edge->opos; 4381 cur_len = af_latin_compute_stem_width( hints, dim, 4382 org_len, 0, 4383 edge->flags, 4384 edge2->flags ); 4385 4386 /* some voodoo to specially round edges for small stem widths; */ 4387 /* the idea is to align the center of a stem, then shifting */ 4388 /* the stem edges to suitable positions */ 4389 if ( cur_len <= 64 ) 4390 { 4391 /* width <= 1px */ 4392 u_off = 32; 4393 d_off = 32; 4394 } 4395 else 4396 { 4397 /* 1px < width < 1.5px */ 4398 u_off = 38; 4399 d_off = 26; 4400 } 4401 4402 if ( cur_len < 96 ) 4403 { 4404 org_center = edge->opos + ( org_len >> 1 ); 4405 cur_pos1 = FT_PIX_ROUND( org_center ); 4406 4407 error1 = org_center - ( cur_pos1 - u_off ); 4408 if ( error1 < 0 ) 4409 error1 = -error1; 4410 4411 error2 = org_center - ( cur_pos1 + d_off ); 4412 if ( error2 < 0 ) 4413 error2 = -error2; 4414 4415 if ( error1 < error2 ) 4416 cur_pos1 -= u_off; 4417 else 4418 cur_pos1 += d_off; 4419 4420 edge->pos = cur_pos1 - cur_len / 2; 4421 edge2->pos = edge->pos + cur_len; 4422 } 4423 else 4424 edge->pos = FT_PIX_ROUND( edge->opos ); 4425 4426 anchor = edge; 4427 edge->flags |= AF_EDGE_DONE; 4428 4429 FT_TRACE5(( " ANCHOR: edge %td (opos=%.2f) and %td (opos=%.2f)" 4430 " snapped to %.2f and %.2f\n", 4431 edge - edges, (double)edge->opos / 64, 4432 edge2 - edges, (double)edge2->opos / 64, 4433 (double)edge->pos / 64, (double)edge2->pos / 64 )); 4434 4435 af_latin_align_linked_edge( hints, dim, edge, edge2 ); 4436 4437 #ifdef FT_DEBUG_LEVEL_TRACE 4438 num_actions += 2; 4439 #endif 4440 } 4441 else 4442 { 4443 FT_Pos org_pos, org_len, org_center, cur_len; 4444 FT_Pos cur_pos1, cur_pos2, delta1, delta2; 4445 4446 4447 org_pos = anchor->pos + ( edge->opos - anchor->opos ); 4448 org_len = edge2->opos - edge->opos; 4449 org_center = org_pos + ( org_len >> 1 ); 4450 4451 cur_len = af_latin_compute_stem_width( hints, dim, 4452 org_len, 0, 4453 edge->flags, 4454 edge2->flags ); 4455 4456 if ( edge2->flags & AF_EDGE_DONE ) 4457 { 4458 FT_TRACE5(( " ADJUST: edge %td (pos=%.2f) moved to %.2f\n", 4459 edge - edges, (double)edge->pos / 64, 4460 (double)( edge2->pos - cur_len ) / 64 )); 4461 4462 edge->pos = edge2->pos - cur_len; 4463 } 4464 4465 else if ( cur_len < 96 ) 4466 { 4467 FT_Pos u_off, d_off; 4468 4469 4470 cur_pos1 = FT_PIX_ROUND( org_center ); 4471 4472 if ( cur_len <= 64 ) 4473 { 4474 u_off = 32; 4475 d_off = 32; 4476 } 4477 else 4478 { 4479 u_off = 38; 4480 d_off = 26; 4481 } 4482 4483 delta1 = org_center - ( cur_pos1 - u_off ); 4484 if ( delta1 < 0 ) 4485 delta1 = -delta1; 4486 4487 delta2 = org_center - ( cur_pos1 + d_off ); 4488 if ( delta2 < 0 ) 4489 delta2 = -delta2; 4490 4491 if ( delta1 < delta2 ) 4492 cur_pos1 -= u_off; 4493 else 4494 cur_pos1 += d_off; 4495 4496 edge->pos = cur_pos1 - cur_len / 2; 4497 edge2->pos = cur_pos1 + cur_len / 2; 4498 4499 FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)" 4500 " snapped to %.2f and %.2f\n", 4501 edge - edges, (double)edge->opos / 64, 4502 edge2 - edges, (double)edge2->opos / 64, 4503 (double)edge->pos / 64, (double)edge2->pos / 64 )); 4504 } 4505 4506 else 4507 { 4508 org_pos = anchor->pos + ( edge->opos - anchor->opos ); 4509 org_len = edge2->opos - edge->opos; 4510 org_center = org_pos + ( org_len >> 1 ); 4511 4512 cur_len = af_latin_compute_stem_width( hints, dim, 4513 org_len, 0, 4514 edge->flags, 4515 edge2->flags ); 4516 4517 cur_pos1 = FT_PIX_ROUND( org_pos ); 4518 delta1 = cur_pos1 + ( cur_len >> 1 ) - org_center; 4519 if ( delta1 < 0 ) 4520 delta1 = -delta1; 4521 4522 cur_pos2 = FT_PIX_ROUND( org_pos + org_len ) - cur_len; 4523 delta2 = cur_pos2 + ( cur_len >> 1 ) - org_center; 4524 if ( delta2 < 0 ) 4525 delta2 = -delta2; 4526 4527 edge->pos = ( delta1 < delta2 ) ? cur_pos1 : cur_pos2; 4528 edge2->pos = edge->pos + cur_len; 4529 4530 FT_TRACE5(( " STEM: edge %td (opos=%.2f) linked to %td (opos=%.2f)" 4531 " snapped to %.2f and %.2f\n", 4532 edge - edges, (double)edge->opos / 64, 4533 edge2 - edges, (double)edge2->opos / 64, 4534 (double)edge->pos / 64, (double)edge2->pos / 64 )); 4535 } 4536 4537 #ifdef FT_DEBUG_LEVEL_TRACE 4538 num_actions++; 4539 #endif 4540 4541 edge->flags |= AF_EDGE_DONE; 4542 edge2->flags |= AF_EDGE_DONE; 4543 4544 if ( edge > edges && 4545 ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos ) 4546 : ( edge->pos < edge[-1].pos ) ) ) 4547 { 4548 /* don't move if stem would (almost) disappear otherwise; */ 4549 /* the ad-hoc value 16 corresponds to 1/4px */ 4550 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 ) 4551 { 4552 #ifdef FT_DEBUG_LEVEL_TRACE 4553 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n", 4554 edge - edges, 4555 (double)edge->pos / 64, 4556 (double)edge[-1].pos / 64 )); 4557 4558 num_actions++; 4559 #endif 4560 4561 edge->pos = edge[-1].pos; 4562 } 4563 } 4564 } 4565 } 4566 4567 /* make sure that lowercase m's maintain their symmetry */ 4568 4569 /* In general, lowercase m's have six vertical edges if they are sans */ 4570 /* serif, or twelve if they are with serifs. This implementation is */ 4571 /* based on that assumption, and seems to work very well with most */ 4572 /* faces. However, if for a certain face this assumption is not */ 4573 /* true, the m is just rendered like before. In addition, any stem */ 4574 /* correction will only be applied to symmetrical glyphs (even if the */ 4575 /* glyph is not an m), so the potential for unwanted distortion is */ 4576 /* relatively low. */ 4577 4578 /* We don't handle horizontal edges since we can't easily assure that */ 4579 /* the third (lowest) stem aligns with the base line; it might end up */ 4580 /* one pixel higher or lower. */ 4581 4582 n_edges = edge_limit - edges; 4583 if ( dim == AF_DIMENSION_HORZ && ( n_edges == 6 || n_edges == 12 ) ) 4584 { 4585 AF_Edge edge1, edge2, edge3; 4586 FT_Pos dist1, dist2, span, delta; 4587 4588 4589 if ( n_edges == 6 ) 4590 { 4591 edge1 = edges; 4592 edge2 = edges + 2; 4593 edge3 = edges + 4; 4594 } 4595 else 4596 { 4597 edge1 = edges + 1; 4598 edge2 = edges + 5; 4599 edge3 = edges + 9; 4600 } 4601 4602 dist1 = edge2->opos - edge1->opos; 4603 dist2 = edge3->opos - edge2->opos; 4604 4605 span = dist1 - dist2; 4606 if ( span < 0 ) 4607 span = -span; 4608 4609 if ( span < 8 ) 4610 { 4611 delta = edge3->pos - ( 2 * edge2->pos - edge1->pos ); 4612 edge3->pos -= delta; 4613 if ( edge3->link ) 4614 edge3->link->pos -= delta; 4615 4616 /* move the serifs along with the stem */ 4617 if ( n_edges == 12 ) 4618 { 4619 ( edges + 8 )->pos -= delta; 4620 ( edges + 11 )->pos -= delta; 4621 } 4622 4623 edge3->flags |= AF_EDGE_DONE; 4624 if ( edge3->link ) 4625 edge3->link->flags |= AF_EDGE_DONE; 4626 } 4627 } 4628 4629 if ( has_non_stem_edges || !anchor ) 4630 { 4631 /* 4632 * now hint the remaining edges (serifs and single) in order 4633 * to complete our processing 4634 */ 4635 for ( edge = edges; edge < edge_limit; edge++ ) 4636 { 4637 FT_Pos delta; 4638 4639 4640 if ( edge->flags & AF_EDGE_DONE ) 4641 continue; 4642 4643 delta = 1000; 4644 4645 if ( edge->serif ) 4646 { 4647 AF_Edge e, top, bottom; 4648 FT_Pos min_pos, max_pos; 4649 4650 4651 /* Check whether we have a real serif -- if there are */ 4652 /* other edges with overlapping (or enclosed) segments */ 4653 /* between the primary and serif edge, we have not. */ 4654 /* */ 4655 /* Such a situation might happen if an accent is very */ 4656 /* near to its base glyph (for example, Vietnamese */ 4657 /* uppercase letters with two accents in `arial.ttf`), */ 4658 /* and the segment detection algorithm classifies the */ 4659 /* top of the accent incorrectly as a serif. */ 4660 delta = edge->serif->opos - edge->opos; 4661 if ( delta < 0 ) 4662 { 4663 delta = -delta; 4664 4665 top = edge; 4666 bottom = edge->serif; 4667 } 4668 else 4669 { 4670 top = edge->serif; 4671 bottom = edge; 4672 } 4673 4674 if ( delta < 64 + 32 ) 4675 { 4676 /* take care of outline orientation while computing extrema */ 4677 min_pos = FT_MIN( FT_MIN( FT_MIN( top->first->first->v, 4678 top->first->last->v ), 4679 FT_MIN( top->last->first->v, 4680 top->last->last->v ) ), 4681 FT_MIN( FT_MIN( bottom->first->first->v, 4682 bottom->first->last->v ), 4683 FT_MIN( bottom->last->first->v, 4684 bottom->last->last->v ) ) ); 4685 max_pos = FT_MAX( FT_MAX( FT_MAX( top->first->first->v, 4686 top->first->last->v ), 4687 FT_MAX( top->last->first->v, 4688 top->last->last->v ) ), 4689 FT_MAX( FT_MAX( bottom->first->first->v, 4690 bottom->first->last->v ), 4691 FT_MAX( bottom->last->first->v, 4692 bottom->last->last->v ) ) ); 4693 4694 for ( e = bottom + 1; e < top; e++ ) 4695 { 4696 FT_Pos e_min = FT_MIN( FT_MIN( e->first->first->v, 4697 e->first->last->v ), 4698 FT_MIN( e->last->first->v, 4699 e->last->last->v ) ); 4700 FT_Pos e_max = FT_MAX( FT_MAX( e->first->first->v, 4701 e->first->last->v ), 4702 FT_MAX( e->last->first->v, 4703 e->last->last->v ) ); 4704 4705 if ( !( ( e_min < min_pos && e_max < min_pos ) || 4706 ( e_min > max_pos && e_max > max_pos ) ) ) 4707 { 4708 delta = 1000; /* not a real serif */ 4709 break; 4710 } 4711 } 4712 4713 if ( delta == 1000 ) 4714 continue; 4715 } 4716 } 4717 4718 if ( delta < 64 + 16 ) 4719 { 4720 af_latin_align_serif_edge( hints, edge->serif, edge ); 4721 FT_TRACE5(( " SERIF: edge %td (opos=%.2f) serif to %td (opos=%.2f)" 4722 " aligned to %.2f\n", 4723 edge - edges, (double)edge->opos / 64, 4724 edge->serif - edges, (double)edge->serif->opos / 64, 4725 (double)edge->pos / 64 )); 4726 } 4727 else if ( !anchor ) 4728 { 4729 edge->pos = FT_PIX_ROUND( edge->opos ); 4730 anchor = edge; 4731 FT_TRACE5(( " SERIF_ANCHOR: edge %td (opos=%.2f)" 4732 " snapped to %.2f\n", 4733 edge - edges, 4734 (double)edge->opos / 64, (double)edge->pos / 64 )); 4735 } 4736 else 4737 { 4738 AF_Edge before, after; 4739 4740 4741 for ( before = edge - 1; before >= edges; before-- ) 4742 if ( before->flags & AF_EDGE_DONE ) 4743 break; 4744 4745 for ( after = edge + 1; after < edge_limit; after++ ) 4746 if ( after->flags & AF_EDGE_DONE ) 4747 break; 4748 4749 if ( before >= edges && before < edge && 4750 after < edge_limit && after > edge ) 4751 { 4752 if ( after->opos == before->opos ) 4753 edge->pos = before->pos; 4754 else 4755 edge->pos = before->pos + 4756 FT_MulDiv( edge->opos - before->opos, 4757 after->pos - before->pos, 4758 after->opos - before->opos ); 4759 4760 FT_TRACE5(( " SERIF_LINK1: edge %td (opos=%.2f) snapped to %.2f" 4761 " from %td (opos=%.2f)\n", 4762 edge - edges, (double)edge->opos / 64, 4763 (double)edge->pos / 64, 4764 before - edges, (double)before->opos / 64 )); 4765 } 4766 else 4767 { 4768 edge->pos = anchor->pos + 4769 ( ( edge->opos - anchor->opos + 16 ) & ~31 ); 4770 FT_TRACE5(( " SERIF_LINK2: edge %td (opos=%.2f)" 4771 " snapped to %.2f\n", 4772 edge - edges, 4773 (double)edge->opos / 64, (double)edge->pos / 64 )); 4774 } 4775 } 4776 4777 #ifdef FT_DEBUG_LEVEL_TRACE 4778 num_actions++; 4779 #endif 4780 edge->flags |= AF_EDGE_DONE; 4781 4782 if ( edge > edges && 4783 ( top_to_bottom_hinting ? ( edge->pos > edge[-1].pos ) 4784 : ( edge->pos < edge[-1].pos ) ) ) 4785 { 4786 /* don't move if stem would (almost) disappear otherwise; */ 4787 /* the ad-hoc value 16 corresponds to 1/4px */ 4788 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 ) 4789 { 4790 #ifdef FT_DEBUG_LEVEL_TRACE 4791 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n", 4792 edge - edges, 4793 (double)edge->pos / 64, 4794 (double)edge[-1].pos / 64 )); 4795 4796 num_actions++; 4797 #endif 4798 edge->pos = edge[-1].pos; 4799 } 4800 } 4801 4802 if ( edge + 1 < edge_limit && 4803 edge[1].flags & AF_EDGE_DONE && 4804 ( top_to_bottom_hinting ? ( edge->pos < edge[1].pos ) 4805 : ( edge->pos > edge[1].pos ) ) ) 4806 { 4807 /* don't move if stem would (almost) disappear otherwise; */ 4808 /* the ad-hoc value 16 corresponds to 1/4px */ 4809 if ( edge->link && FT_ABS( edge->link->pos - edge[-1].pos ) > 16 ) 4810 { 4811 #ifdef FT_DEBUG_LEVEL_TRACE 4812 FT_TRACE5(( " BOUND: edge %td (pos=%.2f) moved to %.2f\n", 4813 edge - edges, 4814 (double)edge->pos / 64, 4815 (double)edge[1].pos / 64 )); 4816 4817 num_actions++; 4818 #endif 4819 4820 edge->pos = edge[1].pos; 4821 } 4822 } 4823 } 4824 } 4825 4826 #ifdef FT_DEBUG_LEVEL_TRACE 4827 if ( !num_actions ) 4828 FT_TRACE5(( " (none)\n" )); 4829 FT_TRACE5(( "\n" )); 4830 #endif 4831 } 4832 4833 4834 /* Apply the complete hinting algorithm to a latin glyph. */ 4835 4836 static FT_Error 4837 af_latin_hints_apply( FT_UInt glyph_index, 4838 AF_GlyphHints hints, 4839 FT_Outline* outline, 4840 AF_StyleMetrics metrics_ ) /* AF_LatinMetrics */ 4841 { 4842 AF_LatinMetrics metrics = (AF_LatinMetrics)metrics_; 4843 4844 FT_Error error; 4845 int dim; 4846 4847 AF_LatinAxis axis; 4848 4849 FT_Pos accent_height_limit = 0; 4850 4851 4852 error = af_glyph_hints_reload( hints, outline ); 4853 if ( error ) 4854 goto Exit; 4855 4856 /* analyze glyph outline */ 4857 if ( AF_HINTS_DO_HORIZONTAL( hints ) ) 4858 { 4859 axis = &metrics->axis[AF_DIMENSION_HORZ]; 4860 error = af_latin_hints_detect_features( hints, 4861 axis->width_count, 4862 axis->widths, 4863 AF_DIMENSION_HORZ ); 4864 if ( error ) 4865 goto Exit; 4866 } 4867 4868 if ( AF_HINTS_DO_VERTICAL( hints ) ) 4869 { 4870 size_t* val; 4871 4872 FT_Int top_tilde_contour = 0; 4873 FT_Int bottom_tilde_contour = 0; 4874 4875 FT_Int below_top_tilde_contour = 0; 4876 FT_Int above_bottom_tilde_contour = 0; 4877 4878 AF_LatinBlue capital_top_blue = NULL; 4879 AF_LatinBlue capital_bottom_blue = NULL; 4880 4881 AF_LatinBlue small_top_blue = NULL; 4882 AF_LatinBlue small_bottom_blue = NULL; 4883 4884 FT_Bool have_flags = FALSE; 4885 4886 FT_Bool is_top_tilde = FALSE; 4887 FT_Bool is_bottom_tilde = FALSE; 4888 4889 FT_Bool is_below_top_tilde = FALSE; 4890 FT_Bool is_above_bottom_tilde = FALSE; 4891 4892 FT_Bool ignore_capital_top = FALSE; 4893 FT_Bool ignore_capital_bottom = FALSE; 4894 4895 FT_Bool ignore_small_top = FALSE; 4896 FT_Bool ignore_small_bottom = FALSE; 4897 4898 FT_Bool do_height_check = TRUE; 4899 4900 FT_Pos limit; 4901 FT_Pos y_offset; 4902 4903 4904 val = ft_hash_num_lookup( (FT_Int)glyph_index, 4905 metrics->root.reverse_charmap ); 4906 if ( val ) 4907 { 4908 FT_UInt codepoint = *val; 4909 FT_UInt32 adj_type = af_adjustment_database_lookup( codepoint ); 4910 4911 4912 if ( adj_type ) 4913 { 4914 have_flags = !!adj_type; 4915 4916 is_top_tilde = !!( adj_type & AF_ADJUST_TILDE_TOP ); 4917 is_bottom_tilde = !!( adj_type & AF_ADJUST_TILDE_BOTTOM ); 4918 4919 is_below_top_tilde = !!( adj_type & AF_ADJUST_TILDE_TOP2 ); 4920 is_above_bottom_tilde = !!( adj_type & AF_ADJUST_TILDE_BOTTOM2 ); 4921 4922 ignore_capital_top = !!( adj_type & AF_IGNORE_CAPITAL_TOP ); 4923 ignore_capital_bottom = !!( adj_type & AF_IGNORE_CAPITAL_BOTTOM ); 4924 4925 ignore_small_top = !!( adj_type & AF_IGNORE_SMALL_TOP ); 4926 ignore_small_bottom = !!( adj_type & AF_IGNORE_SMALL_BOTTOM ); 4927 4928 do_height_check = !( adj_type & AF_ADJUST_NO_HEIGHT_CHECK ); 4929 } 4930 } 4931 4932 if ( is_top_tilde || is_bottom_tilde || 4933 is_below_top_tilde || is_above_bottom_tilde ) 4934 af_compute_vertical_extrema( hints ); 4935 4936 /* Process inner tilde glyphs first. */ 4937 if ( is_below_top_tilde ) 4938 { 4939 below_top_tilde_contour = af_find_second_highest_contour( hints ); 4940 4941 y_offset = af_latin_stretch_top_tilde( 4942 hints, below_top_tilde_contour ); 4943 y_offset += af_latin_align_top_tilde( 4944 hints, below_top_tilde_contour ); 4945 4946 limit = hints->contour_y_minima[below_top_tilde_contour]; 4947 af_move_contours_up( hints, limit, y_offset ); 4948 } 4949 if ( is_above_bottom_tilde ) 4950 { 4951 above_bottom_tilde_contour = af_find_second_lowest_contour( hints ); 4952 4953 y_offset = af_latin_stretch_bottom_tilde( 4954 hints, above_bottom_tilde_contour ); 4955 y_offset -= af_latin_align_bottom_tilde( 4956 hints, above_bottom_tilde_contour ); 4957 4958 limit = hints->contour_y_maxima[above_bottom_tilde_contour]; 4959 af_move_contours_down( hints, limit, y_offset ); 4960 } 4961 4962 if ( is_top_tilde ) 4963 { 4964 top_tilde_contour = af_find_highest_contour( hints ); 4965 4966 (void)af_latin_stretch_top_tilde( hints, top_tilde_contour ); 4967 (void)af_latin_align_top_tilde( hints, top_tilde_contour ); 4968 } 4969 if ( is_bottom_tilde ) 4970 { 4971 bottom_tilde_contour = af_find_lowest_contour( hints ); 4972 4973 (void)af_latin_stretch_bottom_tilde( hints, bottom_tilde_contour ); 4974 (void)af_latin_align_bottom_tilde( hints, bottom_tilde_contour ); 4975 } 4976 4977 axis = &metrics->axis[AF_DIMENSION_VERT]; 4978 error = af_latin_hints_detect_features( hints, 4979 axis->width_count, 4980 axis->widths, 4981 AF_DIMENSION_VERT ); 4982 4983 if ( have_flags ) 4984 { 4985 af_latin_get_base_glyph_blues( hints, 4986 TRUE, 4987 &capital_top_blue, 4988 &capital_bottom_blue ); 4989 af_latin_get_base_glyph_blues( hints, 4990 FALSE, 4991 &small_top_blue, 4992 &small_bottom_blue ); 4993 4994 if ( do_height_check ) 4995 { 4996 /* Set a heuristic limit for the accent height so that */ 4997 /* `af_glyph_hints_apply_vertical_separation_adjustments` */ 4998 /* can correctly ignore the case where an accent is */ 4999 /* unexpectedly not the highest (or lowest) contour. */ 5000 5001 /* Either 2/3 of the lowercase blue zone height... */ 5002 if ( small_top_blue && small_bottom_blue ) 5003 accent_height_limit = 2 * ( small_top_blue->shoot.cur - 5004 small_bottom_blue->shoot.cur ) / 3; 5005 /* or 1/2 of the uppercase blue zone height... */ 5006 else if ( capital_top_blue && capital_bottom_blue ) 5007 accent_height_limit = ( capital_top_blue->shoot.cur - 5008 capital_bottom_blue->shoot.cur ) / 2; 5009 /* or half of the standard PostScript ascender value (8/10) */ 5010 /* of the EM value, scaled. */ 5011 else 5012 accent_height_limit = FT_MulFix( metrics->units_per_em * 4 / 10, 5013 metrics->root.scaler.y_scale ); 5014 } 5015 } 5016 5017 if ( capital_top_blue && capital_bottom_blue ) 5018 { 5019 if ( ignore_capital_top ) 5020 af_latin_ignore_top( hints, 5021 capital_top_blue, capital_bottom_blue ); 5022 if ( ignore_capital_bottom ) 5023 af_latin_ignore_bottom( hints, 5024 capital_top_blue, capital_bottom_blue ); 5025 } 5026 if ( small_top_blue && small_bottom_blue ) 5027 { 5028 if ( ignore_small_top ) 5029 af_latin_ignore_top( hints, 5030 small_top_blue, small_bottom_blue ); 5031 if ( ignore_small_bottom ) 5032 af_latin_ignore_bottom( hints, 5033 small_top_blue, small_bottom_blue ); 5034 } 5035 5036 if ( error ) 5037 goto Exit; 5038 5039 /* apply blue zones to base characters only */ 5040 if ( !( metrics->root.globals->glyph_styles[glyph_index] & AF_NONBASE ) ) 5041 af_latin_hints_compute_blue_edges( hints, metrics ); 5042 } 5043 5044 /* grid-fit the outline */ 5045 for ( dim = 0; dim < AF_DIMENSION_MAX; dim++ ) 5046 { 5047 if ( ( dim == AF_DIMENSION_HORZ && AF_HINTS_DO_HORIZONTAL( hints ) ) || 5048 ( dim == AF_DIMENSION_VERT && AF_HINTS_DO_VERTICAL( hints ) ) ) 5049 { 5050 af_latin_hint_edges( hints, (AF_Dimension)dim ); 5051 af_glyph_hints_align_edge_points( hints, (AF_Dimension)dim ); 5052 af_glyph_hints_align_strong_points( hints, (AF_Dimension)dim ); 5053 af_glyph_hints_align_weak_points( hints, (AF_Dimension)dim ); 5054 af_glyph_hints_apply_vertical_separation_adjustments( 5055 hints, 5056 (AF_Dimension)dim, 5057 glyph_index, 5058 accent_height_limit, 5059 metrics->root.reverse_charmap ); 5060 } 5061 } 5062 5063 af_glyph_hints_save( hints, outline ); 5064 5065 Exit: 5066 return error; 5067 } 5068 5069 5070 /*************************************************************************/ 5071 /*************************************************************************/ 5072 /***** *****/ 5073 /***** L A T I N S C R I P T C L A S S *****/ 5074 /***** *****/ 5075 /*************************************************************************/ 5076 /*************************************************************************/ 5077 5078 5079 AF_DEFINE_WRITING_SYSTEM_CLASS( 5080 af_latin_writing_system_class, 5081 5082 AF_WRITING_SYSTEM_LATIN, 5083 5084 sizeof ( AF_LatinMetricsRec ), 5085 5086 (AF_WritingSystem_InitMetricsFunc) af_latin_metrics_init, /* style_metrics_init */ 5087 (AF_WritingSystem_ScaleMetricsFunc)af_latin_metrics_scale, /* style_metrics_scale */ 5088 (AF_WritingSystem_DoneMetricsFunc) af_latin_metrics_done, /* style_metrics_done */ 5089 (AF_WritingSystem_GetStdWidthsFunc)af_latin_get_standard_widths, /* style_metrics_getstdw */ 5090 5091 (AF_WritingSystem_InitHintsFunc) af_latin_hints_init, /* style_hints_init */ 5092 (AF_WritingSystem_ApplyHintsFunc) af_latin_hints_apply /* style_hints_apply */ 5093 ) 5094 5095 5096 /* END */