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