support UTF with Freetype 1.x
authorTony Cook <tony@develop=help.com>
Thu, 18 Apr 2002 14:05:00 +0000 (14:05 +0000)
committerTony Cook <tony@develop=help.com>
Thu, 18 Apr 2002 14:05:00 +0000 (14:05 +0000)
Changes
Imager.xs
font.c
freetyp2.c
image.h
imio.h
io.c
lib/Imager/Font/Truetype.pm
t/t35ttfont.t
t/t36oofont.t
t/testtools.pl

diff --git a/Changes b/Changes
index c63bcb9..26330fa 100644 (file)
--- a/Changes
+++ b/Changes
@@ -649,6 +649,7 @@ Revision history for Perl extension Imager.
           and at least mention it in the docs beyond the examples
         - document the values for the read() and write() method type 
           parameter
+        - support UTF8 text with Freetype 1.x 
 
 =================================================================
 
index a247841..4da04aa 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1742,42 +1742,77 @@ MODULE = Imager         PACKAGE = Imager
 
 
 undef_int
-i_tt_text(handle,im,xb,yb,cl,points,str,len,smooth)
+i_tt_text(handle,im,xb,yb,cl,points,str_sv,len_ignored,smooth,utf8)
   Imager::Font::TT     handle
     Imager::ImgRaw     im
               int     xb
               int     yb
      Imager::Color     cl
              float     points
-             char*    str
-              int     len
+             SV *     str_sv
+              int     len_ignored
               int     smooth
+               int     utf8
+             PREINIT:
+               char *str;
+               STRLEN len;
+             CODE:
+#ifdef SvUTF8
+               if (SvUTF8(str_sv))
+                 utf8 = 1;
+#endif
+               str = SvPV(str_sv, len);
+               RETVAL = i_tt_text(handle, im, xb, yb, cl, points, str, 
+                                  len, smooth, utf8);
+             OUTPUT:
+               RETVAL                
 
 
 undef_int
-i_tt_cp(handle,im,xb,yb,channel,points,str,len,smooth)
+i_tt_cp(handle,im,xb,yb,channel,points,str_sv,len_ignored,smooth,utf8)
   Imager::Font::TT     handle
     Imager::ImgRaw     im
               int     xb
               int     yb
               int     channel
              float     points
-             char*    str
-              int     len
+             SV *     str_sv
+              int     len_ignored
               int     smooth
-
+               int     utf8
+             PREINIT:
+               char *str;
+               STRLEN len;
+             CODE:
+#ifdef SvUTF8
+               if (SvUTF8(str_sv))
+                 utf8 = 1;
+#endif
+               str = SvPV(str_sv, len);
+               RETVAL = i_tt_cp(handle, im, xb, yb, channel, points, str, len,
+                                smooth, utf8);
+             OUTPUT:
+                RETVAL
 
 
 undef_int
-i_tt_bbox(handle,point,str,len)
+i_tt_bbox(handle,point,str_sv,len_ignored, utf8)
   Imager::Font::TT     handle
             float     point
-             char*    str
-              int     len
+              SV*    str_sv
+              int     len_ignored
+               int     utf8
             PREINIT:
               int     cords[6],rc;
+               char *  str;
+               STRLEN len;
             PPCODE:
-              if ((rc=i_tt_bbox(handle,point,str,len,cords))) {
+#ifdef SvUTF8
+               if (SvUTF8(ST(2)))
+                 utf8 = 1;
+#endif
+               str = SvPV(str_sv, len);
+              if ((rc=i_tt_bbox(handle,point,str,len,cords, utf8))) {
                  EXTEND(SP, 4);
                  PUSHs(sv_2mortal(newSViv(cords[0])));
                  PUSHs(sv_2mortal(newSViv(cords[1])));
diff --git a/font.c b/font.c
index c3796be..4107526 100644 (file)
--- a/font.c
+++ b/font.c
@@ -374,18 +374,41 @@ i_t1_text(i_img *im,int xb,int yb,i_color *cl,int fontnum,float points,char* str
 #endif /* HAVE_LIBT1 */
 
 
+/* Truetype font support */
 
+#ifdef HAVE_LIBTT
 
+#include <freetype.h>
+#define TT_CHC 5
 
+/* convert a code point into an index in the glyph cache */
+#define TT_HASH(x) ((x) & 0xFF)
 
+typedef struct i_glyph_entry_ {
+  TT_Glyph glyph;
+  unsigned long ch;
+} i_tt_glyph_entry;
 
+#define TT_NOCHAR (~0UL)
 
+struct TT_Instancehandle_ {
+  TT_Instance instance;
+  TT_Instance_Metrics imetrics;
+  TT_Glyph_Metrics gmetrics[256];
+  i_tt_glyph_entry glyphs[256];
+  int smooth;
+  int ptsize;
+  int order;
+};
 
+typedef struct TT_Instancehandle_ TT_Instancehandle;
 
-/* Truetype font support */
-
-#ifdef HAVE_LIBTT
-
+struct TT_Fonthandle_ {
+  TT_Face face;
+  TT_Face_Properties properties;
+  TT_Instancehandle instanceh[TT_CHC];
+  TT_CharMap char_map;
+};
 
 /* Defines */
 
@@ -400,13 +423,21 @@ static void i_tt_init_raster_map( TT_Raster_Map* bit, int width, int height, int
 static void i_tt_done_raster_map( TT_Raster_Map *bit );
 static void i_tt_clear_raster_map( TT_Raster_Map* bit );
 static void i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,int x_off, int y_off );
-static  int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned char j );
-static void i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int x_off, int y_off, int smooth );
-static void i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char* txt, int len, int smooth );
+static  int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j );
+static void 
+i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, 
+                   TT_Raster_Map *bit, TT_Raster_Map *small_bit, 
+                   int x_off, int y_off, int smooth );
+static int
+i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, 
+                        TT_Raster_Map *small_bit, int cords[6], 
+                        char const* txt, int len, int smooth, int utf8 );
 static void i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, int xb, int yb, i_color *cl, int smooth );
 static void i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map* bit, int xb, int yb, int channel, int smooth );
-static  int i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char* txt, int len, int smooth );
-static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6] );
+static  int
+i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], 
+                float points, char const* txt, int len, int smooth, int utf8 );
+static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6], int utf8 );
 
 
 /* static globals needed */
@@ -462,39 +493,52 @@ i_tt_get_instance( TT_Fonthandle *handle, int points, int smooth ) {
   int i,idx;
   TT_Error error;
   
-  mm_log((1,"i_tt_get_instance(handle 0x%X, points %d, smooth %d)\n",handle,points,smooth));
+  mm_log((1,"i_tt_get_instance(handle 0x%X, points %d, smooth %d)\n",
+          handle,points,smooth));
   
   if (smooth == -1) { /* Smooth doesn't matter for this search */
-    for(i=0;i<TT_CHC;i++) if (handle->instanceh[i].ptsize==points) {
-      mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
-      return i;
+    for(i=0;i<TT_CHC;i++) {
+      if (handle->instanceh[i].ptsize==points) {
+        mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
+        return i;
+      }
     }
     smooth=1; /* We will be adding a font - add it as smooth then */
   } else { /* Smooth doesn't matter for this search */
-    for(i=0;i<TT_CHC;i++) if (handle->instanceh[i].ptsize==points && handle->instanceh[i].smooth==smooth) {
-      mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
-      return i;
+    for(i=0;i<TT_CHC;i++) {
+      if (handle->instanceh[i].ptsize == points 
+          && handle->instanceh[i].smooth == smooth) {
+        mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
+        return i;
+      }
     }
   }
   
   /* Found the instance in the cache - return the cache index */
   
-  for(idx=0;idx<TT_CHC;idx++) if (!(handle->instanceh[idx].order)) break; /* find the lru item */
+  for(idx=0;idx<TT_CHC;idx++) {
+    if (!(handle->instanceh[idx].order)) break; /* find the lru item */
+  }
 
   mm_log((1,"i_tt_get_instance: lru item is %d\n",idx));
-  mm_log((1,"i_tt_get_instance: lru pointer 0x%X\n",USTRCT(handle->instanceh[idx].instance) ));
+  mm_log((1,"i_tt_get_instance: lru pointer 0x%X\n",
+          USTRCT(handle->instanceh[idx].instance) ));
   
   if ( USTRCT(handle->instanceh[idx].instance) ) {
     mm_log((1,"i_tt_get_instance: freeing lru item from cache %d\n",idx));
-    /* Free cached glyphs */
 
+    /* Free cached glyphs */
     for(i=0;i<256;i++)
-      if ( USTRCT(handle->instanceh[idx].glyphs[i]) )
-       TT_Done_Glyph( handle->instanceh[idx].glyphs[i] );
+      if ( USTRCT(handle->instanceh[idx].glyphs[i].glyph) )
+       TT_Done_Glyph( handle->instanceh[idx].glyphs[i].glyph );
 
-    for(i=0;i<256;i++) USTRCT(handle->instanceh[idx].glyphs[i])=NULL;    
-    TT_Done_Instance( handle->instanceh[idx].instance ); /* Free instance if needed */
-    
+    for(i=0;i<256;i++) {
+      handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
+      USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
+    }
+
+    /* Free instance if needed */
+    TT_Done_Instance( handle->instanceh[idx].instance );
   }
   
   /* create and initialize instance */
@@ -519,10 +563,13 @@ i_tt_get_instance( TT_Fonthandle *handle, int points, int smooth ) {
   handle->instanceh[idx].smooth=smooth;
   TT_Get_Instance_Metrics( handle->instanceh[idx].instance, &(handle->instanceh[idx].imetrics) );
 
-  /* Zero the memory for the glyph storage so they are not thought as cached if they haven't been cached
-     since this new font was loaded */
+  /* Zero the memory for the glyph storage so they are not thought as
+     cached if they haven't been cached since this new font was loaded */
 
-  for(i=0;i<256;i++) USTRCT(handle->instanceh[idx].glyphs[i])=NULL;
+  for(i=0;i<256;i++) {
+    handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
+    USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
+  }
   
   return idx;
 }
@@ -555,21 +602,28 @@ i_tt_new(char *fontname) {
   /* load the typeface */
   error = TT_Open_Face( engine, fontname, &handle->face );
   if ( error ) {
-    if ( error == TT_Err_Could_Not_Open_File ) { mm_log((1, "Could not find/open %s.\n", fontname )) }
-    else { mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname, error )); }
+    if ( error == TT_Err_Could_Not_Open_File ) {
+      mm_log((1, "Could not find/open %s.\n", fontname ));
+    }
+    else {
+      mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname, 
+              error )); 
+    }
     return NULL;
   }
   
   TT_Get_Face_Properties( handle->face, &(handle->properties) );
+
   /* First, look for a Unicode charmap */
-  
   n = handle->properties.num_CharMaps;
   USTRCT( handle->char_map )=NULL; /* Invalidate character map */
   
   for ( i = 0; i < n; i++ ) {
     TT_Get_CharMap_ID( handle->face, i, &platform, &encoding );
-    if ( (platform == 3 && encoding == 1 ) || (platform == 0 && encoding == 0 ) ) {
-      mm_log((2,"i_tt_new - found char map platform %u encoding %u\n", platform, encoding));
+    if ( (platform == 3 && encoding == 1 ) 
+         || (platform == 0 && encoding == 0 ) ) {
+      mm_log((2,"i_tt_new - found char map platform %u encoding %u\n", 
+              platform, encoding));
       TT_Get_CharMap( handle->face, i, &(handle->char_map) );
       break;
     }
@@ -750,17 +804,27 @@ Function to see if a glyph exists and if so cache it (internal)
 
 static
 int
-i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned char j) { /* FIXME: Check if unsigned char is enough */
+i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j) {
   unsigned short load_flags, code;
   TT_Error error;
 
-  mm_log((1, "i_tt_get_glyph(handle 0x%X, inst %d, j %d (%c))\n",handle,inst,j,j));
-  mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));
+  mm_log((1, "i_tt_get_glyph(handle 0x%X, inst %d, j %d (%c))\n",
+          handle,inst,j, ((j >= ' ' && j <= '~') ? j : '.')));
+  
+  /*mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));*/
 
-  if ( TT_VALID(handle->instanceh[inst].glyphs[j]) ) {
+  if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)
+       && handle->instanceh[inst].glyphs[TT_HASH(j)].ch == j) {
     mm_log((1,"i_tt_get_glyph: %d in cache\n",j));
     return 1;
   }
+
+  if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph) ) {
+    /* clean up the entry */
+    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+    handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
+  }
   
   /* Ok - it wasn't cached - try to get it in */
   load_flags = TTLOAD_SCALE_GLYPH;
@@ -771,16 +835,32 @@ i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned char j) { /* FIXME: Ch
     if ( code >= handle->properties.num_Glyphs ) code = 0;
   } else code = TT_Char_Index( handle->char_map, j );
   
-  if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[j])) ) 
+  if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)) ) {
     mm_log((1, "Cannot allocate and load glyph: error 0x%x.\n", error ));
-  if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[j], code, load_flags)) )
+    return 0;
+  }
+  if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, code, load_flags)) ) {
     mm_log((1, "Cannot allocate and load glyph: error 0x%x.\n", error ));
-  
+    /* Don't leak */
+    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+    return 0;
+  }
+
   /* At this point the glyph should be allocated and loaded */
+  handle->instanceh[inst].glyphs[TT_HASH(j)].ch = j;
+
   /* Next get the glyph metrics */
-  
-  error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[j], &handle->instanceh[inst].gmetrics[j] );
-  mm_log((1, "TT_Get_Glyph_Metrics: error 0x%x.\n", error ));
+  error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
+                                &handle->instanceh[inst].gmetrics[TT_HASH(j)] );
+  if (error) {
+    mm_log((1, "TT_Get_Glyph_Metrics: error 0x%x.\n", error ));
+    TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+    USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+    handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
+    return 0;
+  }
+
   return 1;
 }
 
@@ -880,9 +960,9 @@ calls i_tt_render_glyph to render each glyph into the bit rastermap (internal)
 */
 
 static
-void
-i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char* txt, int len, int smooth ) {
-  unsigned char j;
+int
+i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char const* txt, int len, int smooth, int utf8 ) {
+  unsigned long j;
   int i;
   TT_F26Dot6 x,y;
   
@@ -896,12 +976,27 @@ i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_
   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 */
   y=-cords[4];
   
-  for ( i = 0; i < len; i++ ) {
-    j = txt[i];
-    if ( !i_tt_get_glyph(handle,inst,j) ) continue;
-    i_tt_render_glyph( handle->instanceh[inst].glyphs[j], &handle->instanceh[inst].gmetrics[j], bit, small_bit, x, y, smooth );
-    x += handle->instanceh[inst].gmetrics[j].advance / 64;
+  while (len) {
+    if (utf8) {
+      j = i_utf8_advance(&txt, &len);
+      if (j == ~0UL) {
+        i_push_error(0, "invalid UTF8 character");
+        return 0;
+      }
+    }
+    else {
+      j = (unsigned char)*txt++;
+      --len;
+    }
+    if ( !i_tt_get_glyph(handle,inst,j) ) 
+      continue;
+    i_tt_render_glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, 
+                       &handle->instanceh[inst].gmetrics[TT_HASH(j)], bit, 
+                       small_bit, x, y, smooth );
+    x += handle->instanceh[inst].gmetrics[TT_HASH(j)].advance / 64;
   }
+
+  return 1;
 }
 
 
@@ -1018,7 +1113,7 @@ interface for generating single channel raster of text (internal)
 
 static
 int
-i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char* txt, int len, int smooth ) {
+i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char const* txt, int len, int smooth, int utf8 ) {
   int inst;
   int width, height;
   TT_Raster_Map small_bit;
@@ -1030,7 +1125,7 @@ i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float p
   }
   
   /* calculate bounding box */
-  i_tt_bbox_inst( handle, inst, txt, len, cords );
+  i_tt_bbox_inst( handle, inst, txt, len, cords, 0 );
   
   width  = cords[2]-cords[0];
   height = cords[5]-cords[4];
@@ -1041,7 +1136,8 @@ i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float p
   i_tt_clear_raster_map( bit );
   if ( smooth ) i_tt_init_raster_map( &small_bit, handle->instanceh[inst].imetrics.x_ppem + 32, height, smooth );
   
-  i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, smooth );
+  i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, 
+                          smooth, utf8 );
 
   /*  ascent = ( handle->properties.horizontal->Ascender  * handle->instanceh[inst].imetrics.y_ppem ) / handle->properties.header->Units_Per_EM; */
   
@@ -1057,7 +1153,7 @@ i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float p
 
 
 /*
-=item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth)
+=item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth, utf8)
 
 Interface to text rendering into a single channel in an image
 
@@ -1074,13 +1170,13 @@ Interface to text rendering into a single channel in an image
 */
 
 undef_int
-i_tt_cp( TT_Fonthandle *handle, i_img *im, int xb, int yb, int channel, float points, char* txt, int len, int smooth ) {
+i_tt_cp( TT_Fonthandle *handle, i_img *im, int xb, int yb, int channel, float points, char const* txt, int len, int smooth, int utf8 ) {
 
   int cords[6];
   int ascent, st_offset;
   TT_Raster_Map bit;
   
-  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth ) ) return 0;
+  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
   
   ascent=cords[5];
   st_offset=cords[0];
@@ -1093,7 +1189,7 @@ i_tt_cp( TT_Fonthandle *handle, i_img *im, int xb, int yb, int channel, float po
 
 
 /* 
-=item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth) 
+=item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth, utf8
 
 Interface to text rendering in a single color onto an image
 
@@ -1110,12 +1206,12 @@ Interface to text rendering in a single color onto an image
 */
 
 undef_int
-i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char* txt, int len, int smooth) {
+i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char const* txt, int len, int smooth, int utf8) {
   int cords[6];
   int ascent, st_offset;
   TT_Raster_Map bit;
   
-  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth ) ) return 0;
+  if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
   
   ascent=cords[5];
   st_offset=cords[0];
@@ -1128,7 +1224,7 @@ i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float
 
 
 /*
-=item i_tt_bbox_inst(handle, inst, txt, len, cords) 
+=item i_tt_bbox_inst(handle, inst, txt, len, cords, utf8
 
 Function to get texts bounding boxes given the instance of the font (internal)
 
@@ -1143,13 +1239,13 @@ Function to get texts bounding boxes given the instance of the font (internal)
 
 static
 undef_int
-i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6] ) {
+i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6], int utf8 ) {
   int i, upm, ascent, descent, gascent, gdescent, width, casc, cdesc, first, start;
-  unsigned int j;
+  unsigned long j;
   unsigned char *ustr;
   ustr=(unsigned char*)txt;
   
-  mm_log((1,"i_tt_box_inst(handle 0x%X,inst %d,txt '%.*s', len %d)\n",handle,inst,len,txt,len));
+  mm_log((1,"i_tt_box_inst(handle 0x%X,inst %d,txt '%.*s', len %d, utf8 %d)\n",handle,inst,len,txt,len, utf8));
 
   upm     = handle->properties.header->Units_Per_EM;
   gascent  = ( handle->properties.horizontal->Ascender  * handle->instanceh[inst].imetrics.y_ppem + upm - 1) / upm;
@@ -1161,10 +1257,20 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
   mm_log((1, "i_tt_box_inst: gascent=%d gdescent=%d\n", gascent, gdescent));
 
   first=1;
-  for ( i = 0; i < len; ++i ) {
-    j = ustr[i];
+  while (len) {
+    if (utf8) {
+      j = i_utf8_advance(&txt, &len);
+      if (j == ~0UL) {
+        i_push_error(0, "invalid UTF8 character");
+        return 0;
+      }
+    }
+    else {
+      j = (unsigned char)*txt++;
+      --len;
+    }
     if ( i_tt_get_glyph(handle,inst,j) ) {
-      TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + j;
+      TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + TT_HASH(j);
       width += gm->advance   / 64;
       casc   = (gm->bbox.yMax+63) / 64;
       cdesc  = (gm->bbox.yMin-63) / 64;
@@ -1206,7 +1312,7 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
 
 
 /*
-=item i_tt_bbox(handle, points, txt, len, cords)
+=item i_tt_bbox(handle, points, txt, len, cords, utf8)
 
 Interface to get a strings bounding box
 
@@ -1220,17 +1326,17 @@ Interface to get a strings bounding box
 */
 
 undef_int
-i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6]) {
+i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6], int utf8) {
   int inst;
   
-  mm_log((1,"i_tt_box(handle 0x%X,points %f,txt '%.*s', len %d)\n",handle,points,len,txt,len));
+  mm_log((1,"i_tt_box(handle 0x%X,points %f,txt '%.*s', len %d, utf8 %d)\n",handle,points,len,txt,len, utf8));
 
   if ( (inst=i_tt_get_instance(handle,points,-1)) < 0) {
     mm_log((1,"i_tt_text: get instance failed\n"));
     return 0;
   }
 
-  return i_tt_bbox_inst(handle, inst, txt, len, cords);
+  return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
 }
 
 
index 2faf2b0..b24c83b 100644 (file)
@@ -40,7 +40,6 @@ Truetype, Type1 and Windows FNT.
 #include FT_FREETYPE_H
 
 static void ft2_push_message(int code);
-static unsigned long utf8_advance(char **p, int *len);
 
 static FT_Library library;
 
@@ -286,7 +285,7 @@ Returns non-zero on success.
 */
 int
 i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, 
-           char *text, int len, int *bbox, int utf8) {
+           char const *text, int len, int *bbox, int utf8) {
   FT_Error error;
   int width;
   int index;
@@ -311,7 +310,7 @@ i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
   while (len) {
     unsigned long c;
     if (utf8) {
-      c = utf8_advance(&text, &len);
+      c = i_utf8_advance(&text, &len);
       if (c == ~0UL) {
         i_push_error(0, "invalid UTF8 character");
         return 0;
@@ -443,7 +442,7 @@ Returns non-zero on success.
 */
 int
 i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth, 
-           char *text, int len, int vlayout, int utf8, int *bbox) {
+           char const *text, int len, int vlayout, int utf8, int *bbox) {
   FT_Error error;
   int width;
   int index;
@@ -475,7 +474,7 @@ i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
   while (len) {
     unsigned long c;
     if (utf8) {
-      c = utf8_advance(&text, &len);
+      c = i_utf8_advance(&text, &len);
       if (c == ~0UL) {
         i_push_error(0, "invalid UTF8 character");
         return 0;
@@ -592,7 +591,7 @@ Returns non-zero on success.
 */
 int
 i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
-           double cheight, double cwidth, char *text, int len, int align,
+           double cheight, double cwidth, char const *text, int len, int align,
            int aa, int vlayout, int utf8) {
   FT_Error error;
   int index;
@@ -633,7 +632,7 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
   while (len) {
     unsigned long c;
     if (utf8) {
-      c = utf8_advance(&text, &len);
+      c = i_utf8_advance(&text, &len);
       if (c == ~0UL) {
         i_push_error(0, "invalid UTF8 character");
         return 0;
@@ -733,7 +732,7 @@ Returns non-zero on success.
 */
 
 i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel,
-         double cheight, double cwidth, char *text, int len, int align,
+         double cheight, double cwidth, char const *text, int len, int align,
          int aa, int vlayout, int utf8) {
   int bbox[8];
   i_img *work;
@@ -785,8 +784,8 @@ Returns the number of characters that were checked.
 
 =cut
 */
-int i_ft2_has_chars(FT2_Fonthandle *handle, char *text, int len, int utf8
-                       char *out) {
+int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len
+                    int utf8, char *out) {
   int count = 0;
   mm_log((1, "i_ft2_check_chars(handle %p, text %p, len %d, utf8 %d)\n", 
          handle, text, len, utf8));
@@ -795,7 +794,7 @@ int i_ft2_has_chars(FT2_Fonthandle *handle, char *text, int len, int utf8,
     unsigned long c;
     int index;
     if (utf8) {
-      c = utf8_advance(&text, &len);
+      c = i_utf8_advance(&text, &len);
       if (c == ~0UL) {
         i_push_error(0, "invalid UTF8 character");
         return 0;
@@ -879,83 +878,6 @@ make_bmp_map(FT_Bitmap *bitmap, unsigned char *map) {
   return 1;
 }
 
-struct utf8_size {
-  int mask, expect;
-  int size;
-};
-
-struct utf8_size utf8_sizes[] =
-{
-  { 0x80, 0x00, 1 },
-  { 0xE0, 0xC0, 2 },
-  { 0xF0, 0xE0, 3 },
-  { 0xF8, 0xF0, 4 },
-};
-
-/*
-=item utf8_advance(char **p, int *len)
-
-Retreive a UTF8 character from the stream.
-
-Modifies *p and *len to indicate the consumed characters.
-
-This doesn't support the extended UTF8 encoding used by later versions
-of Perl.
-
-=cut
-*/
-
-unsigned long utf8_advance(char **p, int *len) {
-  unsigned char c;
-  int i, ci, clen = 0;
-  unsigned char codes[3];
-  if (*len == 0)
-    return ~0UL;
-  c = *(*p)++; --*len;
-
-  for (i = 0; i < sizeof(utf8_sizes)/sizeof(*utf8_sizes); ++i) {
-    if ((c & utf8_sizes[i].mask) == utf8_sizes[i].expect) {
-      clen = utf8_sizes[i].size;
-    }
-  }
-  if (clen == 0 || *len < clen-1) {
-    --*p; ++*len;
-    return ~0UL;
-  }
-
-  /* check that each character is well formed */
-  i = 1;
-  ci = 0;
-  while (i < clen) {
-    if (((*p)[ci] & 0xC0) != 0x80) {
-      --*p; ++*len;
-      return ~0UL;
-    }
-    codes[ci] = (*p)[ci];
-    ++ci; ++i;
-  }
-  *p += clen-1; *len -= clen-1;
-  if (c & 0x80) {
-    if ((c & 0xE0) == 0xC0) {
-      return ((c & 0x1F) << 6) + (codes[0] & 0x3F);
-    }
-    else if ((c & 0xF0) == 0xE0) {
-      return ((c & 0x0F) << 12) | ((codes[0] & 0x3F) << 6) | (codes[1] & 0x3f);
-    }
-    else if ((c & 0xF8) == 0xF0) {
-      return ((c & 0x07) << 18) | ((codes[0] & 0x3F) << 12) 
-              | ((codes[1] & 0x3F) << 6) | (codes[2] & 0x3F);
-    }
-    else {
-      *p -= clen; *len += clen;
-      return ~0UL;
-    }
-  }
-  else {
-    return c;
-  }
-}
-
 /*
 =back
 
diff --git a/image.h b/image.h
index ae728dc..a918ca5 100644 (file)
--- a/image.h
+++ b/image.h
@@ -256,39 +256,16 @@ void      close_t1( void );
 
 #ifdef HAVE_LIBTT
 
-#include <freetype.h>
-#define TT_CHC 5
-
-struct TT_Instancehandle_ {
-  TT_Instance instance;
-  TT_Instance_Metrics imetrics;
-  TT_Glyph_Metrics gmetrics[256];
-  TT_Glyph glyphs[256];
-  int smooth;
-  int ptsize;
-  int order;
-};
-
-typedef struct TT_Instancehandle_ TT_Instancehandle;
-
-struct TT_Fonthandle_ {
-  TT_Face face;
-  TT_Face_Properties properties;
-  TT_Instancehandle instanceh[TT_CHC];
-  TT_CharMap char_map;
-};
+struct TT_Fonthandle_;
 
 typedef struct TT_Fonthandle_ TT_Fonthandle;
 
-
-
 undef_int i_init_tt( void );
 TT_Fonthandle* i_tt_new(char *fontname);
 void i_tt_destroy( TT_Fonthandle *handle );
-undef_int i_tt_cp( TT_Fonthandle *handle,i_img *im,int xb,int yb,int channel,float points,char* txt,int len,int smooth);
-undef_int i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char* txt, int len, int smooth);
-undef_int i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6]);
-
+undef_int i_tt_cp( TT_Fonthandle *handle,i_img *im,int xb,int yb,int channel,float points,char const* txt,int len,int smooth, int utf8);
+undef_int i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char const* txt, int len, int smooth, int utf8);
+undef_int i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6], int utf8);
 
 #endif  /* End of freetype headers */
 
@@ -303,16 +280,16 @@ extern int i_ft2_getdpi(FT2_Fonthandle *handle, int *xdpi, int *ydpi);
 extern int i_ft2_settransform(FT2_Fonthandle *handle, double *matrix);
 extern int i_ft2_sethinting(FT2_Fonthandle *handle, int hinting);
 extern int i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth, 
-                      char *text, int len, int *bbox, int utf8);
+                      char const *text, int len, int *bbox, int utf8);
 extern int i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, 
                       i_color *cl, double cheight, double cwidth, 
-                      char *text, int len, int align, int aa, int vlayout,
-                      int utf8);
+                      char const *text, int len, int align, int aa, 
+                      int vlayout, int utf8);
 extern int i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, 
                     int channel, double cheight, double cwidth, 
-                    char *text, int len, int align, int aa, int vlayout, 
+                    char const *text, int len, int align, int aa, int vlayout, 
                     int utf8);
-extern int i_ft2_has_chars(FT2_Fonthandle *handle, char *text, int len,
+extern int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len,
                            int utf8, char *work);
 
 #endif
diff --git a/imio.h b/imio.h
index 88031bf..aeb20f8 100644 (file)
--- a/imio.h
+++ b/imio.h
@@ -55,6 +55,8 @@ void  i_mempool_destroy(i_mempool *mp);
 #undef max
 #endif
 
+extern unsigned long i_utf8_advance(char const **p, int *len);
+
 /* XXX Shouldn't these go away? */
 
 int i_min(int a,int b);
diff --git a/io.c b/io.c
index cbdce63..1fdbed5 100644 (file)
--- a/io.c
+++ b/io.c
@@ -309,3 +309,81 @@ i_max(int a,int b) {
   if (a>b) return a; else return b;
 }
 
+
+struct utf8_size {
+  int mask, expect;
+  int size;
+};
+
+struct utf8_size utf8_sizes[] =
+{
+  { 0x80, 0x00, 1 },
+  { 0xE0, 0xC0, 2 },
+  { 0xF0, 0xE0, 3 },
+  { 0xF8, 0xF0, 4 },
+};
+
+/*
+=item utf8_advance(char **p, int *len)
+
+Retreive a UTF8 character from the stream.
+
+Modifies *p and *len to indicate the consumed characters.
+
+This doesn't support the extended UTF8 encoding used by later versions
+of Perl.
+
+=cut
+*/
+
+unsigned long i_utf8_advance(char const **p, int *len) {
+  unsigned char c;
+  int i, ci, clen = 0;
+  unsigned char codes[3];
+  if (*len == 0)
+    return ~0UL;
+  c = *(*p)++; --*len;
+
+  for (i = 0; i < sizeof(utf8_sizes)/sizeof(*utf8_sizes); ++i) {
+    if ((c & utf8_sizes[i].mask) == utf8_sizes[i].expect) {
+      clen = utf8_sizes[i].size;
+    }
+  }
+  if (clen == 0 || *len < clen-1) {
+    --*p; ++*len;
+    return ~0UL;
+  }
+
+  /* check that each character is well formed */
+  i = 1;
+  ci = 0;
+  while (i < clen) {
+    if (((*p)[ci] & 0xC0) != 0x80) {
+      --*p; ++*len;
+      return ~0UL;
+    }
+    codes[ci] = (*p)[ci];
+    ++ci; ++i;
+  }
+  *p += clen-1; *len -= clen-1;
+  if (c & 0x80) {
+    if ((c & 0xE0) == 0xC0) {
+      return ((c & 0x1F) << 6) + (codes[0] & 0x3F);
+    }
+    else if ((c & 0xF0) == 0xE0) {
+      return ((c & 0x0F) << 12) | ((codes[0] & 0x3F) << 6) | (codes[1] & 0x3f);
+    }
+    else if ((c & 0xF8) == 0xF0) {
+      return ((c & 0x07) << 18) | ((codes[0] & 0x3F) << 12) 
+              | ((codes[1] & 0x3F) << 6) | (codes[2] & 0x3F);
+    }
+    else {
+      *p -= clen; *len += clen;
+      return ~0UL;
+    }
+  }
+  else {
+    return c;
+  }
+}
+
index 1080d33..2bf14b8 100644 (file)
@@ -39,15 +39,22 @@ sub new {
 sub _draw {
   my $self = shift;
   my %input = @_;
+
+  # note that the string length parameter is ignored and calculated in
+  # XS with SvPV(), since we want the number of bytes rather than the
+  # number of characters, which is what we'd get in perl for a UTF8
+  # encoded string in 5.6 and later
+
   if ( exists $input{channel} ) {
     Imager::i_tt_cp($self->{id},$input{image}{IMG},
                    $input{'x'}, $input{'y'}, $input{channel}, $input{size},
-                   $input{string}, length($input{string}),$input{aa}); 
+                   $input{string}, length($input{string}),$input{aa},
+                    $input{utf8}); 
   } else {
     Imager::i_tt_text($self->{id}, $input{image}{IMG}, 
                      $input{'x'}, $input{'y'}, $input{color},
                      $input{size}, $input{string}, 
-                     length($input{string}), $input{aa}); 
+                     length($input{string}), $input{aa}, $input{utf8}); 
   }
 }
 
@@ -55,9 +62,12 @@ sub _bounding_box {
   my $self = shift;
   my %input = @_;
   return Imager::i_tt_bbox($self->{id}, $input{size},
-                          $input{string}, length($input{string}));
+                          $input{string}, length($input{string}),
+                           $input{utf8});
 }
 
+sub utf8 { 1 }
+
 1;
 
 __END__
index 39eb7c8..5e68b3e 100644 (file)
@@ -5,26 +5,27 @@
 
 # Change 1..1 below to 1..last_test_to_print .
 # (It may become useful if the test is moved to ./t subdirectory.)
-
-BEGIN { $| = 1; print "1..3\n"; }
+use strict;
+my $loaded;
+BEGIN { $| = 1; print "1..23\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all);
+require "t/testtools.pl";
 $loaded = 1;
-print "ok 1\n";
+
+okx(1, "Loaded");
 
 init_log("testout/t35ttfont.log",2);
 
-sub skip { 
-  print "ok 2 # skip\n";
-  print "ok 3 # skip\n";
+unless (i_has_format("tt")) { 
+  skipx(22, "freetype 1.x unavailable or disabled");
   malloc_state();
-  exit(0);
+  exit;
 }
-
-if (!(i_has_format("tt")) ) { skip(); } 
 print "# has tt\n";
 
-$fontname=$ENV{'TTFONTTEST'}||'./fontfiles/dodge.ttf';
+my $deffont = './fontfiles/dodge.ttf';
+my $fontname=$ENV{'TTFONTTEST'} || $deffont;
 
 if (! -f $fontname) {
   print "# cannot find fontfile for truetype test $fontname\n";
@@ -34,48 +35,96 @@ if (! -f $fontname) {
 i_init_fonts();
 #     i_tt_set_aa(1);
 
-$bgcolor = i_color_new(255,0,0,0);
-$overlay = Imager::ImgRaw::new(200,70,3);
+my $bgcolor = i_color_new(255,0,0,0);
+my $overlay = Imager::ImgRaw::new(200,70,3);
 
-$ttraw = Imager::i_tt_new($fontname);
+my $ttraw = Imager::i_tt_new($fontname);
+okx($ttraw, "create font");
 
 #use Data::Dumper;
 #warn Dumper($ttraw);
 
-@bbox = i_tt_bbox($ttraw,50.0,'XMCLH',5);
+my @bbox = i_tt_bbox($ttraw,50.0,'XMCLH',5,0);
+okx(@bbox == 6, "bounding box");
 print "#bbox: ($bbox[0], $bbox[1]) - ($bbox[2], $bbox[3])\n";
 
-i_tt_cp($ttraw,$overlay,5,50,1,50.0,'XMCLH',5,1);
+okx(i_tt_cp($ttraw,$overlay,5,50,1,50.0,'XMCLH',5,1,0), "cp output");
 i_draw($overlay,0,50,100,50,$bgcolor);
 
 open(FH,">testout/t35ttfont.ppm") || die "cannot open testout/t35ttfont.ppm\n";
 binmode(FH);
-$IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($overlay, $IO);
+my $IO = Imager::io_new_fd( fileno(FH) );
+okx(i_writeppm_wiol($overlay, $IO), "save t35ttfont.ppm");
 close(FH);
 
-print "ok 2\n";
-
 $bgcolor=i_color_set($bgcolor,200,200,200,0);
-$backgr=Imager::ImgRaw::new(500,300,3);
+my $backgr=Imager::ImgRaw::new(500,300,3);
 
 #     i_tt_set_aa(2);
 
-i_tt_text($ttraw,$backgr,100,120,$bgcolor,50.0,'test',4,1);
+okx(i_tt_text($ttraw,$backgr,100,120,$bgcolor,50.0,'test',4,1,0),
+   "normal output");
 
 my $ugly = Imager::i_tt_new("./fontfiles/ImUgly.ttf");
-i_tt_text($ugly, $backgr,100, 80, $bgcolor, 14, 'g%g', 3, 1);
-i_tt_text($ugly, $backgr,150, 80, $bgcolor, 14, 'delta', 5, 1);
+okx($ugly, "create ugly font");
+# older versions were dropping the bottom of g and the right of a
+okx(i_tt_text($ugly, $backgr,100, 80, $bgcolor, 14, 'g%g', 3, 1, 0), 
+    "draw g%g");
+okx(i_tt_text($ugly, $backgr,150, 80, $bgcolor, 14, 'delta', 5, 1, 0),
+   "draw delta");
 i_draw($backgr,0,20,499,20,i_color_new(0,127,0,0));
-i_tt_text($ttraw, $backgr, 20, 20, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1);
-i_tt_text($ttraw, $backgr, 20, 50, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1);
-
+okx(i_tt_text($ttraw, $backgr, 20, 20, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1, 0), "alphabet");
+okx(i_tt_text($ttraw, $backgr, 20, 50, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1, 0), "ALPHABET");
+
+# UTF8 tests
+# for perl < 5.6 we can hand-encode text
+# the following is "A\x{2010}A"
+# 
+my $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
+my $alttext = "A-A";
+
+my @utf8box = i_tt_bbox($ttraw, 50.0, $text, length($text), 1);
+okx(@utf8box == 6, "utf8 bbox element count");
+my @base = i_tt_bbox($ttraw, 50.0, $alttext, length($alttext), 0);
+okx(@base == 6, "alt bbox element count");
+my $maxdiff = $fontname eq $deffont ? 0 : $base[2] / 3;
+print "# (@utf8box vs @base)\n";
+okx(abs($utf8box[2] - $base[2]) <= $maxdiff, 
+    "compare box sizes $utf8box[2] vs $base[2] (maxerror $maxdiff)");
+
+# hand-encoded UTF8 drawing
+okx(i_tt_text($ttraw, $backgr, 200, 80, $bgcolor, 14, $text, length($text), 1, 1), "draw hand-encoded UTF8");
+
+okx(i_tt_cp($ttraw, $backgr, 250, 80, 1, 14, $text, length($text), 1, 1), 
+    "cp hand-encoded UTF8");
+
+# ok, try native perl UTF8 if available
+if ($] >= 5.006) {
+  my $text;
+  # we need to do this in eval to prevent compile time errors in older
+  # versions
+  eval q{$text = "A\x{2010}A"}; # A, HYPHEN, A in our test font
+  #$text = "A".chr(0x2010)."A"; # this one works too
+  okx(i_tt_text($ttraw, $backgr, 300, 80, $bgcolor, 14, $text, 0, 1, 0),
+      "draw UTF8");
+  okx(i_tt_cp($ttraw, $backgr, 350, 80, 0, 14, $text, 0, 1, 0),
+      "cp UTF8");
+  @utf8box = i_tt_bbox($ttraw, 50.0, $text, length($text), 0);
+  okx(@utf8box == 6, "native utf8 bbox element count");
+  okx(abs($utf8box[2] - $base[2]) <= $maxdiff, 
+    "compare box sizes native $utf8box[2] vs $base[2] (maxerror $maxdiff)");
+  eval q{$text = "A\x{0905}\x{0906}\x{0103}A"}; # Devanagari
+  okx(i_tt_text($ugly, $backgr, 100, 160, $bgcolor, 36, $text, 0, 1, 0),
+      "more complex output");
+}
+else {
+  skipx(5, "perl too old to test native UTF8 support");
+}
 
 open(FH,">testout/t35ttfont2.ppm") || die "cannot open testout/t35ttfont.ppm\n";
 binmode(FH);
 $IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($backgr, $IO);
+okx(i_writeppm_wiol($backgr, $IO), "save t35ttfont2.ppm");
 close(FH);
 
-print "ok 3\n";
-
+okx(1, "end of code");
index f42f9ef..2fe5b69 100644 (file)
@@ -11,11 +11,13 @@ use strict;
 # Change 1..1 below to 1..last_test_to_print .
 # (It may become useful if the test is moved to ./t subdirectory.)
 
-BEGIN { $| = 1; print "1..7\n"; }
-END {print "not ok 1\n" unless $::loaded;}
+my $loaded;
+BEGIN { $| = 1; print "1..13\n"; }
+END {print "not ok 1\n" unless $loaded;}
 use Imager;
-$::loaded=1;
-print "ok 1\n";
+require "t/testtools.pl";
+$loaded=1;
+okx(1, "loaded");
 
 init_log("testout/t36oofont.log", 1);
 
@@ -28,8 +30,6 @@ die $Imager::ERRSTR unless $green;
 my $red=Imager::Color->new(205, 92, 92, 255);
 die $Imager::ERRSTR unless $red;
 
-
-
 if (i_has_format("t1") and -f $fontname_pfb) {
 
   my $img=Imager->new(xsize=>300, ysize=>100) or die "$Imager::ERRSTR\n";
@@ -37,29 +37,25 @@ if (i_has_format("t1") and -f $fontname_pfb) {
   my $font=Imager::Font->new(file=>$fontname_pfb,size=>25)
     or die $img->{ERRSTR};
 
-  print "ok 2\n";
-
-  $img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100) 
-    or die $img->{ERRSTR};
-
-  print "ok 3\n";
+  okx(1, "created font");
 
+  okx($img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100),
+      "draw text");
   $img->line(x1=>0, x2=>300, y1=>50, y2=>50, color=>$green);
 
   my $text="LLySja";
   my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
 
-  print @bbox ? '' : 'not ', "ok 4\n";
+  okx(@bbox == 6, "bounding box list length");
 
   $img->box(box=>\@bbox, color=>$green);
 
-  $img->write(file=>"testout/t36oofont1.ppm", type=>'pnm')
-    or die "cannot write to testout/t36oofont1.ppm: $img->{ERRSTR}\n";
+  okx($img->write(file=>"testout/t36oofont1.ppm", type=>'pnm'),
+      "write t36oofont1.ppm")
+    or print "# ",$img->errstr,"\n";
 
 } else {
-  print "ok 2 # skip\n";
-  print "ok 3 # skip\n";
-  print "ok 4 # skip\n";
+  skipx(4, "T1lib missing or disabled");
 }
 
 if (i_has_format("tt") and -f $fontname_tt) {
@@ -69,28 +65,41 @@ if (i_has_format("tt") and -f $fontname_tt) {
   my $font=Imager::Font->new(file=>$fontname_tt,size=>25)
     or die $img->{ERRSTR};
 
-  print "ok 5\n";
+  okx(1, "create TT font object");
 
-  $img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100) 
-    or die $img->{ERRSTR};
-
-  print "ok 6\n";
+  okx($img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100),
+      "draw text");
 
   $img->line(x1=>0, x2=>300, y1=>50, y2=>50, color=>$green);
 
   my $text="LLySja";
   my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
 
-  print @bbox ? '' : 'not ', "ok 7\n";
+  okx(@bbox == 6, "bbox list size");
 
   $img->box(box=>\@bbox, color=>$green);
 
-  $img->write(file=>"testout/t36oofont2.ppm", type=>'pnm')
-    or die "cannot write to testout/t36oofont2.ppm: $img->{ERRSTR}\n";
+  $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
+  okx($img->string(font=>$font, text=>$text, 'x'=>100, 'y'=>50, utf8=>1),
+      "draw hand-encoded UTF8 text");
+
+  if($] >= 5.006) {
+    eval q{$text = "A\x{2010}A"};
+    okx($img->string(font=>$font, text=>$text, 'x'=>200, 'y'=>50),
+       "draw native UTF8 text");
+  }
+  else {
+    skipx(1, "perl too old for native utf8");
+  }
+
+  okx($img->write(file=>"testout/t36oofont2.ppm", type=>'pnm'),
+      "write t36oofont2.ppm")
+    or print "# ", $img->errstr,"\n";
+
+  okx($font->utf8, "make sure utf8 method returns true");
 
 } else {
-  print "ok 5 # skip\n";
-  print "ok 6 # skip\n";
-  print "ok 7 # skip\n";
+  skipx(7, "FT1.x missing or disabled");
 }
 
+okx(1, "end");
index 4503888..997e831 100644 (file)
@@ -1,5 +1,8 @@
 # this doesn't need a new namespace - I hope
 use Imager qw(:all);
+use vars qw($TESTNUM);
+
+$TESTNUM = 1;
 
 sub test_img {
   my $green=i_color_new(0,255,0,255);
@@ -24,5 +27,31 @@ sub skipn {
   print "ok $_ # skip $why\n" for $testnum ... $testnum+$count-1;
 }
 
+sub skipx {
+  my ($count, $why) = @_;
+
+  skipn($TESTNUM, $count, $why);
+  $TESTNUM += $count;
+}
+
+sub okx {
+  my ($ok, $comment) = @_;
+
+  return okn($TESTNUM++, $ok, $comment);
+}
+
+sub okn {
+  my ($num, $ok, $comment) = @_;
+
+  if ($ok) {
+    print "ok $num # $comment\n";
+  }
+  else {
+    print "not ok $num # $comment\n";
+  }
+
+  return $ok;
+}
+
 1;