Werner LEMBERG wrote:
> While looking at the whole issue more closely, I've identified at
> least four bugs :-(  One is extremely serious, making
> `FT_Get_Advance' return wrongly scaled values if fast advance
> loading isn't available (now fixed in git).

Yesterday I was away from my mail, but I also found this bug, and used
about the same fix as you committed. The only difference is a minor nit,
in the case FT_LOAD_NO_SCALE is used and the quick way failed with
ErrNotImplem, <<10 scaling might not be done.

Also, see below at the end for another point.


> The next two are more subtle and not directly related to
> `FT_Get_Advance' (I get wrong metrics from some fonts), and I need
> more investigation to fix them.

Details please. I did found something myself, most likely a double
rounding issue (more below), but I cannot figure where is the problem
exactly because I do not know the internals enough :-(.


>> Another thing we need to document is that the ->glyph instance
>> inside the face object might be erased after a call through
>> FT_Get_Advance().
> 
> Yes.  Can you provide a patch?

Will do. I noticed there are some places (about GlyphSlotRec) where it
is mentioned the API functions which modify them, I should update that.


>> Well, there is also the "problem" that they used the FT_Get_Advance
>> API specifying light rendering, just to found out later that the
>> lightly hinted advance was actually different...
> 
> One important issue has been missed completely by me: Advance widths
> don't change during the light hinting process, however, they get
> rounded to integer values, similar to TrueType. 

Yeah, and I understand this was the problem Chromium guys did encounter
in the first place.

> After some thinking I now believe that it is a bug that
> `FT_Get_Advance' returns unrounded advance widths for the light
> hinting mode.

Okay, but then we have to mimic exactly the rounding process used by the
Load_Glyph logic*s*... More below.


>>>> [...] in TrueType there is a way to get the hinted advances
>>>> quicker than executing the full bytecode program, even if it is
>>>> not alluded above: when the requested ppem has an associated
>>>> hdmx/VDMX table within the font.
>>>
>>> Do you volunteer to implement support for those two tables?
>>
>> Yes, I will do that (but not today.)

I looked at it, but it does not appear to be very easy to do,
particularly because the current (*...clazz->get_advances)() functions
returns _unscaled_ values (which are to be scaled within FT_Get_Advance
while "hdmx" values are pixels, hence are already scaled.

A possible way to deal with that could be for (*clazz->get_advances)()
to return FT_PseudoErr_InternalAlreadyScaled (it's internal after all),
and dealing with that case at FT_Get_Advance.
How do you feel it?


>> I wonder also about a new demo program to check whether
>> FT_Get_Advance() gives, or not, the same results as FT_Load_Glyph().
> 
> This might be useful, yes.

Attached. -f and -i options behaves the same as with ftbench: so -f
10000 forces the use of the light hinting mode; -i is useful when you
got many failures, to skip up to "interesting" glyphs. The -a option
checks the value of glyph->advance.x; without -a, it checks the value of
glyph->linearHoriAdvance (which are supposedly the base; more below.)
I added the -r option after discovering as you did the issue with
grid-fitting: so -r rounds the 16.16 values before doing the comparison.

The problem I detected then was the behaviour of invocations like

C>ftchkadv -f 10000 -a -r 147 \Windows\Fonts\times.ttf
\Windows\Fonts\times.ttf: glyph  376: Load_Glyph->->advance: 92.0000 !=
Get_Advance: 91.7e20
glyph  377: Load_Glyph->->advance: 92.0000 != Get_Advance: 91.7e20
glyph  378: Load_Glyph->->advance: 92.0000 != Get_Advance: 91.7e20
glyph  379: Load_Glyph->->advance: 92.0000 != Get_Advance: 91.7e20
glyph 1620: Load_Glyph->->advance: 1d.0000 != Get_Advance: 1c.7ee0
glyph 1962: Load_Glyph->->advance: 2b.0000 != Get_Advance: 2a.7e00
glyph 2974: Load_Glyph->->advance: 2c.0000 != Get_Advance: 2b.7f40
glyph 3065: Load_Glyph->->advance: 2b.0000 != Get_Advance: 2a.7e00
8 fails.

Changing ppem only changes the problematic glyphs :-/.
Notice that every problem are always with Get_Advance being slightly
below the xx.8000 mark. I know the path for Get_Advance: grabs the raw
font units in 26.6 then MulDiv(,x_scale,64), and the result is in 16.16;
my current guess is that within the Load_Glyph codepath for ->advance,
there is two rounding steps... but I do not where/how; also the fact I
was not able to spot a similar problem with Type1 fonts suggests there
is something specific to TrueType happening.


But these experimentations with the two values (with -a or without)
reminds me with the first point, about the computation of FT_Get_Advance
in the fall-back case, and the use of the computed advance.x<<10 values
as references: would it make more sense to use linearHoriAdvance<<0 there?

I tried my test program with that change to ftadvanc.c, and detected
another variation, this time with FT_LOAD_NO_HINTING
C>ftchkadv -f 2 -a 147 \Windows\Fonts\times.ttf
\Windows\Fonts\times.ttf: glyph    0: Load_Glyph->->advance: 72.5800 !=
Get_Advance: 72.5760
glyph    4: Load_Glyph->->advance: 30.f400 != Get_Advance: 30.f3c0
glyph    5: Load_Glyph->->advance: 3c.0000 != Get_Advance: 3c.0180
glyph    8: Load_Glyph->->advance: 7a.7400 != Get_Advance: 7a.73c0
glyph    9: Load_Glyph->->advance: 72.5800 != Get_Advance: 72.5760
glyph   10: Load_Glyph->->advance: 1a.7c00 != Get_Advance: 1a.7c60
glyph   11: Load_Glyph->->advance: 30.f400 != Get_Advance: 30.f3c0
glyph   12: Load_Glyph->->advance: 30.f400 != Get_Advance: 30.f3c0
glyph   14: Load_Glyph->->advance: 52.e800 != Get_Advance: 52.e720
glyph   16: Load_Glyph->->advance: 30.f400 != Get_Advance: 30.f3c0
2549 fails.

Clearly, the problem now is with the precision: FT_Get_Advance gives
16.16 values, same as the precision of linearHoriAdvance; but advance.x
is a 26.6 value, so there is a loss of precision which the test program
detects here. I believe these results are to be expected, and they all
concur to the point that FT_Get_Advance is a quick way to find out the
"linearHoriAdvance" value, _not_ advance.x.


Antoine
/****************************************************************************/
/*                                                                          */
/*  The FreeType project -- a free and portable quality font engine         */
/*                                                                          */
/*  Copyright 1996-2011 by A. Leca,                                         */
/*  D. Turner, R. Wilhelm, and W. Lemberg                                   */
/*                                                                          */
/*  ftchkadv: test FT_Get_Advance. This program compares the advances       */
/*            computed in various ways for all the glyphs of a given font.  */
/*                                                                          */
/*  NOTE:  This is just a test program that is used to debug the            */
/*         current engine.                                                  */
/*                                                                          */
/****************************************************************************/

#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_ADVANCES_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "common.h"


#define gettext( x )  ( x )

  FT_Error      error;

  FT_Library    library;
  FT_Face       face;
  FT_Size       size;
  FT_GlyphSlot  slot;

  int           check_advance_x = 0;
  unsigned int  first_index = 0;
  FT_Int32      load_flags = FT_LOAD_DEFAULT;
  FT_Fixed*     advances;

  unsigned int  num_glyphs;
  int           ptsize;

  int  Fail;
  int  Num;


  FT_Fixed NoRoundFix( FT_Fixed  a )
  {
    return a;
  } 

  /* By default, do not round.                                     */
  /* FT_RoundFix, FT_CeilFix, or FT_FloorFix are valid alternates. */
  FT_Fixed (*roundFix_func)( FT_Fixed ) = NoRoundFix;


  static void  Usage( char*  name )
  {
    printf( "ftchkadv: FT_Get_Advance tester -- part of the FreeType project\n" 
);
    printf( "---------------------------------------------------------------\n" 
);
    printf( "\n" );
    printf( "Usage: %s [options] ppem fontname[.ttf|.ttc] [fontname2..]\n", 
name );
    fprintf( stderr, "options:\n");
    fprintf( stderr, "   -a : check advance.x instead of linearHoriAdvance\n"
                     "   -f HHHH : load flags (hexadecimal)\n"
                     "   -i NNN : first index to start with (default is 0)\n"
                     "   -r : rounds advances before comparison\n");
    printf( "\n" );

    exit( 1 );
  }


  static void  Panic( const char*  message )
  {
    fprintf( stderr, "%s\n  error code = 0x%04x\n", message, error );
    exit(1);
  }


  int  main( int  argc, char**  argv )
  {
    int           i, file_index;
    unsigned int  id;
    char          filename[128 + 4];
    char          alt_filename[128 + 4];
    char*         execname;
    char*         fname;
    FT_Fixed      advance;


    execname = argv[0];

    while ( 1 )
    {
      int  opt;

      opt = getopt( argc, argv, "af:i:r" );

      if ( opt == -1 )
        break;

      switch ( opt )
      {
      case 'a':
        check_advance_x = 1;
        break;
      case 'f':
        load_flags = strtol( optarg, NULL, 16 );
        break;
      case 'i':
        first_index = atoi( optarg );
        break;
      case 'r':
        roundFix_func = FT_RoundFix;
        break;
      default:
        Usage( execname );
        break;
      }
    }

    argc -= optind;
    argv += optind;

    if ( argc < 2 )
      Usage( execname );

    if ( sscanf( argv[0], "%d", &ptsize ) != 1 )
      Usage( execname );

    error = FT_Init_FreeType( &library );
    if (error) Panic( "Could not create library object" );

    /* Now check all files */
    for ( file_index = 1; file_index < argc; file_index++ )
    {
      fname = argv[file_index];

      /* try to open the file with no extra extension first */
      error = FT_New_Face( library, fname, 0, &face );
      if (!error)
      {
        printf( "%s: ", fname );
        goto Success;
      }


      if ( error == FT_Err_Unknown_File_Format )
      {
        printf( "unknown format\n" );
        continue;
      }

      /* ok, we could not load the file, try to add an extension to */
      /* its name if possible..                                     */

      i     = strlen( fname );
      while ( i > 0 && fname[i] != '\\' && fname[i] != '/' )
      {
        if ( fname[i] == '.' )
          i = 0;
        i--;
      }

      filename[128] = '\0';
      alt_filename[128] = '\0';

      strncpy( filename, fname, 128 );
      strncpy( alt_filename, fname, 128 );

#ifndef macintosh
      if ( i >= 0 )
      {
        strncpy( filename + strlen( filename ), ".ttf", 4 );
        strncpy( alt_filename + strlen( alt_filename ), ".ttc", 4 );
      }
#endif
      i     = strlen( filename );
      fname = filename;

      while ( i >= 0 )
#ifndef macintosh
        if ( filename[i] == '/' || filename[i] == '\\' )
#else
        if ( filename[i] == ':' )
#endif
        {
          fname = filename + i + 1;
          i = -1;
        }
        else
          i--;

      printf( "%s: ", fname );

      /* Load face */
      error = FT_New_Face( library, filename, 0, &face );
      if (error)
      {
        if (error == FT_Err_Unknown_File_Format)
          printf( "unknown format\n" );
        else
          printf( "could not find/open file (error: %d)\n", error );
        continue;
      }
      if (error) Panic( "Could not open file" );

  Success:
      num_glyphs = face->num_glyphs;

      if ( !check_advance_x &&
           load_flags & FT_LOAD_NO_SCALE &&
           FT_IS_SCALABLE(face) &&
           face->units_per_EM != ptsize )
      {
          printf( "unscaled values, design EM=%d != %d!!! ",
                  face->units_per_EM, ptsize );
      }

      error = FT_Set_Char_Size( face, ptsize << 6, ptsize << 6, 72, 72 );
      if (error) Panic( "Could not set character size" );

      advances = (FT_Fixed *)calloc( sizeof ( FT_Fixed ), num_glyphs );
      if (!advances) Panic( "Out of memory" );

      error = FT_Get_Advances( face, first_index, num_glyphs - first_index,
                               FT_ADVANCE_FLAG_FAST_ONLY|load_flags, advances );

      if (error)
      {
        if ( error == FT_Err_Unimplemented_Feature )
        {
          printf( "(no fast-computed advances) " );
          error = FT_Get_Advances( face, first_index, num_glyphs - first_index,
                                   load_flags, advances );
        }

        if ( error )
        {
          printf( "FT_Get_Advances failed 0x%04x\n" , error );
          goto Done;
        }
      }

      Fail = 0;

      for ( id = 0; id < num_glyphs - first_index; id++ )
      {
        error = FT_Load_Glyph( face, id + first_index, load_flags );

        if (error)
        {
          if ( Fail < 10 )
            printf( "glyph %4u: fail to load 0x%04x\n" , id, error );
          Fail++;
        }
        else
        {
          if ( check_advance_x )
          {
 
            advance = ( load_flags & FT_LOAD_VERTICAL_LAYOUT )
                      ? (FT_Fixed)face->glyph->advance.y
                      : (FT_Fixed)face->glyph->advance.x;

            if ( load_flags & FT_LOAD_NO_SCALE )
            {
              /* Glyph advance is in font units, advances also :-) */
            }
            else
            {
              /* Glyph advance is in FUnit (26.6), advances are in 16.16 */
              advance <<= (16 - 6);
            }
          }
          else
          {
            advance = ( load_flags & FT_LOAD_VERTICAL_LAYOUT )
                      ? (FT_Fixed)face->glyph->linearVertAdvance
                      : (FT_Fixed)face->glyph->linearHoriAdvance;

            if ( ( load_flags & FT_LOAD_NO_SCALE ) == 0 )
            {
              /* Glyph linear advance is in 16.16, advances also :-) */
            }
            else
            {
              /* advances are unscaled in font units, while glyph linear */
              /* advance is scaled in 16.16... The requested size better */
              /* have to be the same as the design EM size!              */
              /* Should we round before loosing precision?               */
              advance = advance >> 16;
            }
          }

          /* Note that if hinted, glyph->advance is grid-fitted, so will    */
          /* not match the value returned by FT_Get_Advance unless rounded. */
          if ( roundFix_func(advance) != roundFix_func(advances[id]) )
          {
            if ( Fail < 10 )
              printf( "glyph %4u: Load_Glyph->->%s: %x.%04x != Get_Advance: 
%x.%04x\n" ,
                      id + first_index, check_advance_x ? "advance" : 
"linearAdv",
                      advance >> 16, (advance & 0xFFFF),
                      advances[id] >> 16, (advances[id] & 0xFFFF) );

            Fail++;
          }
        }
      }

      if ( Fail == 0 )
        printf( "OK.\n" );
      else
        if ( Fail == 1 )
          printf( "1 fail.\n" );
        else
          printf( "%d fails.\n", Fail );

  Done:
      free( advances );

      FT_Done_Face( face );
    }

    FT_Done_FreeType(library);
    exit( 0 );      /* for safety reasons */

    return 0;       /* never reached */
  }


/* End */
_______________________________________________
Freetype-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/freetype-devel

Reply via email to