• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /****************************************************************************
2  *
3  * afshaper.c
4  *
5  *   HarfBuzz interface for accessing OpenType features (body).
6  *
7  * Copyright (C) 2013-2022 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     {
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      * (this 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', this is, they have a zero vertical offset, but
317      * most of them are small caps glyphs shifted up to the superscript
318      * position (this 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*
af_shaper_buf_create(FT_Face face)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
af_shaper_buf_destroy(FT_Face face,void * buf)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*
af_shaper_get_cluster(const char * p,AF_StyleMetrics metrics,void * buf_,unsigned int * count)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
af_shaper_get_elem(AF_StyleMetrics metrics,void * buf_,unsigned int idx,FT_Long * advance,FT_Long * y_offset)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
af_shaper_get_coverage(AF_FaceGlobals globals,AF_StyleClass style_class,FT_UShort * gstyles,FT_Bool default_script)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*
af_shaper_buf_create(FT_Face face)606   af_shaper_buf_create( FT_Face  face )
607   {
608     FT_UNUSED( face );
609 
610     return NULL;
611   }
612 
613 
614   void
af_shaper_buf_destroy(FT_Face face,void * buf)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*
af_shaper_get_cluster(const char * p,AF_StyleMetrics metrics,void * buf_,unsigned int * count)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
af_shaper_get_elem(AF_StyleMetrics metrics,void * buf_,unsigned int idx,FT_Long * advance,FT_Long * y_offset)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