afshaper.c (23309B)
1 /**************************************************************************** 2 * 3 * afshaper.c 4 * 5 * HarfBuzz interface for accessing OpenType features (body). 6 * 7 * Copyright (C) 2013-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/freetype.h> 20 #include <freetype/ftadvanc.h> 21 #include "afglobal.h" 22 #include "aftypes.h" 23 #include "afshaper.h" 24 25 26 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 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 afshaper 36 37 38 /* 39 * We use `sets' (in the HarfBuzz sense, which comes quite near to the 40 * usual mathematical meaning) to manage both lookups and glyph indices. 41 * 42 * 1. For each coverage, collect lookup IDs in a set. Note that an 43 * auto-hinter `coverage' is represented by one `feature', and a 44 * feature consists of an arbitrary number of (font specific) `lookup's 45 * that actually do the mapping job. Please check the OpenType 46 * specification for more details on features and lookups. 47 * 48 * 2. Create glyph ID sets from the corresponding lookup sets. 49 * 50 * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed 51 * with all lookups specific to the OpenType script activated. It 52 * relies on the order of AF_DEFINE_STYLE_CLASS entries so that 53 * special coverages (like `oldstyle figures') don't get overwritten. 54 * 55 */ 56 57 58 /* load coverage tags */ 59 #undef COVERAGE 60 #define COVERAGE( name, NAME, description, \ 61 tag1, tag2, tag3, tag4 ) \ 62 static const hb_tag_t name ## _coverage[] = \ 63 { \ 64 HB_TAG( tag1, tag2, tag3, tag4 ), \ 65 HB_TAG_NONE \ 66 }; 67 68 69 #include "afcover.h" 70 71 72 /* define mapping between coverage tags and AF_Coverage */ 73 #undef COVERAGE 74 #define COVERAGE( name, NAME, description, \ 75 tag1, tag2, tag3, tag4 ) \ 76 name ## _coverage, 77 78 79 static const hb_tag_t* coverages[] = 80 { 81 #include "afcover.h" 82 83 NULL /* AF_COVERAGE_DEFAULT */ 84 }; 85 86 87 /* load HarfBuzz script tags */ 88 #undef SCRIPT 89 #define SCRIPT( s, S, d, h, H, ss ) h, 90 91 92 FT_LOCAL_ARRAY_DEF( hb_script_t ) 93 af_hb_scripts[] = 94 { 95 #include "afscript.h" 96 }; 97 98 99 static FT_Error 100 af_shaper_get_coverage_hb( AF_FaceGlobals globals, 101 AF_StyleClass style_class, 102 FT_UShort* gstyles, 103 FT_Bool default_script ) 104 { 105 hb_face_t* face; 106 107 hb_set_t* gsub_lookups = NULL; /* GSUB lookups for a given script */ 108 hb_set_t* gsub_glyphs = NULL; /* glyphs covered by GSUB lookups */ 109 hb_set_t* gpos_lookups = NULL; /* GPOS lookups for a given script */ 110 hb_set_t* gpos_glyphs = NULL; /* glyphs covered by GPOS lookups */ 111 112 hb_script_t script; 113 const hb_tag_t* coverage_tags; 114 hb_tag_t script_tags[] = { HB_TAG_NONE, 115 HB_TAG_NONE, 116 HB_TAG_NONE, 117 HB_TAG_NONE }; 118 119 hb_codepoint_t idx; 120 #ifdef FT_DEBUG_LEVEL_TRACE 121 int count; 122 #endif 123 124 125 if ( !globals || !style_class || !gstyles ) 126 return FT_THROW( Invalid_Argument ); 127 128 face = hb( font_get_face )( globals->hb_font ); 129 130 coverage_tags = coverages[style_class->coverage]; 131 script = af_hb_scripts[style_class->script]; 132 133 /* Convert a HarfBuzz script tag into the corresponding OpenType */ 134 /* tag or tags -- some Indic scripts like Devanagari have an old */ 135 /* and a new set of features. */ 136 { 137 unsigned int tags_count = 3; 138 hb_tag_t tags[3]; 139 140 141 hb( ot_tags_from_script_and_language )( script, 142 HB_LANGUAGE_INVALID, 143 &tags_count, 144 tags, 145 NULL, 146 NULL ); 147 script_tags[0] = tags_count > 0 ? tags[0] : HB_TAG_NONE; 148 script_tags[1] = tags_count > 1 ? tags[1] : HB_TAG_NONE; 149 script_tags[2] = tags_count > 2 ? tags[2] : HB_TAG_NONE; 150 } 151 152 /* If the second tag is HB_OT_TAG_DEFAULT_SCRIPT, change that to */ 153 /* HB_TAG_NONE except for the default script. */ 154 if ( default_script ) 155 { 156 if ( script_tags[0] == HB_TAG_NONE ) 157 script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT; 158 else 159 { 160 if ( script_tags[1] == HB_TAG_NONE ) 161 script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT; 162 else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT ) 163 script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT; 164 } 165 } 166 else 167 { 168 /* we use non-standard tags like `khms' for special purposes; */ 169 /* HarfBuzz maps them to `DFLT', which we don't want to handle here */ 170 if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT ) 171 goto Exit; 172 } 173 174 gsub_lookups = hb( set_create )(); 175 hb( ot_layout_collect_lookups )( face, 176 HB_OT_TAG_GSUB, 177 script_tags, 178 NULL, 179 coverage_tags, 180 gsub_lookups ); 181 182 if ( hb( set_is_empty )( gsub_lookups ) ) 183 goto Exit; /* nothing to do */ 184 185 FT_TRACE4(( "GSUB lookups (style `%s'):\n", 186 af_style_names[style_class->style] )); 187 FT_TRACE4(( " " )); 188 189 #ifdef FT_DEBUG_LEVEL_TRACE 190 count = 0; 191 #endif 192 193 gsub_glyphs = hb( set_create )(); 194 for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_lookups, &idx ); ) 195 { 196 #ifdef FT_DEBUG_LEVEL_TRACE 197 FT_TRACE4(( " %u", idx )); 198 count++; 199 #endif 200 201 /* get output coverage of GSUB feature */ 202 hb( ot_layout_lookup_collect_glyphs )( face, 203 HB_OT_TAG_GSUB, 204 idx, 205 NULL, 206 NULL, 207 NULL, 208 gsub_glyphs ); 209 } 210 211 #ifdef FT_DEBUG_LEVEL_TRACE 212 if ( !count ) 213 FT_TRACE4(( " (none)" )); 214 FT_TRACE4(( "\n" )); 215 FT_TRACE4(( "\n" )); 216 #endif 217 218 FT_TRACE4(( "GPOS lookups (style `%s'):\n", 219 af_style_names[style_class->style] )); 220 FT_TRACE4(( " " )); 221 222 gpos_lookups = hb( set_create )(); 223 hb( ot_layout_collect_lookups )( face, 224 HB_OT_TAG_GPOS, 225 script_tags, 226 NULL, 227 coverage_tags, 228 gpos_lookups ); 229 230 #ifdef FT_DEBUG_LEVEL_TRACE 231 count = 0; 232 #endif 233 234 gpos_glyphs = hb( set_create )(); 235 for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gpos_lookups, &idx ); ) 236 { 237 #ifdef FT_DEBUG_LEVEL_TRACE 238 FT_TRACE4(( " %u", idx )); 239 count++; 240 #endif 241 242 /* get input coverage of GPOS feature */ 243 hb( ot_layout_lookup_collect_glyphs )( face, 244 HB_OT_TAG_GPOS, 245 idx, 246 NULL, 247 gpos_glyphs, 248 NULL, 249 NULL ); 250 } 251 252 #ifdef FT_DEBUG_LEVEL_TRACE 253 if ( !count ) 254 FT_TRACE4(( " (none)" )); 255 FT_TRACE4(( "\n" )); 256 FT_TRACE4(( "\n" )); 257 #endif 258 259 /* 260 * We now check whether we can construct blue zones, using glyphs 261 * covered by the feature only. In case there is not a single zone 262 * (that is, not a single character is covered), we skip this coverage. 263 * 264 */ 265 if ( style_class->coverage != AF_COVERAGE_DEFAULT ) 266 { 267 AF_Blue_Stringset bss = style_class->blue_stringset; 268 const AF_Blue_StringRec* bs = &af_blue_stringsets[bss]; 269 270 FT_Bool found = 0; 271 272 273 for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ ) 274 { 275 const char* p = &af_blue_strings[bs->string]; 276 277 278 while ( *p ) 279 { 280 hb_codepoint_t ch; 281 282 283 GET_UTF8_CHAR( ch, p ); 284 285 for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_lookups, 286 &idx ); ) 287 { 288 hb_codepoint_t gidx = FT_Get_Char_Index( globals->face, ch ); 289 290 291 if ( hb( ot_layout_lookup_would_substitute )( face, idx, 292 &gidx, 1, 1 ) ) 293 { 294 found = 1; 295 break; 296 } 297 } 298 } 299 } 300 301 if ( !found ) 302 { 303 FT_TRACE4(( " no blue characters found; style skipped\n" )); 304 goto Exit; 305 } 306 } 307 308 /* 309 * Various OpenType features might use the same glyphs at different 310 * vertical positions; for example, superscript and subscript glyphs 311 * could be the same. However, the auto-hinter is completely 312 * agnostic of OpenType features after the feature analysis has been 313 * completed: The engine then simply receives a glyph index and returns a 314 * hinted and usually rendered glyph. 315 * 316 * Consider the superscript feature of font `pala.ttf': Some of the 317 * glyphs are `real', that is, they have a zero vertical offset, but 318 * most of them are small caps glyphs shifted up to the superscript 319 * position (that is, the `sups' feature is present in both the GSUB and 320 * GPOS tables). The code for blue zones computation actually uses a 321 * feature's y offset so that the `real' glyphs get correct hints. But 322 * later on it is impossible to decide whether a glyph index belongs to, 323 * say, the small caps or superscript feature. 324 * 325 * For this reason, we don't assign a style to a glyph if the current 326 * feature covers the glyph in both the GSUB and the GPOS tables. This 327 * is quite a broad condition, assuming that 328 * 329 * (a) glyphs that get used in multiple features are present in a 330 * feature without vertical shift, 331 * 332 * and 333 * 334 * (b) a feature's GPOS data really moves the glyph vertically. 335 * 336 * Not fulfilling condition (a) makes a font larger; it would also 337 * reduce the number of glyphs that could be addressed directly without 338 * using OpenType features, so this assumption is rather strong. 339 * 340 * Condition (b) is much weaker, and there might be glyphs which get 341 * missed. However, the OpenType features we are going to handle are 342 * primarily located in GSUB, and HarfBuzz doesn't provide an API to 343 * directly get the necessary information from the GPOS table. A 344 * possible solution might be to directly parse the GPOS table to find 345 * out whether a glyph gets shifted vertically, but this is something I 346 * would like to avoid if not really necessary. 347 * 348 * Note that we don't follow this logic for the default coverage. 349 * Complex scripts like Devanagari have mandatory GPOS features to 350 * position many glyph elements, using mark-to-base or mark-to-ligature 351 * tables; the number of glyphs missed due to condition (b) would be far 352 * too large. 353 * 354 */ 355 if ( style_class->coverage != AF_COVERAGE_DEFAULT ) 356 hb( set_subtract )( gsub_glyphs, gpos_glyphs ); 357 358 #ifdef FT_DEBUG_LEVEL_TRACE 359 FT_TRACE4(( " glyphs without GPOS data (`*' means already assigned)" )); 360 count = 0; 361 #endif 362 363 for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_glyphs, &idx ); ) 364 { 365 #ifdef FT_DEBUG_LEVEL_TRACE 366 if ( !( count % 10 ) ) 367 { 368 FT_TRACE4(( "\n" )); 369 FT_TRACE4(( " " )); 370 } 371 372 FT_TRACE4(( " %u", idx )); 373 count++; 374 #endif 375 376 /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */ 377 /* can be arbitrary: some fonts use fake indices for processing */ 378 /* internal to GSUB or GPOS, which is fully valid */ 379 if ( idx >= (hb_codepoint_t)globals->glyph_count ) 380 continue; 381 382 if ( gstyles[idx] == AF_STYLE_UNASSIGNED ) 383 gstyles[idx] = (FT_UShort)style_class->style; 384 #ifdef FT_DEBUG_LEVEL_TRACE 385 else 386 FT_TRACE4(( "*" )); 387 #endif 388 } 389 390 #ifdef FT_DEBUG_LEVEL_TRACE 391 if ( !count ) 392 { 393 FT_TRACE4(( "\n" )); 394 FT_TRACE4(( " (none)" )); 395 } 396 FT_TRACE4(( "\n" )); 397 FT_TRACE4(( "\n" )); 398 #endif 399 400 Exit: 401 hb( set_destroy )( gsub_lookups ); 402 hb( set_destroy )( gsub_glyphs ); 403 hb( set_destroy )( gpos_lookups ); 404 hb( set_destroy )( gpos_glyphs ); 405 406 return FT_Err_Ok; 407 } 408 409 410 /* construct HarfBuzz features */ 411 #undef COVERAGE 412 #define COVERAGE( name, NAME, description, \ 413 tag1, tag2, tag3, tag4 ) \ 414 static const hb_feature_t name ## _feature[] = \ 415 { \ 416 { \ 417 HB_TAG( tag1, tag2, tag3, tag4 ), \ 418 1, 0, (unsigned int)-1 \ 419 } \ 420 }; 421 422 423 #include "afcover.h" 424 425 426 /* define mapping between HarfBuzz features and AF_Coverage */ 427 #undef COVERAGE 428 #define COVERAGE( name, NAME, description, \ 429 tag1, tag2, tag3, tag4 ) \ 430 name ## _feature, 431 432 433 static const hb_feature_t* features[] = 434 { 435 #include "afcover.h" 436 437 NULL /* AF_COVERAGE_DEFAULT */ 438 }; 439 440 441 static void* 442 af_shaper_buf_create_hb( AF_FaceGlobals globals ) 443 { 444 FT_UNUSED( globals ); 445 446 return (void*)hb( buffer_create )(); 447 } 448 449 450 static void 451 af_shaper_buf_destroy_hb( AF_FaceGlobals globals, 452 void* buf ) 453 { 454 FT_UNUSED( globals ); 455 456 hb( buffer_destroy )( (hb_buffer_t*)buf ); 457 } 458 459 460 static const char* 461 af_shaper_get_cluster_hb( const char* p, 462 AF_StyleMetrics metrics, 463 void* buf_, 464 unsigned int* count ) 465 { 466 AF_FaceGlobals globals = metrics->globals; 467 468 AF_StyleClass style_class; 469 const hb_feature_t* feature; 470 FT_Int upem; 471 const char* q; 472 int len; 473 474 hb_buffer_t* buf = (hb_buffer_t*)buf_; 475 hb_font_t* font; 476 hb_codepoint_t dummy; 477 478 FT_UNUSED( globals ); 479 480 481 upem = (FT_Int)metrics->globals->face->units_per_EM; 482 style_class = metrics->style_class; 483 feature = features[style_class->coverage]; 484 485 font = metrics->globals->hb_font; 486 487 /* we shape at a size of units per EM; this means font units */ 488 hb( font_set_scale )( font, upem, upem ); 489 490 while ( *p == ' ' ) 491 p++; 492 493 /* count bytes up to next space (or end of buffer) */ 494 q = p; 495 while ( !( *q == ' ' || *q == '\0' ) ) 496 GET_UTF8_CHAR( dummy, q ); 497 len = (int)( q - p ); 498 499 /* feed character(s) to the HarfBuzz buffer */ 500 hb( buffer_clear_contents )( buf ); 501 hb( buffer_add_utf8 )( buf, p, len, 0, len ); 502 503 /* we let HarfBuzz guess the script and writing direction */ 504 hb( buffer_guess_segment_properties )( buf ); 505 506 /* shape buffer, which means conversion from character codes to */ 507 /* glyph indices, possibly applying a feature */ 508 hb( shape )( font, buf, feature, feature ? 1 : 0 ); 509 510 if ( feature ) 511 { 512 hb_buffer_t* hb_buf = metrics->globals->hb_buf; 513 514 unsigned int gcount; 515 hb_glyph_info_t* ginfo; 516 517 unsigned int hb_gcount; 518 hb_glyph_info_t* hb_ginfo; 519 520 521 /* we have to check whether applying a feature does actually change */ 522 /* glyph indices; otherwise the affected glyph or glyphs aren't */ 523 /* available at all in the feature */ 524 525 hb( buffer_clear_contents )( hb_buf ); 526 hb( buffer_add_utf8 )( hb_buf, p, len, 0, len ); 527 hb( buffer_guess_segment_properties )( hb_buf ); 528 hb( shape )( font, hb_buf, NULL, 0 ); 529 530 ginfo = hb( buffer_get_glyph_infos )( buf, &gcount ); 531 hb_ginfo = hb( buffer_get_glyph_infos )( hb_buf, &hb_gcount ); 532 533 if ( gcount == hb_gcount ) 534 { 535 unsigned int i; 536 537 538 for (i = 0; i < gcount; i++ ) 539 if ( ginfo[i].codepoint != hb_ginfo[i].codepoint ) 540 break; 541 542 if ( i == gcount ) 543 { 544 /* both buffers have identical glyph indices */ 545 hb( buffer_clear_contents )( buf ); 546 } 547 } 548 } 549 550 *count = hb( buffer_get_length )( buf ); 551 552 #ifdef FT_DEBUG_LEVEL_TRACE 553 if ( feature && *count > 1 ) 554 FT_TRACE1(( "af_shaper_get_cluster:" 555 " input character mapped to multiple glyphs\n" )); 556 #endif 557 558 return q; 559 } 560 561 562 static FT_ULong 563 af_shaper_get_elem_hb( AF_StyleMetrics metrics, 564 void* buf_, 565 unsigned int idx, 566 FT_Long* advance, 567 FT_Long* y_offset ) 568 { 569 AF_FaceGlobals globals = metrics->globals; 570 571 hb_buffer_t* buf = (hb_buffer_t*)buf_; 572 hb_glyph_info_t* ginfo; 573 hb_glyph_position_t* gpos; 574 unsigned int gcount; 575 576 FT_UNUSED( globals ); 577 578 579 ginfo = hb( buffer_get_glyph_infos )( buf, &gcount ); 580 gpos = hb( buffer_get_glyph_positions )( buf, &gcount ); 581 582 if ( idx >= gcount ) 583 return 0; 584 585 if ( advance ) 586 *advance = gpos[idx].x_advance; 587 if ( y_offset ) 588 *y_offset = gpos[idx].y_offset; 589 590 return ginfo[idx].codepoint; 591 } 592 593 594 #endif /* FT_CONFIG_OPTION_USE_HARFBUZZ */ 595 596 597 static FT_Error 598 af_shaper_get_coverage_nohb( AF_FaceGlobals globals, 599 AF_StyleClass style_class, 600 FT_UShort* gstyles, 601 FT_Bool default_script ) 602 { 603 FT_UNUSED( globals ); 604 FT_UNUSED( style_class ); 605 FT_UNUSED( gstyles ); 606 FT_UNUSED( default_script ); 607 608 return FT_Err_Ok; 609 } 610 611 612 static void* 613 af_shaper_buf_create_nohb( AF_FaceGlobals globals ) 614 { 615 FT_UNUSED( globals ); 616 617 return NULL; 618 } 619 620 621 static void 622 af_shaper_buf_destroy_nohb( AF_FaceGlobals globals, 623 void* buf ) 624 { 625 FT_UNUSED( globals ); 626 FT_UNUSED( buf ); 627 } 628 629 630 static const char* 631 af_shaper_get_cluster_nohb( const char* p, 632 AF_StyleMetrics metrics, 633 void* buf_, 634 unsigned int* count ) 635 { 636 FT_Face face = metrics->globals->face; 637 FT_ULong ch, dummy = 0; 638 FT_ULong* buf = (FT_ULong*)buf_; 639 640 641 while ( *p == ' ' ) 642 p++; 643 644 GET_UTF8_CHAR( ch, p ); 645 646 /* since we don't have an engine to handle clusters, */ 647 /* we scan the characters but return zero */ 648 while ( !( *p == ' ' || *p == '\0' ) ) 649 GET_UTF8_CHAR( dummy, p ); 650 651 if ( dummy ) 652 { 653 *buf = 0; 654 *count = 0; 655 } 656 else 657 { 658 *buf = FT_Get_Char_Index( face, ch ); 659 *count = 1; 660 } 661 662 return p; 663 } 664 665 666 static FT_ULong 667 af_shaper_get_elem_nohb( AF_StyleMetrics metrics, 668 void* buf_, 669 unsigned int idx, 670 FT_Long* advance, 671 FT_Long* y_offset ) 672 { 673 FT_Face face = metrics->globals->face; 674 FT_ULong glyph_index = *(FT_ULong*)buf_; 675 676 FT_UNUSED( idx ); 677 678 679 if ( advance ) 680 FT_Get_Advance( face, 681 glyph_index, 682 FT_LOAD_NO_SCALE | 683 FT_LOAD_NO_HINTING | 684 FT_LOAD_IGNORE_TRANSFORM, 685 advance ); 686 687 if ( y_offset ) 688 *y_offset = 0; 689 690 return glyph_index; 691 } 692 693 694 /********************************************************************/ 695 696 FT_Error 697 af_shaper_get_coverage( AF_FaceGlobals globals, 698 AF_StyleClass style_class, 699 FT_UShort* gstyles, 700 FT_Bool default_script ) 701 { 702 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 703 if ( ft_hb_enabled( globals ) ) 704 return af_shaper_get_coverage_hb( globals, 705 style_class, 706 gstyles, 707 default_script ); 708 else 709 #endif 710 return af_shaper_get_coverage_nohb( globals, 711 style_class, 712 gstyles, 713 default_script ); 714 } 715 716 717 void* 718 af_shaper_buf_create( AF_FaceGlobals globals ) 719 { 720 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 721 if ( ft_hb_enabled( globals ) ) 722 return af_shaper_buf_create_hb( globals ); 723 else 724 #endif 725 return af_shaper_buf_create_nohb( globals ); 726 } 727 728 729 void 730 af_shaper_buf_destroy( AF_FaceGlobals globals, 731 void* buf ) 732 { 733 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 734 if ( ft_hb_enabled( globals ) ) 735 af_shaper_buf_destroy_hb( globals, buf ); 736 else 737 #endif 738 af_shaper_buf_destroy_nohb( globals, buf ); 739 } 740 741 742 const char* 743 af_shaper_get_cluster( const char* p, 744 AF_StyleMetrics metrics, 745 void* buf_, 746 unsigned int* count ) 747 { 748 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 749 if ( ft_hb_enabled( metrics->globals ) ) 750 return af_shaper_get_cluster_hb( p, metrics, buf_, count ); 751 else 752 #endif 753 return af_shaper_get_cluster_nohb( p, metrics, buf_, count ); 754 } 755 756 757 FT_ULong 758 af_shaper_get_elem( AF_StyleMetrics metrics, 759 void* buf_, 760 unsigned int idx, 761 FT_Long* advance, 762 FT_Long* y_offset ) 763 { 764 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 765 if ( ft_hb_enabled( metrics->globals ) ) 766 return af_shaper_get_elem_hb( metrics, 767 buf_, 768 idx, 769 advance, 770 y_offset ); 771 #endif 772 return af_shaper_get_elem_nohb( metrics, 773 buf_, 774 idx, 775 advance, 776 y_offset ); 777 } 778 779 780 /* END */