[rt #73359] draw non-aa text in alpha combining mode for FT1
[imager.git] / fontft1.c
1 #include "imager.h"
2 #include "imrender.h"
3
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <fcntl.h>
7
8 #include <stdio.h>
9 #include <stdlib.h>
10
11
12 /*
13 =head1 NAME
14
15 fontft1.c - Freetype 1.x font driver for Imager
16
17 =head1 SYNOPSIS
18
19   handle = i_tt_new(path_to_ttf);
20   rc = i_tt_bbox(handle, points, "foo", 3, int cords[6], utf8);
21   i_tt_destroy(handle);
22
23   // and much more
24
25 =head1 DESCRIPTION
26
27 fontft1.c implements font creation, rendering, bounding box functions and
28 more for Imager using Freetype 1.x.
29
30 In general this driver should be ignored in favour of the FT2 driver.
31
32 =head1 FUNCTION REFERENCE
33
34 Some of these functions are internal.
35
36 =over
37
38 =cut
39
40 */
41
42
43 /* Truetype font support */
44 /* These are enabled by default when configuring Freetype 1.x
45    I haven't a clue how to reliably detect it at compile time.
46
47    We need a compilation probe in Makefile.PL
48 */
49 #define FTXPOST 1
50 #define FTXERR18 1
51
52 #include <freetype.h>
53 #define TT_CHC 5
54
55 #ifdef FTXPOST
56 #include <ftxpost.h>
57 #endif
58
59 #ifdef FTXERR18
60 #include <ftxerr18.h>
61 #endif
62
63 /* some versions of FT1.x don't seem to define this - it's font defined
64    so it won't change */
65 #ifndef TT_MS_LANGID_ENGLISH_GENERAL
66 #define TT_MS_LANGID_ENGLISH_GENERAL 0x0409
67 #endif
68
69 static im_slot_t slot = -1;
70
71 /* convert a code point into an index in the glyph cache */
72 #define TT_HASH(x) ((x) & 0xFF)
73
74 typedef struct {
75   int initialized;
76   TT_Engine engine;
77 } i_tt_engine;
78
79 typedef struct i_glyph_entry_ {
80   TT_Glyph glyph;
81   unsigned long ch;
82 } i_tt_glyph_entry;
83
84 #define TT_NOCHAR (~0UL)
85
86 struct TT_Instancehandle_ {
87   TT_Instance instance;
88   TT_Instance_Metrics imetrics;
89   TT_Glyph_Metrics gmetrics[256];
90   i_tt_glyph_entry glyphs[256];
91   int smooth;
92   int order;
93   i_img_dim ptsize;
94 };
95
96 typedef struct TT_Instancehandle_ TT_Instancehandle;
97
98 struct TT_Fonthandle_ {
99   TT_Face face;
100   TT_Face_Properties properties;
101   TT_Instancehandle instanceh[TT_CHC];
102   TT_CharMap char_map;
103 #ifdef FTXPOST
104   int loaded_names;
105   TT_Error load_cond;
106 #endif
107 };
108
109 /* Defines */
110
111 #define USTRCT(x) ((x).z)
112 #define TT_VALID( handle )  ( ( handle ).z != NULL )
113
114 static void i_tt_push_error(TT_Error rc);
115 static void i_tt_uninit(void *);
116
117 /* Prototypes */
118
119 static  int i_tt_get_instance( TT_Fonthandle *handle, i_img_dim points, int smooth );
120 static void i_tt_init_raster_map( TT_Raster_Map* bit, i_img_dim width, i_img_dim height, int smooth );
121 static void i_tt_done_raster_map( TT_Raster_Map *bit );
122 static void i_tt_clear_raster_map( TT_Raster_Map* bit );
123 static void i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,i_img_dim x_off, i_img_dim y_off );
124 static  int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j );
125 static void 
126 i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, 
127                    TT_Raster_Map *bit, TT_Raster_Map *small_bit, 
128                    i_img_dim x_off, i_img_dim y_off, int smooth );
129 static int
130 i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, 
131                         TT_Raster_Map *small_bit, i_img_dim cords[6], 
132                         char const* txt, size_t len, int smooth, int utf8 );
133 static void i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, const i_color *cl, int smooth );
134 static void i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, int channel, int smooth );
135 static  int
136 i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, i_img_dim cords[6], 
137                 double points, char const* txt, size_t len, int smooth, int utf8 );
138 static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, size_t len, i_img_dim cords[6], int utf8 );
139
140
141 /* static globals needed */
142
143 static int  LTT_dpi    = 72; /* FIXME: this ought to be a part of the call interface */
144 static int  LTT_hinted = 1;  /* FIXME: this too */
145
146
147 /*
148  * FreeType interface
149  */
150
151 void
152 i_tt_start(void) {
153   if (slot == -1)
154     slot = im_context_slot_new(i_tt_uninit);
155 }
156
157
158 /*
159 =item init_tt()
160
161 Initializes the freetype font rendering engine (if needed)
162
163 =cut
164 */
165
166 static i_tt_engine *
167 i_init_tt(void) {
168   TT_Error  error;
169   im_context_t ctx = im_get_context();
170   TT_Byte palette[] = { 0, 64, 127, 191, 255 };
171   i_tt_engine *result = im_context_slot_get(ctx, slot);
172
173   i_clear_error();
174
175   if (result == NULL) {
176     result = mymalloc(sizeof(i_tt_engine));
177     memset(result, 0, sizeof(*result));
178     im_context_slot_set(ctx, slot, result);
179     mm_log((1, "allocated FT1 state %p\n", result));
180   }
181
182   mm_log((1,"init_tt()\n"));
183
184   if (result->initialized)
185     return result;
186
187   error = TT_Init_FreeType( &result->engine );
188   if ( error ){
189     mm_log((1,"Initialization of freetype failed, code = 0x%x\n",
190             (unsigned)error));
191     i_tt_push_error(error);
192     i_push_error(0, "Could not initialize freetype 1.x");
193     return NULL;
194   }
195
196 #ifdef FTXPOST
197   error = TT_Init_Post_Extension( result->engine );
198   if (error) {
199     mm_log((1, "Initialization of Post extension failed = 0x%x\n",
200             (unsigned)error));
201     
202     i_tt_push_error(error);
203     i_push_error(0, "Could not initialize FT 1.x POST extension");
204     return NULL;
205   }
206 #endif
207
208   error = TT_Set_Raster_Gray_Palette(result->engine, palette);
209   if (error) {
210     mm_log((1, "Initialization of gray levels failed = 0x%x\n",
211             (unsigned)error));
212     i_tt_push_error(error);
213     i_push_error(0, "Could not initialize FT 1.x POST extension");
214     return NULL;
215   }
216
217   mm_log((1, "initialized FT1 state %p\n", result));
218
219   result->initialized = 1;
220
221   return result;
222 }
223
224 static void
225 i_tt_uninit(void *p) {
226   i_tt_engine *tteng = p;
227
228   if (tteng->initialized) {
229     mm_log((1, "finalizing FT1 state %p\n", tteng));
230     TT_Done_FreeType(tteng->engine);
231   }
232   mm_log((1, "freeing FT1 state %p\n", tteng));
233   myfree(tteng);
234 }
235
236 /* 
237 =item i_tt_get_instance(handle, points, smooth)
238
239 Finds a points+smooth instance or if one doesn't exist in the cache
240 allocates room and returns its cache entry
241
242    fontname - path to the font to load
243    handle   - handle to the font.
244    points   - points of the requested font
245    smooth   - boolean (True: antialias on, False: antialias is off)
246
247 =cut
248 */
249
250 static
251 int
252 i_tt_get_instance( TT_Fonthandle *handle, i_img_dim points, int smooth ) {
253   int i,idx;
254   TT_Error error;
255   
256   mm_log((1,"i_tt_get_instance(handle %p, points %" i_DF ", smooth %d)\n",
257           handle, i_DFc(points), smooth));
258   
259   if (smooth == -1) { /* Smooth doesn't matter for this search */
260     for(i=0;i<TT_CHC;i++) {
261       if (handle->instanceh[i].ptsize==points) {
262         mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
263         return i;
264       }
265     }
266     smooth=1; /* We will be adding a font - add it as smooth then */
267   } else { /* Smooth doesn't matter for this search */
268     for(i=0;i<TT_CHC;i++) {
269       if (handle->instanceh[i].ptsize == points 
270           && handle->instanceh[i].smooth == smooth) {
271         mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
272         return i;
273       }
274     }
275   }
276   
277   /* Found the instance in the cache - return the cache index */
278   
279   for(idx=0;idx<TT_CHC;idx++) {
280     if (!(handle->instanceh[idx].order)) break; /* find the lru item */
281   }
282
283   mm_log((1,"i_tt_get_instance: lru item is %d\n",idx));
284   mm_log((1,"i_tt_get_instance: lru pointer %p\n",
285           USTRCT(handle->instanceh[idx].instance) ));
286   
287   if ( USTRCT(handle->instanceh[idx].instance) ) {
288     mm_log((1,"i_tt_get_instance: freeing lru item from cache %d\n",idx));
289
290     /* Free cached glyphs */
291     for(i=0;i<256;i++)
292       if ( USTRCT(handle->instanceh[idx].glyphs[i].glyph) )
293         TT_Done_Glyph( handle->instanceh[idx].glyphs[i].glyph );
294
295     for(i=0;i<256;i++) {
296       handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
297       USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
298     }
299
300     /* Free instance if needed */
301     TT_Done_Instance( handle->instanceh[idx].instance );
302   }
303   
304   /* create and initialize instance */
305   /* FIXME: probably a memory leak on fail */
306   
307   (void) (( error = TT_New_Instance( handle->face, &handle->instanceh[idx].instance ) ) || 
308           ( error = TT_Set_Instance_Resolutions( handle->instanceh[idx].instance, LTT_dpi, LTT_dpi ) ) ||
309           ( error = TT_Set_Instance_CharSize( handle->instanceh[idx].instance, points*64 ) ) );
310   
311   if ( error ) {
312     mm_log((1, "Could not create and initialize instance: error %x.\n",
313             (unsigned)error ));
314     return -1;
315   }
316   
317   /* Now that the instance should the inplace we need to lower all of the
318      ru counts and put `this' one with the highest entry */
319   
320   for(i=0;i<TT_CHC;i++) handle->instanceh[i].order--;
321
322   handle->instanceh[idx].order=TT_CHC-1;
323   handle->instanceh[idx].ptsize=points;
324   handle->instanceh[idx].smooth=smooth;
325   TT_Get_Instance_Metrics( handle->instanceh[idx].instance, &(handle->instanceh[idx].imetrics) );
326
327   /* Zero the memory for the glyph storage so they are not thought as
328      cached if they haven't been cached since this new font was loaded */
329
330   for(i=0;i<256;i++) {
331     handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
332     USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
333   }
334   
335   return idx;
336 }
337
338
339 /*
340 =item i_tt_new(fontname)
341
342 Creates a new font handle object, finds a character map and initialise the
343 the font handle's cache
344
345    fontname - path to the font to load
346
347 =cut
348 */
349
350 TT_Fonthandle*
351 i_tt_new(const char *fontname) {
352   TT_Error error;
353   TT_Fonthandle *handle;
354   unsigned short i,n;
355   unsigned short platform,encoding;
356   i_tt_engine *tteng;
357
358   if ((tteng = i_init_tt()) == NULL) {
359     i_push_error(0, "Could not initialize FT1 engine");
360     return NULL;
361   }
362
363   i_clear_error();
364   
365   mm_log((1,"i_tt_new(fontname '%s')\n",fontname));
366   
367   /* allocate memory for the structure */
368   
369   handle = mymalloc( sizeof(TT_Fonthandle) ); /* checked 5Nov05 tonyc */
370
371   /* load the typeface */
372   error = TT_Open_Face( tteng->engine, fontname, &handle->face );
373   if ( error ) {
374     if ( error == TT_Err_Could_Not_Open_File ) {
375       mm_log((1, "Could not find/open %s.\n", fontname ));
376     }
377     else {
378       mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname, 
379               (unsigned)error )); 
380     }
381     i_tt_push_error(error);
382     return NULL;
383   }
384   
385   TT_Get_Face_Properties( handle->face, &(handle->properties) );
386
387   /* First, look for a Unicode charmap */
388   n = handle->properties.num_CharMaps;
389   USTRCT( handle->char_map )=NULL; /* Invalidate character map */
390   
391   for ( i = 0; i < n; i++ ) {
392     TT_Get_CharMap_ID( handle->face, i, &platform, &encoding );
393     if ( (platform == 3 && encoding == 1 ) 
394          || (platform == 0 && encoding == 0 ) ) {
395       mm_log((2,"i_tt_new - found char map platform %u encoding %u\n", 
396               platform, encoding));
397       TT_Get_CharMap( handle->face, i, &(handle->char_map) );
398       break;
399     }
400   }
401   if (!USTRCT(handle->char_map) && n != 0) {
402     /* just use the first one */
403     TT_Get_CharMap( handle->face, 0, &(handle->char_map));
404   }
405
406   /* Zero the pointsizes - and ordering */
407   
408   for(i=0;i<TT_CHC;i++) {
409     USTRCT(handle->instanceh[i].instance)=NULL;
410     handle->instanceh[i].order=i;
411     handle->instanceh[i].ptsize=0;
412     handle->instanceh[i].smooth=-1;
413   }
414
415 #ifdef FTXPOST
416   handle->loaded_names = 0;
417 #endif
418
419   mm_log((1,"i_tt_new <- %p\n",handle));
420   return handle;
421 }
422
423
424
425 /*
426  * raster map management
427  */
428
429 /* 
430 =item i_tt_init_raster_map(bit, width, height, smooth)
431
432 Allocates internal memory for the bitmap as needed by the parameters (internal)
433                  
434    bit    - bitmap to allocate into
435    width  - width of the bitmap
436    height - height of the bitmap
437    smooth - boolean (True: antialias on, False: antialias is off)
438
439 =cut
440 */
441
442 static
443 void
444 i_tt_init_raster_map( TT_Raster_Map* bit, i_img_dim width, i_img_dim height, int smooth ) {
445
446   mm_log((1,"i_tt_init_raster_map( bit %p, width %" i_DF ", height %" i_DF
447           ", smooth %d)\n", bit, i_DFc(width), i_DFc(height), smooth));
448   
449   bit->rows  = height;
450   bit->width = ( width + 3 ) & -4;
451   bit->flow  = TT_Flow_Down;
452   
453   if ( smooth ) {
454     bit->cols  = bit->width;
455     bit->size  = bit->rows * bit->width;
456   } else {
457     bit->cols  = ( bit->width + 7 ) / 8;    /* convert to # of bytes     */
458     bit->size  = bit->rows * bit->cols;     /* number of bytes in buffer */
459   }
460
461   /* rows can be 0 for some glyphs, for example ' ' */
462   if (bit->rows && bit->size / bit->rows != bit->cols) {
463     i_fatal(0, "Integer overflow calculating bitmap size (%d, %d)\n",
464             bit->width, bit->rows);
465   }
466   
467   mm_log((1,"i_tt_init_raster_map: bit->width %d, bit->cols %d, bit->rows %d, bit->size %ld)\n", bit->width, bit->cols, bit->rows, bit->size ));
468
469   bit->bitmap = (void *) mymalloc( bit->size ); /* checked 6Nov05 tonyc */
470   if ( !bit->bitmap ) i_fatal(0,"Not enough memory to allocate bitmap (%d)!\n",bit->size );
471 }
472
473
474 /*
475 =item i_tt_clear_raster_map(bit)
476
477 Frees the bitmap data and sets pointer to NULL (internal)
478                  
479    bit - bitmap to free
480
481 =cut
482 */
483
484 static
485 void
486 i_tt_done_raster_map( TT_Raster_Map *bit ) {
487   myfree( bit->bitmap );
488   bit->bitmap = NULL;
489 }
490
491
492 /*
493 =item i_tt_clear_raster_map(bit)
494
495 Clears the specified bitmap (internal)
496                  
497    bit - bitmap to zero
498
499 =cut
500 */
501
502
503 static
504 void
505 i_tt_clear_raster_map( TT_Raster_Map*  bit ) {
506   memset( bit->bitmap, 0, bit->size );
507 }
508
509
510 /* 
511 =item i_tt_blit_or(dst, src, x_off, y_off)
512
513 function that blits one raster map into another (internal)
514                  
515    dst   - destination bitmap
516    src   - source bitmap
517    x_off - x offset into the destination bitmap
518    y_off - y offset into the destination bitmap
519
520 =cut
521 */
522
523 static
524 void
525 i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,i_img_dim x_off, i_img_dim y_off ) {
526   i_img_dim  x,  y;
527   i_img_dim  x1, x2, y1, y2;
528   unsigned char *s, *d;
529   
530   x1 = x_off < 0 ? -x_off : 0;
531   y1 = y_off < 0 ? -y_off : 0;
532   
533   x2 = (int)dst->cols - x_off;
534   if ( x2 > src->cols ) x2 = src->cols;
535   
536   y2 = (int)dst->rows - y_off;
537   if ( y2 > src->rows ) y2 = src->rows;
538
539   if ( x1 >= x2 ) return;
540
541   /* do the real work now */
542
543   for ( y = y1; y < y2; ++y ) {
544     s = ( (unsigned char*)src->bitmap ) + y * src->cols + x1;
545     d = ( (unsigned char*)dst->bitmap ) + ( y + y_off ) * dst->cols + x1 + x_off;
546     
547     for ( x = x1; x < x2; ++x ) {
548       if (*s > *d)
549         *d = *s;
550       d++;
551       s++;
552     }
553   }
554 }
555
556 /* useful for debugging */
557 #if 0
558
559 static void dump_raster_map(FILE *out, TT_Raster_Map *bit ) {
560   int x, y;
561   fprintf(out, "cols %d rows %d  flow %d\n", bit->cols, bit->rows, bit->flow);
562   for (y = 0; y < bit->rows; ++y) {
563     fprintf(out, "%2d:", y);
564     for (x = 0; x < bit->cols; ++x) {
565       if ((x & 7) == 0 && x) putc(' ', out);
566       fprintf(out, "%02x", ((unsigned char *)bit->bitmap)[y*bit->cols+x]);
567     }
568     putc('\n', out);
569   }
570 }
571
572 #endif
573
574 /* 
575 =item i_tt_get_glyph(handle, inst, j) 
576
577 Function to see if a glyph exists and if so cache it (internal)
578                  
579    handle - pointer to font handle
580    inst   - font instance
581    j      - charcode of glyph
582
583 =cut
584 */
585
586 static
587 int
588 i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j) {
589   unsigned short load_flags, code;
590   TT_Error error;
591
592   mm_log((1, "i_tt_get_glyph(handle %p, inst %d, j %lu (%c))\n",
593           handle,inst,j, (int)((j >= ' ' && j <= '~') ? j : '.')));
594   
595   /*mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));*/
596
597   if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)
598        && handle->instanceh[inst].glyphs[TT_HASH(j)].ch == j) {
599     mm_log((1,"i_tt_get_glyph: %lu in cache\n",j));
600     return 1;
601   }
602
603   if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph) ) {
604     /* clean up the entry */
605     TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
606     USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
607     handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
608   }
609   
610   /* Ok - it wasn't cached - try to get it in */
611   load_flags = TTLOAD_SCALE_GLYPH;
612   if ( LTT_hinted ) load_flags |= TTLOAD_HINT_GLYPH;
613   
614   if ( !TT_VALID(handle->char_map) ) {
615     code = (j - ' ' + 1) < 0 ? 0 : (j - ' ' + 1);
616     if ( code >= handle->properties.num_Glyphs ) code = 0;
617   } else code = TT_Char_Index( handle->char_map, j );
618   
619   if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)) ) {
620     mm_log((1, "Cannot allocate and load glyph: error %#x.\n", (unsigned)error ));
621     i_push_error(error, "TT_New_Glyph()");
622     return 0;
623   }
624   if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, code, load_flags)) ) {
625     mm_log((1, "Cannot allocate and load glyph: error %#x.\n", (unsigned)error ));
626     /* Don't leak */
627     TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
628     USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
629     i_push_error(error, "TT_Load_Glyph()");
630     return 0;
631   }
632
633   /* At this point the glyph should be allocated and loaded */
634   handle->instanceh[inst].glyphs[TT_HASH(j)].ch = j;
635
636   /* Next get the glyph metrics */
637   error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
638                                 &handle->instanceh[inst].gmetrics[TT_HASH(j)] );
639   if (error) {
640     mm_log((1, "TT_Get_Glyph_Metrics: error %#x.\n", (unsigned)error ));
641     TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
642     USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
643     handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
644     i_push_error(error, "TT_Get_Glyph_Metrics()");
645     return 0;
646   }
647
648   return 1;
649 }
650
651 /*
652 =item i_tt_has_chars(handle, text, len, utf8, out)
653
654 Check if the given characters are defined by the font.  Note that len
655 is the number of bytes, not the number of characters (when utf8 is
656 non-zero).
657
658 Returns the number of characters that were checked.
659
660 =cut
661 */
662
663 size_t
664 i_tt_has_chars(TT_Fonthandle *handle, char const *text, size_t len, int utf8,
665                char *out) {
666   size_t count = 0;
667   mm_log((1, "i_tt_has_chars(handle %p, text %p, len %ld, utf8 %d)\n", 
668           handle, text, (long)len, utf8));
669
670   while (len) {
671     unsigned long c;
672     int index;
673     if (utf8) {
674       c = i_utf8_advance(&text, &len);
675       if (c == ~0UL) {
676         i_push_error(0, "invalid UTF8 character");
677         return 0;
678       }
679     }
680     else {
681       c = (unsigned char)*text++;
682       --len;
683     }
684     
685     if (TT_VALID(handle->char_map)) {
686       index = TT_Char_Index(handle->char_map, c);
687     }
688     else {
689       index = (c - ' ' + 1) < 0 ? 0 : (c - ' ' + 1);
690       if (index >= handle->properties.num_Glyphs)
691         index = 0;
692     }
693     *out++ = index != 0;
694     ++count;
695   }
696
697   return count;
698 }
699
700 /* 
701 =item i_tt_destroy(handle)
702
703 Clears the data taken by a font including all cached data such as
704 pixmaps and glyphs
705                  
706    handle - pointer to font handle
707
708 =cut
709 */
710
711 void
712 i_tt_destroy( TT_Fonthandle *handle) {
713   TT_Close_Face( handle->face );
714   myfree( handle );
715   
716   /* FIXME: Should these be freed automatically by the library? 
717
718   TT_Done_Instance( instance );
719   void
720     i_tt_done_glyphs( void ) {
721     int  i;
722
723     if ( !glyphs ) return;
724     
725     for ( i = 0; i < 256; ++i ) TT_Done_Glyph( glyphs[i] );
726     free( glyphs );
727     
728     glyphs = NULL;
729   }
730   */
731 }
732
733
734 /*
735  * FreeType Rendering functions
736  */
737
738
739 /* 
740 =item i_tt_render_glyph(handle, gmetrics, bit, smallbit, x_off, y_off, smooth)
741
742 Renders a single glyph into the bit rastermap (internal)
743
744    handle   - pointer to font handle
745    gmetrics - the metrics for the glyph to be rendered
746    bit      - large bitmap that is the destination for the text
747    smallbit - small bitmap that is used only if smooth is true
748    x_off    - x offset of glyph
749    y_off    - y offset of glyph
750    smooth   - boolean (True: antialias on, False: antialias is off)
751
752 =cut
753 */
754
755 static
756 void
757 i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, TT_Raster_Map *bit, TT_Raster_Map *small_bit, i_img_dim x_off, i_img_dim y_off, int smooth ) {
758   
759   mm_log((1,"i_tt_render_glyph(glyph %p, gmetrics %p, bit %p, small_bit %p, x_off %" i_DF ", y_off %" i_DF ", smooth %d)\n",
760           USTRCT(glyph), gmetrics, bit, small_bit, i_DFc(x_off),
761           i_DFc(y_off), smooth));
762   
763   if ( !smooth ) TT_Get_Glyph_Bitmap( glyph, bit, x_off * 64, y_off * 64);
764   else {
765     TT_F26Dot6 xmin, ymin, xmax, ymax;
766
767     xmin =  gmetrics->bbox.xMin & -64;
768     ymin =  gmetrics->bbox.yMin & -64;
769     xmax = (gmetrics->bbox.xMax + 63) & -64;
770     ymax = (gmetrics->bbox.yMax + 63) & -64;
771     
772     i_tt_clear_raster_map( small_bit );
773     TT_Get_Glyph_Pixmap( glyph, small_bit, -xmin, -ymin );
774     i_tt_blit_or( bit, small_bit, xmin/64 + x_off, -ymin/64 - y_off );
775   }
776 }
777
778
779 /*
780 =item i_tt_render_all_glyphs(handle, inst, bit, small_bit, cords, txt, len, smooth)
781
782 calls i_tt_render_glyph to render each glyph into the bit rastermap (internal)
783
784    handle   - pointer to font handle
785    inst     - font instance
786    bit      - large bitmap that is the destination for the text
787    smallbit - small bitmap that is used only if smooth is true
788    txt      - string to render
789    len      - length of the string to render
790    smooth   - boolean (True: antialias on, False: antialias is off)
791
792 =cut
793 */
794
795 static
796 int
797 i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit,
798                         TT_Raster_Map *small_bit, i_img_dim cords[6], 
799                         char const* txt, size_t len, int smooth, int utf8 ) {
800   unsigned long j;
801   TT_F26Dot6 x,y;
802   
803   mm_log((1,"i_tt_render_all_glyphs( handle %p, inst %d, bit %p, small_bit %p, txt '%.*s', len %ld, smooth %d, utf8 %d)\n",
804           handle, inst, bit, small_bit, (int)len, txt, (long)len, smooth, utf8));
805   
806   /* 
807      y=-( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem )/(handle->properties.header->Units_Per_EM);
808   */
809
810   x=-cords[0]; /* FIXME: If you font is antialiased this should be expanded by one to allow for aa expansion and the allocation too - do before passing here */
811   y=-cords[4];
812   
813   while (len) {
814     if (utf8) {
815       j = i_utf8_advance(&txt, &len);
816       if (j == ~0UL) {
817         i_push_error(0, "invalid UTF8 character");
818         return 0;
819       }
820     }
821     else {
822       j = (unsigned char)*txt++;
823       --len;
824     }
825     if ( !i_tt_get_glyph(handle,inst,j) ) 
826       continue;
827     i_tt_render_glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
828                        &handle->instanceh[inst].gmetrics[TT_HASH(j)], bit, 
829                        small_bit, x, y, smooth );
830     x += handle->instanceh[inst].gmetrics[TT_HASH(j)].advance / 64;
831   }
832
833   return 1;
834 }
835
836
837 /*
838  * Functions to render rasters (single channel images) onto images
839  */
840
841 /* 
842 =item i_tt_dump_raster_map2(im, bit, xb, yb, cl, smooth)
843
844 Function to dump a raster onto an image in color used by i_tt_text() (internal).
845
846    im     - image to dump raster on
847    bit    - bitmap that contains the text to be dumped to im
848    xb, yb - coordinates, left edge and baseline
849    cl     - color to use for text
850    smooth - boolean (True: antialias on, False: antialias is off)
851
852 =cut
853 */
854
855 static
856 void
857 i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, i_img_dim xb, i_img_dim yb, const i_color *cl, int smooth ) {
858   unsigned char *bmap;
859   i_img_dim x, y;
860   mm_log((1,"i_tt_dump_raster_map2(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", cl %p)\n",
861           im, bit, i_DFc(xb), i_DFc(yb), cl));
862   
863   bmap = bit->bitmap;
864
865   if ( smooth ) {
866
867     i_render r;
868     i_render_init(&r, im, bit->cols);
869     for(y=0;y<bit->rows;y++) {
870       i_render_color(&r, xb, yb+y, bit->cols, bmap + y*bit->cols, cl);
871     }
872     i_render_done(&r);
873   } else {
874     unsigned char *bmp = mymalloc(bit->width);
875     i_render r;
876
877     i_render_init(&r, im, bit->width);
878
879     for(y=0;y<bit->rows;y++) {
880       unsigned mask = 0x80;
881       unsigned char *p = bmap + y * bit->cols;
882       unsigned char *pout = bmp;
883
884       for(x = 0; x < bit->width; x++) {
885         *pout++ = (*p & mask) ? 0xFF : 0;
886         mask >>= 1;
887         if (!mask) {
888           mask = 0x80;
889           ++p;
890         }
891       }
892
893       i_render_color(&r, xb, yb+y, bit->cols, bmp, cl);
894     }
895
896     i_render_done(&r);
897     myfree(bmp);
898   }
899 }
900
901
902 /*
903 =item i_tt_dump_raster_map_channel(im, bit, xb, yb, channel, smooth)
904
905 Function to dump a raster onto a single channel image in color (internal)
906
907    im      - image to dump raster on
908    bit     - bitmap that contains the text to be dumped to im
909    xb, yb  - coordinates, left edge and baseline
910    channel - channel to copy to
911    smooth  - boolean (True: antialias on, False: antialias is off)
912
913 =cut
914 */
915
916 static
917 void
918 i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map*  bit, i_img_dim xb, i_img_dim yb, int channel, int smooth ) {
919   unsigned char *bmap;
920   i_color val;
921   int c;
922   i_img_dim x,y;
923   int old_mask = im->ch_mask;
924   im->ch_mask = 1 << channel;
925
926   mm_log((1,"i_tt_dump_raster_channel(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", channel %d)\n",
927           im, bit, i_DFc(xb), i_DFc(yb), channel));
928   
929   bmap = bit->bitmap;
930   
931   if ( smooth ) {
932     for(y=0;y<bit->rows;y++) for(x=0;x<bit->width;x++) {
933       c = bmap[y*(bit->cols)+x];
934       val.channel[channel] = c;
935       i_ppix(im,x+xb,y+yb,&val);
936     }
937   } else {
938     for(y=0;y<bit->rows;y++) {
939       unsigned mask = 0x80;
940       unsigned char *p = bmap + y * bit->cols;
941
942       for(x=0;x<bit->width;x++) {
943         val.channel[channel] = (*p & mask) ? 255 : 0;
944         i_ppix(im,x+xb,y+yb,&val);
945         
946         mask >>= 1;
947         if (!mask) {
948           ++p;
949           mask = 0x80;
950         }
951       }
952     }
953   }
954   im->ch_mask = old_mask;
955 }
956
957
958 /* 
959 =item i_tt_rasterize(handle, bit, cords, points, txt, len, smooth) 
960
961 interface for generating single channel raster of text (internal)
962
963    handle - pointer to font handle
964    bit    - the bitmap that is allocated, rendered into and NOT freed
965    cords  - the bounding box (modified in place)
966    points - font size to use
967    txt    - string to render
968    len    - length of the string to render
969    smooth - boolean (True: antialias on, False: antialias is off)
970
971 =cut
972 */
973
974 static
975 int
976 i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, i_img_dim cords[6], double points, char const* txt, size_t len, int smooth, int utf8 ) {
977   int inst;
978   i_img_dim width, height;
979   TT_Raster_Map small_bit;
980   
981   /* find or install an instance */
982   if ( (inst=i_tt_get_instance(handle,points,smooth)) < 0) { 
983     mm_log((1,"i_tt_rasterize: get instance failed\n"));
984     return 0;
985   }
986   
987   /* calculate bounding box */
988   if (!i_tt_bbox_inst( handle, inst, txt, len, cords, utf8 ))
989     return 0;
990     
991   
992   width  = cords[2]-cords[0];
993   height = cords[5]-cords[4];
994   
995   mm_log((1,"i_tt_rasterize: width=%" i_DF ", height=%" i_DF "\n",
996           i_DFc(width), i_DFc(height) )); 
997   
998   i_tt_init_raster_map ( bit, width, height, smooth );
999   i_tt_clear_raster_map( bit );
1000   if ( smooth ) i_tt_init_raster_map( &small_bit, handle->instanceh[inst].imetrics.x_ppem + 32, height, smooth );
1001   
1002   if (!i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, 
1003                                smooth, utf8 )) {
1004     if ( smooth ) 
1005       i_tt_done_raster_map( &small_bit );
1006     return 0;
1007   }
1008
1009   if ( smooth ) i_tt_done_raster_map( &small_bit );
1010   return 1;
1011 }
1012
1013
1014
1015 /* 
1016  * Exported text rendering interfaces
1017  */
1018
1019
1020 /*
1021 =item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth, utf8)
1022
1023 Interface to text rendering into a single channel in an image
1024
1025    handle  - pointer to font handle
1026    im      - image to render text on to
1027    xb, yb  - coordinates, left edge and baseline
1028    channel - channel to render into
1029    points  - font size to use
1030    txt     - string to render
1031    len     - length of the string to render
1032    smooth  - boolean (True: antialias on, False: antialias is off)
1033
1034 =cut
1035 */
1036
1037 undef_int
1038 i_tt_cp( TT_Fonthandle *handle, i_img *im, i_img_dim xb, i_img_dim yb, int channel, double points, char const* txt, size_t len, int smooth, int utf8, int align ) {
1039
1040   i_img_dim cords[BOUNDING_BOX_COUNT];
1041   i_img_dim ascent, st_offset, y;
1042   TT_Raster_Map bit;
1043   
1044   i_clear_error();
1045   if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
1046   
1047   ascent=cords[BBOX_ASCENT];
1048   st_offset=cords[BBOX_NEG_WIDTH];
1049   y = align ? yb-ascent : yb;
1050
1051   i_tt_dump_raster_map_channel( im, &bit, xb-st_offset , y, channel, smooth );
1052   i_tt_done_raster_map( &bit );
1053
1054   return 1;
1055 }
1056
1057
1058 /* 
1059 =item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth, utf8) 
1060
1061 Interface to text rendering in a single color onto an image
1062
1063    handle  - pointer to font handle
1064    im      - image to render text on to
1065    xb, yb  - coordinates, left edge and baseline
1066    cl      - color to use for text
1067    points  - font size to use
1068    txt     - string to render
1069    len     - length of the string to render
1070    smooth  - boolean (True: antialias on, False: antialias is off)
1071
1072 =cut
1073 */
1074
1075 undef_int
1076 i_tt_text( TT_Fonthandle *handle, i_img *im, i_img_dim xb, i_img_dim yb, const i_color *cl, double points, char const* txt, size_t len, int smooth, int utf8, int align) {
1077   i_img_dim cords[BOUNDING_BOX_COUNT];
1078   i_img_dim ascent, st_offset, y;
1079   TT_Raster_Map bit;
1080
1081   i_clear_error();
1082   
1083   if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
1084   
1085   ascent=cords[BBOX_ASCENT];
1086   st_offset=cords[BBOX_NEG_WIDTH];
1087   y = align ? yb-ascent : yb;
1088
1089   i_tt_dump_raster_map2( im, &bit, xb+st_offset, y, cl, smooth ); 
1090   i_tt_done_raster_map( &bit );
1091
1092   return 1;
1093 }
1094
1095
1096 /*
1097 =item i_tt_bbox_inst(handle, inst, txt, len, cords, utf8) 
1098
1099 Function to get texts bounding boxes given the instance of the font (internal)
1100
1101    handle - pointer to font handle
1102    inst   -  font instance
1103    txt    -  string to measure
1104    len    -  length of the string to render
1105    cords  - the bounding box (modified in place)
1106
1107 =cut
1108 */
1109
1110 static
1111 undef_int
1112 i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, size_t len, i_img_dim cords[BOUNDING_BOX_COUNT], int utf8 ) {
1113   int upm, casc, cdesc, first;
1114   
1115   int start    = 0;
1116   i_img_dim width    = 0;
1117   int gdescent = 0;
1118   int gascent  = 0;
1119   int descent  = 0;
1120   int ascent   = 0;
1121   int rightb   = 0;
1122
1123   unsigned long j;
1124   unsigned char *ustr;
1125   ustr=(unsigned char*)txt;
1126
1127   mm_log((1,"i_tt_box_inst(handle %p,inst %d,txt '%.*s', len %ld, utf8 %d)\n",
1128           handle, inst, (int)len, txt, (long)len, utf8));
1129
1130   upm     = handle->properties.header->Units_Per_EM;
1131   gascent  = ( handle->properties.horizontal->Ascender  * handle->instanceh[inst].imetrics.y_ppem + upm - 1) / upm;
1132   gdescent = ( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem - upm + 1) / upm;
1133   
1134   width   = 0;
1135   start   = 0;
1136   
1137   mm_log((1, "i_tt_box_inst: gascent=%d gdescent=%d\n", gascent, gdescent));
1138
1139   first=1;
1140   while (len) {
1141     if (utf8) {
1142       j = i_utf8_advance(&txt, &len);
1143       if (j == ~0UL) {
1144         i_push_error(0, "invalid UTF8 character");
1145         return 0;
1146       }
1147     }
1148     else {
1149       j = (unsigned char)*txt++;
1150       --len;
1151     }
1152     if ( i_tt_get_glyph(handle,inst,j) ) {
1153       TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + TT_HASH(j);
1154       width += gm->advance   / 64;
1155       casc   = (gm->bbox.yMax+63) / 64;
1156       cdesc  = (gm->bbox.yMin-63) / 64;
1157
1158       mm_log((1, "i_tt_box_inst: glyph='%c' casc=%d cdesc=%d\n", 
1159               (int)((j >= ' ' && j <= '~') ? j : '.'), casc, cdesc));
1160
1161       if (first) {
1162         start    = gm->bbox.xMin / 64;
1163         ascent   = (gm->bbox.yMax+63) / 64;
1164         descent  = (gm->bbox.yMin-63) / 64;
1165         first = 0;
1166       }
1167       if (!len) { /* if at end of string */
1168         /* the right-side bearing - in case the right-side of a 
1169            character goes past the right of the advance width,
1170            as is common for italic fonts
1171         */
1172         rightb = gm->advance - gm->bearingX 
1173           - (gm->bbox.xMax - gm->bbox.xMin);
1174         /* fprintf(stderr, "font info last: %d %d %d %d\n", 
1175            gm->bbox.xMax, gm->bbox.xMin, gm->advance, rightb); */
1176       }
1177
1178       ascent  = (ascent  >  casc ?  ascent : casc );
1179       descent = (descent < cdesc ? descent : cdesc);
1180     }
1181   }
1182   
1183   cords[BBOX_NEG_WIDTH]=start;
1184   cords[BBOX_GLOBAL_DESCENT]=gdescent;
1185   cords[BBOX_POS_WIDTH]=width;
1186   if (rightb < 0)
1187     cords[BBOX_POS_WIDTH] -= rightb / 64;
1188   cords[BBOX_GLOBAL_ASCENT]=gascent;
1189   cords[BBOX_DESCENT]=descent;
1190   cords[BBOX_ASCENT]=ascent;
1191   cords[BBOX_ADVANCE_WIDTH] = width;
1192   cords[BBOX_RIGHT_BEARING] = rightb / 64;
1193
1194   return BBOX_RIGHT_BEARING + 1;
1195 }
1196
1197
1198 /*
1199 =item i_tt_bbox(handle, points, txt, len, cords, utf8)
1200
1201 Interface to get a strings bounding box
1202
1203    handle - pointer to font handle
1204    points - font size to use
1205    txt    - string to render
1206    len    - length of the string to render
1207    cords  - the bounding box (modified in place)
1208
1209 =cut
1210 */
1211
1212 undef_int
1213 i_tt_bbox( TT_Fonthandle *handle, double points,const char *txt,size_t len,i_img_dim cords[6], int utf8) {
1214   int inst;
1215
1216   i_clear_error();
1217   mm_log((1,"i_tt_box(handle %p,points %f,txt '%.*s', len %ld, utf8 %d)\n",
1218           handle, points, (int)len, txt, (long)len, utf8));
1219
1220   if ( (inst=i_tt_get_instance(handle,points,-1)) < 0) {
1221     i_push_errorf(0, "i_tt_get_instance(%g)", points);
1222     mm_log((1,"i_tt_text: get instance failed\n"));
1223     return 0;
1224   }
1225
1226   return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
1227 }
1228
1229 /*
1230 =item i_tt_face_name(handle, name_buf, name_buf_size)
1231
1232 Retrieve's the font's postscript name.
1233
1234 This is complicated by the need to handle encodings and so on.
1235
1236 =cut
1237  */
1238 size_t
1239 i_tt_face_name(TT_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
1240   TT_Face_Properties props;
1241   int name_count;
1242   int i;
1243   TT_UShort platform_id, encoding_id, lang_id, name_id;
1244   TT_UShort name_len;
1245   TT_String *name;
1246   int want_index = -1; /* an acceptable but not perfect name */
1247   int score = 0;
1248
1249   i_clear_error();
1250   
1251   TT_Get_Face_Properties(handle->face, &props);
1252   name_count = props.num_Names;
1253   for (i = 0; i < name_count; ++i) {
1254     TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
1255                    &name_id);
1256
1257     TT_Get_Name_String(handle->face, i, &name, &name_len);
1258
1259     if (platform_id != TT_PLATFORM_APPLE_UNICODE && name_len
1260         && name_id == TT_NAME_ID_PS_NAME) {
1261       int might_want_index = -1;
1262       int might_score = 0;
1263       if ((platform_id == TT_PLATFORM_MACINTOSH && encoding_id == TT_MAC_ID_ROMAN)
1264           ||
1265           (platform_id == TT_PLATFORM_MICROSOFT && encoding_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)) {
1266         /* exactly what we want */
1267         want_index = i;
1268         break;
1269       }
1270       
1271       if (platform_id == TT_PLATFORM_MICROSOFT
1272           && (encoding_id & 0xFF) == TT_MS_LANGID_ENGLISH_GENERAL) {
1273         /* any english is good */
1274         might_want_index = i;
1275         might_score = 9;
1276       }
1277       /* there might be something in between */
1278       else {
1279         /* anything non-unicode is better than nothing */
1280         might_want_index = i;
1281         might_score = 1;
1282       }
1283       if (might_score > score) {
1284         score = might_score;
1285         want_index = might_want_index;
1286       }
1287     }
1288   }
1289
1290   if (want_index != -1) {
1291     TT_Get_Name_String(handle->face, want_index, &name, &name_len);
1292     
1293     strncpy(name_buf, name, name_buf_size);
1294     name_buf[name_buf_size-1] = '\0';
1295
1296     return strlen(name) + 1;
1297   }
1298   else {
1299     i_push_error(0, "no face name present");
1300     return 0;
1301   }
1302 }
1303
1304 void i_tt_dump_names(TT_Fonthandle *handle) {
1305   TT_Face_Properties props;
1306   int name_count;
1307   int i;
1308   TT_UShort platform_id, encoding_id, lang_id, name_id;
1309   TT_UShort name_len;
1310   TT_String *name;
1311   
1312   TT_Get_Face_Properties(handle->face, &props);
1313   name_count = props.num_Names;
1314   for (i = 0; i < name_count; ++i) {
1315     TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
1316                    &name_id);
1317     TT_Get_Name_String(handle->face, i, &name, &name_len);
1318
1319     printf("# %d: plat %d enc %d lang %d name %d value ", i, platform_id,
1320            encoding_id, lang_id, name_id);
1321     if (platform_id == TT_PLATFORM_APPLE_UNICODE) {
1322       printf("(unicode)\n");
1323     }
1324     else {
1325       printf("'%s'\n", name);
1326     }
1327   }
1328   fflush(stdout);
1329 }
1330
1331 size_t
1332 i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf, 
1333                  size_t name_buf_size) {
1334 #ifdef FTXPOST
1335   TT_Error rc;
1336   TT_String *psname;
1337   TT_UShort index;
1338
1339   i_clear_error();
1340
1341   if (!handle->loaded_names) {
1342     TT_Post post;
1343     mm_log((1, "Loading PS Names"));
1344     handle->load_cond = TT_Load_PS_Names(handle->face, &post);
1345     ++handle->loaded_names;
1346   }
1347
1348   if (handle->load_cond) {
1349     i_push_errorf(handle->load_cond, "error loading names (%#x)",
1350                   (unsigned)handle->load_cond);
1351     return 0;
1352   }
1353   
1354   index = TT_Char_Index(handle->char_map, ch);
1355   if (!index) {
1356     i_push_error(0, "no such character");
1357     return 0;
1358   }
1359
1360   rc = TT_Get_PS_Name(handle->face, index, &psname);
1361
1362   if (rc) {
1363     i_push_error(rc, "error getting name");
1364     return 0;
1365   }
1366
1367   strncpy(name_buf, psname, name_buf_size);
1368   name_buf[name_buf_size-1] = '\0';
1369
1370   return strlen(psname) + 1;
1371 #else
1372   mm_log((1, "FTXPOST extension not enabled\n"));
1373   i_clear_error();
1374   i_push_error(0, "Use of FTXPOST extension disabled");
1375
1376   return 0;
1377 #endif
1378 }
1379
1380 /*
1381 =item i_tt_push_error(code)
1382
1383 Push an error message and code onto the Imager error stack.
1384
1385 =cut
1386 */
1387 static void
1388 i_tt_push_error(TT_Error rc) {
1389 #ifdef FTXERR18
1390   TT_String const *msg = TT_ErrToString18(rc);
1391
1392   i_push_error(rc, msg);
1393 #else
1394   i_push_errorf(rc, "Error code 0x%04x", (unsigned)rc);
1395 #endif
1396 }
1397
1398
1399 /*
1400 =back
1401
1402 =head1 AUTHOR
1403
1404 Arnar M. Hrafnkelsson <addi@umich.edu>
1405
1406 =head1 SEE ALSO
1407
1408 Imager(3)
1409
1410 =cut
1411 */