code:
ftrandom
--------

This program expects a set of directories containing good fonts, and a set
of extensions of fonts to be tested.  It will randomly pick a font, copy it,
introduce and error and then test it.

The FreeType tests are quite basic:

  For each erroneous font it
    forks off a new tester;
    initializes the library;
    opens each font in the file;
    loads each glyph;
      (optionally reviewing the contours of the glyph)
      (optionally rasterizing)
    closes the face.

If the tester exits with a signal, or takes longer than 20 seconds then
ftrandom saves the erroneous font and continues.  If the tester exits
normally or with an error, then the superstructure removes the test font and
continues.

Arguments are:

  --all                    Test every font in the directory(ies) no matter
                           what its extension (some CID-keyed fonts have no
                           extension).
  --check-outlines         Call FT_Outline_Decompose on each glyph.
  --dir <dir>              Append <dir> to the list of directories to search
                           for good fonts.
  --error-count <cnt>      Introduce <cnt> single-byte errors into the
                           erroneous fonts.
  --error-fraction <frac>  Multiply the file size of the font by <frac> and
                           introduce that many errors into the erroneous
                           font file.
  --ext <ext>              Add <ext> to the set of font types tested.  Known
                           extensions are `ttf', `otf', `ttc', `cid', `pfb',
                           `pfa', `bdf', `pcf', `pfr', `fon', `otb', and
                           `cff'.
  --help                   Print out this list of options.
  --nohints                Specify FT_LOAD_NO_HINTING when loading glyphs.
  --rasterize              Call FT_Render_Glyph as well as loading it.
  --result <dir>           This is the directory in which test files are
                           placed.
  --test <file>            Run a single test on a pre-generated testcase.
                           Done in the current process so it can be debugged
                           more easily.


code:
/* Copyright (C) 2005, 2007, 2008 by George Williams */
/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.

 * The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* modified by Werner Lemberg <wl@gnu.org>       */
/* This file is now part of the FreeType library */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dirent.h>
#include <math.h>
#include <signal.h>
#include <time.h>

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

#define true     1
#define false    0
#define forever  for (;;)

  static int    check_outlines = false;
  static int    nohints        = false;
  static int    rasterize      = false;
  static char*  results_dir    = "results";

#define GOOD_FONTS_DIR  "/home/wl/freetype-testfonts"

  static char*  default_dir_list[] =
  {
    GOOD_FONTS_DIR,
    NULL
  };

  static char*  default_ext_list[] =
  {
    "ttf",
    "otf",
    "ttc",
    "cid",
    "pfb",
    "pfa",
    "bdf",
    "pcf",
    "pfr",
    "fon",
    "otb",
    "cff",
    NULL
  };

  static int  error_count    = 1;
  static int  error_fraction = 0;

  static FT_F26Dot6  font_size = 12 * 64;

  static struct fontlist
  {
    char*         name;
    int           len;
    unsigned int  isbinary: 1;
    unsigned int  isascii: 1;
    unsigned int  ishex: 1;

  } *fontlist;

  static int  fcnt;

  static int
  FT_MoveTo( const FT_Vector  *to,
             void             *user )
  {
    return 0;
  }

  static int
  FT_LineTo( const FT_Vector  *to,
             void             *user )
  {
    return 0;
  }

  static int
  FT_ConicTo( const FT_Vector  *_cp,
              const FT_Vector  *to,
              void             *user )
  {
    return 0;
  }

  static int
  FT_CubicTo( const FT_Vector  *cp1,
              const FT_Vector  *cp2,
              const FT_Vector  *to,
              void             *user )
  {
    return 0;
  }

  static FT_Outline_Funcs outlinefuncs =
  {
    FT_MoveTo,
    FT_LineTo,
    FT_ConicTo,
    FT_CubicTo,
    0, 0          /* No shift, no delta */
  };

  static void
  TestFace( FT_Face  face )
  {
    int  gid;
    int  load_flags = FT_LOAD_DEFAULT;

    if ( check_outlines         &&
         FT_IS_SCALABLE( face ) )
      load_flags = FT_LOAD_NO_BITMAP;

    if ( nohints )
      load_flags |= FT_LOAD_NO_HINTING;

    FT_Set_Char_Size( face, 0, font_size, 72, 72 );

    for ( gid = 0; gid < face->num_glyphs; ++gid )
    {
      if ( check_outlines         &&
           FT_IS_SCALABLE( face ) )
      {
        if ( !FT_Load_Glyph( face, gid, load_flags ) )
          FT_Outline_Decompose( &face->glyph->outline, &outlinefuncs, NULL );
      }
      else
        FT_Load_Glyph( face, gid, load_flags );

      if ( rasterize )
        FT_Render_Glyph( face->glyph, ft_render_mode_normal );
    }

    FT_Done_Face( face );
  }

  static void
  ExecuteTest( char*  testfont )
  {
    FT_Library  context;
    FT_Face     face;
    int         i, num;

    if ( FT_Init_FreeType( &context ) )
    {
      fprintf( stderr, "Can't initialize FreeType.\n" );
      exit( 1 );
    }

    if ( FT_New_Face( context, testfont, 0, &face ) )
    {
      /* The font is erroneous, so if this fails that's ok. */
      exit( 0 );
    }

    if ( face->num_faces == 1 )
      TestFace( face );
    else
    {
      num = face->num_faces;
      FT_Done_Face( face );

      for ( i = 0; i < num; ++i )
      {
        if ( !FT_New_Face( context, testfont, i, &face ) )
          TestFace( face );
      }
    }

    exit( 0 );
  }

  static int
  extmatch( char*   filename,
            char**  extensions )
  {
    int    i;
    char*  pt;

    if ( extensions == NULL )
      return true;

    pt = strrchr( filename, '.' );
    if ( pt == NULL )
      return false;
    if ( pt < strrchr( filename, '/' ) )
      return false;

    for ( i = 0; extensions[i] != NULL; ++i )
      if ( strcasecmp( pt + 1, extensions[i] ) == 0 ||
           strcasecmp( pt,     extensions[i] ) == 0 )
        return true;

    return false;
  }

  static void
  figurefiletype( struct fontlist*  item )
  {
    FILE*  foo;

    item->isbinary = item->isascii = item->ishex = false;

    foo = fopen( item->name, "rb" );
    if ( foo != NULL )
    {
      /* Try to guess the file type from the first few characters... */
      int  ch1 = getc( foo );
      int  ch2 = getc( foo );
      int  ch3 = getc( foo );
      int  ch4 = getc( foo );

      fclose( foo );

      if ( ( ch1 == 0   && ch2 == 1   && ch3 == 0   && ch4 == 0   ) ||
           ( ch1 == 'O' && ch2 == 'T' && ch3 == 'T' && ch4 == 'O' ) ||
           ( ch1 == 't' && ch2 == 'r' && ch3 == 'u' && ch4 == 'e' ) ||
           ( ch1 == 't' && ch2 == 't' && ch3 == 'c' && ch4 == 'f' ) )
      {
        /* ttf, otf, ttc files */
        item->isbinary = true;
      }
      else if ( ch1 == 0x80 && ch2 == '\01' )
      {
        /* PFB header */
        item->isbinary = true;
      }
      else if ( ch1 == '%' && ch2 == '!' )
      {
        /* Random PostScript */
        if ( strstr( item->name, ".pfa" ) != NULL ||
             strstr( item->name, ".PFA" ) != NULL )
          item->ishex = true;
        else
          item->isascii = true;
      }
      else if ( ch1 == 1 && ch2 == 0 && ch3 == 4 )
      {
        /* Bare CFF */
        item->isbinary = true;
      }
      else if ( ch1 == 'S' && ch2 == 'T' && ch3 == 'A' && ch4 == 'R' )
      {
        /* BDF */
        item->ishex = true;
      }
      else if ( ch1 == 'P' && ch2 == 'F' && ch3 == 'R' && ch4 == '0' )
      {
        /* PFR */
        item->isbinary = true;
      }
      else if ( ( ch1 == '\1' && ch2 == 'f' && ch3 == 'c' && ch4 == 'p' ) ||
                ( ch1 == 'M'  && ch2 == 'Z' )                             )
      {
        /* Windows FON */
        item->isbinary = true;
      }
      else
      {
        fprintf( stderr,
                 "Can't recognize file type of `%s', assuming binary\n",
                 item->name );
        item->isbinary = true;
      }
    }
    else
    {
      fprintf( stderr, "Can't open `%s' for typing the file.\n",
               item->name );
      item->isbinary = true;
    }
  }

  static void
  FindFonts( char**  fontdirs,
             char**  extensions )
  {
    DIR*            examples;
    struct dirent*  ent;

    int             i, max;
    char            buffer[1025];
    struct stat     statb;

    max  = 0;
    fcnt = 0;

    for ( i = 0; fontdirs[i] != NULL; ++i )
    {
      examples = opendir( fontdirs[i] );
      if ( examples == NULL )
      {
        fprintf( stderr,
                 "Can't open example font directory `%s'\n",
                 fontdirs[i] );
        exit( 1 );
      }

      while ( ( ent = readdir( examples ) ) != NULL )
      {
        snprintf( buffer, sizeof ( buffer ),
                  "%s/%s", fontdirs[i], ent->d_name );
        if ( stat( buffer, &statb ) == -1 || S_ISDIR( statb.st_mode ) )
          continue;
        if ( extensions == NULL || extmatch( buffer, extensions ) )
        {
          if ( fcnt >= max )
          {
            max += 100;
            fontlist = realloc( fontlist, max * sizeof ( struct fontlist ) );
            if ( fontlist == NULL )
            {
              fprintf( stderr, "Can't allocate memory\n" );
              exit( 1 );
            }
          }

          fontlist[fcnt].name = strdup( buffer );
          fontlist[fcnt].len  = statb.st_size;

          figurefiletype( &fontlist[fcnt] );
          ++fcnt;
        }
      }

      closedir( examples );
    }

    if ( fcnt == 0 )
    {
      fprintf( stderr, "Can't find matching font files.\n" );
      exit( 1 );
    }

    fontlist[fcnt].name = NULL;
  }

  static int
  getErrorCnt( struct fontlist*  item )
  {
    if ( error_count == 0 && error_fraction == 0 )
      return 0;

    return error_count + ceil( error_fraction * item->len );
  }

  static int
  getRandom( int  low,
             int  high )
  {
    if ( low - high < 0x10000L )
      return low + ( ( random() >> 8 ) % ( high + 1 - low ) );

    return low + ( random() % ( high + 1 - low ) );
  }

  static int
  copyfont( struct fontlist*  item,
            char*             newfont )
  {
    static char  buffer[8096];
    FILE         *good, *new;
    int          len;
    int          i, err_cnt;

    good = fopen( item->name, "r" );
    if ( good == NULL )
    {
      fprintf( stderr, "Can't open `%s'\n", item->name );
      return false;
    }

    new = fopen( newfont, "w+" );
    if ( new == NULL )
    {
      fprintf( stderr, "Can't create temporary output file `%s'\n",
               newfont );
      exit( 1 );
    }

    while ( ( len = fread( buffer, 1, sizeof ( buffer ), good ) ) > 0 )
      fwrite( buffer, 1, len, new );

    fclose( good );

    err_cnt = getErrorCnt( item );
    for ( i = 0; i < err_cnt; ++i )
    {
      fseek( new, getRandom( 0, item->len - 1 ), SEEK_SET );

      if ( item->isbinary )
        putc( getRandom( 0, 0xff ), new );
      else if ( item->isascii )
        putc( getRandom( 0x20, 0x7e ), new );
      else
      {
        int  hex = getRandom( 0, 15 );

        if ( hex < 10 )
          hex += '0';
        else
          hex += 'A' - 10;

        putc( hex, new );
      }
    }

    if ( ferror( new ) )
    {
      fclose( new );
      unlink( newfont );
      return false;
    }

    fclose( new );

    return true;
  }

  static int  child_pid;

  static void
  abort_test( int  sig )
  {
    /* If a time-out happens, then kill the child */
    kill( child_pid, SIGFPE );
    write( 2, "Timeout... ", 11 );
  }

  static void
  do_test( void )
  {
    int         i        = getRandom( 0, fcnt - 1 );
    static int  test_num = 0;
    char        buffer[1024];

    sprintf( buffer, "%s/test%d", results_dir, test_num++ );

    if ( copyfont ( &fontlist[i], buffer ) )
    {
      signal( SIGALRM, abort_test );
      /* Anything that takes more than 20 seconds */
      /* to parse and/or rasterize is an error.   */
      alarm( 20 );
      if ( ( child_pid = fork() ) == 0 )
        ExecuteTest( buffer );
      else if ( child_pid != -1 )
      {
        int  status;

        waitpid( child_pid, &status, 0 );
        alarm( 0 );
        if ( WIFSIGNALED ( status ) )
          printf( "Error found in file `%s'\n", buffer );
        else
          unlink( buffer );
      }
      else
      {
        fprintf( stderr, "Can't fork test case.\n" );
        exit( 1 );
      }
      alarm( 0 );
    }
  }

  static void
  usage( FILE*  out,
         char*  name )
  {
    fprintf( out, "%s [options] -- Generate random erroneous fonts\n"
                  "  and attempt to parse them with FreeType.\n\n", name );

    fprintf( out, "  --all                    All non-directory files are assumed to be fonts.\n" );
    fprintf( out, "  --check-outlines         Make sure we can parse the outlines of each glyph.\n" );
    fprintf( out, "  --dir <path>             Append <path> to list of font search directories.\n" );
    fprintf( out, "  --error-count <cnt>      Introduce <cnt> single byte errors into each font.\n" );
    fprintf( out, "  --error-fraction <frac>  Introduce <frac>*filesize single byte errors\n"
                  "                           into each font.\n" );
    fprintf( out, "  --ext <ext>              Add <ext> to list of extensions indicating fonts.\n" );
    fprintf( out, "  --help                   Print this.\n" );
    fprintf( out, "  --nohints                Turn off hinting.\n" );
    fprintf( out, "  --rasterize              Attempt to rasterize each glyph.\n" );
    fprintf( out, "  --results <dir>          Directory in which to place the test fonts.\n" );
    fprintf( out, "  --size <float>           Use the given font size for the tests.\n" );
    fprintf( out, "  --test <file>            Run a single test on an already existing file.\n" );
  }

  int
  main( int     argc,
        char**  argv )
  {
    char    **dirs, **exts;
    char    *pt, *end;
    int     dcnt = 0, ecnt = 0, rset = false, allexts = false;
    int     i;
    time_t  now;
    char*   testfile = NULL;

    dirs = calloc( argc + 1, sizeof ( char ** ) );
    exts = calloc( argc + 1, sizeof ( char ** ) );

    for ( i = 1; i < argc; ++i )
    {
      pt = argv[i];
      if ( pt[0] == '-' && pt[1] == '-' )
        ++pt;

      if ( strcmp( pt, "-all" ) == 0 )
        allexts = true;
      else if ( strcmp( pt, "-check-outlines" ) == 0 )
        check_outlines = true;
      else if ( strcmp( pt, "-dir" ) == 0 )
        dirs[dcnt++] = argv[++i];
      else if ( strcmp( pt, "-error-count" ) == 0 )
      {
        if ( !rset )
          error_fraction = 0;
        rset = true;
        error_count = strtol( argv[++i], &end, 10 );
        if ( *end != '\0' )
        {
          fprintf( stderr, "Bad value for error-count: %s\n", argv[i] );
          exit( 1 );
        }
      }
      else if ( strcmp( pt, "-error-fraction" ) == 0 )
      {
        if ( !rset )
          error_count = 0;
        rset = true;
        error_fraction = strtod( argv[++i], &end );
        if ( *end != '\0' )
        {
          fprintf( stderr, "Bad value for error-fraction: %s\n", argv[i] );
          exit( 1 );
        }
      }
      else if ( strcmp( pt, "-ext" ) == 0 )
        exts[ecnt++] = argv[++i];
      else if ( strcmp( pt, "-help" ) == 0 )
      {
        usage( stdout, argv[0] );
        exit( 0 );
      }
      else if ( strcmp( pt, "-nohints" ) == 0 )
        nohints = true;
      else if ( strcmp( pt, "-rasterize" ) == 0 )
        rasterize = true;
      else if ( strcmp( pt, "-results" ) == 0 )
        results_dir = argv[++i];
      else if ( strcmp( pt, "-size" ) == 0 )
      {
        font_size = (FT_F26Dot6)( strtod( argv[++i], &end ) * 64 );
        if ( *end != '\0' || font_size < 64 )
        {
          fprintf( stderr, "Bad value for size: %s\n", argv[i] );
          exit( 1 );
        }
      }
      else if ( strcmp( pt, "-test" ) == 0 )
        testfile = argv[++i];
      else
      {
        usage( stderr, argv[0] );
        exit( 1 );
      }
    }

    if ( allexts )
      exts = NULL;
    else if ( ecnt == 0 )
      exts = default_ext_list;

    if ( dcnt == 0 )
      dirs = default_dir_list;

    if ( testfile != NULL )
      ExecuteTest( testfile );         /* This should never return */

    time( &now );
    srandom( now );

    FindFonts( dirs, exts );
    mkdir( results_dir, 0755 );

    forever
      do_test();

    return 0;
  }

/* EOF */

license: mit