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