sdk-world v2026.11.05
PS2 SDK - world tier (copyleft-allowed, non-essential packages)
Loading...
Searching...
No Matches
afshaper.c
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;
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 */
hb_script_t