1 /**************************************************************************** 2 * 3 * afshaper.c 4 * 5 * HarfBuzz interface for accessing OpenType features (body). 6 * 7 * Copyright (C) 2013-2020 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 #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ 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 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 static const hb_script_t scripts[] = 93 { 94 #include "afscript.h" 95 }; 96 97 98 FT_Error af_shaper_get_coverage(AF_FaceGlobals globals,AF_StyleClass style_class,FT_UShort * gstyles,FT_Bool default_script)99 af_shaper_get_coverage( AF_FaceGlobals globals, 100 AF_StyleClass style_class, 101 FT_UShort* gstyles, 102 FT_Bool default_script ) 103 { 104 hb_face_t* face; 105 106 hb_set_t* gsub_lookups = NULL; /* GSUB lookups for a given script */ 107 hb_set_t* gsub_glyphs = NULL; /* glyphs covered by GSUB lookups */ 108 hb_set_t* gpos_lookups = NULL; /* GPOS lookups for a given script */ 109 hb_set_t* gpos_glyphs = NULL; /* glyphs covered by GPOS lookups */ 110 111 hb_script_t script; 112 const hb_tag_t* coverage_tags; 113 hb_tag_t script_tags[] = { HB_TAG_NONE, 114 HB_TAG_NONE, 115 HB_TAG_NONE, 116 HB_TAG_NONE }; 117 118 hb_codepoint_t idx; 119 #ifdef FT_DEBUG_LEVEL_TRACE 120 int count; 121 #endif 122 123 124 if ( !globals || !style_class || !gstyles ) 125 return FT_THROW( Invalid_Argument ); 126 127 face = hb_font_get_face( globals->hb_font ); 128 129 coverage_tags = coverages[style_class->coverage]; 130 script = scripts[style_class->script]; 131 132 /* Convert a HarfBuzz script tag into the corresponding OpenType */ 133 /* tag or tags -- some Indic scripts like Devanagari have an old */ 134 /* and a new set of features. */ 135 hb_ot_tags_from_script( script, 136 &script_tags[0], 137 &script_tags[1] ); 138 139 /* `hb_ot_tags_from_script' usually returns HB_OT_TAG_DEFAULT_SCRIPT */ 140 /* as the second tag. We change that to HB_TAG_NONE except for the */ 141 /* default script. */ 142 if ( default_script ) 143 { 144 if ( script_tags[0] == HB_TAG_NONE ) 145 script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT; 146 else 147 { 148 if ( script_tags[1] == HB_TAG_NONE ) 149 script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT; 150 else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT ) 151 script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT; 152 } 153 } 154 else 155 { 156 /* we use non-standard tags like `khms' for special purposes; */ 157 /* HarfBuzz maps them to `DFLT', which we don't want to handle here */ 158 if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT ) 159 goto Exit; 160 161 if ( script_tags[1] == HB_OT_TAG_DEFAULT_SCRIPT ) 162 script_tags[1] = HB_TAG_NONE; 163 } 164 165 gsub_lookups = hb_set_create(); 166 hb_ot_layout_collect_lookups( face, 167 HB_OT_TAG_GSUB, 168 script_tags, 169 NULL, 170 coverage_tags, 171 gsub_lookups ); 172 173 if ( hb_set_is_empty( gsub_lookups ) ) 174 goto Exit; /* nothing to do */ 175 176 FT_TRACE4(( "GSUB lookups (style `%s'):\n" 177 " ", 178 af_style_names[style_class->style] )); 179 180 #ifdef FT_DEBUG_LEVEL_TRACE 181 count = 0; 182 #endif 183 184 gsub_glyphs = hb_set_create(); 185 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, &idx ); ) 186 { 187 #ifdef FT_DEBUG_LEVEL_TRACE 188 FT_TRACE4(( " %d", idx )); 189 count++; 190 #endif 191 192 /* get output coverage of GSUB feature */ 193 hb_ot_layout_lookup_collect_glyphs( face, 194 HB_OT_TAG_GSUB, 195 idx, 196 NULL, 197 NULL, 198 NULL, 199 gsub_glyphs ); 200 } 201 202 #ifdef FT_DEBUG_LEVEL_TRACE 203 if ( !count ) 204 FT_TRACE4(( " (none)" )); 205 FT_TRACE4(( "\n\n" )); 206 #endif 207 208 FT_TRACE4(( "GPOS lookups (style `%s'):\n" 209 " ", 210 af_style_names[style_class->style] )); 211 212 gpos_lookups = hb_set_create(); 213 hb_ot_layout_collect_lookups( face, 214 HB_OT_TAG_GPOS, 215 script_tags, 216 NULL, 217 coverage_tags, 218 gpos_lookups ); 219 220 #ifdef FT_DEBUG_LEVEL_TRACE 221 count = 0; 222 #endif 223 224 gpos_glyphs = hb_set_create(); 225 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gpos_lookups, &idx ); ) 226 { 227 #ifdef FT_DEBUG_LEVEL_TRACE 228 FT_TRACE4(( " %d", idx )); 229 count++; 230 #endif 231 232 /* get input coverage of GPOS feature */ 233 hb_ot_layout_lookup_collect_glyphs( face, 234 HB_OT_TAG_GPOS, 235 idx, 236 NULL, 237 gpos_glyphs, 238 NULL, 239 NULL ); 240 } 241 242 #ifdef FT_DEBUG_LEVEL_TRACE 243 if ( !count ) 244 FT_TRACE4(( " (none)" )); 245 FT_TRACE4(( "\n\n" )); 246 #endif 247 248 /* 249 * We now check whether we can construct blue zones, using glyphs 250 * covered by the feature only. In case there is not a single zone 251 * (this is, not a single character is covered), we skip this coverage. 252 * 253 */ 254 if ( style_class->coverage != AF_COVERAGE_DEFAULT ) 255 { 256 AF_Blue_Stringset bss = style_class->blue_stringset; 257 const AF_Blue_StringRec* bs = &af_blue_stringsets[bss]; 258 259 FT_Bool found = 0; 260 261 262 for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ ) 263 { 264 const char* p = &af_blue_strings[bs->string]; 265 266 267 while ( *p ) 268 { 269 hb_codepoint_t ch; 270 271 272 GET_UTF8_CHAR( ch, p ); 273 274 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_lookups, 275 &idx ); ) 276 { 277 hb_codepoint_t gidx = FT_Get_Char_Index( globals->face, ch ); 278 279 280 if ( hb_ot_layout_lookup_would_substitute( face, idx, 281 &gidx, 1, 1 ) ) 282 { 283 found = 1; 284 break; 285 } 286 } 287 } 288 } 289 290 if ( !found ) 291 { 292 FT_TRACE4(( " no blue characters found; style skipped\n" )); 293 goto Exit; 294 } 295 } 296 297 /* 298 * Various OpenType features might use the same glyphs at different 299 * vertical positions; for example, superscript and subscript glyphs 300 * could be the same. However, the auto-hinter is completely 301 * agnostic of OpenType features after the feature analysis has been 302 * completed: The engine then simply receives a glyph index and returns a 303 * hinted and usually rendered glyph. 304 * 305 * Consider the superscript feature of font `pala.ttf': Some of the 306 * glyphs are `real', this is, they have a zero vertical offset, but 307 * most of them are small caps glyphs shifted up to the superscript 308 * position (this is, the `sups' feature is present in both the GSUB and 309 * GPOS tables). The code for blue zones computation actually uses a 310 * feature's y offset so that the `real' glyphs get correct hints. But 311 * later on it is impossible to decide whether a glyph index belongs to, 312 * say, the small caps or superscript feature. 313 * 314 * For this reason, we don't assign a style to a glyph if the current 315 * feature covers the glyph in both the GSUB and the GPOS tables. This 316 * is quite a broad condition, assuming that 317 * 318 * (a) glyphs that get used in multiple features are present in a 319 * feature without vertical shift, 320 * 321 * and 322 * 323 * (b) a feature's GPOS data really moves the glyph vertically. 324 * 325 * Not fulfilling condition (a) makes a font larger; it would also 326 * reduce the number of glyphs that could be addressed directly without 327 * using OpenType features, so this assumption is rather strong. 328 * 329 * Condition (b) is much weaker, and there might be glyphs which get 330 * missed. However, the OpenType features we are going to handle are 331 * primarily located in GSUB, and HarfBuzz doesn't provide an API to 332 * directly get the necessary information from the GPOS table. A 333 * possible solution might be to directly parse the GPOS table to find 334 * out whether a glyph gets shifted vertically, but this is something I 335 * would like to avoid if not really necessary. 336 * 337 * Note that we don't follow this logic for the default coverage. 338 * Complex scripts like Devanagari have mandatory GPOS features to 339 * position many glyph elements, using mark-to-base or mark-to-ligature 340 * tables; the number of glyphs missed due to condition (b) would be far 341 * too large. 342 * 343 */ 344 if ( style_class->coverage != AF_COVERAGE_DEFAULT ) 345 hb_set_subtract( gsub_glyphs, gpos_glyphs ); 346 347 #ifdef FT_DEBUG_LEVEL_TRACE 348 FT_TRACE4(( " glyphs without GPOS data (`*' means already assigned)" )); 349 count = 0; 350 #endif 351 352 for ( idx = HB_SET_VALUE_INVALID; hb_set_next( gsub_glyphs, &idx ); ) 353 { 354 #ifdef FT_DEBUG_LEVEL_TRACE 355 if ( !( count % 10 ) ) 356 FT_TRACE4(( "\n" 357 " " )); 358 359 FT_TRACE4(( " %d", idx )); 360 count++; 361 #endif 362 363 /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */ 364 /* can be arbitrary: some fonts use fake indices for processing */ 365 /* internal to GSUB or GPOS, which is fully valid */ 366 if ( idx >= (hb_codepoint_t)globals->glyph_count ) 367 continue; 368 369 if ( gstyles[idx] == AF_STYLE_UNASSIGNED ) 370 gstyles[idx] = (FT_UShort)style_class->style; 371 #ifdef FT_DEBUG_LEVEL_TRACE 372 else 373 FT_TRACE4(( "*" )); 374 #endif 375 } 376 377 #ifdef FT_DEBUG_LEVEL_TRACE 378 if ( !count ) 379 FT_TRACE4(( "\n" 380 " (none)" )); 381 FT_TRACE4(( "\n\n" )); 382 #endif 383 384 Exit: 385 hb_set_destroy( gsub_lookups ); 386 hb_set_destroy( gsub_glyphs ); 387 hb_set_destroy( gpos_lookups ); 388 hb_set_destroy( gpos_glyphs ); 389 390 return FT_Err_Ok; 391 } 392 393 394 /* construct HarfBuzz features */ 395 #undef COVERAGE 396 #define COVERAGE( name, NAME, description, \ 397 tag1, tag2, tag3, tag4 ) \ 398 static const hb_feature_t name ## _feature[] = \ 399 { \ 400 { \ 401 HB_TAG( tag1, tag2, tag3, tag4 ), \ 402 1, 0, (unsigned int)-1 \ 403 } \ 404 }; 405 406 407 #include "afcover.h" 408 409 410 /* define mapping between HarfBuzz features and AF_Coverage */ 411 #undef COVERAGE 412 #define COVERAGE( name, NAME, description, \ 413 tag1, tag2, tag3, tag4 ) \ 414 name ## _feature, 415 416 417 static const hb_feature_t* features[] = 418 { 419 #include "afcover.h" 420 421 NULL /* AF_COVERAGE_DEFAULT */ 422 }; 423 424 425 void* af_shaper_buf_create(FT_Face face)426 af_shaper_buf_create( FT_Face face ) 427 { 428 FT_UNUSED( face ); 429 430 return (void*)hb_buffer_create(); 431 } 432 433 434 void af_shaper_buf_destroy(FT_Face face,void * buf)435 af_shaper_buf_destroy( FT_Face face, 436 void* buf ) 437 { 438 FT_UNUSED( face ); 439 440 hb_buffer_destroy( (hb_buffer_t*)buf ); 441 } 442 443 444 const char* af_shaper_get_cluster(const char * p,AF_StyleMetrics metrics,void * buf_,unsigned int * count)445 af_shaper_get_cluster( const char* p, 446 AF_StyleMetrics metrics, 447 void* buf_, 448 unsigned int* count ) 449 { 450 AF_StyleClass style_class; 451 const hb_feature_t* feature; 452 FT_Int upem; 453 const char* q; 454 int len; 455 456 hb_buffer_t* buf = (hb_buffer_t*)buf_; 457 hb_font_t* font; 458 hb_codepoint_t dummy; 459 460 461 upem = (FT_Int)metrics->globals->face->units_per_EM; 462 style_class = metrics->style_class; 463 feature = features[style_class->coverage]; 464 465 font = metrics->globals->hb_font; 466 467 /* we shape at a size of units per EM; this means font units */ 468 hb_font_set_scale( font, upem, upem ); 469 470 while ( *p == ' ' ) 471 p++; 472 473 /* count bytes up to next space (or end of buffer) */ 474 q = p; 475 while ( !( *q == ' ' || *q == '\0' ) ) 476 GET_UTF8_CHAR( dummy, q ); 477 len = (int)( q - p ); 478 479 /* feed character(s) to the HarfBuzz buffer */ 480 hb_buffer_clear_contents( buf ); 481 hb_buffer_add_utf8( buf, p, len, 0, len ); 482 483 /* we let HarfBuzz guess the script and writing direction */ 484 hb_buffer_guess_segment_properties( buf ); 485 486 /* shape buffer, which means conversion from character codes to */ 487 /* glyph indices, possibly applying a feature */ 488 hb_shape( font, buf, feature, feature ? 1 : 0 ); 489 490 if ( feature ) 491 { 492 hb_buffer_t* hb_buf = metrics->globals->hb_buf; 493 494 unsigned int gcount; 495 hb_glyph_info_t* ginfo; 496 497 unsigned int hb_gcount; 498 hb_glyph_info_t* hb_ginfo; 499 500 501 /* we have to check whether applying a feature does actually change */ 502 /* glyph indices; otherwise the affected glyph or glyphs aren't */ 503 /* available at all in the feature */ 504 505 hb_buffer_clear_contents( hb_buf ); 506 hb_buffer_add_utf8( hb_buf, p, len, 0, len ); 507 hb_buffer_guess_segment_properties( hb_buf ); 508 hb_shape( font, hb_buf, NULL, 0 ); 509 510 ginfo = hb_buffer_get_glyph_infos( buf, &gcount ); 511 hb_ginfo = hb_buffer_get_glyph_infos( hb_buf, &hb_gcount ); 512 513 if ( gcount == hb_gcount ) 514 { 515 unsigned int i; 516 517 518 for (i = 0; i < gcount; i++ ) 519 if ( ginfo[i].codepoint != hb_ginfo[i].codepoint ) 520 break; 521 522 if ( i == gcount ) 523 { 524 /* both buffers have identical glyph indices */ 525 hb_buffer_clear_contents( buf ); 526 } 527 } 528 } 529 530 *count = hb_buffer_get_length( buf ); 531 532 #ifdef FT_DEBUG_LEVEL_TRACE 533 if ( feature && *count > 1 ) 534 FT_TRACE1(( "af_shaper_get_cluster:" 535 " input character mapped to multiple glyphs\n" )); 536 #endif 537 538 return q; 539 } 540 541 542 FT_ULong af_shaper_get_elem(AF_StyleMetrics metrics,void * buf_,unsigned int idx,FT_Long * advance,FT_Long * y_offset)543 af_shaper_get_elem( AF_StyleMetrics metrics, 544 void* buf_, 545 unsigned int idx, 546 FT_Long* advance, 547 FT_Long* y_offset ) 548 { 549 hb_buffer_t* buf = (hb_buffer_t*)buf_; 550 hb_glyph_info_t* ginfo; 551 hb_glyph_position_t* gpos; 552 unsigned int gcount; 553 554 FT_UNUSED( metrics ); 555 556 557 ginfo = hb_buffer_get_glyph_infos( buf, &gcount ); 558 gpos = hb_buffer_get_glyph_positions( buf, &gcount ); 559 560 if ( idx >= gcount ) 561 return 0; 562 563 if ( advance ) 564 *advance = gpos[idx].x_advance; 565 if ( y_offset ) 566 *y_offset = gpos[idx].y_offset; 567 568 return ginfo[idx].codepoint; 569 } 570 571 572 #else /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ 573 574 575 FT_Error af_shaper_get_coverage(AF_FaceGlobals globals,AF_StyleClass style_class,FT_UShort * gstyles,FT_Bool default_script)576 af_shaper_get_coverage( AF_FaceGlobals globals, 577 AF_StyleClass style_class, 578 FT_UShort* gstyles, 579 FT_Bool default_script ) 580 { 581 FT_UNUSED( globals ); 582 FT_UNUSED( style_class ); 583 FT_UNUSED( gstyles ); 584 FT_UNUSED( default_script ); 585 586 return FT_Err_Ok; 587 } 588 589 590 void* af_shaper_buf_create(FT_Face face)591 af_shaper_buf_create( FT_Face face ) 592 { 593 FT_UNUSED( face ); 594 595 return NULL; 596 } 597 598 599 void af_shaper_buf_destroy(FT_Face face,void * buf)600 af_shaper_buf_destroy( FT_Face face, 601 void* buf ) 602 { 603 FT_UNUSED( face ); 604 FT_UNUSED( buf ); 605 } 606 607 608 const char* af_shaper_get_cluster(const char * p,AF_StyleMetrics metrics,void * buf_,unsigned int * count)609 af_shaper_get_cluster( const char* p, 610 AF_StyleMetrics metrics, 611 void* buf_, 612 unsigned int* count ) 613 { 614 FT_Face face = metrics->globals->face; 615 FT_ULong ch, dummy = 0; 616 FT_ULong* buf = (FT_ULong*)buf_; 617 618 619 while ( *p == ' ' ) 620 p++; 621 622 GET_UTF8_CHAR( ch, p ); 623 624 /* since we don't have an engine to handle clusters, */ 625 /* we scan the characters but return zero */ 626 while ( !( *p == ' ' || *p == '\0' ) ) 627 GET_UTF8_CHAR( dummy, p ); 628 629 if ( dummy ) 630 { 631 *buf = 0; 632 *count = 0; 633 } 634 else 635 { 636 *buf = FT_Get_Char_Index( face, ch ); 637 *count = 1; 638 } 639 640 return p; 641 } 642 643 644 FT_ULong af_shaper_get_elem(AF_StyleMetrics metrics,void * buf_,unsigned int idx,FT_Long * advance,FT_Long * y_offset)645 af_shaper_get_elem( AF_StyleMetrics metrics, 646 void* buf_, 647 unsigned int idx, 648 FT_Long* advance, 649 FT_Long* y_offset ) 650 { 651 FT_Face face = metrics->globals->face; 652 FT_ULong glyph_index = *(FT_ULong*)buf_; 653 654 FT_UNUSED( idx ); 655 656 657 if ( advance ) 658 FT_Get_Advance( face, 659 glyph_index, 660 FT_LOAD_NO_SCALE | 661 FT_LOAD_NO_HINTING | 662 FT_LOAD_IGNORE_TRANSFORM, 663 advance ); 664 665 if ( y_offset ) 666 *y_offset = 0; 667 668 return glyph_index; 669 } 670 671 672 #endif /* !FT_CONFIG_OPTION_USE_HARFBUZZ */ 673 674 675 /* END */ 676