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