- implemented i_t1_has_chars(), tests for same
authorTony Cook <tony@develop=help.com>
Tue, 31 Dec 2002 14:38:39 +0000 (14:38 +0000)
committerTony Cook <tony@develop=help.com>
Tue, 31 Dec 2002 14:38:39 +0000 (14:38 +0000)
        - added ExistenceTest.{pfb,afm,ttf} for testing $font->has_chars
        - tests for Imager::Font::Type1::has_chars();
        - tests for Imager::Font::Truetype::has_chars();
        - internal and external bounding box calculations now use
          the same hint flags as text output for Freetype 2.x
        - made the i_foo_bbox() interface more expandable by using
          symbolic constants for the sizes and array offsets
        - added a / character to the ExistenceTest.foo fonts that
          overlaps the right side of the character cell, to test the
          advance width reporting.
        - added advance width to the i_foo_bbox() interface, and
          implemented it for FT2, FT1 and Type 1
        - Imager::Font::bounding_box() now returns an Imager::Font::BBox
          object in scalar context.
        - implemented $font->align() text output method, for simple output
          of aligned text
        - created Imager::Font::Wrap::wrap_text to perform simple text
          wrapping
        - FT1, FT2 and T1 fonts now support the face_name method
        - FT1, FT2 and T1 now support the glyph_names() method

29 files changed:
Changes
Imager.pm
Imager.xs
MANIFEST
TODO
bigtest.perl
datatypes.h
font.c
fontfiles/ExistenceTest.afm [new file with mode: 0644]
fontfiles/ExistenceTest.pfb [new file with mode: 0644]
fontfiles/ExistenceTest.sfd [new file with mode: 0644]
fontfiles/ExistenceTest.ttf [new file with mode: 0644]
freetyp2.c
image.h
lib/Imager/Engines.pod
lib/Imager/Font.pm
lib/Imager/Font/BBox.pm [new file with mode: 0644]
lib/Imager/Font/FreeType2.pm
lib/Imager/Font/Truetype.pm
lib/Imager/Font/Type1.pm
lib/Imager/Font/Wrap.pm [new file with mode: 0644]
samples/transform.pl [new file with mode: 0644]
samples/transform1.ppm [new file with mode: 0644]
t/t30t1font.t
t/t35ttfont.t
t/t36oofont.t
t/t38ft2font.t
t/t80texttools.t [new file with mode: 0644]
t/testtools.pl

diff --git a/Changes b/Changes
index 1d46f9e..6afb350 100644 (file)
--- a/Changes
+++ b/Changes
@@ -690,6 +690,27 @@ Revision history for Perl extension Imager.
         - the code to read multiple tiffs didn't handle files with more
           than five images correctly, causing a memory overrun.
         - fix some minor test code hiccups
+        - implemented i_t1_has_chars(), tests for same
+        - added ExistenceTest.{pfb,afm,ttf} for testing $font->has_chars
+        - tests for Imager::Font::Type1::has_chars();
+        - tests for Imager::Font::Truetype::has_chars();
+        - internal and external bounding box calculations now use
+          the same hint flags as text output for Freetype 2.x
+        - made the i_foo_bbox() interface more expandable by using
+          symbolic constants for the sizes and array offsets
+        - added a / character to the ExistenceTest.foo fonts that 
+          overlaps the right side of the character cell, to test the 
+          advance width reporting.
+        - added advance width to the i_foo_bbox() interface, and 
+          implemented it for FT2, FT1 and Type 1
+        - Imager::Font::bounding_box() now returns an Imager::Font::BBox
+          object in scalar context.
+        - implemented $font->align() text output method, for simple output
+          of aligned text
+        - created Imager::Font::Wrap::wrap_text to perform simple text 
+          wrapping
+        - FT1, FT2 and T1 fonts now support the face_name method
+        - FT1, FT2 and T1 now support the glyph_names() method
 
 =================================================================
 
index 744f221..6d282df 100644 (file)
--- a/Imager.pm
+++ b/Imager.pm
@@ -2708,7 +2708,7 @@ In cases where no image object is associated with an operation
 C<$Imager::ERRSTR> is used to report errors not directly associated
 with an image object.
 
-The C<Imager-><gt>new> method is described in detail in the 
+The C<Imager-E<gt>new> method is described in detail in the 
 Imager::ImageTypes manpage.
 
 =head1 SUPPORT
index 1d8afcb..d7504d3 100644 (file)
--- a/Imager.xs
+++ b/Imager.xs
@@ -1715,18 +1715,21 @@ i_t1_bbox(fontnum,point,str_sv,len_ignored,utf8=0,flags="")
             PREINIT:
                char *str;
                STRLEN len;
-              int     cords[6];
+              int     cords[BOUNDING_BOX_COUNT];
                int i;
+               int rc;
             PPCODE:
 #ifdef SvUTF8
                if (SvUTF8(str_sv))
                  utf8 = 1;
 #endif
                str = SvPV(str_sv, len);
-              i_t1_bbox(fontnum,point,str,len,cords,utf8,flags);
-               EXTEND(SP, 6);
-               for (i = 0; i < 6; ++i)
-                 PUSHs(sv_2mortal(newSViv(cords[i])));
+               rc = i_t1_bbox(fontnum,point,str,len,cords,utf8,flags);
+               if (rc > 0) {
+                 EXTEND(SP, rc);
+                 for (i = 0; i < rc; ++i)
+                   PUSHs(sv_2mortal(newSViv(cords[i])));
+               }
 
 
 
@@ -1756,6 +1759,89 @@ i_t1_text(im,xb,yb,cl,fontnum,points,str_sv,len_ignored,align,utf8=0,flags="")
            OUTPUT:
              RETVAL
 
+void
+i_t1_has_chars(handle, text_sv, utf8 = 0)
+        int handle
+        SV  *text_sv
+        int utf8
+      PREINIT:
+        char const *text;
+        STRLEN len;
+        char *work;
+        int count;
+        int i;
+      PPCODE:
+#ifdef SvUTF8
+        if (SvUTF8(text_sv))
+          utf8 = 1;
+#endif
+        text = SvPV(text_sv, len);
+        work = mymalloc(len);
+        count = i_t1_has_chars(handle, text, len, utf8, work);
+        if (GIMME_V == G_ARRAY) {
+          EXTEND(SP, count);
+          for (i = 0; i < count; ++i) {
+            PUSHs(sv_2mortal(newSViv(work[i])));
+          }
+        }
+        else {
+          EXTEND(SP, 1);
+          PUSHs(sv_2mortal(newSVpv(work, count)));
+        }
+        myfree(work);
+
+void
+i_t1_face_name(handle)
+        int handle
+      PREINIT:
+        char name[255];
+        int len;
+      PPCODE:
+        len = i_t1_face_name(handle, name, sizeof(name));
+        if (len) {
+          EXTEND(SP, 1);
+          PUSHs(sv_2mortal(newSVpv(name, strlen(name))));
+        }
+
+void i_t1_glyph_name(handle, text_sv, utf8 = 0)
+        int handle
+        SV *text_sv
+        int utf8
+      PREINIT:
+        char const *text;
+        STRLEN work_len;
+        int len;
+        int outsize;
+        char name[255];
+      PPCODE:
+#ifdef SvUTF8
+        if (SvUTF8(text_sv))
+          utf8 = 1;
+#endif
+        text = SvPV(text_sv, work_len);
+        len = work_len;
+        while (len) {
+          unsigned char ch;
+          if (utf8) {
+            ch = i_utf8_advance(&text, &len);
+            if (ch == ~0UL) {
+              i_push_error(0, "invalid UTF8 character");
+              break;
+            }
+          }
+          else {
+            ch = *text++;
+            --len;
+          }
+          EXTEND(SP, 1);
+          if (outsize = i_t1_glyph_name(handle, ch, name, sizeof(name))) {
+            PUSHs(sv_2mortal(newSVpv(name, 0)));
+          }
+          else {
+            PUSHs(&PL_sv_undef);
+          } 
+        }
+
 #endif 
 
 #ifdef HAVE_LIBTT
@@ -1840,9 +1926,10 @@ i_tt_bbox(handle,point,str_sv,len_ignored, utf8)
               int     len_ignored
                int     utf8
             PREINIT:
-              int     cords[6],rc;
+              int     cords[BOUNDING_BOX_COUNT],rc;
                char *  str;
                STRLEN len;
+               int i;
             PPCODE:
 #ifdef SvUTF8
                if (SvUTF8(ST(2)))
@@ -1850,13 +1937,10 @@ i_tt_bbox(handle,point,str_sv,len_ignored, utf8)
 #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])));
-                 PUSHs(sv_2mortal(newSViv(cords[2])));
-                 PUSHs(sv_2mortal(newSViv(cords[3])));
-                 PUSHs(sv_2mortal(newSViv(cords[4])));
-                 PUSHs(sv_2mortal(newSViv(cords[5])));
+                 EXTEND(SP, rc);
+                 for (i = 0; i < rc; ++i) {
+                   PUSHs(sv_2mortal(newSViv(cords[i])));
+                 }
                }
 
 void
@@ -1890,9 +1974,63 @@ i_tt_has_chars(handle, text_sv, utf8)
         }
         myfree(work);
 
-#endif 
+void
+i_tt_dump_names(handle)
+        Imager::Font::TT handle
 
+void
+i_tt_face_name(handle)
+        Imager::Font::TT handle
+      PREINIT:
+        char name[255];
+        int len;
+      PPCODE:
+        len = i_tt_face_name(handle, name, sizeof(name));
+        if (len) {
+          EXTEND(SP, 1);
+          PUSHs(sv_2mortal(newSVpv(name, strlen(name))));
+        }
 
+void i_tt_glyph_name(handle, text_sv, utf8 = 0)
+        Imager::Font::TT handle
+        SV *text_sv
+        int utf8
+      PREINIT:
+        char const *text;
+        STRLEN work_len;
+        int len;
+        int outsize;
+        char name[255];
+      PPCODE:
+#ifdef SvUTF8
+        if (SvUTF8(text_sv))
+          utf8 = 1;
+#endif
+        text = SvPV(text_sv, work_len);
+        len = work_len;
+        while (len) {
+          unsigned char ch;
+          if (utf8) {
+            ch = i_utf8_advance(&text, &len);
+            if (ch == ~0UL) {
+              i_push_error(0, "invalid UTF8 character");
+              break;
+            }
+          }
+          else {
+            ch = *text++;
+            --len;
+          }
+          EXTEND(SP, 1);
+          if (outsize = i_tt_glyph_name(handle, ch, name, sizeof(name))) {
+            PUSHs(sv_2mortal(newSVpv(name, 0)));
+          }
+          else {
+            PUSHs(&PL_sv_undef);
+          } 
+        }
+
+#endif 
 
 
 #ifdef HAVE_LIBJPEG
@@ -3813,17 +3951,14 @@ i_wf_bbox(face, size, text)
        char *face
        int size
        char *text
+        int rc, i;
       PREINIT:
-       int cords[6];
+       int cords[BOUNDING_BOX_COUNT];
       PPCODE:
-        if (i_wf_bbox(face, size, text, strlen(text), cords)) {
-          EXTEND(SP, 6);  
-          PUSHs(sv_2mortal(newSViv(cords[0])));
-          PUSHs(sv_2mortal(newSViv(cords[1])));
-          PUSHs(sv_2mortal(newSViv(cords[2])));
-          PUSHs(sv_2mortal(newSViv(cords[3])));
-          PUSHs(sv_2mortal(newSViv(cords[4])));
-          PUSHs(sv_2mortal(newSViv(cords[5])));
+        if (rc = i_wf_bbox(face, size, text, strlen(text), cords)) {
+          EXTEND(SP, rc);  
+          for (i = 0; i < rc; ++i) 
+            PUSHs(sv_2mortal(newSViv(cords[i])));
         }
 
 undef_int
@@ -3930,23 +4065,28 @@ i_ft2_settransform(font, matrix)
         RETVAL
 
 void
-i_ft2_bbox(font, cheight, cwidth, text, utf8)
+i_ft2_bbox(font, cheight, cwidth, text_sv, utf8)
         Imager::Font::FT2 font
         double cheight
         double cwidth
-        char *text
+        SV *text_sv
        int utf8
       PREINIT:
-        int bbox[6];
+        int bbox[BOUNDING_BOX_COUNT];
         int i;
+        char *text;
+        STRLEN text_len;
+        int rc;
       PPCODE:
+        text = SvPV(text_sv, text_len);
 #ifdef SvUTF8
-        if (SvUTF8(ST(3)))
+        if (SvUTF8(text_sv))
           utf8 = 1;
 #endif
-        if (i_ft2_bbox(font, cheight, cwidth, text, strlen(text), bbox, utf8)) {
-          EXTEND(SP, 6);
-          for (i = 0; i < 6; ++i)
+        rc = i_ft2_bbox(font, cheight, cwidth, text, text_len, bbox, utf8);
+        if (rc) {
+          EXTEND(SP, rc);
+          for (i = 0; i < rc; ++i)
             PUSHs(sv_2mortal(newSViv(bbox[i])));
         }
 
@@ -4074,6 +4214,65 @@ i_ft2_has_chars(handle, text_sv, utf8)
         }
         myfree(work);
 
+void
+i_ft2_face_name(handle)
+        Imager::Font::FT2 handle
+      PREINIT:
+        char name[255];
+        int len;
+      PPCODE:
+        len = i_ft2_face_name(handle, name, sizeof(name));
+        if (len) {
+          EXTEND(SP, 1);
+          PUSHs(sv_2mortal(newSVpv(name, 0)));
+        }
+
+void i_ft2_glyph_name(handle, text_sv, utf8 = 0)
+        Imager::Font::FT2 handle
+        SV *text_sv
+        int utf8
+      PREINIT:
+        char const *text;
+        STRLEN work_len;
+        int len;
+        int outsize;
+        char name[255];
+      PPCODE:
+#ifdef SvUTF8
+        if (SvUTF8(text_sv))
+          utf8 = 1;
+#endif
+        text = SvPV(text_sv, work_len);
+        len = work_len;
+        while (len) {
+          unsigned char ch;
+          if (utf8) {
+            ch = i_utf8_advance(&text, &len);
+            if (ch == ~0UL) {
+              i_push_error(0, "invalid UTF8 character");
+              break;
+            }
+          }
+          else {
+            ch = *text++;
+            --len;
+          }
+          EXTEND(SP, 1);
+          if (outsize = i_ft2_glyph_name(handle, ch, name, sizeof(name))) {
+            PUSHs(sv_2mortal(newSVpv(name, 0)));
+          }
+          else {
+            PUSHs(&PL_sv_undef);
+          } 
+        }
+
+int
+i_ft2_can_do_glyph_names()
+
+int
+i_ft2_face_has_glyph_names(handle)
+        Imager::Font::FT2 handle
+
 #endif
 
 MODULE = Imager         PACKAGE = Imager::FillHandle PREFIX=IFILL_
index fcabafb..ac14cf1 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -52,6 +52,9 @@ tags.c
 trans2.c
 iolayer.h
 iolayer.c
+fontfiles/ExistenceTest.afm     please edit ExistenceTest.sfd in CVS
+fontfiles/ExistenceTest.pfb     to change these files, edited and
+fontfiles/ExistenceTest.ttf     generated using pfaedit
 fontfiles/ImUgly.ttf
 fontfiles/dcr10.afm
 fontfiles/dcr10.pfb
@@ -68,10 +71,12 @@ lib/Imager/Filters.pod
 lib/Imager/Engines.pod
 lib/Imager/Fill.pm
 lib/Imager/Font.pm
+lib/Imager/Font/BBox.pm
 lib/Imager/Font/Type1.pm
 lib/Imager/Font/Truetype.pm
 lib/Imager/Font/FreeType2.pm
 lib/Imager/Font/Win32.pm
+lib/Imager/Font/Wrap.pm
 lib/Imager/Fountain.pm
 lib/Imager/interface.pod
 lib/Imager/Matrix2d.pm
@@ -120,6 +125,7 @@ t/t68map.t
 t/t69rubthru.t
 t/t70newgif.t
 t/t75polyaa.t
+t/t80texttools.t        Test text wrapping
 t/t90cc.t
 testimg/bandw.gif
 testimg/comp4.bmp       Compressed 4-bit/pixel BMP
diff --git a/TODO b/TODO
index daf7d2e..42e2df7 100644 (file)
--- a/TODO
+++ b/TODO
@@ -136,9 +136,9 @@ Clean up:
 
 - try to clean up the inconsistencies between font types:
   - utf8 (even if we just treat characters over 0xFF as missing for T1)
-    (done for FT2, FT1)
+    (done for FT2, FT1, T1)
   - transformations (done for FT2)
-  - has_char() method (done for FT2, FT1)
+  - has_char() method (done for FT2, FT1, T1)
 
 Format specific issues:
 - provide patches for libgif and libungif that fix their bugs
index 0fdcca8..dd04dac 100644 (file)
@@ -25,7 +25,7 @@ system("$make clean") if -e 'Makefile' && !$opts{d};
 for my $set (0..$top) {
   ++$total;
   $ENV{IM_ENABLE} = join(' ', grep($set & $bits{$_}, @opts));
-  print STDERR $opts{v} ? "Enable: $ENV{IM_ENABLE}\n" : '.';
+  print STDERR $opts{v} ? "$set/$top Enable: $ENV{IM_ENABLE}\n" : '.';
   system("echo '****' \$IM_ENABLE >>testout/bigtest.txt");
   if ($opts{d}) {
     if (system("$make $makeopts disttest >>testout/bigtest.txt 2>&1")) {
index 7e2ac9b..5fa528d 100644 (file)
@@ -226,5 +226,17 @@ void octt_dump(struct octt *ct);
 void octt_count(struct octt *ct,int *tot,int max,int *overflow);
 void octt_delete(struct octt *ct);
 
+/* font bounding box results */
+enum bounding_box_index_t {
+  BBOX_NEG_WIDTH,
+  BBOX_GLOBAL_DESCENT,
+  BBOX_POS_WIDTH,
+  BBOX_GLOBAL_ASCENT,
+  BBOX_DESCENT,
+  BBOX_ASCENT,
+  BBOX_ADVANCE_WIDTH,
+  BOUNDING_BOX_COUNT
+};
+
 #endif
 
diff --git a/font.c b/font.c
index 15037ad..5a40feb 100644 (file)
--- a/font.c
+++ b/font.c
@@ -44,14 +44,6 @@ Some of these functions are internal.
 
 */
 
-
-
-
-
-
-
-
-
 /* 
 =item i_init_fonts()
 
@@ -88,6 +80,8 @@ i_init_fonts(int t1log) {
 static int t1_get_flags(char const *flags);
 static char *t1_from_utf8(char const *in, int len, int *outlen);
 
+static void t1_push_error(void);
+
 /* 
 =item i_init_t1(t1log)
 
@@ -294,11 +288,12 @@ function to get a strings bounding box given the font id and sizes
 =cut
 */
 
-void
+int
 i_t1_bbox(int fontnum,float points,char *str,int len,int cords[6], int utf8,char const *flags) {
   BBox bbox;
   BBox gbbox;
   int mod_flags = t1_get_flags(flags);
+  int advance;
   
   mm_log((1,"i_t1_bbox(fontnum %d,points %.2f,str '%.*s', len %d)\n",fontnum,points,len,str,len));
   T1_LoadFont(fontnum);  /* FIXME: Here a return code is ignored - haw haw haw */ 
@@ -312,6 +307,7 @@ i_t1_bbox(int fontnum,float points,char *str,int len,int cords[6], int utf8,char
     bbox = T1_GetStringBBox(fontnum,str,len,0,mod_flags);
   }
   gbbox = T1_GetFontBBox(fontnum);
+  advance = T1_GetStringWidth(fontnum, str, len, 0, mod_flags);
   
   mm_log((1,"bbox: (%d,%d,%d,%d)\n",
          (int)(bbox.llx*points/1000),
@@ -322,14 +318,18 @@ i_t1_bbox(int fontnum,float points,char *str,int len,int cords[6], int utf8,char
          (int)(bbox.ury*points/1000) ));
 
 
-  cords[0]=((float)bbox.llx*points)/1000;
-  cords[2]=((float)bbox.urx*points)/1000;
+  cords[BBOX_NEG_WIDTH]=((float)bbox.llx*points)/1000;
+  cords[BBOX_POS_WIDTH]=((float)bbox.urx*points)/1000;
+
+  cords[BBOX_GLOBAL_DESCENT]=((float)gbbox.lly*points)/1000;
+  cords[BBOX_GLOBAL_ASCENT]=((float)gbbox.ury*points)/1000;
+
+  cords[BBOX_DESCENT]=((float)bbox.lly*points)/1000;
+  cords[BBOX_ASCENT]=((float)bbox.ury*points)/1000;
 
-  cords[1]=((float)gbbox.lly*points)/1000;
-  cords[3]=((float)gbbox.ury*points)/1000;
+  cords[BBOX_ADVANCE_WIDTH] = ((float)advance * points)/1000;
 
-  cords[4]=((float)bbox.lly*points)/1000;
-  cords[5]=((float)bbox.ury*points)/1000;
+  return BBOX_ADVANCE_WIDTH+1;
 }
 
 
@@ -455,16 +455,239 @@ t1_from_utf8(char const *in, int len, int *outlen) {
   return out;
 }
 
+/*
+=item i_t1_has_chars(font_num, text, len, utf8, out)
+
+Check if the given characters are defined by the font.  Note that len
+is the number of bytes, not the number of characters (when utf8 is
+non-zero).
+
+out[char index] will be true if the character exists.
+
+Accepts UTF-8, but since T1 can only have 256 characters, any chars
+with values over 255 will simply be returned as false.
+
+Returns the number of characters that were checked.
+
+=cut
+*/
+
+int
+i_t1_has_chars(int font_num, const char *text, int len, int utf8,
+               char *out) {
+  int count = 0;
+  
+  mm_log((1, "i_t1_has_chars(font_num %d, text %p, len %d, utf8 %d)\n", 
+          font_num, text, len, utf8));
+
+  i_clear_error();
+  if (T1_LoadFont(font_num)) {
+    t1_push_error();
+    return 0;
+  }
+
+  while (len) {
+    unsigned long c;
+    int index;
+    if (utf8) {
+      c = i_utf8_advance(&text, &len);
+      if (c == ~0UL) {
+        i_push_error(0, "invalid UTF8 character");
+        return 0;
+      }
+    }
+    else {
+      c = (unsigned char)*text++;
+      --len;
+    }
+    
+    if (c >= 0x100) {
+      /* limit of 256 characters for T1 */
+      *out++ = 0;
+    }
+    else {
+      char const * name = T1_GetCharName(font_num, (unsigned char)c);
+
+      if (name) {
+        *out++ = strcmp(name, ".notdef") != 0;
+      }
+      else {
+        mm_log((2, "  No name found for character %lx\n", c));
+        *out++ = 0;
+      }
+    }
+    ++count;
+  }
+
+  return count;
+}
+
+/*
+=item i_t1_face_name(font_num, name_buf, name_buf_size)
+
+Copies the face name of the given C<font_num> to C<name_buf>.  Returns
+the number of characters required to store the name (which can be
+larger than C<name_buf_size>, including the space required to store
+the terminating NUL).
+
+If name_buf is too small (as specified by name_buf_size) then the name
+will be truncated.  name_buf will always be NUL termintaed.
+
+=cut
+*/
+
+int
+i_t1_face_name(int font_num, char *name_buf, size_t name_buf_size) {
+  char *name;
+
+  T1_errno = 0;
+  if (T1_LoadFont(font_num)) {
+    t1_push_error();
+    return 0;
+  }
+  name = T1_GetFontName(font_num);
+
+  if (name) {
+    strncpy(name_buf, name, name_buf_size);
+    name_buf[name_buf_size-1] = '\0';
+    return strlen(name) + 1;
+  }
+  else {
+    t1_push_error();
+    return 0;
+  }
+}
+
+int
+i_t1_glyph_name(int font_num, unsigned long ch, char *name_buf, 
+                 size_t name_buf_size) {
+  char *name;
+
+  i_clear_error();
+  if (ch > 0xFF) {
+    return 0;
+  }
+  if (T1_LoadFont(font_num)) {
+    t1_push_error();
+    return 0;
+  }
+  name = T1_GetCharName(font_num, (unsigned char)ch);
+  if (name) {
+    if (strcmp(name, ".notdef")) {
+      strncpy(name_buf, name, name_buf_size);
+      name_buf[name_buf_size-1] = '\0';
+      return strlen(name) + 1;
+    }
+    else {
+      return 0;
+    }
+  }
+  else {
+    t1_push_error();
+    return 0;
+  }
+}
+
+static void
+t1_push_error(void) {
+  switch (T1_errno) {
+  case 0: 
+    i_push_error(0, "No error"); 
+    break;
+
+  case T1ERR_SCAN_FONT_FORMAT:
+    i_push_error(T1ERR_SCAN_FONT_FORMAT, "SCAN_FONT_FORMAT"); 
+    break;
+
+  case T1ERR_SCAN_FILE_OPEN_ERR:
+    i_push_error(T1ERR_SCAN_FILE_OPEN_ERR, "SCAN_FILE_OPEN_ERR"); 
+    break;
+
+  case T1ERR_SCAN_OUT_OF_MEMORY:
+    i_push_error(T1ERR_SCAN_OUT_OF_MEMORY, "SCAN_OUT_OF_MEMORY"); 
+    break;
+
+  case T1ERR_SCAN_ERROR:
+    i_push_error(T1ERR_SCAN_ERROR, "SCAN_ERROR"); 
+    break;
+
+  case T1ERR_SCAN_FILE_EOF:
+    i_push_error(T1ERR_SCAN_FILE_EOF, "SCAN_FILE_EOF"); 
+    break;
+
+  case T1ERR_PATH_ERROR:
+    i_push_error(T1ERR_PATH_ERROR, "PATH_ERROR"); 
+    break;
+
+  case T1ERR_PARSE_ERROR:
+    i_push_error(T1ERR_PARSE_ERROR, "PARSE_ERROR"); 
+    break;
+
+  case T1ERR_TYPE1_ABORT:
+    i_push_error(T1ERR_TYPE1_ABORT, "TYPE1_ABORT"); 
+    break;
+
+  case T1ERR_INVALID_FONTID:
+    i_push_error(T1ERR_INVALID_FONTID, "INVALID_FONTID"); 
+    break;
+
+  case T1ERR_INVALID_PARAMETER:
+    i_push_error(T1ERR_INVALID_PARAMETER, "INVALID_PARAMETER"); 
+    break;
+
+  case T1ERR_OP_NOT_PERMITTED:
+    i_push_error(T1ERR_OP_NOT_PERMITTED, "OP_NOT_PERMITTED"); 
+    break;
+
+  case T1ERR_ALLOC_MEM:
+    i_push_error(T1ERR_ALLOC_MEM, "ALLOC_MEM"); 
+    break;
+
+  case T1ERR_FILE_OPEN_ERR:
+    i_push_error(T1ERR_FILE_OPEN_ERR, "FILE_OPEN_ERR"); 
+    break;
+
+  case T1ERR_UNSPECIFIED:
+    i_push_error(T1ERR_UNSPECIFIED, "UNSPECIFIED"); 
+    break;
+
+  case T1ERR_NO_AFM_DATA:
+    i_push_error(T1ERR_NO_AFM_DATA, "NO_AFM_DATA"); 
+    break;
+
+  case T1ERR_X11:
+    i_push_error(T1ERR_X11, "X11"); 
+    break;
+
+  case T1ERR_COMPOSITE_CHAR:
+    i_push_error(T1ERR_COMPOSITE_CHAR, "COMPOSITE_CHAR"); 
+    break;
+
+  default:
+    i_push_errorf(T1_errno, "unknown error %d", (int)T1_errno);
+  }
+}
+
 #endif /* HAVE_LIBT1 */
 
 
 /* Truetype font support */
-
 #ifdef HAVE_LIBTT
 
+/* This is enabled by default when configuring Freetype 1.x
+   I haven't a clue how to reliably detect it at compile time.
+
+   We need a compilation probe in Makefile.PL
+*/
+#define FTXPOST 1
+
 #include <freetype.h>
 #define TT_CHC 5
 
+#ifdef FTXPOST
+#include <ftxpost.h>
+#endif
+
 /* convert a code point into an index in the glyph cache */
 #define TT_HASH(x) ((x) & 0xFF)
 
@@ -492,6 +715,10 @@ struct TT_Fonthandle_ {
   TT_Face_Properties properties;
   TT_Instancehandle instanceh[TT_CHC];
   TT_CharMap char_map;
+#ifdef FTXPOST
+  int loaded_names;
+  TT_Error load_cond;
+#endif
 };
 
 /* Defines */
@@ -553,6 +780,15 @@ i_init_tt() {
     mm_log((1,"Initialization of freetype failed, code = 0x%x\n",error));
     return(1);
   }
+
+#ifdef FTXPOST
+  error = TT_Init_Post_Extension( engine );
+  if (error) {
+    mm_log((1, "Initialization of Post extension failed = 0x%x\n", error));
+    return 1;
+  }
+#endif
+
   return(0);
 }
 
@@ -726,6 +962,10 @@ i_tt_new(char *fontname) {
     handle->instanceh[i].smooth=-1;
   }
 
+#ifdef FTXPOST
+  handle->loaded_names = 0;
+#endif
+
   mm_log((1,"i_tt_new <- 0x%X\n",handle));
   return handle;
 }
@@ -968,7 +1208,7 @@ i_tt_has_chars(TT_Fonthandle *handle, char const *text, int len, int utf8,
                char *out) {
   int count = 0;
   int inst;
-  mm_log((1, "i_ft2_has_chars(handle %p, text %p, len %d, utf8 %d)\n", 
+  mm_log((1, "i_tt_has_chars(handle %p, text %p, len %d, utf8 %d)\n", 
           handle, text, len, utf8));
 
   while (len) {
@@ -1316,7 +1556,7 @@ 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 const* txt, int len, int smooth, int utf8 ) {
 
-  int cords[6];
+  int cords[BOUNDING_BOX_COUNT];
   int ascent, st_offset;
   TT_Raster_Map bit;
   
@@ -1352,7 +1592,7 @@ 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 const* txt, int len, int smooth, int utf8) {
-  int cords[6];
+  int cords[BOUNDING_BOX_COUNT];
   int ascent, st_offset;
   TT_Raster_Map bit;
 
@@ -1386,7 +1626,7 @@ 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], int utf8 ) {
+i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[BOUNDING_BOX_COUNT], int utf8 ) {
   int i, upm, casc, cdesc, first;
   
   int start    = 0;
@@ -1395,7 +1635,7 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
   int gascent  = 0;
   int descent  = 0;
   int ascent   = 0;
-  
+  int rightb   = 0;
 
   unsigned long j;
   unsigned char *ustr;
@@ -1445,12 +1685,12 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
           character goes past the right of the advance width,
           as is common for italic fonts
        */
-       int rightb = gm->advance - gm->bearingX 
+       rightb = gm->advance - gm->bearingX 
          - (gm->bbox.xMax - gm->bbox.xMin);
        /* fprintf(stderr, "font info last: %d %d %d %d\n", 
           gm->bbox.xMax, gm->bbox.xMin, gm->advance, rightb); */
-       if (rightb < 0)
-         width -= rightb/64;
+       if (rightb > 0)
+         rightb = 0;
       }
 
       ascent  = (ascent  >  casc ?  ascent : casc );
@@ -1458,13 +1698,15 @@ i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int c
     }
   }
   
-  cords[0]=start;
-  cords[1]=gdescent;
-  cords[2]=width;
-  cords[3]=gascent;
-  cords[4]=descent;
-  cords[5]=ascent;
-  return 1;
+  cords[BBOX_NEG_WIDTH]=start;
+  cords[BBOX_GLOBAL_DESCENT]=gdescent;
+  cords[BBOX_POS_WIDTH]=width - rightb / 64;
+  cords[BBOX_GLOBAL_ASCENT]=gascent;
+  cords[BBOX_DESCENT]=descent;
+  cords[BBOX_ASCENT]=ascent;
+  cords[BBOX_ADVANCE_WIDTH] = width;
+
+  return BBOX_ADVANCE_WIDTH + 1;
 }
 
 
@@ -1498,7 +1740,154 @@ i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6], i
   return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
 }
 
+/*
+=item i_tt_face_name(handle, name_buf, name_buf_size)
+
+Retrieve's the font's postscript name.
 
+This is complicated by the need to handle encodings and so on.
+
+=cut
+ */
+int
+i_tt_face_name(TT_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
+  TT_Face_Properties props;
+  int name_count;
+  int i;
+  TT_UShort platform_id, encoding_id, lang_id, name_id;
+  TT_UShort name_len;
+  TT_String *name;
+  int want_index = -1; /* an acceptable but not perfect name */
+  int score = 0;
+
+  i_clear_error();
+  
+  TT_Get_Face_Properties(handle->face, &props);
+  name_count = props.num_Names;
+  for (i = 0; i < name_count; ++i) {
+    TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
+                   &name_id);
+
+    TT_Get_Name_String(handle->face, i, &name, &name_len);
+
+    if (platform_id != TT_PLATFORM_APPLE_UNICODE && name_len
+        && name_id == TT_NAME_ID_PS_NAME) {
+      int might_want_index = -1;
+      int might_score = 0;
+      if ((platform_id == TT_PLATFORM_MACINTOSH && encoding_id == TT_MAC_ID_ROMAN)
+          ||
+          (platform_id == TT_PLATFORM_MICROSOFT && encoding_id == TT_MS_LANGID_ENGLISH_UNITED_STATES)) {
+        /* exactly what we want */
+        want_index = i;
+        break;
+      }
+      
+      if (platform_id == TT_PLATFORM_MICROSOFT
+          && (encoding_id & 0xFF) == TT_MS_LANGID_ENGLISH_GENERAL) {
+        /* any english is good */
+        might_want_index = i;
+        might_score = 9;
+      }
+      /* there might be something in between */
+      else {
+        /* anything non-unicode is better than nothing */
+        might_want_index = i;
+        might_score = 1;
+      }
+      if (might_score > score) {
+        score = might_score;
+        want_index = might_want_index;
+      }
+    }
+  }
+
+  if (want_index != -1) {
+    TT_Get_Name_String(handle->face, want_index, &name, &name_len);
+    
+    strncpy(name_buf, name, name_buf_size);
+    name_buf[name_buf_size-1] = '\0';
+
+    return strlen(name) + 1;
+  }
+  else {
+    i_push_error(0, "no face name present");
+    return 0;
+  }
+}
+
+void i_tt_dump_names(TT_Fonthandle *handle) {
+  TT_Face_Properties props;
+  int name_count;
+  int i;
+  TT_UShort platform_id, encoding_id, lang_id, name_id;
+  TT_UShort name_len;
+  TT_String *name;
+  
+  TT_Get_Face_Properties(handle->face, &props);
+  name_count = props.num_Names;
+  for (i = 0; i < name_count; ++i) {
+    TT_Get_Name_ID(handle->face, i, &platform_id, &encoding_id, &lang_id, 
+                   &name_id);
+    TT_Get_Name_String(handle->face, i, &name, &name_len);
+
+    printf("# %d: plat %d enc %d lang %d name %d value ", i, platform_id,
+           encoding_id, lang_id, name_id);
+    if (platform_id == TT_PLATFORM_APPLE_UNICODE) {
+      printf("(unicode)\n");
+    }
+    else {
+      printf("'%s'\n", name);
+    }
+  }
+}
+
+int
+i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf, 
+                 size_t name_buf_size) {
+#ifdef FTXPOST
+  TT_Error rc;
+  TT_String *psname;
+  TT_UShort index;
+
+  i_clear_error();
+
+  if (!handle->loaded_names) {
+    TT_Post post;
+    mm_log((1, "Loading PS Names"));
+    handle->load_cond = TT_Load_PS_Names(handle->face, &post);
+    ++handle->loaded_names;
+  }
+
+  if (handle->load_cond) {
+    i_push_errorf(rc, "error loading names (%d)", handle->load_cond);
+    return 0;
+  }
+  
+  index = TT_Char_Index(handle->char_map, ch);
+  if (!index) {
+    i_push_error(0, "no such character");
+    return 0;
+  }
+
+  rc = TT_Get_PS_Name(handle->face, index, &psname);
+
+  if (rc) {
+    i_push_error(rc, "error getting name");
+    return 0;
+  }
+
+  strncpy(name_buf, psname, name_buf_size);
+  name_buf[name_buf_size-1] = '\0';
+
+  return strlen(psname) + 1;
+#else
+  mm_log((1, "FTXPOST extension not enabled\n"));
+  i_clear_error();
+  i_push_error(0, "Use of FTXPOST extension disabled");
+
+  return 0;
+#endif
+}
 
 #endif /* HAVE_LIBTT */
 
diff --git a/fontfiles/ExistenceTest.afm b/fontfiles/ExistenceTest.afm
new file mode 100644 (file)
index 0000000..7dd627c
--- /dev/null
@@ -0,0 +1,20 @@
+StartFontMetrics 2.0
+Comment Generated by pfaedit
+Comment Creation Date: Sat Dec 28 17:06:43 2002
+FontName ExistenceTest
+FullName ExistenceTest
+FamilyName ExistenceTest
+Weight Medium
+Notice (Created by Tony Cook,,, with PfaEdit 1.0 (http://pfaedit.sf.net))
+ItalicAngle 0
+IsFixedPitch false
+UnderlinePosition -100
+UnderlineThickness 50
+Version 001.000
+EncodingScheme AdobeStandardEncoding
+FontBBox -60 -55 819 775
+StartCharMetrics 2
+C 33 ; WX 310 ; N exclam ; B 51 0 207 738 ;
+C 47 ; WX 761 ; N slash ; B -60 -55 819 775 ;
+EndCharMetrics
+EndFontMetrics
diff --git a/fontfiles/ExistenceTest.pfb b/fontfiles/ExistenceTest.pfb
new file mode 100644 (file)
index 0000000..46a9874
Binary files /dev/null and b/fontfiles/ExistenceTest.pfb differ
diff --git a/fontfiles/ExistenceTest.sfd b/fontfiles/ExistenceTest.sfd
new file mode 100644 (file)
index 0000000..012568a
--- /dev/null
@@ -0,0 +1,440 @@
+SplineFontDB: 1.0
+FontName: ExistenceTest
+FullName: ExistenceTest
+FamilyName: ExistenceTest
+Weight: Medium
+Copyright: Created by Tony Cook,,, with PfaEdit 1.0 (http://pfaedit.sf.net)
+Comments: 2002-12-23: Created.
+Version: 001.000
+ItalicAngle: 0
+UnderlinePosition: -100
+UnderlineWidth: 50
+Ascent: 800
+Descent: 200
+FSType: 12
+PfmFamily: 17
+TTFWeight: 500
+TTFWidth: 5
+Panose: 2 0 6 3 0 0 0 0 0 0
+LineGap: 90
+VLineGap: 0
+
+Encoding: adobestandard
+DisplaySize: -24
+AntiAlias: 1
+BeginChars: 256 95
+StartChar: space
+Encoding: 32 32
+Width: 1000
+EndChar
+StartChar: exclam
+Encoding: 33 33
+Width: 310
+Flags: W
+HStem: 0 126<99 163> 180 558<99 147>
+VStem: 51 144<224 690> 57 150<48 80>
+Fore
+107 126 m 2
+ 157 126 l 2
+ 184.6 126 207 103.6 207 76 c 2
+ 207 50 l 2
+ 207 22.4004 184.6 0 157 0 c 2
+ 107 0 l 2
+ 79.4004 0 57 22.4004 57 50 c 2
+ 57 76 l 2
+ 57 103.6 79.4004 126 107 126 c 2
+101 738 m 2
+ 145 738 l 2
+ 172.6 738 195 715.6 195 688 c 2
+ 195 230 l 2
+ 195 202.4 172.6 180 145 180 c 2
+ 101 180 l 2
+ 73.4004 180 51 202.4 51 230 c 2
+ 51 688 l 2
+ 51 715.6 73.4004 738 101 738 c 2
+EndSplineSet
+MinimumDistance: x2,-1 
+EndChar
+StartChar: quotedbl
+Encoding: 34 34
+Width: 1000
+EndChar
+StartChar: numbersign
+Encoding: 35 35
+Width: 1000
+EndChar
+StartChar: dollar
+Encoding: 36 36
+Width: 1000
+EndChar
+StartChar: percent
+Encoding: 37 37
+Width: 1000
+EndChar
+StartChar: ampersand
+Encoding: 38 38
+Width: 1000
+EndChar
+StartChar: parenleft
+Encoding: 40 40
+Width: 1000
+EndChar
+StartChar: parenright
+Encoding: 41 41
+Width: 1000
+EndChar
+StartChar: asterisk
+Encoding: 42 42
+Width: 1000
+EndChar
+StartChar: plus
+Encoding: 43 43
+Width: 1000
+EndChar
+StartChar: comma
+Encoding: 44 44
+Width: 1000
+EndChar
+StartChar: hyphen
+Encoding: 45 45
+Width: 1000
+EndChar
+StartChar: period
+Encoding: 46 46
+Width: 1000
+EndChar
+StartChar: slash
+Encoding: 47 47
+Width: 761
+Flags: W
+DStem: 783.882 774.221 818.118 737.779 -59.1182 -17.7793 -24.8818 -54.2207
+Fore
+-59.1182 -17.7793 m 1
+ 783.882 774.221 l 1
+ 818.118 737.779 l 1
+ -24.8818 -54.2207 l 1
+ -59.1182 -17.7793 l 1
+EndSplineSet
+EndChar
+StartChar: zero
+Encoding: 48 48
+Width: 1000
+EndChar
+StartChar: one
+Encoding: 49 49
+Width: 1000
+EndChar
+StartChar: two
+Encoding: 50 50
+Width: 1000
+EndChar
+StartChar: three
+Encoding: 51 51
+Width: 1000
+EndChar
+StartChar: four
+Encoding: 52 52
+Width: 1000
+EndChar
+StartChar: five
+Encoding: 53 53
+Width: 1000
+EndChar
+StartChar: six
+Encoding: 54 54
+Width: 1000
+EndChar
+StartChar: seven
+Encoding: 55 55
+Width: 1000
+EndChar
+StartChar: eight
+Encoding: 56 56
+Width: 1000
+EndChar
+StartChar: nine
+Encoding: 57 57
+Width: 1000
+EndChar
+StartChar: colon
+Encoding: 58 58
+Width: 1000
+EndChar
+StartChar: semicolon
+Encoding: 59 59
+Width: 1000
+EndChar
+StartChar: less
+Encoding: 60 60
+Width: 1000
+EndChar
+StartChar: equal
+Encoding: 61 61
+Width: 1000
+EndChar
+StartChar: greater
+Encoding: 62 62
+Width: 1000
+EndChar
+StartChar: question
+Encoding: 63 63
+Width: 1000
+EndChar
+StartChar: at
+Encoding: 64 64
+Width: 1000
+EndChar
+StartChar: A
+Encoding: 65 65
+Width: 1000
+EndChar
+StartChar: B
+Encoding: 66 66
+Width: 1000
+EndChar
+StartChar: C
+Encoding: 67 67
+Width: 1000
+EndChar
+StartChar: D
+Encoding: 68 68
+Width: 1000
+EndChar
+StartChar: E
+Encoding: 69 69
+Width: 1000
+EndChar
+StartChar: F
+Encoding: 70 70
+Width: 1000
+EndChar
+StartChar: G
+Encoding: 71 71
+Width: 1000
+EndChar
+StartChar: H
+Encoding: 72 72
+Width: 1000
+EndChar
+StartChar: I
+Encoding: 73 73
+Width: 1000
+EndChar
+StartChar: J
+Encoding: 74 74
+Width: 1000
+EndChar
+StartChar: K
+Encoding: 75 75
+Width: 1000
+EndChar
+StartChar: L
+Encoding: 76 76
+Width: 1000
+EndChar
+StartChar: M
+Encoding: 77 77
+Width: 1000
+EndChar
+StartChar: N
+Encoding: 78 78
+Width: 1000
+EndChar
+StartChar: O
+Encoding: 79 79
+Width: 1000
+EndChar
+StartChar: P
+Encoding: 80 80
+Width: 1000
+EndChar
+StartChar: Q
+Encoding: 81 81
+Width: 1000
+EndChar
+StartChar: R
+Encoding: 82 82
+Width: 1000
+EndChar
+StartChar: S
+Encoding: 83 83
+Width: 1000
+EndChar
+StartChar: T
+Encoding: 84 84
+Width: 1000
+EndChar
+StartChar: U
+Encoding: 85 85
+Width: 1000
+EndChar
+StartChar: V
+Encoding: 86 86
+Width: 1000
+EndChar
+StartChar: W
+Encoding: 87 87
+Width: 1000
+EndChar
+StartChar: X
+Encoding: 88 88
+Width: 1000
+EndChar
+StartChar: Y
+Encoding: 89 89
+Width: 1000
+EndChar
+StartChar: Z
+Encoding: 90 90
+Width: 1000
+EndChar
+StartChar: bracketleft
+Encoding: 91 91
+Width: 1000
+EndChar
+StartChar: backslash
+Encoding: 92 92
+Width: 1000
+EndChar
+StartChar: bracketright
+Encoding: 93 93
+Width: 1000
+EndChar
+StartChar: asciicircum
+Encoding: 94 94
+Width: 1000
+EndChar
+StartChar: underscore
+Encoding: 95 95
+Width: 1000
+EndChar
+StartChar: a
+Encoding: 97 97
+Width: 1000
+EndChar
+StartChar: b
+Encoding: 98 98
+Width: 1000
+EndChar
+StartChar: c
+Encoding: 99 99
+Width: 1000
+EndChar
+StartChar: d
+Encoding: 100 100
+Width: 1000
+EndChar
+StartChar: e
+Encoding: 101 101
+Width: 1000
+EndChar
+StartChar: f
+Encoding: 102 102
+Width: 1000
+EndChar
+StartChar: g
+Encoding: 103 103
+Width: 1000
+EndChar
+StartChar: h
+Encoding: 104 104
+Width: 1000
+EndChar
+StartChar: i
+Encoding: 105 105
+Width: 1000
+EndChar
+StartChar: j
+Encoding: 106 106
+Width: 1000
+EndChar
+StartChar: k
+Encoding: 107 107
+Width: 1000
+EndChar
+StartChar: l
+Encoding: 108 108
+Width: 1000
+EndChar
+StartChar: m
+Encoding: 109 109
+Width: 1000
+EndChar
+StartChar: n
+Encoding: 110 110
+Width: 1000
+EndChar
+StartChar: o
+Encoding: 111 111
+Width: 1000
+EndChar
+StartChar: p
+Encoding: 112 112
+Width: 1000
+EndChar
+StartChar: q
+Encoding: 113 113
+Width: 1000
+EndChar
+StartChar: r
+Encoding: 114 114
+Width: 1000
+EndChar
+StartChar: s
+Encoding: 115 115
+Width: 1000
+EndChar
+StartChar: t
+Encoding: 116 116
+Width: 1000
+EndChar
+StartChar: u
+Encoding: 117 117
+Width: 1000
+EndChar
+StartChar: v
+Encoding: 118 118
+Width: 1000
+EndChar
+StartChar: w
+Encoding: 119 119
+Width: 1000
+EndChar
+StartChar: x
+Encoding: 120 120
+Width: 1000
+EndChar
+StartChar: y
+Encoding: 121 121
+Width: 1000
+EndChar
+StartChar: z
+Encoding: 122 122
+Width: 1000
+EndChar
+StartChar: braceleft
+Encoding: 123 123
+Width: 1000
+EndChar
+StartChar: bar
+Encoding: 124 124
+Width: 1000
+EndChar
+StartChar: braceright
+Encoding: 125 125
+Width: 1000
+EndChar
+StartChar: asciitilde
+Encoding: 126 126
+Width: 1000
+EndChar
+StartChar: quotesingle
+Encoding: 169 39
+Width: 1000
+EndChar
+StartChar: grave
+Encoding: 193 96
+Width: 1000
+EndChar
+EndChars
+EndSplineFont
diff --git a/fontfiles/ExistenceTest.ttf b/fontfiles/ExistenceTest.ttf
new file mode 100644 (file)
index 0000000..a4a7e56
Binary files /dev/null and b/fontfiles/ExistenceTest.ttf differ
index 0b23791..d64757d 100644 (file)
@@ -12,7 +12,7 @@ freetyp2.c - font support via the FreeType library version 2.
   if (!i_ft2_getdpi(font, &xdpi, &ydpi)) { error }
   double matrix[6];
   if (!i_ft2_settransform(font, matrix)) { error }
-  int bbox[6];
+  int bbox[BOUNDING_BOX_COUNT];
   if (!i_ft2_bbox(font, cheight, cwidth, text, length, bbox, utf8)) { error }
   i_img *im = ...;
   i_color cl;
@@ -294,6 +294,8 @@ i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
   int glyph_ascent, glyph_descent;
   FT_Glyph_Metrics *gm;
   int start = 0;
+  int loadFlags = FT_LOAD_DEFAULT;
+  int rightb;
 
   mm_log((1, "i_ft2_bbox(handle %p, cheight %f, cwidth %f, text %p, len %d, bbox %p)\n",
          handle, cheight, cwidth, text, len, bbox));
@@ -305,6 +307,9 @@ i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
     i_push_error(0, "setting size");
   }
 
+  if (!handle->hint)
+    loadFlags |= FT_LOAD_NO_HINTING;
+
   first = 1;
   width = 0;
   while (len) {
@@ -322,7 +327,7 @@ i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
     }
 
     index = FT_Get_Char_Index(handle->face, c);
-    error = FT_Load_Glyph(handle->face, index, FT_LOAD_DEFAULT);
+    error = FT_Load_Glyph(handle->face, index, loadFlags);
     if (error) {
       ft2_push_message(error);
       i_push_errorf(0, "loading glyph for character \\x%02x (glyph 0x%04X)", 
@@ -351,20 +356,21 @@ i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
       /* last character 
        handle the case where the right the of the character overlaps the 
        right*/
-      int rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
-      if (rightb < 0)
-        width -= rightb / 64;
+      rightb = gm->horiAdvance - gm->horiBearingX - gm->width;
+      if (rightb > 0)
+        rightb = 0;
     }
   }
 
-  bbox[0] = start;
-  bbox[1] = handle->face->size->metrics.descender / 64;
-  bbox[2] = width;
-  bbox[3] = handle->face->size->metrics.ascender / 64;
-  bbox[4] = descent;
-  bbox[5] = ascent;
+  bbox[BBOX_NEG_WIDTH] = start;
+  bbox[BBOX_GLOBAL_DESCENT] = handle->face->size->metrics.descender / 64;
+  bbox[BBOX_POS_WIDTH] = width - rightb;
+  bbox[BBOX_GLOBAL_ASCENT] = handle->face->size->metrics.ascender / 64;
+  bbox[BBOX_DESCENT] = descent;
+  bbox[BBOX_ASCENT] = ascent;
+  bbox[BBOX_ADVANCE_WIDTH] = width;
 
-  return 1;
+  return BBOX_ADVANCE_WIDTH + 1;
 }
 
 /*
@@ -461,6 +467,8 @@ i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
 
   if (vlayout)
     loadFlags |= FT_LOAD_VERTICAL_LAYOUT;
+  if (!handle->hint)
+    loadFlags |= FT_LOAD_NO_HINTING;
 
   error = FT_Set_Char_Size(handle->face, cwidth*64, cheight*64, 
                            handle->xdpi, handle->ydpi);
@@ -534,7 +542,7 @@ i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
     }
     x += slot->advance.x / 64;
     y += slot->advance.y / 64;
-    
+
     if (glyph_ascent > ascent)
       ascent = glyph_ascent;
     if (glyph_descent > descent)
@@ -567,8 +575,6 @@ i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
   return 1;
 }
 
-
-
 static int
 make_bmp_map(FT_Bitmap *bitmap, unsigned char *map);
 
@@ -596,7 +602,7 @@ i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
   FT_Error error;
   int index;
   FT_Glyph_Metrics *gm;
-  int bbox[6];
+  int bbox[BOUNDING_BOX_COUNT];
   FT_GlyphSlot slot;
   int x, y;
   unsigned char *bmp;
@@ -878,6 +884,88 @@ make_bmp_map(FT_Bitmap *bitmap, unsigned char *map) {
   return 1;
 }
 
+int
+i_ft2_face_name(FT2_Fonthandle *handle, char *name_buf, size_t name_buf_size) {
+  char const *name = FT_Get_Postscript_Name(handle->face);
+
+  i_clear_error();
+
+  if (name) {
+    strncpy(name_buf, name, name_buf_size);
+    name_buf[name_buf_size-1] = '\0';
+
+    return strlen(name) + 1;
+  }
+  else {
+    i_push_error(0, "no face name available");
+    *name_buf = '\0';
+
+    return 0;
+  }
+}
+
+int
+i_ft2_glyph_name(FT2_Fonthandle *handle, unsigned char ch, char *name_buf, 
+                 size_t name_buf_size) {
+#ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
+  i_clear_error();
+  *name_buf = '\0';
+  i_push_error(0, "FT2 configured without glyph name support");
+
+  return 0;
+#else
+  i_clear_error();
+
+  if (FT_Has_PS_Glyph_Names(handle->face)) {
+    FT_UInt index = FT_Get_Char_Index(handle->face, ch);
+
+    if (index) {
+      FT_Error error = FT_Get_Glyph_Name(handle->face, index, name_buf, 
+                                         name_buf_size);
+      if (error) {
+        ft2_push_message(error);
+        *name_buf = '\0';
+        return;
+      }
+      if (*name_buf) {
+        return strlen(name_buf) + 1;
+      }
+      else {
+        return 0;
+      }
+    }
+    else {
+      i_push_error(0, "no glyph for that character");
+      *name_buf = 0;
+      return 0;
+    }
+  }
+  else {
+    i_push_error(0, "no glyph names in font");
+    *name_buf = '\0';
+    return 0;
+  }
+#endif
+}
+
+int
+i_ft2_can_do_glyph_names(void) {
+#ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
+  return 0;
+#else
+  return 1;
+#endif
+}
+
+int 
+i_ft2_face_has_glyph_names(FT2_Fonthandle *handle) {
+#ifdef FT_CONFIG_OPTION_NO_GLYPH_NAMES
+  return 0;
+#else
+  return FT_Has_PS_Glyph_Names(handle->face);
+#endif
+}
+
 /*
 =back
 
diff --git a/image.h b/image.h
index 9ca74a3..1acd9f8 100644 (file)
--- a/image.h
+++ b/image.h
@@ -250,10 +250,13 @@ int       i_t1_new( char *pfb, char *afm );
 int       i_t1_destroy( int font_id );
 undef_int i_t1_cp( i_img *im, int xb, int yb, int channel, int fontnum, float points, char* str, int len, int align, int utf8, char const *flags );
 undef_int i_t1_text( i_img *im, int xb, int yb, i_color *cl, int fontnum, float points, char* str, int len, int align, int utf8, char const *flags );
-void      i_t1_bbox( int fontnum, float point, char *str, int len, int cords[6], int utf8, char const *flags );
+int      i_t1_bbox( int fontnum, float point, char *str, int len, int cords[6], int utf8, char const *flags );
 void      i_t1_set_aa( int st );
 void      close_t1( void );
-
+int       i_t1_has_chars(int font_num, char const *text, int len, int utf8, char *out);
+extern int i_t1_face_name(int font_num, char *name_buf, size_t name_buf_size);
+extern int i_t1_glyph_name(int font_num, unsigned long ch, char *name_buf, 
+                           size_t name_buf_size);
 #endif
 
 #ifdef HAVE_LIBTT
@@ -269,6 +272,11 @@ undef_int i_tt_cp( TT_Fonthandle *handle,i_img *im,int xb,int yb,int channel,flo
 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);
 int i_tt_has_chars(TT_Fonthandle *handle, char const *text, int len, int utf8, char *out);
+void i_tt_dump_names(TT_Fonthandle *handle);
+int i_tt_face_name(TT_Fonthandle *handle, char *name_buf, 
+                   size_t name_buf_size);
+int i_tt_glyph_name(TT_Fonthandle *handle, unsigned long ch, char *name_buf,
+                    size_t name_buf_size);
 
 #endif  /* End of freetype headers */
 
@@ -294,6 +302,12 @@ extern int i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty,
                     int utf8);
 extern int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len,
                            int utf8, char *work);
+extern int i_ft2_face_name(FT2_Fonthandle *handle, char *name_buf, 
+                           size_t name_buf_size);
+extern int i_ft2_glyph_name(FT2_Fonthandle *handle, unsigned char ch, 
+                            char *name_buf, size_t name_buf_size);
+extern int i_ft2_can_do_glyph_names(void);
+extern int i_ft2_face_has_glyph_names(FT2_Fonthandle *handle);
 
 #endif
 
index 5a33036..1791400 100644 (file)
@@ -150,7 +150,9 @@ on colour values too - though you need to be careful - adding 2 white
 values together and multiplying by 0.5 will give you grey, not white.
 
 Division by zero (or a small number) just results in a large number.
-Modulo zero (or a small number) results in zero.
+Modulo zero (or a small number) results in zero.  % is implemented
+using fmod() so you can use this to take a value mod a floating point
+value.
 
 =item sin(N), cos(N), atan2(y,x)
 
@@ -248,6 +250,17 @@ For details on expression parsing see L<Imager::Expr>.  For details on
 the virtual machine used to transform the images, see
 L<Imager::regmach.pod>.
 
+  # generate a colorful spiral
+  # requires that Parse::RecDescent be installed
+  my $newimg = Imager::transform2({
+                                   width => 160, height=>160,
+                                   expr => <<EOS
+  dist = distance(x, y, w/2, h/2);
+  angle = atan2(y-h/2, x-w/2);
+  angle2 = (dist / 10 + angle) % ( 2 * pi );
+  return hsv(angle*180/pi, 1, (sin(angle2)+1)/2);
+  EOS
+                                  });
 
 =head2 Matrix Transformations
 
index 8f8851e..a296967 100644 (file)
@@ -117,13 +117,15 @@ sub draw {
   }
   $input{string} = _first($input{string}, $input{text});
   unless (defined $input{string}) {
-    $Imager::ERRSTR = "Missing require parameter 'string'";
+    $Imager::ERRSTR = "Missing required parameter 'string'";
     return;
   }
   $input{aa} = _first($input{aa}, $input{antialias}, $self->{aa}, 1);
   # the original draw code worked this out but didn't use it
   $input{align} = _first($input{align}, $self->{align});
   $input{color} = _first($input{color}, $self->{color});
+  $input{color} = Imager::_color($input{'color'});
+
   $input{size} = _first($input{size}, $self->{size});
   unless (defined $input{size}) {
     $input{image}{ERRSTR} = "No font size provided";
@@ -136,6 +138,81 @@ sub draw {
   $self->_draw(%input);
 }
 
+sub align {
+  my $self = shift;
+  my %input = ( halign => 'left', valign => 'baseline', 
+                'x' => 0, 'y' => 0, @_ );
+
+  my $text = _first($input{string}, $input{text});
+  unless (defined $text) {
+    Imager->_set_error("Missing required parameter 'string'");
+    return;
+  }
+  
+  # image needs to be supplied, but can be supplied as undef
+  unless (exists $input{image}) {
+    Imager->_set_error("Missing required parameter 'image'");
+    return;
+  }
+  my $size = _first($input{size}, $self->{size});
+  my $utf8 = _first($input{utf8}, 0);
+  
+  my $bbox = $self->bounding_box(string=>$text, size=>$size, utf8=>$utf8);
+  my $valign = $input{valign};
+  $valign = 'baseline'
+    unless $valign && $valign =~ /^(?:top|center|bottom|baseline)$/;
+  
+  my $halign = $input{halign};
+  $halign = 'start' 
+    unless $halign && $halign =~ /^(?:left|start|center|end|right)$/;
+
+  my $x = $input{'x'};
+  my $y = $input{'y'};
+  
+  if ($valign eq 'top') {
+    $y += $bbox->ascent;
+  }
+  elsif ($valign eq 'center') {
+    $y += $bbox->ascent - $bbox->text_height / 2;
+  }
+  elsif ($valign eq 'bottom') {
+    $y += $bbox->descent;
+  }
+  # else baseline is the default
+
+  if ($halign eq 'left') {
+    $x -= $bbox->start_offset;
+  }
+  elsif ($halign eq 'start') {
+    # nothing to do
+  }
+  elsif ($halign eq 'center') {
+    $x -= $bbox->start_offset + $bbox->total_width / 2;
+  }
+  elsif ($halign eq 'end' || $halign eq 'right') {
+    $x -= $bbox->start_offset + $bbox->total_width - 1;
+  }
+  $x = int($x);
+  $y = int($y);
+
+  if ($input{image}) {
+    delete @input{qw/x y/};
+    $self->draw(%input, 'x' => $x, 'y' => $y, align=>1)
+      or return;
+#      for my $i (1 .. length $text) {
+#        my $work = substr($text, 0, $i);
+#        my $bbox = $self->bounding_box(string=>$work, size=>$size, utf8=>$utf8);
+#        my $nx = $x + $bbox->end_offset;
+#        $input{image}->setpixel(x=>[ ($nx) x 5 ],
+#                                'y'=>[ $y-2, $y-1, $y, $y+1, $y+2 ],
+#                                color=>'FF0000');
+#      }
+  }
+
+  return ($x+$bbox->start_offset, $y-$bbox->ascent, 
+          $x+$bbox->end_offset, $y-$bbox->descent+1);
+}
+
 sub bounding_box {
   my $self=shift;
   my %input=@_;
@@ -150,17 +227,24 @@ sub bounding_box {
 
   my @box = $self->_bounding_box(%input);
 
-  if(@box && exists $input{'x'} and exists $input{'y'}) {
-    my($gdescent, $gascent)=@box[1,3];
-    $box[1]=$input{'y'}-$gascent;      # top = base - ascent (Y is down)
-    $box[3]=$input{'y'}-$gdescent;     # bottom = base - descent (Y is down, descent is negative)
-    $box[0]+=$input{'x'};
-    $box[2]+=$input{'x'};
-  } elsif (@box && $input{'canon'}) {
-    $box[3]-=$box[1];    # make it cannoical (ie (0,0) - (width, height))
-    $box[2]-=$box[0];
+  if (wantarray) {
+    if(@box && exists $input{'x'} and exists $input{'y'}) {
+      my($gdescent, $gascent)=@box[1,3];
+      $box[1]=$input{'y'}-$gascent;      # top = base - ascent (Y is down)
+      $box[3]=$input{'y'}-$gdescent;     # bottom = base - descent (Y is down, descent is negative)
+      $box[0]+=$input{'x'};
+      $box[2]+=$input{'x'};
+    } elsif (@box && $input{'canon'}) {
+      $box[3]-=$box[1];    # make it cannoical (ie (0,0) - (width, height))
+      $box[2]-=$box[0];
+    }
+    return @box;
+  }
+  else {
+    require Imager::Font::BBox;
+
+    return Imager::Font::BBox->new(@box);
   }
-  return @box;
 }
 
 sub dpi {
@@ -314,24 +398,53 @@ Other logical font attributes may be added if there is sufficient demand.
 
 Returns the bounding box for the specified string.  Example:
 
-  ($neg_width,
-   $global_descent,
-   $pos_width,
-   $global_ascent,
-   $descent,
-   $ascent) = $font->bounding_box(string => "A Fool");
+  my ($neg_width,
+      $global_descent,
+      $pos_width,
+      $global_ascent,
+      $descent,
+      $ascent,
+      $advance_width) = $font->bounding_box(string => "A Fool");
+
+  my $bbox_object = $font->bounding_box(string => "A Fool");
+
+=over
+
+=item C<$neg_width>
 
-The C<$neg_width> is the relative start of a the string.  In some
+the relative start of a the string.  In some
 cases this can be a negative number, in that case the first letter
 stretches to the left of the starting position that is specified in
-the string method of the Imager class.  <$global_descent> is the how
-far down the lowest letter of the entire font reaches below the
-baseline (this is often j).  C<$pos_width> is how wide the string
-from the starting position is.  The total width of the string is
-C<$pos_width-$neg_width>.  C<$descent> and C<$ascent> are the same as
-<$global_descent> and <$global_ascent> except that they are only for
-the characters that appear in the string.  Obviously we can stuff all
-the results into an array just as well:
+the string method of the Imager class
+
+=item C<$global_descent> 
+
+how far down the lowest letter of the entire font reaches below the
+baseline (this is often j).
+
+=item C<$pos_width>
+
+how wide the string from
+the starting position is.  The total width of the string is
+C<$pos_width-$neg_width>.
+
+=item C<$descent> 
+
+=item C<$ascent> 
+
+the same as <$global_descent> and <$global_ascent> except that they
+are only for the characters that appear in the string.
+
+=item C<$advance_width>
+
+the distance from the start point that the next string output should
+start at, this is often the same as C<$pos_width>, but can be
+different if the final character overlaps the right side of its
+character cell.
+
+=back
+
+Obviously we can stuff all the results into an array just as well:
 
   @metrics = $font->bounding_box(string => "testing 123");
 
@@ -353,7 +466,9 @@ but:
  $bbox[2] - horizontal space taken by glyphs
  $bbox[3] - vertical space taken by glyphs
 
-
+Returns an L<Imager::Font::BBox> object in scalar context, so you can
+avoid all those confusing indices.  This has methods as named above,
+with some extra convenience methods.
 
 =item string
 
@@ -444,6 +559,84 @@ Sometimes it is necessary to know how much space a string takes before
 rendering it.  The bounding_box() method described earlier can be used
 for that.
 
+=item align(string=>$text, size=>$size, x=>..., y=>..., valign => ..., halign=>...)
+
+Higher level text output - outputs the text aligned as specified
+around the given point (x,y).
+
+  # "Hello" centered at 100, 100 in the image.
+  my ($left, $top, $bottom, $right) = 
+    $font->align(string=>"Hello",
+                 x=>100, y=>100, 
+                 halign=>'center', valign=>'center', 
+                 image=>$image);
+
+Takes the same parameters as $font->draw(), and the following extra
+parameters:
+
+=over
+
+=item valign
+
+Possible values are:
+
+=over
+
+=item top
+
+Point is at the top of the text.
+
+=item bottom
+
+Point is at the bottom of the text.
+
+=item baseline
+
+Point is on the baseline of the text (default.)
+
+=item center
+
+Point is vertically centered within the text.
+
+=back
+
+=item halign
+
+=over
+
+=item left
+
+The point is at the left of the text.
+
+=item start
+
+The point is at the start point of the text.
+
+=item center
+
+The point is horizontally centered within the text.
+
+=item right
+
+The point is at the right end of the text.
+
+=item end
+
+The point is at the right end of the text.  This will change to the
+end point of the text (once the proper bounding box interfaces are
+available).
+
+=back
+
+=item image
+
+The image to draw to.  Set to C<undef> to avoid drawing but still
+calculate the bounding box.
+
+=back
+
+Returns a list specifying the bounds of the drawn text.
+
 =item dpi()
 
 =item dpi(xdpi=>$xdpi, ydpi=>$ydpi)
@@ -494,6 +687,24 @@ Imager::Font->new(file=>"arial.ttf", color=>$blue, aa=>1)
             ->string(text=>"Plan XYZ", border=>5)
             ->write(file=>"xyz.png");
 
+=item face_name()
+
+Returns the internal name of the face.  Not all font types support
+this method yet.
+
+=item glyph_names(string=>$string [, utf8=>$utf8 ] );
+
+Returns a list of glyph names for each of the characters in the
+string.  If the character has no name then C<undef> is returned for
+the character.
+
+Some font files do not include glyph names, in this case Freetype 2
+will not return any names.  Freetype 1 can return standard names even
+if there are no glyph names in the font.
+
+Both Freetype 1.x and 2.x allow support for glyph names to not be
+included.
+
 =back
 
 =head1 UTF8
@@ -588,7 +799,7 @@ You need to modify this class to add new font types.
 =head1 SEE ALSO
 
 Imager(3), Imager::Font::FreeType2(3), Imager::Font::Type1(3),
-Imager::Font::Win32(3), Imager::Font::Truetype(3)
+Imager::Font::Win32(3), Imager::Font::Truetype(3), Imager::Font::BBox(3)
 
 
 http://www.eecs.umich.edu/~addi/perl/Imager/
diff --git a/lib/Imager/Font/BBox.pm b/lib/Imager/Font/BBox.pm
new file mode 100644 (file)
index 0000000..f39c864
--- /dev/null
@@ -0,0 +1,208 @@
+package Imager::Font::BBox;
+use strict;
+
+=head1 NAME
+
+Imager::Font::BBox - objects representing the bounding box of a string.
+
+=head1 SYNOPSIS
+
+  use Imager::Font;
+
+  # get the object
+  my $font = Imager::Font->new(...);
+  my $bbox = $font->bounding_box(string=>$text, size=>$size);
+
+  # methods
+  my $start = $bbox->start_offset;
+  my $end = $bbox->end_offset;
+  my $gdescent = $box->global_descent;
+  my $gascent = $bbox->global_ascent;
+  my $ascent = $bbox->ascent;
+  my $decent = $bbox->descent;
+  my $total_width = $bbox->total_width;
+  my $fheight = $bbox->font_height;
+  my $theight = $bbox->text_height;
+
+=head1 DESCRIPTION
+
+Objects of this class are returned by the Imager::Font bounding_box()
+method when it is called in scalar context.
+
+This will hopefully make the information from this method more
+accessible.
+
+=head1 METHODS
+
+=over
+
+=item start_offset()
+
+Returns the horizonatal offset from the selected drawing location to
+the left edge of the first character drawn.  If this is positive, the
+first glyph is to the right of the drawing location.
+
+The alias neg_width() is present to match the bounding_box()
+documentation for list context.
+
+=cut
+
+sub start_offset {
+  return $_[0][0];
+}
+
+sub neg_width {
+  return $_[0][0];
+}
+
+=item end_offset()
+
+The offset from the selected drawing location to the right edge of the
+last character drawn.  Should always be positive.
+
+You can use the alias pos_width() if you are used to the
+bounding_box() documentation for list context.
+
+=cut
+
+sub end_offset {
+  return $_[0][2];
+}
+
+sub pos_width {
+  return $_[0][2];
+}
+
+=item global_descent()
+
+The lowest position relative to the font baseline that any character
+in the font reaches in the character cell.  Normally negative.
+
+At least one font we've seen has reported a positive number for this.
+
+=cut
+
+sub global_descent {
+  return $_[0][1];
+}
+
+=item global_ascent()
+
+The highest position relative to the font baseline that any character
+in the font reaches in the character cell.  Normally positive.
+
+=cut
+
+sub global_ascent {
+  return $_[0][3];
+}
+
+=item descent()
+
+The lowest position relative to the font baseline that any character
+in the supplied string reaches.  Negative when any character's glyph
+reaches below the baseline.
+
+=cut
+
+sub descent {
+  return $_[0][4];
+}
+
+=item ascent()
+
+The highest position relative to the font baseline that any character
+in the supplied string reaches.  Positive if any character's glyph
+reaches above the baseline.
+
+=cut
+
+sub ascent {
+  return $_[0][5];
+}
+
+=item advance_width()
+
+=cut
+
+sub advance_width {
+  my $self = shift;
+
+  @$self > 6 ? $self->[6] : $self->[5];
+}
+
+=item total_width()
+
+The total displayed width of the string.
+
+=cut
+
+sub total_width {
+  my $self = shift;
+
+  $self->end_offset - $self->start_offset;
+}
+
+=item font_height()
+
+The maximum displayed height of any string using this font.
+
+=cut
+
+sub font_height {
+  my $self = shift;
+  $self->global_ascent - $self->global_descent;
+}
+
+=item text_height()
+
+The displayed height of the supplied string.
+
+=cut
+
+sub text_height {
+  my $self = shift;
+
+  $self->ascent - $self->descent;
+}
+
+=back
+
+=head1 INTERNAL FUNCTIONS
+
+=over
+
+=item new(...)
+
+Called by Imager::Font->bounding_box() to create the object.
+
+=cut
+
+sub new {
+  my $class = shift;
+  return bless [ @_ ], $class;
+}
+
+=back
+
+=head1 BUGS
+
+Doesn't reproduce the functionality that you get using the x and y
+parameters to Imager::Font->bounding_box().  I considered:
+
+  my ($left, $top, $right, $bottom) = $box->offset(x=>$x, y=>$y)
+
+but this is about as clumsy as the original.
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 SEE ALSO
+
+Imager(3), Imager::Font(3)
+
+=cut
+
+1;
+
index f22a723..bcf108d 100644 (file)
@@ -3,6 +3,9 @@ use strict;
 use Imager::Color;
 use vars qw(@ISA);
 @ISA = qw(Imager::Font);
+
+*_first = \&Imager::Font::_first;
+
 sub new {
   my $class = shift;
   my %hsh=(color=>Imager::Color->new(255,0,0,0),
@@ -114,6 +117,27 @@ sub has_chars {
   return i_ft2_has_chars($self->{id}, $hsh{string}, $hsh{'utf8'} || 0);
 }
 
+sub face_name {
+  my ($self) = @_;
+
+  i_ft2_face_name($self->{id});
+}
+
+sub can_glyph_names {
+  i_ft2_can_do_glyph_names();
+}
+
+sub glyph_names {
+  my ($self, %input) = @_;
+
+  my $string = $input{string};
+  defined $string
+    or return Imager->_seterror("no string parameter passed to glyph_names");
+  my $utf8 = _first($input{utf8} || 0);
+
+  i_ft2_glyph_name($self->{id}, $string, $utf8);
+}
+
 1;
 
 __END__
index 3b98504..ebb5ce6 100644 (file)
@@ -3,6 +3,8 @@ use strict;
 use vars qw(@ISA);
 @ISA = qw(Imager::Font);
 
+*_first = \&Imager::Font::_first;
+
 sub new {
   my $class = shift;
   my %hsh=(color=>Imager::Color->new(255,0,0,0),
@@ -79,6 +81,23 @@ sub has_chars {
   return Imager::i_tt_has_chars($self->{id}, $hsh{string}, $hsh{'utf8'} || 0);
 }
 
+sub face_name {
+  my ($self) = @_;
+
+  Imager::i_tt_face_name($self->{id});
+}
+
+sub glyph_names {
+  my ($self, %input) = @_;
+
+  my $string = $input{string};
+  defined $string
+    or return Imager->_seterror("no string parameter passed to glyph_names");
+  my $utf8 = _first($input{utf8} || 0);
+
+  Imager::i_tt_glyph_name($self->{id}, $string, $utf8);
+}
+
 1;
 
 __END__
index 78e78a4..bae8969 100644 (file)
@@ -4,6 +4,8 @@ use Imager::Color;
 use vars qw(@ISA);
 @ISA = qw(Imager::Font);
 
+*_first = \&Imager::Font::_first;
+
 my $t1aa;
 
 # $T1AA is in there because for some reason (probably cache related) antialiasing
@@ -99,6 +101,39 @@ sub _bounding_box {
                           length($input{string}), $input{utf8}, $flags);
 }
 
+# check if the font has the characters in the given string
+sub has_chars {
+  my ($self, %hsh) = @_;
+
+  unless (defined $hsh{string} && length $hsh{string}) {
+    $Imager::ERRSTR = "No string supplied to \$font->has_chars()";
+    return;
+  }
+  return Imager::i_t1_has_chars($self->{id}, $hsh{string}, $hsh{'utf8'} || 0);
+}
+
+sub utf8 {
+  1;
+}
+
+sub face_name {
+  my ($self) = @_;
+
+  Imager::i_t1_face_name($self->{id});
+}
+
+sub glyph_names {
+  my ($self, %input) = @_;
+
+  my $string = $input{string};
+  defined $string
+    or return Imager->_seterror("no string parameter passed to glyph_names");
+  my $utf8 = _first($input{utf8} || 0);
+
+  Imager::i_t1_glyph_name($self->{id}, $string, $utf8);
+}
+
+
 1;
 
 __END__
diff --git a/lib/Imager/Font/Wrap.pm b/lib/Imager/Font/Wrap.pm
new file mode 100644 (file)
index 0000000..b962a4c
--- /dev/null
@@ -0,0 +1,380 @@
+#!perl -w
+package Imager::Font::Wrap;
+use strict;
+use Imager;
+use Imager::Font;
+
+*_first = \&Imager::Font::_first;
+
+# we can't accept the utf8 parameter, too hard at this level
+
+# the %state contains:
+#  font - the font
+#  im - the image
+#  x - the left position
+#  w - the width
+#  justify - fill, left, right or center
+
+sub _format_line {
+  my ($state, $spaces, $text, $fill) = @_;
+
+  $text =~ s/ +$//;
+  my $box = $state->{font}->bounding_box(string=>$text,
+                                         size=>$state->{size});
+
+  my $y = $state->{linepos} + $box->global_ascent;
+  
+  if ($state->{bottom} 
+      && $state->{linepos} + $box->font_height > $state->{bottom}) {
+    $state->{full} = 1;
+    return 0;
+  }
+
+  if ($text =~ /\S/ && $state->{im}) {
+    my $justify = $fill ? $state->{justify} : 
+      $state->{justify} eq 'fill' ? 'left' : $state->{justify};
+    if ($justify ne 'fill') {
+      my $x = $state->{x};
+      if ($justify eq 'right') {
+        $x += $state->{w} - $box->advance_width;
+      }
+      elsif ($justify eq 'center') {
+        $x += ($state->{w} - $box->advance_width) / 2;
+      }
+      $state->{font}->draw(image=>$state->{im}, string=>$text, 
+                           x=>$x, 'y'=>$y,
+                           size=>$state->{size}, %{$state->{input}});
+    }
+    else {
+      (my $nospaces = $text) =~ tr/ //d;
+      my $nospace_bbox = $state->{font}->bounding_box(string=>$nospaces,
+                                                      size=>$state->{size});
+      my $gap = $state->{w} - $nospace_bbox->advance_width;
+      my $x = $state->{x};
+      $spaces = $text =~ tr/ / /;
+      while (length $text) {
+        if ($text =~ s/^(\S+)//) {
+          my $word = $1;
+          my $bbox = $state->{font}->bounding_box(string=>$word,
+                                                  size=>$state->{size});
+          $state->{font}->draw(image=>$state->{im}, string=>$1,
+                               x=>$x, 'y'=>$y,
+                               size=>$state->{size}, %{$state->{input}});
+          $x += $bbox->advance_width;
+        }
+        elsif ($text =~ s/^( +)//) {
+          my $sep = $1;
+          my $advance = int($gap * length($sep) / $spaces);
+          $spaces -= length $sep;
+          $gap -= $advance;
+          $x += $advance;
+        }
+        else {
+          die "This shouldn't happen\n";
+        }
+      }
+    }
+  }
+  $state->{linepos} += $box->font_height + $state->{linegap};
+
+  1;
+}
+
+sub wrap_text {
+  my $class = shift;
+  my %input = @_;
+
+  # try to get something useful
+  my $x = _first(delete $input{'x'}, 0);
+  my $y = _first(delete $input{'y'}, 0);
+  exists $input{image}
+    or return Imager->_set_error('No image parameter supplied');
+  my $im = delete $input{image};
+  my $imerr = $im || 'Imager';
+  my $width = delete $input{width};
+  if (!defined $width) {
+    defined $im && $im->getwidth > $x
+      or return $imerr->_set_error("No width supplied and can't guess");
+    $width = $im->getwidth - $x;
+  }
+  my $font = delete $input{font}
+    or return $imerr->_set_error("No font parameter supplied");
+  my $size = _first(delete $input{size}, $font->{size});
+  defined $size
+    or return $imerr->_set_error("No font size supplied");
+
+  2 * $size < $width
+    or return $imerr->_set_error("Width too small for font size");
+  
+  my $text = delete $input{string};
+  defined $text
+    or return $imerr->_set_error("No string parameter supplied");
+
+  my $justify = _first($input{justify}, "left");
+
+  my %state =
+    (
+     font => $font,
+     im => $im,
+     x => $x,
+     w => $width,
+     justify => $justify,
+     'y' => $y,
+     linepos=>$y,
+     size=>$size,
+     input => \%input,
+     linegap => delete $input{linegap} || 0,
+    );
+  $state{height} = delete $input{height};
+  if ($state{height}) {
+    $state{bottom} = $y + $state{height};
+  }
+  my $line = '';
+  my $spaces = 0;
+  my $charpos = 0;
+  my $linepos = 0;
+  pos($text) = 0; # avoid a warning
+  while (pos($text) < length($text)) {
+    #print pos($text), "\n";
+    if ($text =~ /\G( +)/gc) {
+      #print "spaces\n";
+      $line .= $1;
+      $spaces += length($1);
+    }
+    elsif ($text =~ /\G(?:\x0D\x0A?|\x0A\x0D?)/gc) {
+      #print "newline\n";
+      _format_line(\%state, $spaces, $line, 0)
+        or last;
+      $line = '';
+      $spaces = 0;
+      $linepos = pos($text);
+    }
+    elsif ($text =~ /\G(\S+)/gc) {
+      #print "word\n";
+      my $word = $1;
+      my $bbox = $font->bounding_box(string=>$line . $word, size=>$size);
+      if ($bbox->advance_width > $width) {
+        _format_line(\%state, $spaces, $line, 1)
+          or last;
+        $line = '';
+        $spaces = 0;
+        $linepos = pos($text) - length($word);
+      }
+      $line .= $word;
+      # check for long words
+      $bbox = $font->bounding_box(string=>$line, size=>$size);
+      while ($bbox->advance_width > $width) {
+        my $len = length($line) - 1;
+        $bbox = $font->bounding_box(string=>substr($line, 0, $len),
+                                    size=>$size);
+        while ($bbox->advance_width > $width) {
+          --$len;
+          $bbox = $font->bounding_box(string=>substr($line, 0, $len),
+                                      size=>$size);
+        }
+        _format_line(\%state, 0, substr($line, 0, $len), 0)
+          or last;
+        $line = substr($line, $len);
+        $bbox = $font->bounding_box(string=>$line, size=>$size);
+        $linepos = pos($text) - length($line);
+      }
+    }
+    elsif ($text =~ /\G\s/gc) {
+      # skip a single unrecognized whitespace char
+      #print "skip\n";
+      $linepos = pos($text);
+    }
+  }
+
+  if (length $line && !$state{full}) {
+    _format_line(\%state, 0, $line, 0);
+  }
+
+  if ($input{savepos}) {
+    ${$input{savepos}} = $linepos;
+  }
+
+  return ($x, $y, $x+$width, $state{linepos});
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+  Imager::Font::Wrap - simple wrapped text output
+
+=head1 SYNOPSIS
+
+  use Imager::Font::Wrap;
+
+  my $img = Imager->new(xsize=>$xsize, ysize=>$ysize);
+
+  my $font = Imager::Font->new(file=>$fontfile);
+
+  my $string = "..."; # text with or without newlines
+
+  Imager::Font::Wrap->wrap_text( image  => $img,
+                                 font   => $font,
+                                 string => $string,
+                                 x      => $left,
+                                 y      => $top,
+                                 width  => $width,
+                                 .... );
+
+=head1 DESCRIPTION
+
+This is a simple text wrapper with options to control the layout of
+text within the line.
+
+You can control the position, width and height of the text with the
+C<image>, C<x>, C<y>, C<width> and C<height> options.
+
+You can simply calculate space usage by setting C<image> to C<undef>,
+or set C<savepos> to see how much text can fit within the given
+C<height>.
+
+=head1 OPTIONS
+
+=over
+
+=item x
+
+=item y
+
+The top-left corner of the rectangle the text is formatted into.
+Defaults to (0, 0).
+
+=item width
+
+The width of the formatted text in pixels.  Defaults to the horizontal
+gap between the top-left corner and the right edge of the image.  If
+no image is supplied then this is required.
+
+=item height
+
+The maximum height of the formated text in pixels.  Not required.
+
+=item savepos
+
+The amount of text consumed (as a count of characters) will be stored
+into the scalar this refers to.
+
+  my $pagenum = 1;
+  my $string = "...";
+  my $font = ...;
+  my $savepos;
+
+  while (length $string) { 
+    my $img = Imager->new(xsize=>$xsize, ysize=>$ysize);
+    Imager::Font::Wrap->wrap_text(string=>$string, font=>$font, 
+                                  image=>$img, savepos => \$savepos)
+      or die $img->errstr;
+    $savepos > 0
+      or die "Could not fit any text on page\n";
+    $string = substr($string, $savepos);
+    $img->write(file=>"page$pagenum.ppm");
+  }
+
+=item image
+
+The image to render the text to.  Can be supplied as C<undef> to
+simply calculate the bounding box.
+
+=item font
+
+The font used to render the text.  Required.
+
+=item size
+
+The size to render the font in.  Defaults to the size stored in the
+font object.  Required if it isn't stored in the font object.
+
+=item string
+
+The text to render.  This can contain non-whitespace, blanks (ASCII
+0x20), and newlines.
+
+Newlines must match /(?:\x0A\x0D?|\x0D\x0A?)/.  Whitespace other than
+blanks and newlines are completely ignored.
+
+=item justify
+
+The way text is formatted within each line.  Possible values include:
+
+=over
+
+=item left
+
+Left aligned against the left edge of the text box.
+
+=item right
+
+Right aligned against the right edge of the text box.
+
+=item center
+
+Centered horizontally in the text box.
+
+=item fill
+
+All but the final line of the paragraph has spaces expanded so that
+the line fills from the left to the right edge of the text box.
+
+=back
+
+=item linegap
+
+Gap between lines of text in pixels.  This is in addition to the size
+from $font->font_height.  Can be positive or negative.  Default 0.
+
+=back
+
+Any other parameters are passed onto Imager::Font->draw().
+
+=head1 RETURNS
+
+Returns a list:
+
+  ($left, $top, $right, $bottom)
+
+which are the bounds of the space used to layout the text.
+
+If C<height> is set then this is the space used within that height.
+
+You can use this to calculate the space required to format the text
+before doing it:
+
+  my ($left, $top, $right, $bottom) =
+    Imager::Font::Wrap->wrap_text(string => $string,
+                                  font   => $font,
+                                  width  => $xsize);
+  my $img = Imager->new(xsize=>$xsize, ysize=>$bottom);
+  Imager::Font::Wrap->wrap_text(string => $string,
+                                font   => $font,
+                                width  => $xsize,
+                                image  => $image);
+
+=head1 BUGS
+
+Imager::Font can handle UTF8 encoded text itself, but this module
+doesn't support that (and probably won't).  This could probably be
+done with regex magic.
+
+Currently ignores the C<sizew> parameter, if you supply one it will be
+supplied to the draw() function and the text will be too short or too
+long for the C<width>.
+
+Uses a simplistic text model, which is why there's no hyphenation, and
+no tabs.
+
+=head1 AUTHOR
+
+Tony Cook <tony@develop-help.com>
+
+=head1 SEE ALSO
+
+Imager(3), Imager::Font(3)
+
+=cut
diff --git a/samples/transform.pl b/samples/transform.pl
new file mode 100644 (file)
index 0000000..793c504
--- /dev/null
@@ -0,0 +1,14 @@
+#perl -w
+use Imager;
+
+# generate a colorful spiral
+my $newimg = Imager::transform2({
+                                 width => 160, height=>160,
+                                 expr => <<EOS
+dist = distance(x, y, w/2, h/2);
+angle = atan2(y-h/2, x-w/2);
+angle2 = (dist / 10 + angle) % ( 2 * pi );
+return hsv(angle*180/pi, 1, (sin(angle2)+1)/2);
+EOS
+                                });
+$newimg->write(file=>'transform1.ppm');
diff --git a/samples/transform1.ppm b/samples/transform1.ppm
new file mode 100644 (file)
index 0000000..b8c5918
Binary files /dev/null and b/samples/transform1.ppm differ
index cd68975..22b1264 100644 (file)
@@ -8,7 +8,7 @@
 # (It may become useful if the test is moved to ./t subdirectory.)
 use strict;
 my $loaded;
-BEGIN { $| = 1; print "1..18\n"; }
+BEGIN { $| = 1; print "1..38\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all);
 use Imager::Color;
@@ -28,13 +28,13 @@ my $fontname_afm=$ENV{'T1FONTTESTAFM'}||'./fontfiles/dcr10.afm';
 
 
 if (!(i_has_format("t1")) ) {
-  skipx(17, "t1lib unavailable or disabled");
+  skipx(37, "t1lib unavailable or disabled");
 }
 elsif (! -f $fontname_pfb) {
-  skipx(17, "cannot find fontfile for truetype test $fontname_pfb");
+  skipx(37, "cannot find fontfile for type 1 test $fontname_pfb");
 }
 elsif (! -f $fontname_afm) {
-  skipx(17, "cannot find fontfile for truetype test $fontname_afm");
+  skipx(37, "cannot find fontfile for type 1 test $fontname_afm");
 } else {
 
   print "# has t1\n";
@@ -43,7 +43,7 @@ elsif (! -f $fontname_afm) {
 
   my $fnum=Imager::i_t1_new($fontname_pfb,$fontname_afm); # this will load the pfb font
   unless (okx($fnum >= 0, "load font $fontname_pfb")) {
-    skipx(6, "without the font I can't do a thing");
+    skipx(31, "without the font I can't do a thing");
     exit;
   }
 
@@ -55,7 +55,7 @@ elsif (! -f $fontname_afm) {
   i_line($overlay,0,50,100,50,$bgcolor,1);
 
   my @bbox=i_t1_bbox(0,50.0,'XMCLH',5);
-  okx(@bbox == 6, "i_t1_bbox");
+  okx(@bbox == 7, "i_t1_bbox");
   print "# bbox: ($bbox[0], $bbox[1]) - ($bbox[2], $bbox[3])\n";
 
   open(FH,">testout/t30t1font.ppm") || die "cannot open testout/t35t1font.ppm\n";
@@ -80,9 +80,9 @@ elsif (! -f $fontname_afm) {
   my $alttext = "A\xA1A";
   
   my @utf8box = i_t1_bbox($fnum, 50.0, $text, length($text), 1);
-  okx(@utf8box == 6, "utf8 bbox element count");
+  okx(@utf8box == 7, "utf8 bbox element count");
   my @base = i_t1_bbox($fnum, 50.0, $alttext, length($alttext), 0);
-  okx(@base == 6, "alt bbox element count");
+  okx(@base == 7, "alt bbox element count");
   my $maxdiff = $fontname_pfb eq $deffont ? 0 : $base[2] / 3;
   print "# (@utf8box vs @base)\n";
   okx(abs($utf8box[2] - $base[2]) <= $maxdiff, 
@@ -106,7 +106,7 @@ elsif (! -f $fontname_afm) {
     okx(i_t1_cp($backgr, 80, 180, 1, $fnum, 32, $text, length($text), 1),
         "cp UTF8");
     @utf8box = i_t1_bbox($fnum, 50.0, $text, length($text), 0);
-    okx(@utf8box == 6, "native utf8 bbox element count");
+    okx(@utf8box == 7, "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\xA1\xA2\x01\x1F\x{0100}A"};
@@ -138,6 +138,64 @@ elsif (! -f $fontname_afm) {
   okx(-e("t1lib.log"), "enable t1log");
   init(t1log=>0);
   unlink "t1lib.log";
+
+  # character existance tests - uses the special ExistenceTest font
+  my $exists_font = 'fontfiles/ExistenceTest.pfb';
+  my $exists_afm = 'fontfiles/ExistenceText.afm';
+  
+  -e $exists_font or die;
+    
+  my $font_num = Imager::i_t1_new($exists_font, $exists_afm);
+  if (okx($font_num >= 0, 'loading test font')) {
+    # first the list interface
+    my @exists = Imager::i_t1_has_chars($font_num, "!A");
+    okx(@exists == 2, "return count from has_chars");
+    okx($exists[0], "we have an exclamation mark");
+    okx(!$exists[1], "we have no uppercase A");
+
+    # then the scalar interface
+    my $exists = Imager::i_t1_has_chars($font_num, "!A");
+    okx(length($exists) == 2, "return scalar length");
+    okx(ord(substr($exists, 0, 1)), "we have an exclamation mark");
+    okx(!ord(substr($exists, 1, 1)), "we have no upper-case A");
+  }
+  else {
+    skipx(6, 'Could not load test font');
+  }
+  
+  my $font = Imager::Font->new(file=>$exists_font, type=>'t1');
+  if (okx($font, "loaded OO font")) {
+    my @exists = $font->has_chars(string=>"!A");
+    okx(@exists == 2, "return count from has_chars");
+    okx($exists[0], "we have an exclamation mark");
+    okx(!$exists[1], "we have no uppercase A");
+    
+    # then the scalar interface
+    my $exists = $font->has_chars(string=>"!A");
+    okx(length($exists) == 2, "return scalar length");
+    okx(ord(substr($exists, 0, 1)), "we have an exclamation mark");
+    okx(!ord(substr($exists, 1, 1)), "we have no upper-case A");
+
+    # check the advance width
+    my @bbox = $font->bounding_box(string=>'/', size=>100);
+    print "# @bbox\n";
+    okx($bbox[2] != $bbox[5], "different advance to pos_width");
+
+    # names
+    my $face_name = Imager::i_t1_face_name($font->{id});
+    print "# face $face_name\n";
+    okx($face_name eq 'ExistenceTest', "face name");
+    $face_name = $font->face_name;
+    okx($face_name eq 'ExistenceTest', "face name");
+
+    my @glyph_names = $font->glyph_names(string=>"!J/");
+    okx($glyph_names[0] eq 'exclam', "check exclam name OO");
+    okx(!defined($glyph_names[1]), "check for no J name OO");
+    okx($glyph_names[2] eq 'slash', "check slash name OO");
+  }
+  else {
+    skipx(12, "Could not load test font");
+  }
 }
 
 #malloc_state();
index 65b6941..c2dc3df 100644 (file)
@@ -7,7 +7,7 @@
 # (It may become useful if the test is moved to ./t subdirectory.)
 use strict;
 my $loaded;
-BEGIN { $| = 1; print "1..23\n"; }
+BEGIN { $| = 1; print "1..35\n"; }
 END {print "not ok 1\n" unless $loaded;}
 use Imager qw(:all);
 require "t/testtools.pl";
@@ -18,7 +18,7 @@ okx(1, "Loaded");
 init_log("testout/t35ttfont.log",2);
 
 unless (i_has_format("tt")) { 
-  skipx(22, "freetype 1.x unavailable or disabled");
+  skipx(34, "freetype 1.x unavailable or disabled");
   malloc_state();
   exit;
 }
@@ -29,7 +29,8 @@ my $fontname=$ENV{'TTFONTTEST'} || $deffont;
 
 if (! -f $fontname) {
   print "# cannot find fontfile for truetype test $fontname\n";
-  skip();      
+  skipx(34, 'Cannot load test font');  
+  exit;
 }
 
 i_init_fonts();
@@ -45,7 +46,7 @@ okx($ttraw, "create font");
 #warn Dumper($ttraw);
 
 my @bbox = i_tt_bbox($ttraw,50.0,'XMCLH',5,0);
-okx(@bbox == 6, "bounding box");
+okx(@bbox == 7, "bounding box");
 print "#bbox: ($bbox[0], $bbox[1]) - ($bbox[2], $bbox[3])\n";
 
 okx(i_tt_cp($ttraw,$overlay,5,50,1,50.0,'XMCLH',5,1,0), "cp output");
@@ -84,9 +85,9 @@ 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");
+okx(@utf8box == 7, "utf8 bbox element count");
 my @base = i_tt_bbox($ttraw, 50.0, $alttext, length($alttext), 0);
-okx(@base == 6, "alt bbox element count");
+okx(@base == 7, "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, 
@@ -110,7 +111,7 @@ if ($] >= 5.006) {
   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(@utf8box == 7, "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
@@ -127,4 +128,39 @@ $IO = Imager::io_new_fd( fileno(FH) );
 okx(i_writeppm_wiol($backgr, $IO), "save t35ttfont2.ppm");
 close(FH);
 
+my $exists_font = "fontfiles/ExistenceTest.ttf";
+my $hcfont = Imager::Font->new(file=>$exists_font, type=>'tt');
+if (okx($hcfont, "loading existence test font")) {
+  # list interface
+  my @exists = $hcfont->has_chars(string=>'!A');
+  okx(@exists == 2, "check return count");
+  okx($exists[0], "we have an exclamation mark");
+  okx(!$exists[1], "we have no exclamation mark");
+
+  # scalar interface
+  my $exists = $hcfont->has_chars(string=>'!A');
+  okx(length($exists) == 2, "check return length");
+  okx(ord(substr($exists, 0, 1)), "we have an exclamation mark");
+  okx(!ord(substr($exists, 1, 1)), "we have no upper-case A");
+
+  my $face_name = Imager::i_tt_face_name($hcfont->{id});
+  print "# face $face_name\n";
+  okx($face_name eq 'ExistenceTest', "face name");
+  $face_name = $hcfont->face_name;
+  okx($face_name eq 'ExistenceTest', "face name");
+
+  # FT 1.x cheats and gives names even if the font doesn't have them
+  my @glyph_names = $hcfont->glyph_names(string=>"!J/");
+  okx($glyph_names[0] eq 'exclam', "check exclam name OO");
+  okx(!defined($glyph_names[1]), "check for no J name OO");
+  okx($glyph_names[2] eq 'slash', "check slash name OO");
+
+  print "# ** name table of the test font **\n";
+  Imager::i_tt_dump_names($hcfont->{id});
+}
+else {
+  skipx(11, "could not load test font");
+}
+undef $hcfont;
+
 okx(1, "end of code");
index 7cc80b3..cab30fe 100644 (file)
@@ -46,7 +46,7 @@ if (i_has_format("t1") and -f $fontname_pfb) {
   my $text="LLySja";
   my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
 
-  okx(@bbox == 6, "bounding box list length");
+  okx(@bbox == 7, "bounding box list length");
 
   $img->box(box=>\@bbox, color=>$green);
 
@@ -97,7 +97,7 @@ if (i_has_format("tt") and -f $fontname_tt) {
   my $text="LLySja";
   my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
 
-  okx(@bbox == 6, "bbox list size");
+  okx(@bbox == 7, "bbox list size");
 
   $img->box(box=>\@bbox, color=>$green);
 
index e78187d..0ffd141 100644 (file)
@@ -7,33 +7,31 @@
 # 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..14\n"; }
+BEGIN { $| = 1; print "1..125\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/t38ft2font.log",2);
 
-sub skip { 
-  for (2..14) {
-    print "ok $_ # skip no Freetype2 library\n";
-  }
-  malloc_state();
-  exit(0);
+if (!(i_has_format("ft2")) ) { 
+  skipx(124, "No freetype2 library found");
+  exit;
 }
-
-if (!(i_has_format("ft2")) ) { skip(); }
 print "# has ft2\n";
 
 $fontname=$ENV{'TTFONTTEST'}||'./fontfiles/dodge.ttf';
 
 if (! -f $fontname) {
-  print "# cannot find fontfile for truetype test $fontname\n";
-  skip();      
+  skipx(124, "cannot find fontfile $fontname");
+  malloc_state();
+  exit;
 }
 
-i_init_fonts();
+#i_init_fonts();
 #     i_tt_set_aa(1);
 
 $bgcolor=i_color_new(255,0,0,0);
@@ -42,62 +40,57 @@ $overlay=Imager::ImgRaw::new(200,70,3);
 $ttraw=Imager::Font::FreeType2::i_ft2_new($fontname, 0);
 
 $ttraw or print Imager::_error_as_msg(),"\n";
+okx($ttraw, "loaded raw font");
 #use Data::Dumper;
 #warn Dumper($ttraw);
 
 @bbox=Imager::Font::FreeType2::i_ft2_bbox($ttraw, 50.0, 0, 'XMCLH', 0);
-print "#bbox: ($bbox[0], $bbox[1]) - ($bbox[2], $bbox[3])\n";
+print "#bbox @bbox\n";
+
+okx(@bbox == 7, "i_ft2_bbox() returns 7 values");
 
-Imager::Font::FreeType2::i_ft2_cp($ttraw,$overlay,5,50,1,50.0,50, 'XMCLH',1,1, 0, 0);
+okx(Imager::Font::FreeType2::i_ft2_cp($ttraw,$overlay,5,50,1,50.0,50, 'XMCLH',1,1, 0, 0), "drawn to channel");
 i_line($overlay,0,50,100,50,$bgcolor,1);
 
 open(FH,">testout/t38ft2font.ppm") || die "cannot open testout/t38ft2font.ppm\n";
 binmode(FH);
 my $IO = Imager::io_new_fd(fileno(FH));
-i_writeppm_wiol($overlay, $IO);
+okx(i_writeppm_wiol($overlay, $IO), "saved image");
 close(FH);
 
-print "ok 2\n";
-
-dotest:
-
 $bgcolor=i_color_set($bgcolor,200,200,200,0);
 $backgr=Imager::ImgRaw::new(500,300,3);
 
 #     i_tt_set_aa(2);
-Imager::Font::FreeType2::i_ft2_text($ttraw,$backgr,100,150,NC(255, 64, 64),200.0,50, 'MAW',1,1,0, 0);
+okx(Imager::Font::FreeType2::i_ft2_text($ttraw,$backgr,100,150,NC(255, 64, 64),200.0,50, 'MAW',1,1,0, 0), "drew MAW");
 Imager::Font::FreeType2::i_ft2_settransform($ttraw, [0.9659, 0.2588, 0, -0.2588, 0.9659, 0 ]);
-Imager::Font::FreeType2::i_ft2_text($ttraw,$backgr,100,150,NC(0, 128, 0),200.0,50, 'MAW',0,1, 0, 0);
+okx(Imager::Font::FreeType2::i_ft2_text($ttraw,$backgr,100,150,NC(0, 128, 0),200.0,50, 'MAW',0,1, 0, 0), "drew rotated MAW");
 i_line($backgr, 0,150, 499, 150, NC(0, 0, 255),1);
 
 open(FH,">testout/t38ft2font2.ppm") || die "cannot open testout/t38ft2font.ppm\n";
 binmode(FH);
 $IO = Imager::io_new_fd(fileno(FH));
-i_writeppm_wiol($backgr,$IO);
+okx(i_writeppm_wiol($backgr,$IO), "saved second image");
 close(FH);
 
-print "ok 3\n";
-
 #$fontname = 'fontfiles/arial.ttf';
-my $oof = Imager::Font->new(file=>$fontname, type=>'ft2', 'index'=>0)
-  or print "not ";
-print "ok 4\n";
+my $oof = Imager::Font->new(file=>$fontname, type=>'ft2', 'index'=>0);
+
+okx($oof, "loaded OO font");
 
 my $im = Imager->new(xsize=>400, ysize=>250);
 
-$im->string(font=>$oof,
+okx($im->string(font=>$oof,
             text=>"Via OO",
             'x'=>20,
             'y'=>20,
             size=>60,
             color=>NC(255, 128, 255),
             aa => 1,
-            align=>0) or print "not ";
-print "ok 5\n";
-$oof->transform(matrix=>[1, 0.1, 0, 0, 1, 0])
-  or print "not ";
-print "ok 6\n";
-$im->string(font=>$oof,
+            align=>0), "drawn through OO interface");
+okx($oof->transform(matrix=>[1, 0.1, 0, 0, 1, 0]),
+    "set matrix via OO interface");
+okx($im->string(font=>$oof,
             text=>"Shear",
             'x'=>20,
             'y'=>40,
@@ -105,12 +98,12 @@ $im->string(font=>$oof,
             sizew=>50,
             channel=>1,
             aa=>1,
-            align=>1) or print "not ";
-print "ok 7\n";
+            align=>1), "drawn transformed through OO");
 use Imager::Matrix2d ':handy';
-$oof->transform(matrix=>m2d_rotate(degrees=>-30));
+okx($oof->transform(matrix=>m2d_rotate(degrees=>-30)),
+                   "set transform from m2d module");
 #$oof->transform(matrix=>m2d_identity());
-$im->string(font=>$oof,
+okx($im->string(font=>$oof,
             text=>"SPIN",
             'x'=>20,
             'y'=>50,
@@ -118,9 +111,9 @@ $im->string(font=>$oof,
            sizew=>40,
             color=>NC(255,255,0),
             aa => 1,
-            align=>0, vlayout=>0)
-and
-$im->string(font=>$oof,
+            align=>0, vlayout=>0), "drawn first rotated");
+
+okx($im->string(font=>$oof,
             text=>"SPIN",
             'x'=>20,
             'y'=>50,
@@ -128,8 +121,7 @@ $im->string(font=>$oof,
            sizew=>40,
             channel=>2,
             aa => 1,
-            align=>0, vlayout=>0) or print "not ";
-print "ok 8\n";
+            align=>0, vlayout=>0), "drawn second rotated");
 
 $oof->transform(matrix=>m2d_identity());
 $oof->hinting(hinting=>1);
@@ -145,39 +137,33 @@ if ($] >= 5.006) {
   # versions
   eval q{$text = "A\x{2010}A"}; # A, HYPHEN, A in our test font
   #$text = "A".chr(0x2010)."A"; # this one works too
-  if ($im->string(font=>$oof,
+  unless (okx($im->string(font=>$oof,
               text=>$text,
               'x'=>20,
               'y'=>200,
               size=>50,
               color=>NC(0,255,0),
-              aa=>1)) {
-    print "ok 9\n";
-  }
-  else {
-    print "not ok 9 # ",$im->errstr,"\n";
+              aa=>1), "drawn UTF natively")) {
+    print "# ",$im->errstr,"\n";
   }
 }
 else {
-  print "ok 9 # skip no native UTF8 support in this version of perl\n";
+  skipx(1, "no native UTF8 support in this version of perl");
 }
 
 # an attempt using emulation of UTF8
 my $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
 #my $text = "A\xE2\x80\x90\x41\x{2010}";
 #substr($text, -1, 0) = '';
-if ($im->string(font=>$oof,
+unless (okx($im->string(font=>$oof,
                 text=>$text,
                 'x'=>20,
                 'y'=>230,
                 size=>50,
                 color=>NC(255,128,0),
                 aa=>1, 
-                utf8=>1)) {
-  print "ok 10\n";
-}
-else {
-  print "not ok 10 # ",$im->errstr,"\n";
+                utf8=>1), "drawn UTF emulated")) {
+  print "# ",$im->errstr,"\n";
 }
 
 # just a bit of fun
@@ -196,18 +182,160 @@ for my $steps (0..39) {
               size=>65,
               color=>NC(255, $steps * 5, 200-$steps * 5),
               aa => 1,
-              align=>0, ) or print "not ";
+              align=>0, );
 }
 
 $im->write(file=>'testout/t38_oo.ppm')
   or print "# could not save OO output: ",$im->errstr,"\n";
 
 my (@got) = $oof->has_chars(string=>"\x01H");
-@got == 2 or print "not ";
-print "ok 11\n";
-$got[0] and print "not ";
-print "ok 12 # check if \\x01 is defined\n";
-$got[1] or print "not ";
-print "ok 13 # check if 'H' is defined\n";
-$oof->has_chars(string=>"H\x01") eq "\x01\x00" or print "not ";
-print "ok 14\n";
+okx(@got == 2, "has_chars returned 2 items");
+okx(!$got[0], "have no chr(1)");
+okx($got[1], "have 'H'");
+okx($oof->has_chars(string=>"H\x01") eq "\x01\x00",
+    "scalar has_chars()");
+
+print "# OO bounding boxes\n";
+my @bbox = $oof->bounding_box(string=>"hello", size=>30);
+my $bbox = $oof->bounding_box(string=>"hello", size=>30);
+
+okx(@bbox == 7, "list bbox returned 7 items");
+okx($bbox->isa('Imager::Font::BBox'), "scalar bbox returned right class");
+okx($bbox->start_offset == $bbox[0], "start_offset");
+okx($bbox->end_offset == $bbox[2], "end_offset");
+okx($bbox->global_ascent == $bbox[3], "global_ascent");
+okx($bbox->global_descent == $bbox[1], "global_descent");
+okx($bbox->ascent == $bbox[5], "ascent");
+okx($bbox->descent == $bbox[4], "descent");
+okx($bbox->advance_width == $bbox[6], "advance_width");
+
+print "# aligned text output\n";
+my $alimg = Imager->new(xsize=>300, ysize=>300);
+$alimg->box(color=>'40FF40', filled=>1);
+my @base_color = (64, 255, 64);
+
+$oof->transform(matrix=>m2d_identity());
+$oof->hinting(hinting=>1);
+
+align_test('left', 'top', 10, 10, $oof, $alimg);
+align_test('start', 'top', 10, 40, $oof, $alimg);
+align_test('center', 'top', 150, 70, $oof, $alimg);
+align_test('end', 'top', 290, 100, $oof, $alimg);
+align_test('right', 'top', 290, 130, $oof, $alimg);
+
+align_test('center', 'top', 150, 160, $oof, $alimg);
+align_test('center', 'center', 150, 190, $oof, $alimg);
+align_test('center', 'bottom', 150, 220, $oof, $alimg);
+align_test('center', 'baseline', 150, 250, $oof, $alimg);
+
+okx($alimg->write(file=>'testout/t38aligned.ppm'), 
+    "saving aligned output image");
+
+my $exfont = Imager::Font->new(file=>'fontfiles/ExistenceTest.ttf',
+                               type=>'ft2');
+if (okx($exfont, "loaded existence font")) {
+  # the test font is known to have a shorter advance width for that char
+  my @bbox = $exfont->bounding_box(string=>"/", size=>100);
+  okx(@bbox == 7, "should be 7 entries");
+  okx($bbox[6] != $bbox[4], "different advance width");
+  my $bbox = $exfont->bounding_box(string=>"/", size=>100);
+  okx($bbox->pos_width != $bbox->advance_width, "OO check");
+
+  # name tests
+  my $facename = Imager::Font::FreeType2::i_ft2_face_name($exfont->{id});
+  print "# face name '$facename'\n";
+  okx($facename eq 'ExistenceTest', "test face name");
+  $facename = $exfont->face_name;
+  okx($facename eq 'ExistenceTest', "test face name OO");
+
+}
+else {
+  skipx(5, "couldn't load test font");
+}
+
+if (Imager::Font::FreeType2->can_glyph_names) {
+  # pfaedit doesn't seem to save glyph names into TTF files
+  my $exfont = Imager::Font->new(file=>'fontfiles/ExistenceTest.pfb',
+                               type=>'ft2');
+  if (okx($exfont, "load Type 1 via FT2")) {
+    my @glyph_names = 
+      Imager::Font::FreeType2::i_ft2_glyph_name($exfont->{id}, "!J/");
+    #use Data::Dumper;
+    #print Dumper \@glyph_names;
+    okx($glyph_names[0] eq 'exclam', "check exclam name");
+    okx(!defined($glyph_names[1]), "check for no J name");
+    okx($glyph_names[2] eq 'slash', "check slash name");
+
+    # oo interfaces
+    @glyph_names = $exfont->glyph_names(string=>"!J/");
+    okx($glyph_names[0] eq 'exclam', "check exclam name OO");
+    okx(!defined($glyph_names[1]), "check for no J name OO");
+    okx($glyph_names[2] eq 'slash', "check slash name OO");
+  }
+  else {
+    skipx(6, "couldn't load type 1 with FT2");
+  }
+}
+else {
+  skipx(7, "FT2 compiled without glyph names support");
+}
+
+sub align_test {
+  my ($h, $v, $x, $y, $f, $img) = @_;
+
+  my @pos = $f->align(halign=>$h, valign=>$v, 'x'=>$x, 'y'=>$y,
+                      image=>$img, size=>15, color=>'FFFFFF',
+                      string=>"x$h ${v}y", channel=>1, aa=>1);
+  if (okx(@pos == 4, "$h $v aligned output")) {
+    # checking corners
+    my $cx = int(($pos[0] + $pos[2]) / 2);
+    my $cy = int(($pos[1] + $pos[3]) / 2);
+    
+    print "# @pos cx $cx cy $cy\n";
+    okmatchcolor($img, $cx, $pos[1]-1, @base_color, "outer top edge");
+    okmatchcolor($img, $cx, $pos[3], @base_color, "outer bottom edge");
+    okmatchcolor($img, $pos[0]-1, $cy, @base_color, "outer left edge");
+    okmatchcolor($img, $pos[2], $cy, @base_color, "outer right edge");
+    
+    okmismatchcolor($img, $cx, $pos[1], @base_color, "inner top edge");
+    okmismatchcolor($img, $cx, $pos[3]-1, @base_color, "inner bottom edge");
+    okmismatchcolor($img, $pos[0], $cy, @base_color, "inner left edge");
+    okmismatchcolor($img, $pos[2]-1, $cy, @base_color, "inner right edge");
+    
+    cross($img, $x, $y, 'FF0000');
+    cross($img, $cx, $pos[1]-1, '0000FF');
+    cross($img, $cx, $pos[3], '0000FF');
+    cross($img, $pos[0]-1, $cy, '0000FF');
+    cross($img, $pos[2], $cy, '0000FF');
+  }
+  else {
+    skipx(8, "couldn't draw text");
+  }
+}
+
+sub okmatchcolor {
+  my ($img, $x, $y, $r, $g, $b, $about) = @_;
+
+  my $c = $img->getpixel('x'=>$x, 'y'=>$y);
+  my ($fr, $fg, $fb) = $c->rgba;
+  okx($fr == $r && $fg == $g && $fb == $b,
+      "want ($r,$g,$b) found ($fr,$fg,$fb)\@($x,$y) $about");
+}
+
+sub okmismatchcolor {
+  my ($img, $x, $y, $r, $g, $b, $about) = @_;
+
+  my $c = $img->getpixel('x'=>$x, 'y'=>$y);
+  my ($fr, $fg, $fb) = $c->rgba;
+  okx($fr != $r || $fg != $g || $fb != $b,
+      "don't want ($r,$g,$b) found ($fr,$fg,$fb)\@($x,$y) $about");
+}
+
+sub cross {
+  my ($img, $x, $y, $color) = @_;
+
+  $img->setpixel('x'=>[$x, $x, $x, $x, $x, $x-2, $x-1, $x+1, $x+2], 
+                 'y'=>[$y-2, $y-1, $y, $y+1, $y+2, $y, $y, $y, $y], 
+                 color => $color);
+  
+}
diff --git a/t/t80texttools.t b/t/t80texttools.t
new file mode 100644 (file)
index 0000000..8483b5e
--- /dev/null
@@ -0,0 +1,88 @@
+use strict;
+my $loaded;
+BEGIN { 
+  require "t/testtools.pl";
+  $| = 1; print "1..11\n"; 
+}
+END { okx(0, "loading") unless $loaded; }
+use Imager;
+$loaded = 1;
+
+okx(1, "Loaded");
+
+requireokx("Imager/Font/Wrap.pm", "load basic wrapping");
+
+my $img = Imager->new(xsize=>400, ysize=>400);
+
+my $text = <<EOS;
+This is a test of text wrapping. This is a test of text wrapping. This =
+is a test of text wrapping. This is a test of text wrapping. This is a =
+test of text wrapping. This is a test of text wrapping. This is a test =
+of text wrapping. This is a test of text wrapping. This is a test of =
+text wrapping. XX.
+
+Xxxxxxxxxxxxxxxxxxxxxxxxxxxwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww xxxx.
+
+This is a test of text wrapping. This is a test of text wrapping. This =
+is a test of text wrapping. This is a test of text wrapping. This is a =
+test of text wrapping. This is a test of text wrapping. This is a test =
+of text wrapping. This is a test of text wrapping. This is a test of =
+text wrapping. This is a test of text wrapping. This is a test of text =
+wrapping. This is a test of text wrapping. This is a test of text =
+wrapping. This is a test of text wrapping. This is a test of text =
+wrapping. This is a test of text wrapping. This is a test of text =
+wrapping. XX.
+EOS
+
+$text =~ s/=\n//g;
+
+my $fontfile = $ENV{WRAPTESTFONT} || $ENV{TTFONTTEST} || "fontfiles/ImUgly.ttf";
+
+my $font = Imager::Font->new(file=>$fontfile);
+
+unless (Imager::i_has_format('tt') || Imager::i_has_format('ft2')) {
+  skipx(9, "Need Freetype 1.x or 2.x to test");
+  exit;
+}
+
+if (okx($font, "loading font")) {
+  Imager::Font->priorities(qw(t1 ft2 tt));
+  okx(scalar Imager::Font::Wrap->wrap_text(string => $text,
+                                font=>$font,
+                                image=>$img,
+                                size=>13,
+                                width => 380, aa=>1,
+                                x=>10, 'y'=>10,
+                                justify=>'fill',
+                                color=>'FFFFFF'),
+      "basic test");
+  okx($img->write(file=>'testout/t80wrapped.ppm'), "save to file");
+  okx(scalar Imager::Font::Wrap->wrap_text(string => $text,
+                                font=>$font,
+                                image=>undef,
+                                size=>13,
+                                width => 380,
+                                x=>10, 'y'=>10,
+                                justify=>'left',
+                                color=>'FFFFFF'),
+      "no image test");
+  my $bbox = $font->bounding_box(string=>"Xx", size=>13);
+  okx($bbox, "get height for check");
+
+  my $used;
+  okx(scalar Imager::Font::Wrap->wrap_text
+      (string=>$text, font=>$font, image=>undef, size=>13, width=>380,
+       savepos=> \$used, height => $bbox->font_height), "savepos call");
+  okx($used > 20 && $used < length($text), "savepos value");
+  print "# $used\n";
+  my @box = Imager::Font::Wrap->wrap_text
+    (string=>substr($text, 0, $used), font=>$font, image=>undef, size=>13,
+     width=>380);
+
+  okx(@box == 4, "bounds list count");
+  print "# @box\n";
+  okx($box[3] == $bbox->font_height, "check height");
+}
+else {
+  skipx(8, "Could not load test font");
+}
index 997e831..ac5cbaf 100644 (file)
@@ -53,5 +53,23 @@ sub okn {
   return $ok;
 }
 
+sub requireokx {
+  my ($file, $comment) = @_;
+
+  eval {
+    require $file;
+  };
+  if ($@) {
+    my $msg = $@;
+    $msg =~ s/\n+$//;
+    $msg =~ s/\n/\n# /g;
+    okx(0, $comment);
+    print "# $msg\n";
+  }
+  else {
+    okx(1, $comment);
+  }
+}
+
 1;