[rt.cpan.org #88993] use the correct width drawing non-AA 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;
766
767     xmin =  gmetrics->bbox.xMin & -64;
768     ymin =  gmetrics->bbox.yMin & -64;
769     
770     i_tt_clear_raster_map( small_bit );
771     TT_Get_Glyph_Pixmap( glyph, small_bit, -xmin, -ymin );
772     i_tt_blit_or( bit, small_bit, xmin/64 + x_off, -ymin/64 - y_off );
773   }
774 }
775
776
777 /*
778 =item i_tt_render_all_glyphs(handle, inst, bit, small_bit, cords, txt, len, smooth)
779
780 calls i_tt_render_glyph to render each glyph into the bit rastermap (internal)
781
782    handle   - pointer to font handle
783    inst     - font instance
784    bit      - large bitmap that is the destination for the text
785    smallbit - small bitmap that is used only if smooth is true
786    txt      - string to render
787    len      - length of the string to render
788    smooth   - boolean (True: antialias on, False: antialias is off)
789
790 =cut
791 */
792
793 static
794 int
795 i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit,
796                         TT_Raster_Map *small_bit, i_img_dim cords[6], 
797                         char const* txt, size_t len, int smooth, int utf8 ) {
798   unsigned long j;
799   TT_F26Dot6 x,y;
800   
801   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",
802           handle, inst, bit, small_bit, (int)len, txt, (long)len, smooth, utf8));
803   
804   /* 
805      y=-( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem )/(handle->properties.header->Units_Per_EM);
806   */
807
808   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 */
809   y=-cords[4];
810   
811   while (len) {
812     if (utf8) {
813       j = i_utf8_advance(&txt, &len);
814       if (j == ~0UL) {
815         i_push_error(0, "invalid UTF8 character");
816         return 0;
817       }
818     }
819     else {
820       j = (unsigned char)*txt++;
821       --len;
822     }
823     if ( !i_tt_get_glyph(handle,inst,j) ) 
824       continue;
825     i_tt_render_glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
826                        &handle->instanceh[inst].gmetrics[TT_HASH(j)], bit, 
827                        small_bit, x, y, smooth );
828     x += handle->instanceh[inst].gmetrics[TT_HASH(j)].advance / 64;
829   }
830
831   return 1;
832 }
833
834
835 /*
836  * Functions to render rasters (single channel images) onto images
837  */
838
839 /* 
840 =item i_tt_dump_raster_map2(im, bit, xb, yb, cl, smooth)
841
842 Function to dump a raster onto an image in color used by i_tt_text() (internal).
843
844    im     - image to dump raster on
845    bit    - bitmap that contains the text to be dumped to im
846    xb, yb - coordinates, left edge and baseline
847    cl     - color to use for text
848    smooth - boolean (True: antialias on, False: antialias is off)
849
850 =cut
851 */
852
853 static
854 void
855 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 ) {
856   unsigned char *bmap;
857   i_img_dim x, y;
858   mm_log((1,"i_tt_dump_raster_map2(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", cl %p)\n",
859           im, bit, i_DFc(xb), i_DFc(yb), cl));
860   
861   bmap = bit->bitmap;
862
863   if ( smooth ) {
864
865     i_render r;
866     i_render_init(&r, im, bit->cols);
867     for(y=0;y<bit->rows;y++) {
868       i_render_color(&r, xb, yb+y, bit->cols, bmap + y*bit->cols, cl);
869     }
870     i_render_done(&r);
871   } else {
872     unsigned char *bmp = mymalloc(bit->width);
873     i_render r;
874
875     i_render_init(&r, im, bit->width);
876
877     for(y=0;y<bit->rows;y++) {
878       unsigned mask = 0x80;
879       unsigned char *p = bmap + y * bit->cols;
880       unsigned char *pout = bmp;
881
882       for(x = 0; x < bit->width; x++) {
883         *pout++ = (*p & mask) ? 0xFF : 0;
884         mask >>= 1;
885         if (!mask) {
886           mask = 0x80;
887           ++p;
888         }
889       }
890
891       i_render_color(&r, xb, yb+y, bit->width, bmp, cl);
892     }
893
894     i_render_done(&r);
895     myfree(bmp);
896   }
897 }
898
899
900 /*
901 =item i_tt_dump_raster_map_channel(im, bit, xb, yb, channel, smooth)
902
903 Function to dump a raster onto a single channel image in color (internal)
904
905    im      - image to dump raster on
906    bit     - bitmap that contains the text to be dumped to im
907    xb, yb  - coordinates, left edge and baseline
908    channel - channel to copy to
909    smooth  - boolean (True: antialias on, False: antialias is off)
910
911 =cut
912 */
913
914 static
915 void
916 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 ) {
917   unsigned char *bmap;
918   i_color val;
919   int c;
920   i_img_dim x,y;
921   int old_mask = im->ch_mask;
922   im->ch_mask = 1 << channel;
923
924   mm_log((1,"i_tt_dump_raster_channel(im %p, bit %p, xb %" i_DF ", yb %" i_DF ", channel %d)\n",
925           im, bit, i_DFc(xb), i_DFc(yb), channel));
926   
927   bmap = bit->bitmap;
928   
929   if ( smooth ) {
930     for(y=0;y<bit->rows;y++) for(x=0;x<bit->width;x++) {
931       c = bmap[y*(bit->cols)+x];
932       val.channel[channel] = c;
933       i_ppix(im,x+xb,y+yb,&val);
934     }
935   } else {
936     for(y=0;y<bit->rows;y++) {
937       unsigned mask = 0x80;
938       unsigned char *p = bmap + y * bit->cols;
939
940       for(x=0;x<bit->width;x++) {
941         val.channel[channel] = (*p & mask) ? 255 : 0;
942         i_ppix(im,x+xb,y+yb,&val);
943         
944         mask >>= 1;
945         if (!mask) {
946           ++p;
947           mask = 0x80;
948         }
949       }
950     }
951   }
952   im->ch_mask = old_mask;
953 }
954
955
956 /* 
957 =item i_tt_rasterize(handle, bit, cords, points, txt, len, smooth) 
958
959 interface for generating single channel raster of text (internal)
960
961    handle - pointer to font handle
962    bit    - the bitmap that is allocated, rendered into and NOT freed
963    cords  - the bounding box (modified in place)
964    points - font size to use
965    txt    - string to render
966    len    - length of the string to render
967    smooth - boolean (True: antialias on, False: antialias is off)
968
969 =cut
970 */
971
972 static
973 int
974 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 ) {
975   int inst;
976   i_img_dim width, height;
977   TT_Raster_Map small_bit;
978   
979   /* find or install an instance */
980   if ( (inst=i_tt_get_instance(handle,points,smooth)) < 0) { 
981     mm_log((1,"i_tt_rasterize: get instance failed\n"));
982     return 0;
983   }
984   
985   /* calculate bounding box */
986   if (!i_tt_bbox_inst( handle, inst, txt, len, cords, utf8 ))
987     return 0;
988     
989   
990   width  = cords[2]-cords[0];
991   height = cords[5]-cords[4];
992   
993   mm_log((1,"i_tt_rasterize: width=%" i_DF ", height=%" i_DF "\n",
994           i_DFc(width), i_DFc(height) )); 
995   
996   i_tt_init_raster_map ( bit, width, height, smooth );
997   i_tt_clear_raster_map( bit );
998   if ( smooth ) i_tt_init_raster_map( &small_bit, handle->instanceh[inst].imetrics.x_ppem + 32, height, smooth );
999   
1000   if (!i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, 
1001                                smooth, utf8 )) {
1002     if ( smooth ) 
1003       i_tt_done_raster_map( &small_bit );
1004     return 0;
1005   }
1006
1007   if ( smooth ) i_tt_done_raster_map( &small_bit );
1008   return 1;
1009 }
1010
1011
1012
1013 /* 
1014  * Exported text rendering interfaces
1015  */
1016
1017
1018 /*
1019 =item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth, utf8)
1020
1021 Interface to text rendering into a single channel in an image
1022
1023    handle  - pointer to font handle
1024    im      - image to render text on to
1025    xb, yb  - coordinates, left edge and baseline
1026    channel - channel to render into
1027    points  - font size to use
1028    txt     - string to render
1029    len     - length of the string to render
1030    smooth  - boolean (True: antialias on, False: antialias is off)
1031
1032 =cut
1033 */
1034
1035 undef_int
1036 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 ) {
1037
1038   i_img_dim cords[BOUNDING_BOX_COUNT];
1039   i_img_dim ascent, st_offset, y;
1040   TT_Raster_Map bit;
1041   
1042   i_clear_error();
1043   if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
1044   
1045   ascent=cords[BBOX_ASCENT];
1046   st_offset=cords[BBOX_NEG_WIDTH];
1047   y = align ? yb-ascent : yb;
1048
1049   i_tt_dump_raster_map_channel( im, &bit, xb-st_offset , y, channel, smooth );
1050   i_tt_done_raster_map( &bit );
1051
1052   return 1;
1053 }
1054
1055
1056 /* 
1057 =item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth, utf8) 
1058
1059 Interface to text rendering in a single color onto an image
1060
1061    handle  - pointer to font handle
1062    im      - image to render text on to
1063    xb, yb  - coordinates, left edge and baseline
1064    cl      - color to use for text
1065    points  - font size to use
1066    txt     - string to render
1067    len     - length of the string to render
1068    smooth  - boolean (True: antialias on, False: antialias is off)
1069
1070 =cut
1071 */
1072
1073 undef_int
1074 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) {
1075   i_img_dim cords[BOUNDING_BOX_COUNT];
1076   i_img_dim ascent, st_offset, y;
1077   TT_Raster_Map bit;
1078
1079   i_clear_error();
1080   
1081   if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
1082   
1083   ascent=cords[BBOX_ASCENT];
1084   st_offset=cords[BBOX_NEG_WIDTH];
1085   y = align ? yb-ascent : yb;
1086
1087   i_tt_dump_raster_map2( im, &bit, xb+st_offset, y, cl, smooth ); 
1088   i_tt_done_raster_map( &bit );
1089
1090   return 1;
1091 }
1092
1093
1094 /*
1095 =item i_tt_bbox_inst(handle, inst, txt, len, cords, utf8) 
1096
1097 Function to get texts bounding boxes given the instance of the font (internal)
1098
1099    handle - pointer to font handle
1100    inst   -  font instance
1101    txt    -  string to measure
1102    len    -  length of the string to render
1103    cords  - the bounding box (modified in place)
1104
1105 =cut
1106 */
1107
1108 static
1109 undef_int
1110 i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, size_t len, i_img_dim cords[BOUNDING_BOX_COUNT], int utf8 ) {
1111   int upm, casc, cdesc, first;
1112   
1113   int start    = 0;
1114   i_img_dim width    = 0;
1115   int gdescent = 0;
1116   int gascent  = 0;
1117   int descent  = 0;
1118   int ascent   = 0;
1119   int rightb   = 0;
1120
1121   unsigned long j;
1122
1123   mm_log((1,"i_tt_box_inst(handle %p,inst %d,txt '%.*s', len %ld, utf8 %d)\n",
1124           handle, inst, (int)len, txt, (long)len, utf8));
1125
1126   upm     = handle->properties.header->Units_Per_EM;
1127   gascent  = ( handle->properties.horizontal->Ascender  * handle->instanceh[inst].imetrics.y_ppem + upm - 1) / upm;
1128   gdescent = ( handle->properties.horizontal->Descender * handle->instanceh[inst].imetrics.y_ppem - upm + 1) / upm;
1129   
1130   width   = 0;
1131   start   = 0;
1132   
1133   mm_log((1, "i_tt_box_inst: gascent=%d gdescent=%d\n", gascent, gdescent));
1134
1135   first=1;
1136   while (len) {
1137     if (utf8) {
1138       j = i_utf8_advance(&txt, &len);
1139       if (j == ~0UL) {
1140         i_push_error(0, "invalid UTF8 character");
1141         return 0;
1142       }
1143     }
1144     else {
1145       j = (unsigned char)*txt++;
1146       --len;
1147     }
1148     if ( i_tt_get_glyph(handle,inst,j) ) {
1149       TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + TT_HASH(j);
1150       width += gm->advance   / 64;
1151       casc   = (gm->bbox.yMax+63) / 64;
1152       cdesc  = (gm->bbox.yMin-63) / 64;
1153
1154       mm_log((1, "i_tt_box_inst: glyph='%c' casc=%d cdesc=%d\n", 
1155               (int)((j >= ' ' && j <= '~') ? j : '.'), casc, cdesc));
1156
1157       if (first) {
1158         start    = gm->bbox.xMin / 64;
1159         ascent   = (gm->bbox.yMax+63) / 64;
1160         descent  = (gm->bbox.yMin-63) / 64;
1161         first = 0;
1162       }
1163       if (!len) { /* if at end of string */
1164         /* the right-side bearing - in case the right-side of a 
1165            character goes past the right of the advance width,
1166            as is common for italic fonts
1167         */
1168         rightb = gm->advance - gm->bearingX 
1169           - (gm->bbox.xMax - gm->bbox.xMin);
1170         /* fprintf(stderr, "font info last: %d %d %d %d\n", 
1171            gm->bbox.xMax, gm->bbox.xMin, gm->advance, rightb); */
1172       }
1173
1174       ascent  = (ascent  >  casc ?  ascent : casc );
1175       descent = (descent < cdesc ? descent : cdesc);
1176     }
1177   }
1178   
1179   cords[BBOX_NEG_WIDTH]=start;
1180   cords[BBOX_GLOBAL_DESCENT]=gdescent;
1181   cords[BBOX_POS_WIDTH]=width;
1182   if (rightb < 0)
1183     cords[BBOX_POS_WIDTH] -= rightb / 64;
1184   cords[BBOX_GLOBAL_ASCENT]=gascent;
1185   cords[BBOX_DESCENT]=descent;
1186   cords[BBOX_ASCENT]=ascent;
1187   cords[BBOX_ADVANCE_WIDTH] = width;
1188   cords[BBOX_RIGHT_BEARING] = rightb / 64;
1189
1190   return BBOX_RIGHT_BEARING + 1;
1191 }
1192
1193
1194 /*
1195 =item i_tt_bbox(handle, points, txt, len, cords, utf8)
1196
1197 Interface to get a strings bounding box
1198
1199    handle - pointer to font handle
1200    points - font size to use
1201    txt    - string to render
1202    len    - length of the string to render
1203    cords  - the bounding box (modified in place)
1204
1205 =cut
1206 */
1207
1208 undef_int
1209 i_tt_bbox( TT_Fonthandle *handle, double points,const char *txt,size_t len,i_img_dim cords[6], int utf8) {
1210   int inst;
1211
1212   i_clear_error();
1213   mm_log((1,"i_tt_box(handle %p,points %f,txt '%.*s', len %ld, utf8 %d)\n",
1214           handle, points, (int)len, txt, (long)len, utf8));
1215
1216   if ( (inst=i_tt_get_instance(handle,points,-1)) < 0) {
1217     i_push_errorf(0, "i_tt_get_instance(%g)", points);
1218     mm_log((1,"i_tt_text: get instance failed\n"));
1219     return 0;
1220   }
1221
1222   return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
1223 }
1224
1225 /*
1226 =item i_tt_face_name(handle, name_buf, name_buf_size)
1227
1228 Retrieve's the font's postscript name.
1229
1230 This is complicated by the need to handle encodings and so on.
1231
1232 =cut
1233  */
1234 size_t
1235 i_tt_face_name(TT_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
1236   TT_Face_Properties props;
1237   int name_count;
1238   int i;
1239   TT_UShort platform_id, encoding_id, lang_id, name_id;
1240   TT_UShort name_len;
1241   TT_String *name;
1242   int want_index = -1; /* an acceptable but not perfect name */
1243   int score = 0;
1244
1245   i_clear_error();
1246   
1247   TT_Get_Face_Properties(handle->face, &props);
1248   name_count = props.num_Names;
1249   for (i = 0; i < name_count; ++i) {
1250     TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
1251                    &name_id);
1252
1253     TT_Get_Name_String(handle->face, i, &name, &name_len);
1254
1255     if (platform_id != TT_PLATFORM_APPLE_UNICODE && name_len
1256         && name_id == TT_NAME_ID_PS_NAME) {
1257       int might_want_index = -1;
1258       int might_score = 0;
1259       if ((platform_id == TT_PLATFORM_MACINTOSH && encoding_id == TT_MAC_ID_ROMAN)
1260           ||
1261           (platform_id == TT_PLATFORM_MICROSOFT && encoding_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)) {
1262         /* exactly what we want */
1263         want_index = i;
1264         break;
1265       }
1266       
1267       if (platform_id == TT_PLATFORM_MICROSOFT
1268           && (encoding_id & 0xFF) == TT_MS_LANGID_ENGLISH_GENERAL) {
1269         /* any english is good */
1270         might_want_index = i;
1271         might_score = 9;
1272       }
1273       /* there might be something in between */
1274       else {
1275         /* anything non-unicode is better than nothing */
1276         might_want_index = i;
1277         might_score = 1;
1278       }
1279       if (might_score > score) {
1280         score = might_score;
1281         want_index = might_want_index;
1282       }
1283     }
1284   }
1285
1286   if (want_index != -1) {
1287     TT_Get_Name_String(handle->face, want_index, &name, &name_len);
1288     
1289     strncpy(name_buf, name, name_buf_size);
1290     name_buf[name_buf_size-1] = '\0';
1291
1292     return strlen(name) + 1;
1293   }
1294   else {
1295     i_push_error(0, "no face name present");
1296     return 0;
1297   }
1298 }
1299
1300 void i_tt_dump_names(TT_Fonthandle *handle) {
1301   TT_Face_Properties props;
1302   int name_count;
1303   int i;
1304   TT_UShort platform_id, encoding_id, lang_id, name_id;
1305   TT_UShort name_len;
1306   TT_String *name;
1307   
1308   TT_Get_Face_Properties(handle->face, &props);
1309   name_count = props.num_Names;
1310   for (i = 0; i < name_count; ++i) {
1311     TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
1312                    &name_id);
1313     TT_Get_Name_String(handle->face, i, &name, &name_len);
1314
1315     printf("# %d: plat %d enc %d lang %d name %d value ", i, platform_id,
1316            encoding_id, lang_id, name_id);
1317     if (platform_id == TT_PLATFORM_APPLE_UNICODE) {
1318       printf("(unicode)\n");
1319     }
1320     else {
1321       printf("'%s'\n", name);
1322     }
1323   }
1324   fflush(stdout);
1325 }
1326
1327 size_t
1328 i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf, 
1329                  size_t name_buf_size) {
1330 #ifdef FTXPOST
1331   TT_Error rc;
1332   TT_String *psname;
1333   TT_UShort index;
1334
1335   i_clear_error();
1336
1337   if (!handle->loaded_names) {
1338     TT_Post post;
1339     mm_log((1, "Loading PS Names"));
1340     handle->load_cond = TT_Load_PS_Names(handle->face, &post);
1341     ++handle->loaded_names;
1342   }
1343
1344   if (handle->load_cond) {
1345     i_push_errorf(handle->load_cond, "error loading names (%#x)",
1346                   (unsigned)handle->load_cond);
1347     return 0;
1348   }
1349   
1350   index = TT_Char_Index(handle->char_map, ch);
1351   if (!index) {
1352     i_push_error(0, "no such character");
1353     return 0;
1354   }
1355
1356   rc = TT_Get_PS_Name(handle->face, index, &psname);
1357
1358   if (rc) {
1359     i_push_error(rc, "error getting name");
1360     return 0;
1361   }
1362
1363   strncpy(name_buf, psname, name_buf_size);
1364   name_buf[name_buf_size-1] = '\0';
1365
1366   return strlen(psname) + 1;
1367 #else
1368   mm_log((1, "FTXPOST extension not enabled\n"));
1369   i_clear_error();
1370   i_push_error(0, "Use of FTXPOST extension disabled");
1371
1372   return 0;
1373 #endif
1374 }
1375
1376 /*
1377 =item i_tt_push_error(code)
1378
1379 Push an error message and code onto the Imager error stack.
1380
1381 =cut
1382 */
1383 static void
1384 i_tt_push_error(TT_Error rc) {
1385 #ifdef FTXERR18
1386   TT_String const *msg = TT_ErrToString18(rc);
1387
1388   i_push_error(rc, msg);
1389 #else
1390   i_push_errorf(rc, "Error code 0x%04x", (unsigned)rc);
1391 #endif
1392 }
1393
1394
1395 /*
1396 =back
1397
1398 =head1 AUTHOR
1399
1400 Arnar M. Hrafnkelsson <addi@umich.edu>
1401
1402 =head1 SEE ALSO
1403
1404 Imager(3)
1405
1406 =cut
1407 */