1 /* Copyright (C) 2005, 2007, 2008, 2013 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 /* modified by Werner Lemberg <wl@gnu.org>       */
29 /* This file is now part of the FreeType library */
30 
31 
32 #define _XOPEN_SOURCE 500 /* for `kill', `strdup', `random', and `srandom' */
33 
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <strings.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/wait.h>
42 #include <unistd.h>
43 #include <dirent.h>
44 #include <signal.h>
45 #include <time.h>
46 
47 #include <ft2build.h>
48 #include FT_FREETYPE_H
49 #include FT_OUTLINE_H
50 
51 #define true     1
52 #define false    0
53 #define forever  for (;;)
54 
55 
56   static int    check_outlines = false;
57   static int    nohints        = false;
58   static int    rasterize      = false;
59   static char*  results_dir    = "results";
60 
61 #define GOOD_FONTS_DIR  "/usr/local/share/fonts"
62 
63   static char*  default_dir_list[] =
64   {
65     GOOD_FONTS_DIR,
66     NULL
67   };
68 
69   static char*  default_ext_list[] =
70   {
71     "ttf",
72     "otf",
73     "ttc",
74     "cid",
75     "pfb",
76     "pfa",
77     "bdf",
78     "pcf",
79     "pfr",
80     "fon",
81     "otb",
82     "cff",
83     NULL
84   };
85 
86   static unsigned int  error_count    = 1;
87   static double        error_fraction = 0.0;
88 
89   static FT_F26Dot6  font_size = 12 * 64;
90 
91   static struct fontlist
92   {
93     char*         name;
94     long          len;
95     unsigned int  isbinary: 1;
96     unsigned int  isascii: 1;
97     unsigned int  ishex: 1;
98 
99   } *fontlist;
100 
101   static unsigned int  fcnt;
102 
103 
104   static int
FT_MoveTo(const FT_Vector * to,void * user)105   FT_MoveTo( const FT_Vector  *to,
106              void             *user )
107   {
108     FT_UNUSED( to );
109     FT_UNUSED( user );
110 
111     return 0;
112   }
113 
114 
115   static int
FT_LineTo(const FT_Vector * to,void * user)116   FT_LineTo( const FT_Vector  *to,
117              void             *user )
118   {
119     FT_UNUSED( to );
120     FT_UNUSED( user );
121 
122     return 0;
123   }
124 
125 
126   static int
FT_ConicTo(const FT_Vector * _cp,const FT_Vector * to,void * user)127   FT_ConicTo( const FT_Vector  *_cp,
128               const FT_Vector  *to,
129               void             *user )
130   {
131     FT_UNUSED( _cp );
132     FT_UNUSED( to );
133     FT_UNUSED( user );
134 
135     return 0;
136   }
137 
138 
139   static int
FT_CubicTo(const FT_Vector * cp1,const FT_Vector * cp2,const FT_Vector * to,void * user)140   FT_CubicTo( const FT_Vector  *cp1,
141               const FT_Vector  *cp2,
142               const FT_Vector  *to,
143               void             *user )
144   {
145     FT_UNUSED( cp1 );
146     FT_UNUSED( cp2 );
147     FT_UNUSED( to );
148     FT_UNUSED( user );
149 
150     return 0;
151   }
152 
153 
154   static FT_Outline_Funcs outlinefuncs =
155   {
156     FT_MoveTo,
157     FT_LineTo,
158     FT_ConicTo,
159     FT_CubicTo,
160     0, 0          /* No shift, no delta */
161   };
162 
163 
164   static void
TestFace(FT_Face face)165   TestFace( FT_Face  face )
166   {
167     unsigned int  gid;
168     int           load_flags = FT_LOAD_DEFAULT;
169 
170 
171     if ( check_outlines         &&
172          FT_IS_SCALABLE( face ) )
173       load_flags = FT_LOAD_NO_BITMAP;
174 
175     if ( nohints )
176       load_flags |= FT_LOAD_NO_HINTING;
177 
178     FT_Set_Char_Size( face, 0, font_size, 72, 72 );
179 
180     for ( gid = 0; gid < face->num_glyphs; gid++ )
181     {
182       if ( check_outlines         &&
183            FT_IS_SCALABLE( face ) )
184       {
185         if ( !FT_Load_Glyph( face, gid, load_flags ) )
186           FT_Outline_Decompose( &face->glyph->outline, &outlinefuncs, NULL );
187       }
188       else
189         FT_Load_Glyph( face, gid, load_flags );
190 
191       if ( rasterize )
192         FT_Render_Glyph( face->glyph, ft_render_mode_normal );
193     }
194 
195     FT_Done_Face( face );
196   }
197 
198 
199   static void
ExecuteTest(char * testfont)200   ExecuteTest( char*  testfont )
201   {
202     FT_Library  context;
203     FT_Face     face;
204 
205 
206     if ( FT_Init_FreeType( &context ) )
207     {
208       fprintf( stderr, "Can't initialize FreeType.\n" );
209       exit( 1 );
210     }
211 
212     if ( FT_New_Face( context, testfont, 0, &face ) )
213     {
214       /* The font is erroneous, so if this fails that's ok. */
215       exit( 0 );
216     }
217 
218     if ( face->num_faces == 1 )
219       TestFace( face );
220     else
221     {
222       long  i, num;
223 
224 
225       num = face->num_faces;
226       FT_Done_Face( face );
227 
228       for ( i = 0; i < num; i++ )
229       {
230         if ( !FT_New_Face( context, testfont, i, &face ) )
231           TestFace( face );
232       }
233     }
234 
235     FT_Done_FreeType( context );
236 
237     exit( 0 );
238   }
239 
240 
241   static int
extmatch(char * filename,char ** extensions)242   extmatch( char*   filename,
243             char**  extensions )
244   {
245     int    i;
246     char*  pt;
247 
248 
249     if ( !extensions )
250       return true;
251 
252     pt = strrchr( filename, '.' );
253     if ( !pt )
254       return false;
255     if ( pt < strrchr( filename, '/' ) )
256       return false;
257 
258     for ( i = 0; extensions[i] != NULL; i++ )
259       if ( strcasecmp( pt + 1, extensions[i] ) == 0 ||
260            strcasecmp( pt,     extensions[i] ) == 0 )
261         return true;
262 
263     return false;
264   }
265 
266 
267   static void
figurefiletype(struct fontlist * item)268   figurefiletype( struct fontlist*  item )
269   {
270     FILE*  foo;
271 
272 
273     item->isbinary = item->isascii = item->ishex = false;
274 
275     foo = fopen( item->name, "rb" );
276     if ( foo )
277     {
278       /* Try to guess the file type from the first few characters... */
279       int  ch1 = getc( foo );
280       int  ch2 = getc( foo );
281       int  ch3 = getc( foo );
282       int  ch4 = getc( foo );
283 
284 
285       fclose( foo );
286 
287       if ( ( ch1 == 0   && ch2 == 1   && ch3 == 0   && ch4 == 0   ) ||
288            ( ch1 == 'O' && ch2 == 'T' && ch3 == 'T' && ch4 == 'O' ) ||
289            ( ch1 == 't' && ch2 == 'r' && ch3 == 'u' && ch4 == 'e' ) ||
290            ( ch1 == 't' && ch2 == 't' && ch3 == 'c' && ch4 == 'f' ) )
291       {
292         /* ttf, otf, ttc files */
293         item->isbinary = true;
294       }
295       else if ( ch1 == 0x80 && ch2 == '\01' )
296       {
297         /* PFB header */
298         item->isbinary = true;
299       }
300       else if ( ch1 == '%' && ch2 == '!' )
301       {
302         /* Random PostScript */
303         if ( strstr( item->name, ".pfa" ) ||
304              strstr( item->name, ".PFA" ) )
305           item->ishex = true;
306         else
307           item->isascii = true;
308       }
309       else if ( ch1 == 1 && ch2 == 0 && ch3 == 4 )
310       {
311         /* Bare CFF */
312         item->isbinary = true;
313       }
314       else if ( ch1 == 'S' && ch2 == 'T' && ch3 == 'A' && ch4 == 'R' )
315       {
316         /* BDF */
317         item->ishex = true;
318       }
319       else if ( ch1 == 'P' && ch2 == 'F' && ch3 == 'R' && ch4 == '0' )
320       {
321         /* PFR */
322         item->isbinary = true;
323       }
324       else if ( ( ch1 == '\1' && ch2 == 'f' && ch3 == 'c' && ch4 == 'p' ) ||
325                 ( ch1 == 'M'  && ch2 == 'Z' )                             )
326       {
327         /* Windows FON */
328         item->isbinary = true;
329       }
330       else
331       {
332         fprintf( stderr,
333                  "Can't recognize file type of `%s', assuming binary\n",
334                  item->name );
335         item->isbinary = true;
336       }
337     }
338     else
339     {
340       fprintf( stderr, "Can't open `%s' for typing the file.\n",
341                item->name );
342       item->isbinary = true;
343     }
344   }
345 
346 
347   static void
FindFonts(char ** fontdirs,char ** extensions)348   FindFonts( char**  fontdirs,
349              char**  extensions )
350   {
351     int           i;
352     unsigned int  max;
353     char          buffer[1025];
354     struct stat   statb;
355 
356 
357     max  = 0;
358     fcnt = 0;
359 
360     for ( i = 0; fontdirs[i] != NULL; i++ )
361     {
362       DIR*            examples;
363       struct dirent*  ent;
364 
365 
366       examples = opendir( fontdirs[i] );
367       if ( !examples )
368       {
369         fprintf( stderr,
370                  "Can't open example font directory `%s'\n",
371                  fontdirs[i] );
372         exit( 1 );
373       }
374 
375       while ( ( ent = readdir( examples ) ) != NULL )
376       {
377         snprintf( buffer, sizeof ( buffer ),
378                   "%s/%s", fontdirs[i], ent->d_name );
379         if ( stat( buffer, &statb ) == -1 || S_ISDIR( statb.st_mode ) )
380           continue;
381         if ( !extensions || extmatch( buffer, extensions ) )
382         {
383           if ( fcnt >= max )
384           {
385             max += 100;
386             fontlist = realloc( fontlist, max * sizeof ( struct fontlist ) );
387             if ( !fontlist )
388             {
389               fprintf( stderr, "Can't allocate memory\n" );
390               exit( 1 );
391             }
392           }
393 
394           fontlist[fcnt].name = strdup( buffer );
395           fontlist[fcnt].len  = statb.st_size;
396 
397           figurefiletype( &fontlist[fcnt] );
398           fcnt++;
399         }
400       }
401 
402       closedir( examples );
403     }
404 
405     if ( fcnt == 0 )
406     {
407       fprintf( stderr, "Can't find matching font files.\n" );
408       exit( 1 );
409     }
410 
411     fontlist[fcnt].name = NULL;
412   }
413 
414 
415   static unsigned int
getErrorCnt(struct fontlist * item)416   getErrorCnt( struct fontlist*  item )
417   {
418     if ( error_count == 0 && error_fraction == 0.0 )
419       return 0;
420 
421     return error_count + (unsigned int)( error_fraction * item->len );
422   }
423 
424 
425   static int
getRandom(int low,int high)426   getRandom( int  low,
427              int  high )
428   {
429     if ( low - high < 0x10000L )
430       return low + ( ( random() >> 8 ) % ( high + 1 - low ) );
431 
432     return low + ( random() % ( high + 1 - low ) );
433   }
434 
435 
436   static int
copyfont(struct fontlist * item,char * newfont)437   copyfont( struct fontlist*  item,
438             char*             newfont )
439   {
440     static char   buffer[8096];
441     FILE          *good, *newf;
442     size_t        len;
443     unsigned int  i, err_cnt;
444 
445 
446     good = fopen( item->name, "r" );
447     if ( !good )
448     {
449       fprintf( stderr, "Can't open `%s'\n", item->name );
450       return false;
451     }
452 
453     newf = fopen( newfont, "w+" );
454     if ( !newf )
455     {
456       fprintf( stderr, "Can't create temporary output file `%s'\n",
457                newfont );
458       exit( 1 );
459     }
460 
461     while ( ( len = fread( buffer, 1, sizeof ( buffer ), good ) ) > 0 )
462       fwrite( buffer, 1, len, newf );
463 
464     fclose( good );
465 
466     err_cnt = getErrorCnt( item );
467     for ( i = 0; i < err_cnt; i++ )
468     {
469       fseek( newf, getRandom( 0, (int)( item->len - 1 ) ), SEEK_SET );
470 
471       if ( item->isbinary )
472         putc( getRandom( 0, 0xFF ), newf );
473       else if ( item->isascii )
474         putc( getRandom( 0x20, 0x7E ), newf );
475       else
476       {
477         int  hex = getRandom( 0, 15 );
478 
479 
480         if ( hex < 10 )
481           hex += '0';
482         else
483           hex += 'A' - 10;
484 
485         putc( hex, newf );
486       }
487     }
488 
489     if ( ferror( newf ) )
490     {
491       fclose( newf );
492       unlink( newfont );
493       return false;
494     }
495 
496     fclose( newf );
497 
498     return true;
499   }
500 
501 
502   static int  child_pid;
503 
504   static void
abort_test(int sig)505   abort_test( int  sig )
506   {
507     FT_UNUSED( sig );
508 
509     /* If a time-out happens, then kill the child */
510     kill( child_pid, SIGFPE );
511     write( 2, "Timeout... ", 11 );
512   }
513 
514 
515   static void
do_test(void)516   do_test( void )
517   {
518     int         i        = getRandom( 0, (int)( fcnt - 1 ) );
519     static int  test_num = 0;
520     char        buffer[1024];
521 
522 
523     sprintf( buffer, "%s/test%d", results_dir, test_num++ );
524 
525     if ( copyfont ( &fontlist[i], buffer ) )
526     {
527       signal( SIGALRM, abort_test );
528       /* Anything that takes more than 20 seconds */
529       /* to parse and/or rasterize is an error.   */
530       alarm( 20 );
531       if ( ( child_pid = fork() ) == 0 )
532         ExecuteTest( buffer );
533       else if ( child_pid != -1 )
534       {
535         int  status;
536 
537 
538         waitpid( child_pid, &status, 0 );
539         alarm( 0 );
540         if ( WIFSIGNALED ( status ) )
541           printf( "Error found in file `%s'\n", buffer );
542         else
543           unlink( buffer );
544       }
545       else
546       {
547         fprintf( stderr, "Can't fork test case.\n" );
548         exit( 1 );
549       }
550       alarm( 0 );
551     }
552   }
553 
554 
555   static void
usage(FILE * out,char * name)556   usage( FILE*  out,
557          char*  name )
558   {
559     char**  d = default_dir_list;
560     char**  e = default_ext_list;
561 
562 
563     fprintf( out, "%s [options] -- Generate random erroneous fonts\n"
564                   "  and attempt to parse them with FreeType.\n\n", name );
565 
566     fprintf( out, "  --all                    All non-directory files are assumed to be fonts.\n" );
567     fprintf( out, "  --check-outlines         Make sure we can parse the outlines of each glyph.\n" );
568     fprintf( out, "  --dir <path>             Append <path> to list of font search directories\n"
569                   "                           (no recursive search).\n" );
570     fprintf( out, "  --error-count <cnt>      Introduce <cnt> single byte errors into each font\n"
571                   "                           (default: 1)\n" );
572     fprintf( out, "  --error-fraction <frac>  Introduce <frac>*filesize single byte errors\n"
573                   "                           into each font (default: 0.0).\n" );
574     fprintf( out, "  --ext <ext>              Add <ext> to list of extensions indicating fonts.\n" );
575     fprintf( out, "  --help                   Print this.\n" );
576     fprintf( out, "  --nohints                Turn off hinting.\n" );
577     fprintf( out, "  --rasterize              Attempt to rasterize each glyph.\n" );
578     fprintf( out, "  --results <path>         Place the created test fonts into <path>\n"
579                   "                           (default: `results')\n" );
580     fprintf( out, "  --size <float>           Use the given font size for the tests.\n" );
581     fprintf( out, "  --test <file>            Run a single test on an already existing file.\n" );
582     fprintf( out, "\n" );
583 
584     fprintf( out, "Default font extensions:\n" );
585     fprintf( out, " " );
586     while ( *e )
587       fprintf( out, " .%s", *e++ );
588     fprintf( out, "\n" );
589 
590     fprintf( out, "Default font directories:\n" );
591     fprintf( out, " " );
592     while ( *d )
593       fprintf( out, " %s", *d++ );
594     fprintf( out, "\n" );
595   }
596 
597 
598   int
main(int argc,char ** argv)599   main( int     argc,
600         char**  argv )
601   {
602     char    **dirs, **exts;
603     int     dcnt = 0, ecnt = 0, rset = false, allexts = false;
604     int     i;
605     time_t  now;
606     char*   testfile = NULL;
607 
608 
609     dirs = calloc( (size_t)( argc + 1 ), sizeof ( char ** ) );
610     exts = calloc( (size_t)( argc + 1 ), sizeof ( char ** ) );
611 
612     for ( i = 1; i < argc; i++ )
613     {
614       char*  pt = argv[i];
615       char*  end;
616 
617 
618       if ( pt[0] == '-' && pt[1] == '-' )
619         pt++;
620 
621       if ( strcmp( pt, "-all" ) == 0 )
622         allexts = true;
623       else if ( strcmp( pt, "-check-outlines" ) == 0 )
624         check_outlines = true;
625       else if ( strcmp( pt, "-dir" ) == 0 )
626         dirs[dcnt++] = argv[++i];
627       else if ( strcmp( pt, "-error-count" ) == 0 )
628       {
629         if ( !rset )
630           error_fraction = 0.0;
631         rset = true;
632         error_count = (unsigned int)strtoul( argv[++i], &end, 10 );
633         if ( *end != '\0' )
634         {
635           fprintf( stderr, "Bad value for error-count: %s\n", argv[i] );
636           exit( 1 );
637         }
638       }
639       else if ( strcmp( pt, "-error-fraction" ) == 0 )
640       {
641         if ( !rset )
642           error_count = 0;
643         rset = true;
644         error_fraction = strtod( argv[++i], &end );
645         if ( *end != '\0' )
646         {
647           fprintf( stderr, "Bad value for error-fraction: %s\n", argv[i] );
648           exit( 1 );
649         }
650         if ( error_fraction < 0.0 || error_fraction > 1.0 )
651         {
652           fprintf( stderr, "error-fraction must be in the range [0;1]\n" );
653           exit( 1 );
654         }
655       }
656       else if ( strcmp( pt, "-ext" ) == 0 )
657         exts[ecnt++] = argv[++i];
658       else if ( strcmp( pt, "-help" ) == 0 )
659       {
660         usage( stdout, argv[0] );
661         exit( 0 );
662       }
663       else if ( strcmp( pt, "-nohints" ) == 0 )
664         nohints = true;
665       else if ( strcmp( pt, "-rasterize" ) == 0 )
666         rasterize = true;
667       else if ( strcmp( pt, "-results" ) == 0 )
668         results_dir = argv[++i];
669       else if ( strcmp( pt, "-size" ) == 0 )
670       {
671         font_size = (FT_F26Dot6)( strtod( argv[++i], &end ) * 64 );
672         if ( *end != '\0' || font_size < 64 )
673         {
674           fprintf( stderr, "Bad value for size: %s\n", argv[i] );
675           exit( 1 );
676         }
677       }
678       else if ( strcmp( pt, "-test" ) == 0 )
679         testfile = argv[++i];
680       else
681       {
682         usage( stderr, argv[0] );
683         exit( 1 );
684       }
685     }
686 
687     if ( allexts )
688     {
689       free( exts );
690       exts = NULL;
691     }
692     else if ( ecnt == 0 )
693     {
694       free( exts );
695       exts = default_ext_list;
696     }
697 
698     if ( dcnt == 0 )
699     {
700       free( dirs );
701       dirs = default_dir_list;
702     }
703 
704     if ( testfile )
705       ExecuteTest( testfile );         /* This should never return */
706 
707     time( &now );
708     srandom( (unsigned int)now );
709 
710     FindFonts( dirs, exts );
711     mkdir( results_dir, 0755 );
712 
713     forever
714       do_test();
715 
716     return 0;
717   }
718 
719 
720 /* EOF */
721