Edit

kc3-lang/freetype/src/autofit/afshaper.c

Branch :

  • Show log

    Commit

  • Author : Werner Lemberg
    Date : 2025-07-01 17:08:37
    Hash : 8d82c9fa
    Message : */*: Fix trivial signedness issues with format strings in trace messages. As reported with clang 19's `-Wformat` option.

  • src/autofit/afshaper.c
  • /****************************************************************************
     *
     * afshaper.c
     *
     *   HarfBuzz interface for accessing OpenType features (body).
     *
     * Copyright (C) 2013-2024 by
     * David Turner, Robert Wilhelm, and Werner Lemberg.
     *
     * This file is part of the FreeType project, and may only be used,
     * modified, and distributed under the terms of the FreeType project
     * license, LICENSE.TXT.  By continuing to use, modify, or distribute
     * this file you indicate that you have read the license and
     * understand and accept it fully.
     *
     */
    
    
    #include <freetype/freetype.h>
    #include <freetype/ftadvanc.h>
    #include "afglobal.h"
    #include "aftypes.h"
    #include "afshaper.h"
    
    
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
    
      /**************************************************************************
       *
       * The macro FT_COMPONENT is used in trace mode.  It is an implicit
       * parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
       * messages during execution.
       */
    #undef  FT_COMPONENT
    #define FT_COMPONENT  afshaper
    
    
      /*
       * We use `sets' (in the HarfBuzz sense, which comes quite near to the
       * usual mathematical meaning) to manage both lookups and glyph indices.
       *
       * 1. For each coverage, collect lookup IDs in a set.  Note that an
       *    auto-hinter `coverage' is represented by one `feature', and a
       *    feature consists of an arbitrary number of (font specific) `lookup's
       *    that actually do the mapping job.  Please check the OpenType
       *    specification for more details on features and lookups.
       *
       * 2. Create glyph ID sets from the corresponding lookup sets.
       *
       * 3. The glyph set corresponding to AF_COVERAGE_DEFAULT is computed
       *    with all lookups specific to the OpenType script activated.  It
       *    relies on the order of AF_DEFINE_STYLE_CLASS entries so that
       *    special coverages (like `oldstyle figures') don't get overwritten.
       *
       */
    
    
      /* load coverage tags */
    #undef  COVERAGE
    #define COVERAGE( name, NAME, description,             \
                      tag1, tag2, tag3, tag4 )             \
              static const hb_tag_t  name ## _coverage[] = \
              {                                            \
                HB_TAG( tag1, tag2, tag3, tag4 ),          \
                HB_TAG_NONE                                \
              };
    
    
    #include "afcover.h"
    
    
      /* define mapping between coverage tags and AF_Coverage */
    #undef  COVERAGE
    #define COVERAGE( name, NAME, description, \
                      tag1, tag2, tag3, tag4 ) \
              name ## _coverage,
    
    
      static const hb_tag_t*  coverages[] =
      {
    #include "afcover.h"
    
        NULL /* AF_COVERAGE_DEFAULT */
      };
    
    
      /* load HarfBuzz script tags */
    #undef  SCRIPT
    #define SCRIPT( s, S, d, h, H, ss )  h,
    
    
      FT_LOCAL_ARRAY_DEF( hb_script_t )
      af_hb_scripts[] =
      {
    #include "afscript.h"
      };
    
    
      static FT_Error
      af_shaper_get_coverage_hb( AF_FaceGlobals  globals,
                                 AF_StyleClass   style_class,
                                 FT_UShort*      gstyles,
                                 FT_Bool         default_script )
      {
        hb_face_t*  face;
    
        hb_set_t*  gsub_lookups = NULL; /* GSUB lookups for a given script */
        hb_set_t*  gsub_glyphs  = NULL; /* glyphs covered by GSUB lookups  */
        hb_set_t*  gpos_lookups = NULL; /* GPOS lookups for a given script */
        hb_set_t*  gpos_glyphs  = NULL; /* glyphs covered by GPOS lookups  */
    
        hb_script_t      script;
        const hb_tag_t*  coverage_tags;
        hb_tag_t         script_tags[] = { HB_TAG_NONE,
                                           HB_TAG_NONE,
                                           HB_TAG_NONE,
                                           HB_TAG_NONE };
    
        hb_codepoint_t  idx;
    #ifdef FT_DEBUG_LEVEL_TRACE
        int             count;
    #endif
    
    
        if ( !globals || !style_class || !gstyles )
          return FT_THROW( Invalid_Argument );
    
        face = hb( font_get_face )( globals->hb_font );
    
        coverage_tags = coverages[style_class->coverage];
        script        = af_hb_scripts[style_class->script];
    
        /* Convert a HarfBuzz script tag into the corresponding OpenType */
        /* tag or tags -- some Indic scripts like Devanagari have an old */
        /* and a new set of features.                                    */
        {
          unsigned int  tags_count = 3;
          hb_tag_t      tags[3];
    
    
          hb( ot_tags_from_script_and_language )( script,
                                                  HB_LANGUAGE_INVALID,
                                                  &tags_count,
                                                  tags,
                                                  NULL,
                                                  NULL );
          script_tags[0] = tags_count > 0 ? tags[0] : HB_TAG_NONE;
          script_tags[1] = tags_count > 1 ? tags[1] : HB_TAG_NONE;
          script_tags[2] = tags_count > 2 ? tags[2] : HB_TAG_NONE;
        }
    
        /* If the second tag is HB_OT_TAG_DEFAULT_SCRIPT, change that to */
        /* HB_TAG_NONE except for the default script.                    */
        if ( default_script )
        {
          if ( script_tags[0] == HB_TAG_NONE )
            script_tags[0] = HB_OT_TAG_DEFAULT_SCRIPT;
          else
          {
            if ( script_tags[1] == HB_TAG_NONE )
              script_tags[1] = HB_OT_TAG_DEFAULT_SCRIPT;
            else if ( script_tags[1] != HB_OT_TAG_DEFAULT_SCRIPT )
              script_tags[2] = HB_OT_TAG_DEFAULT_SCRIPT;
          }
        }
        else
        {
          /* we use non-standard tags like `khms' for special purposes;       */
          /* HarfBuzz maps them to `DFLT', which we don't want to handle here */
          if ( script_tags[0] == HB_OT_TAG_DEFAULT_SCRIPT )
            goto Exit;
        }
    
        gsub_lookups = hb( set_create )();
        hb( ot_layout_collect_lookups )( face,
                                         HB_OT_TAG_GSUB,
                                         script_tags,
                                         NULL,
                                         coverage_tags,
                                         gsub_lookups );
    
        if ( hb( set_is_empty )( gsub_lookups ) )
          goto Exit; /* nothing to do */
    
        FT_TRACE4(( "GSUB lookups (style `%s'):\n",
                    af_style_names[style_class->style] ));
        FT_TRACE4(( " " ));
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        count = 0;
    #endif
    
        gsub_glyphs = hb( set_create )();
        for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_lookups, &idx ); )
        {
    #ifdef FT_DEBUG_LEVEL_TRACE
          FT_TRACE4(( " %u", idx ));
          count++;
    #endif
    
          /* get output coverage of GSUB feature */
          hb( ot_layout_lookup_collect_glyphs )( face,
                                                 HB_OT_TAG_GSUB,
                                                 idx,
                                                 NULL,
                                                 NULL,
                                                 NULL,
                                                 gsub_glyphs );
        }
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        if ( !count )
          FT_TRACE4(( " (none)" ));
        FT_TRACE4(( "\n" ));
        FT_TRACE4(( "\n" ));
    #endif
    
        FT_TRACE4(( "GPOS lookups (style `%s'):\n",
                    af_style_names[style_class->style] ));
        FT_TRACE4(( " " ));
    
        gpos_lookups = hb( set_create )();
        hb( ot_layout_collect_lookups )( face,
                                         HB_OT_TAG_GPOS,
                                         script_tags,
                                         NULL,
                                         coverage_tags,
                                         gpos_lookups );
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        count = 0;
    #endif
    
        gpos_glyphs = hb( set_create )();
        for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gpos_lookups, &idx ); )
        {
    #ifdef FT_DEBUG_LEVEL_TRACE
          FT_TRACE4(( " %u", idx ));
          count++;
    #endif
    
          /* get input coverage of GPOS feature */
          hb( ot_layout_lookup_collect_glyphs )( face,
                                                 HB_OT_TAG_GPOS,
                                                 idx,
                                                 NULL,
                                                 gpos_glyphs,
                                                 NULL,
                                                 NULL );
        }
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        if ( !count )
          FT_TRACE4(( " (none)" ));
        FT_TRACE4(( "\n" ));
        FT_TRACE4(( "\n" ));
    #endif
    
        /*
         * We now check whether we can construct blue zones, using glyphs
         * covered by the feature only.  In case there is not a single zone
         * (that is, not a single character is covered), we skip this coverage.
         *
         */
        if ( style_class->coverage != AF_COVERAGE_DEFAULT )
        {
          AF_Blue_Stringset         bss = style_class->blue_stringset;
          const AF_Blue_StringRec*  bs  = &af_blue_stringsets[bss];
    
          FT_Bool  found = 0;
    
    
          for ( ; bs->string != AF_BLUE_STRING_MAX; bs++ )
          {
            const char*  p = &af_blue_strings[bs->string];
    
    
            while ( *p )
            {
              hb_codepoint_t  ch;
    
    
              GET_UTF8_CHAR( ch, p );
    
              for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_lookups,
                                                                &idx ); )
              {
                hb_codepoint_t  gidx = FT_Get_Char_Index( globals->face, ch );
    
    
                if ( hb( ot_layout_lookup_would_substitute )( face, idx,
                                                              &gidx, 1, 1 ) )
                {
                  found = 1;
                  break;
                }
              }
            }
          }
    
          if ( !found )
          {
            FT_TRACE4(( "  no blue characters found; style skipped\n" ));
            goto Exit;
          }
        }
    
        /*
         * Various OpenType features might use the same glyphs at different
         * vertical positions; for example, superscript and subscript glyphs
         * could be the same.  However, the auto-hinter is completely
         * agnostic of OpenType features after the feature analysis has been
         * completed: The engine then simply receives a glyph index and returns a
         * hinted and usually rendered glyph.
         *
         * Consider the superscript feature of font `pala.ttf': Some of the
         * glyphs are `real', that is, they have a zero vertical offset, but
         * most of them are small caps glyphs shifted up to the superscript
         * position (that is, the `sups' feature is present in both the GSUB and
         * GPOS tables).  The code for blue zones computation actually uses a
         * feature's y offset so that the `real' glyphs get correct hints.  But
         * later on it is impossible to decide whether a glyph index belongs to,
         * say, the small caps or superscript feature.
         *
         * For this reason, we don't assign a style to a glyph if the current
         * feature covers the glyph in both the GSUB and the GPOS tables.  This
         * is quite a broad condition, assuming that
         *
         *   (a) glyphs that get used in multiple features are present in a
         *       feature without vertical shift,
         *
         * and
         *
         *   (b) a feature's GPOS data really moves the glyph vertically.
         *
         * Not fulfilling condition (a) makes a font larger; it would also
         * reduce the number of glyphs that could be addressed directly without
         * using OpenType features, so this assumption is rather strong.
         *
         * Condition (b) is much weaker, and there might be glyphs which get
         * missed.  However, the OpenType features we are going to handle are
         * primarily located in GSUB, and HarfBuzz doesn't provide an API to
         * directly get the necessary information from the GPOS table.  A
         * possible solution might be to directly parse the GPOS table to find
         * out whether a glyph gets shifted vertically, but this is something I
         * would like to avoid if not really necessary.
         *
         * Note that we don't follow this logic for the default coverage.
         * Complex scripts like Devanagari have mandatory GPOS features to
         * position many glyph elements, using mark-to-base or mark-to-ligature
         * tables; the number of glyphs missed due to condition (b) would be far
         * too large.
         *
         */
        if ( style_class->coverage != AF_COVERAGE_DEFAULT )
          hb( set_subtract )( gsub_glyphs, gpos_glyphs );
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        FT_TRACE4(( "  glyphs without GPOS data (`*' means already assigned)" ));
        count = 0;
    #endif
    
        for ( idx = HB_SET_VALUE_INVALID; hb( set_next )( gsub_glyphs, &idx ); )
        {
    #ifdef FT_DEBUG_LEVEL_TRACE
          if ( !( count % 10 ) )
          {
            FT_TRACE4(( "\n" ));
            FT_TRACE4(( "   " ));
          }
    
          FT_TRACE4(( " %u", idx ));
          count++;
    #endif
    
          /* glyph indices returned by `hb_ot_layout_lookup_collect_glyphs' */
          /* can be arbitrary: some fonts use fake indices for processing   */
          /* internal to GSUB or GPOS, which is fully valid                 */
          if ( idx >= (hb_codepoint_t)globals->glyph_count )
            continue;
    
          if ( gstyles[idx] == AF_STYLE_UNASSIGNED )
            gstyles[idx] = (FT_UShort)style_class->style;
    #ifdef FT_DEBUG_LEVEL_TRACE
          else
            FT_TRACE4(( "*" ));
    #endif
        }
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        if ( !count )
        {
          FT_TRACE4(( "\n" ));
          FT_TRACE4(( "    (none)" ));
        }
        FT_TRACE4(( "\n" ));
        FT_TRACE4(( "\n" ));
    #endif
    
      Exit:
        hb( set_destroy )( gsub_lookups );
        hb( set_destroy )( gsub_glyphs  );
        hb( set_destroy )( gpos_lookups );
        hb( set_destroy )( gpos_glyphs  );
    
        return FT_Err_Ok;
      }
    
    
      /* construct HarfBuzz features */
    #undef  COVERAGE
    #define COVERAGE( name, NAME, description,                \
                      tag1, tag2, tag3, tag4 )                \
              static const hb_feature_t  name ## _feature[] = \
              {                                               \
                {                                             \
                  HB_TAG( tag1, tag2, tag3, tag4 ),           \
                  1, 0, (unsigned int)-1                      \
                }                                             \
              };
    
    
    #include "afcover.h"
    
    
      /* define mapping between HarfBuzz features and AF_Coverage */
    #undef  COVERAGE
    #define COVERAGE( name, NAME, description, \
                      tag1, tag2, tag3, tag4 ) \
              name ## _feature,
    
    
      static const hb_feature_t*  features[] =
      {
    #include "afcover.h"
    
        NULL /* AF_COVERAGE_DEFAULT */
      };
    
    
      static void*
      af_shaper_buf_create_hb( AF_FaceGlobals  globals )
      {
        FT_UNUSED( globals );
    
        return (void*)hb( buffer_create )();
      }
    
    
      static void
      af_shaper_buf_destroy_hb( AF_FaceGlobals  globals,
                                void*           buf )
      {
        FT_UNUSED( globals );
    
        hb( buffer_destroy )( (hb_buffer_t*)buf );
      }
    
    
      static const char*
      af_shaper_get_cluster_hb( const char*      p,
                                AF_StyleMetrics  metrics,
                                void*            buf_,
                                unsigned int*    count )
      {
        AF_FaceGlobals  globals = metrics->globals;
    
        AF_StyleClass        style_class;
        const hb_feature_t*  feature;
        FT_Int               upem;
        const char*          q;
        int                  len;
    
        hb_buffer_t*    buf = (hb_buffer_t*)buf_;
        hb_font_t*      font;
        hb_codepoint_t  dummy;
    
        FT_UNUSED( globals );
    
    
        upem        = (FT_Int)metrics->globals->face->units_per_EM;
        style_class = metrics->style_class;
        feature     = features[style_class->coverage];
    
        font = metrics->globals->hb_font;
    
        /* we shape at a size of units per EM; this means font units */
        hb( font_set_scale )( font, upem, upem );
    
        while ( *p == ' ' )
          p++;
    
        /* count bytes up to next space (or end of buffer) */
        q = p;
        while ( !( *q == ' ' || *q == '\0' ) )
          GET_UTF8_CHAR( dummy, q );
        len = (int)( q - p );
    
        /* feed character(s) to the HarfBuzz buffer */
        hb( buffer_clear_contents )( buf );
        hb( buffer_add_utf8 )( buf, p, len, 0, len );
    
        /* we let HarfBuzz guess the script and writing direction */
        hb( buffer_guess_segment_properties )( buf );
    
        /* shape buffer, which means conversion from character codes to */
        /* glyph indices, possibly applying a feature                   */
        hb( shape )( font, buf, feature, feature ? 1 : 0 );
    
        if ( feature )
        {
          hb_buffer_t*  hb_buf = metrics->globals->hb_buf;
    
          unsigned int      gcount;
          hb_glyph_info_t*  ginfo;
    
          unsigned int      hb_gcount;
          hb_glyph_info_t*  hb_ginfo;
    
    
          /* we have to check whether applying a feature does actually change */
          /* glyph indices; otherwise the affected glyph or glyphs aren't     */
          /* available at all in the feature                                  */
    
          hb( buffer_clear_contents )( hb_buf );
          hb( buffer_add_utf8 )( hb_buf, p, len, 0, len );
          hb( buffer_guess_segment_properties )( hb_buf );
          hb( shape )( font, hb_buf, NULL, 0 );
    
          ginfo    = hb( buffer_get_glyph_infos )( buf, &gcount );
          hb_ginfo = hb( buffer_get_glyph_infos )( hb_buf, &hb_gcount );
    
          if ( gcount == hb_gcount )
          {
            unsigned int  i;
    
    
            for (i = 0; i < gcount; i++ )
              if ( ginfo[i].codepoint != hb_ginfo[i].codepoint )
                break;
    
            if ( i == gcount )
            {
              /* both buffers have identical glyph indices */
              hb( buffer_clear_contents )( buf );
            }
          }
        }
    
        *count = hb( buffer_get_length )( buf );
    
    #ifdef FT_DEBUG_LEVEL_TRACE
        if ( feature && *count > 1 )
          FT_TRACE1(( "af_shaper_get_cluster:"
                      " input character mapped to multiple glyphs\n" ));
    #endif
    
        return q;
      }
    
    
      static FT_ULong
      af_shaper_get_elem_hb( AF_StyleMetrics  metrics,
                             void*            buf_,
                             unsigned int     idx,
                             FT_Long*         advance,
                             FT_Long*         y_offset )
      {
        AF_FaceGlobals  globals = metrics->globals;
    
        hb_buffer_t*          buf = (hb_buffer_t*)buf_;
        hb_glyph_info_t*      ginfo;
        hb_glyph_position_t*  gpos;
        unsigned int          gcount;
    
        FT_UNUSED( globals );
    
    
        ginfo = hb( buffer_get_glyph_infos )( buf, &gcount );
        gpos  = hb( buffer_get_glyph_positions )( buf, &gcount );
    
        if ( idx >= gcount )
          return 0;
    
        if ( advance )
          *advance = gpos[idx].x_advance;
        if ( y_offset )
          *y_offset = gpos[idx].y_offset;
    
        return ginfo[idx].codepoint;
      }
    
    
    #endif /* FT_CONFIG_OPTION_USE_HARFBUZZ */
    
    
      static FT_Error
      af_shaper_get_coverage_nohb( AF_FaceGlobals  globals,
                                   AF_StyleClass   style_class,
                                   FT_UShort*      gstyles,
                                   FT_Bool         default_script )
      {
        FT_UNUSED( globals );
        FT_UNUSED( style_class );
        FT_UNUSED( gstyles );
        FT_UNUSED( default_script );
    
        return FT_Err_Ok;
      }
    
    
      static void*
      af_shaper_buf_create_nohb( AF_FaceGlobals  globals )
      {
        FT_UNUSED( globals );
    
        return NULL;
      }
    
    
      static void
      af_shaper_buf_destroy_nohb( AF_FaceGlobals  globals,
                                  void*    buf )
      {
        FT_UNUSED( globals );
        FT_UNUSED( buf );
      }
    
    
      static const char*
      af_shaper_get_cluster_nohb( const char*      p,
                                  AF_StyleMetrics  metrics,
                                  void*            buf_,
                                  unsigned int*    count )
      {
        FT_Face    face      = metrics->globals->face;
        FT_ULong   ch, dummy = 0;
        FT_ULong*  buf       = (FT_ULong*)buf_;
    
    
        while ( *p == ' ' )
          p++;
    
        GET_UTF8_CHAR( ch, p );
    
        /* since we don't have an engine to handle clusters, */
        /* we scan the characters but return zero            */
        while ( !( *p == ' ' || *p == '\0' ) )
          GET_UTF8_CHAR( dummy, p );
    
        if ( dummy )
        {
          *buf   = 0;
          *count = 0;
        }
        else
        {
          *buf   = FT_Get_Char_Index( face, ch );
          *count = 1;
        }
    
        return p;
      }
    
    
      static FT_ULong
      af_shaper_get_elem_nohb( AF_StyleMetrics  metrics,
                               void*            buf_,
                               unsigned int     idx,
                               FT_Long*         advance,
                               FT_Long*         y_offset )
      {
        FT_Face   face        = metrics->globals->face;
        FT_ULong  glyph_index = *(FT_ULong*)buf_;
    
        FT_UNUSED( idx );
    
    
        if ( advance )
          FT_Get_Advance( face,
                          glyph_index,
                          FT_LOAD_NO_SCALE         |
                          FT_LOAD_NO_HINTING       |
                          FT_LOAD_IGNORE_TRANSFORM,
                          advance );
    
        if ( y_offset )
          *y_offset = 0;
    
        return glyph_index;
      }
    
    
      /********************************************************************/
    
      FT_Error
      af_shaper_get_coverage( AF_FaceGlobals  globals,
                              AF_StyleClass   style_class,
                              FT_UShort*      gstyles,
                              FT_Bool         default_script )
      {
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
        if ( ft_hb_enabled( globals ) )
          return af_shaper_get_coverage_hb( globals,
                                            style_class,
                                            gstyles,
                                            default_script );
        else
    #endif
          return af_shaper_get_coverage_nohb( globals,
                                              style_class,
                                              gstyles,
                                              default_script );
      }
    
    
      void*
      af_shaper_buf_create( AF_FaceGlobals  globals )
      {
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
        if ( ft_hb_enabled( globals ) )
          return af_shaper_buf_create_hb( globals );
        else
    #endif
          return af_shaper_buf_create_nohb( globals );
      }
    
    
      void
      af_shaper_buf_destroy( AF_FaceGlobals  globals,
                             void*           buf )
      {
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
        if ( ft_hb_enabled( globals ) )
          af_shaper_buf_destroy_hb( globals, buf );
        else
    #endif
          af_shaper_buf_destroy_nohb( globals, buf );
      }
    
    
      const char*
      af_shaper_get_cluster( const char*      p,
                             AF_StyleMetrics  metrics,
                             void*            buf_,
                             unsigned int*    count )
      {
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
        if ( ft_hb_enabled( metrics->globals ) )
          return af_shaper_get_cluster_hb( p, metrics, buf_, count );
        else
    #endif
          return af_shaper_get_cluster_nohb( p, metrics, buf_, count );
      }
    
    
      FT_ULong
      af_shaper_get_elem( AF_StyleMetrics  metrics,
                          void*            buf_,
                          unsigned int     idx,
                          FT_Long*         advance,
                          FT_Long*         y_offset )
      {
    #ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
        if ( ft_hb_enabled( metrics->globals ) )
          return af_shaper_get_elem_hb( metrics,
                                        buf_,
                                        idx,
                                        advance,
                                        y_offset );
    #endif
          return af_shaper_get_elem_nohb( metrics,
                                          buf_,
                                          idx,
                                          advance,
                                          y_offset );
      }
    
    
    /* END */