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