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