freetype-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[ft-devel] FT_Render_Glyph(...) errors for high pixel-per-em (ppem) valu


From: Colin Fahey
Subject: [ft-devel] FT_Render_Glyph(...) errors for high pixel-per-em (ppem) values
Date: Fri, 20 Oct 2017 08:17:20 -0700



FT_Render_Glyph(...) errors for high pixel-per-em (ppem) values

2017 October 20



INTRODUCTION

For pixels-per-em (ppem) values up to, and including, 1126 ppem,
FT_Render_Glyph(...) succeeds for ALL 736,482 glyphs in a collection
of 334 TrueType font face files (most of which shipped with Windows 7,
and others which ship with popular open-source applications) --
for ALL combinations of FT_Load_Glyph(...) loading-flags values (e.g.,
specifying hinting algorithm) and FT_Render_Glyph(...) render-mode values
(e.g., anti-aliased, light, and monochrome).

However, for ppem values of 1127 ppem and higher, FT_Render_Glyph(...)
*FAILS* (with error code "1" (generally supposed to represent
"cannot open resource")) for a very small of glyphs -- for most
combinations of FT_Load_Glyph(...) loading-flags values and
FT_Render_Glyph(...) render-mode values.

The error has *NOT* been observed when the render-mode value is set
to FT_RENDER_MODE_MONO.

For *non-MONO* render-mode values, the number of glyphs for which
FT_Render_Glyph(...) returns error "1" increases slowly as the ppem
value increases. 

For example, for 4096 ppem: 363 glyphs, out of the collection
of 736,482 glyphs previously mentioned, cause FT_Render_Glyph(...) to
return an error.  That's only 0.049 % of a vast number of glyphs.
But a few of these glyphs are commonly-used Unicode code points in
mainstream fonts (e.g., upper-case "T" in Courier Bold, and "emdash" in
Times Italic), and many glyphs in each of a few other popular Windows fonts
(ARIALUNI.ttf, msjh.ttf, msjhbd.ttf, msyh.ttf, msybd.ttf, simhei.ttf).

Consider the following two non-MONO render-mode options:

    render_mode: { FT_RENDER_MODE_NORMAL, FT_RENDER_MODE_LIGHT }


The *most* errors seem to be for the following load_flags value:

    load_flags:    FT_LOAD_NO_HINTING

*Slightly* fewer errors for the following two load_flags values:

    load_flags:  { FT_LOAD_DEFAULT, (FT_LOAD_DEFAULT | FT_LOAD_TARGET_MONO) }

And *notably fewer* errors (e.g., only 75% of worst case) for the following load_flags value:

    load_flags:    (FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT)




FREETYPE LIBRARY VERSION

http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/

"freetype2-master.tar.gz"

Commit date: "2017-10-12 18:48:57 +0800"
(i.e., approximately 8 days ago)




BASIC SEQUENCE OF FREETYPE LIBRARY FUNCTION CALLS

(Note: Full test code appears in a later section.)

Test Function:
{
    error    =    FT_Init_FreeType( &library );

    For each face_size in ppem (e.g., 1024, 2048, 4096),
    and for each load_flags value,
    and for each render_mode value:
    {
        For each font-face file name:
        {
            error  =  FT_New_Face( library, file_name, 0, &face );
       
            error  =  FT_Set_Pixel_Sizes( face, face_size, face_size );

            For each glyph_index in the font face:
            {
                error  =  FT_Load_Glyph( face, glyph_index, load_flags );

                error  =  FT_Render_Glyph( face->glyph, render_mode );
            }
        }
    }
}

For face_size=4096 (ppem), load_flags=FT_LOAD_DEFAULT, render_mode=FT_RENDER_MODE_NORMAL,
363 glyphs out of a collection of 736,482 glyphs found in 334 popular Windows font face
files and open-source font face files will cause FT_Render_Glyph(...) to return error
code "1".

A rough stack trace (starting from the parent first):

(1) main(...)                      [Test code file] 
(2) FT_Render_Glyph(...)           [freetype2\src\base\ftobjs.c] 
(3) FT_Render_Glyph_Internal(...)  [freetype2\src\base\ftobjs.c]
 
    // Calls:  renderer->render(renderer,slot,render_mode,NULL)

(4) ft_smooth_render_generic(...)  [freetype\src\smooth\ftsmooth.c]

    // Calls:  render->raster_render(render->raster,&params)

(5) gray_raster_render(...)        [freetype2\src\smooth\ftgrays.c]

There are deeper calls within "gray_raster_render(...)", but right now the library
code I'm testing was compiled with optimizations enabled, and so stepping through
the code with the debugger sometimes does not follow the order of statements in the
source code.  I need to disable optimizations before I can narrow down the
code which is ultimately causing an error to be returned.




COMPLETE SOURCE CODE FOR A TEST PROGRAM

The following code can be put in to a simple Windows "console-mode" application which
links to the FreeType library (e.g., freetype2\objs\freetype.lib) and specifies the
following additional include directory: [...]\freetype2\include\

This test program check various face_size values (in ppem) (1024, 2048, and 4096),
showing that no errors occur for 1024 ppem, and some errors happen for 2048 ppem,
and many more errors happen for 4096 ppem.

This test program checks several possible load_flags values
(a parameter to FT_Load_Glyph(...)):
    FT_LOAD_NO_HINTING,
    FT_LOAD_DEFAULT,      // Same as (FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL)
    (FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT),
    (FT_LOAD_DEFAULT | FT_LOAD_TARGET_MONO)

This test program checks several possible render_mode values
(a parameter to FT_Render_Glyph(...)):
    FT_RENDER_MODE_NORMAL,   FT_RENDER_MODE_LIGHT,   FT_RENDER_MODE_MONO.
No errors ever occur for FT_RENDER_MODE_MONO.

This program checks the following small set of font face files:
    "C:\\Windows\\fonts\\constan.ttf",
    "C:\\Windows\\fonts\\courbd.ttf",
    "C:\\Windows\\fonts\\msjh.ttf",
    "C:\\Windows\\fonts\\timesi.ttf",
    "C:\\Windows\\fonts\\webdings.ttf",
    "C:\\Windows\\fonts\\wingding.ttf",
(One or more of these might not be installed with a clean install of
Windows 7 Ultimate.  You can comment out any of these items to exclude it.)
These font face files were selected because I have observed the problem
for glyphs in each of these files.  The problem occurs for perhaps a
dozen of the 334 font face files installed on my PC.  But, keep in mind
that even across all 334 font face files I tested, the problem only
starts for a single non-popular font face file at 1127 ppem! 
Only when ppem is much higher, like 2048 or 4096 ppem, does the problem
start to appear in a few mainstream files for a notable number of glyphs.

If no error entries appear after a (font_size, load_flags, render_mode)
combination heading, then no errors occurred for that combination.

This program tests 3 *large* face sizes, 4 load-flag values, and 3 render-mode
vaues, and 6 font face files -- a total of 3*4*3*6 = 216 combinations.
And tests ALL glyphs in each file for that combination.
==> This program took approximately 55 MINUTES to execute on my PC.



#include <stdio.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include <freetype/ftbbox.h>

char * FileNames[] =
{
//"C:\\Windows\\fonts\\ARIALUNI.ttf",
"C:\\Windows\\fonts\\constan.ttf",
"C:\\Windows\\fonts\\courbd.ttf",
"C:\\Windows\\fonts\\msjh.ttf",
"C:\\Windows\\fonts\\timesi.ttf",
"C:\\Windows\\fonts\\webdings.ttf",
"C:\\Windows\\fonts\\wingding.ttf",
};

int main( int argc, char * argv[] )
{
    FT_Library        library;
    FT_Face           face;
    FT_Error          error              =  0;
    int               file_index         =  0;
    FT_UInt           glyph_index        =  0;
    char              glyph_name[ 256 ];
    int               i                  =  0;

    int               face_size_index    =  0;
    unsigned int      face_size          =  10;

    int               load_flags_index   =  0;
    FT_Int32          load_flags         =  FT_LOAD_DEFAULT;

    int               render_mode_index  =  0;
    FT_Render_Mode    render_mode        =  FT_RENDER_MODE_NORMAL;

    int               FaceSizes[]        =  { 1024, 2048, 4096 };
   
    FT_Int32 LoadFlags[] =
    {
        FT_LOAD_NO_HINTING,
        FT_LOAD_DEFAULT,      // Same as (FT_LOAD_DEFAULT | FT_LOAD_TARGET_NORMAL)
        (FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT),
        (FT_LOAD_DEFAULT | FT_LOAD_TARGET_MONO)
    };

    char * LoadFlagsNames[] =
    {
        "FT_LOAD_NO_HINTING",
        "FT_LOAD_DEFAULT",
        "(FT_LOAD_DEFAULT | FT_LOAD_TARGET_LIGHT)",
        "(FT_LOAD_DEFAULT | FT_LOAD_TARGET_MONO)"
    };

    FT_Render_Mode    RenderModes[]       = 
    {  FT_RENDER_MODE_NORMAL,   FT_RENDER_MODE_LIGHT,   FT_RENDER_MODE_MONO  };

    char *            RenderModesNames[]  = 
    { "FT_RENDER_MODE_NORMAL", "FT_RENDER_MODE_LIGHT", "FT_RENDER_MODE_MONO" };


    error    =    FT_Init_FreeType( &library );

    if ( error )
    {
        printf( "FT_Init_FreeType error = %d\n", error );
        return( 0 );
    }


    for ( face_size_index = 0; face_size_index < (int)(sizeof(FaceSizes)/sizeof(FaceSizes[0])); face_size_index++ )
    {
        face_size  =  FaceSizes[face_size_index];

        for ( load_flags_index = 0; load_flags_index < (int)(sizeof(LoadFlags)/sizeof(LoadFlags[0])); load_flags_index++ )
        {
            load_flags  =  LoadFlags[load_flags_index];

            for ( render_mode_index = 0; render_mode_index < (int)(sizeof(RenderModes)/sizeof(RenderModes[0])); render_mode_index++ )
            {
                render_mode  =  RenderModes[render_mode_index];

                printf( "TESTING face_size: %d, load_flags: %40s, render_mode: %s\n",
                    face_size, LoadFlagsNames[load_flags_index], RenderModesNames[render_mode_index] );

                for ( file_index = 0; file_index < (int)(sizeof(FileNames)/sizeof(FileNames[0])); file_index++ )
                {
                    error  =  FT_New_Face( library, FileNames[file_index], 0, &face );

                    if ( error )
                    {
                        printf( "FT_New_Face error=%d\n", error );
                        return( 0 );
                    }
       
                    error  =  FT_Set_Pixel_Sizes( face, face_size, face_size );

                    if ( error )
                    {
                        printf( "FT_Set_Pixel_Sizes error=%d\n", error );
                        return( 0 );
                    }

                    for ( glyph_index = 0; glyph_index < (int)((FT_UInt)face->num_glyphs); glyph_index++ )
                    {
                        error  =  FT_Load_Glyph( face, glyph_index, load_flags );

                        if ( error )
                        {
                            printf( "FT_Load_Glyph error=%d\n", error );
                            return( 0 );
                        }

                        error  =  FT_Render_Glyph( face->glyph, render_mode );

                        if ( error )
                        {
                            for ( i = 0; i < 256; i++ ) { glyph_name[i] = (char) 0; }
                            FT_Get_Glyph_Name( face, glyph_index, (FT_Pointer)(&(glyph_name[0])), (FT_UInt)256 );

                            printf( "FT_Render_Glyph error=%d : %30s (total glyphs:%5d) glyph_index=%5d %s\n",
                                error, FileNames[file_index], face->num_glyphs, glyph_index, glyph_name );

                            // Set breakpoint here to conveniently see the error happen again!
                            error = FT_Render_Glyph( face->glyph, render_mode );

                            continue;
                            return( 0 );
                        }
                    }
                }
            }
        }
    }

    return( 0 );
}




THOUGHTS ABOUT THE ERRORS

Observations:

(O1) The problem doesn't happen for any load or render parameters for 1126 ppem or lower.

(O2) The problem never happens for MONO rendering mode, even for 4096 ppem; only for
NORMAL (anti-aliased 8-bit bitmap) and LIGHT (variation similar to NORMAL).

Thus, all of the following assumes non-MONO rendering modes...

(O3) At 1127 ppem, the error first happens for a very small number of glyphs for
obscure fonts (not ones tested by the test code above).  For higher ppem values,
the error happens for gradually more glyphs for more fonts.  At 2048 ppem,
the error happens for some glyphs in mainstream fonts.  At 4096 ppem, the
error happens for even more glyphs -- but is still relatively rare
(e.g., 363 glyphs out of 736,482 glyphs in my collection!).

(O4) The glyphs which cause the errors are clearly not the widest or the tallest
glyphs contained in the same font face file.

(O5) The glyphs which cause the errors are clearly not unusually high complexity
compared to other glyphs in the same font face file.  In some cases, the
errors happen for very simple glyphs!

(O6) The glyphs which cause the errors do not seem to have unusual
positions relative to the EM box area (e.g., the glyphs seem relatively
contained in the EM box, while there are numerous other glyphs in the same
font face file which stray far outside the EM box).

(O7) Some problem cases had a few shared geometric characteristics:

    * A long line segment which is very close to being perfectly
    horizontal, but might be very slightly tilted (by a fraction of
    a pixel over a 1000-pixel span).

    * A long (~500-1000 pixel), nearly-flat quadratic curve, with a tiny
    deviation (1-2 pixels) from perfect flatness.

Conclusions:

(C1) Because of (O2): The problem is limited to non-MONO rendering modes;
i.e., affects gray-scale rendering only.

(C2) Because of (O4), (O5), and (O2): The problem does not seem to be
resource exhaustion problem caused by the size or complexity of the
glyphs.

(C3) Because of (O3), (O4), (O6): I don't think coordinate roundoff or
truncation (due to large coordinate values) is causing the problem.


MY CURRENT GUESS

I think that something about the slope of nearly-flat lines is breaking
part of the gray-scale anti-aliasing calculations in
"gray_raster_render(...)", or more specifically in "gray_convert_glyph(...)",
or some deeper function.




NEXT STEPS

I'm going to try to learn more about the specific causes of this
problem.  However, I have very little experience with this code, and so
I'm hoping someone familiar with the rasterization code can offer
feedback and maybe a fix.






reply via email to

[Prev in Thread] Current Thread [Next in Thread]