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