- 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
=================================================================
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
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])));
+ }
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
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)))
#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
}
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
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
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])));
}
}
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_
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
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
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
- 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
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")) {
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
*/
-
-
-
-
-
-
-
-
/*
=item i_init_fonts()
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)
=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 */
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),
(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;
}
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)
TT_Face_Properties properties;
TT_Instancehandle instanceh[TT_CHC];
TT_CharMap char_map;
+#ifdef FTXPOST
+ int loaded_names;
+ TT_Error load_cond;
+#endif
};
/* Defines */
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);
}
handle->instanceh[i].smooth=-1;
}
+#ifdef FTXPOST
+ handle->loaded_names = 0;
+#endif
+
mm_log((1,"i_tt_new <- 0x%X\n",handle));
return handle;
}
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) {
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;
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;
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;
int gascent = 0;
int descent = 0;
int ascent = 0;
-
+ int rightb = 0;
unsigned long j;
unsigned char *ustr;
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 );
}
}
- 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;
}
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 */
--- /dev/null
+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
--- /dev/null
+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
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;
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));
i_push_error(0, "setting size");
}
+ if (!handle->hint)
+ loadFlags |= FT_LOAD_NO_HINTING;
+
first = 1;
width = 0;
while (len) {
}
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)",
/* 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;
}
/*
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);
}
x += slot->advance.x / 64;
y += slot->advance.y / 64;
-
+
if (glyph_ascent > ascent)
ascent = glyph_ascent;
if (glyph_descent > descent)
return 1;
}
-
-
static int
make_bmp_map(FT_Bitmap *bitmap, unsigned char *map);
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;
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
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
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 */
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
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)
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
}
$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";
$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=@_;
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 {
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");
$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
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)
->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
=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/
--- /dev/null
+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;
+
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),
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__
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),
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__
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
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__
--- /dev/null
+#!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
--- /dev/null
+#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');
# (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;
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";
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;
}
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";
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,
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"};
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();
# (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";
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;
}
if (! -f $fontname) {
print "# cannot find fontfile for truetype test $fontname\n";
- skip();
+ skipx(34, 'Cannot load test font');
+ exit;
}
i_init_fonts();
#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");
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,
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
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");
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);
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);
# 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);
$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,
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,
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,
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);
# 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
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);
+
+}
--- /dev/null
+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");
+}
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;