diff --git a/Changes b/Changes index 1d46f9e7..6afb3505 100644 --- 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 ================================================================= diff --git a/Imager.pm b/Imager.pm index 744f221c..6d282dff 100644 --- 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 Cnew> method is described in detail in the +The Cnew> method is described in detail in the Imager::ImageTypes manpage. =head1 SUPPORT diff --git a/Imager.xs b/Imager.xs index 1d8afcb6..d7504d36 100644 --- 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_ diff --git a/MANIFEST b/MANIFEST index fcabafb8..ac14cf11 100644 --- 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 daf7d2e0..42e2df7a 100644 --- 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 diff --git a/bigtest.perl b/bigtest.perl index 0fdcca8a..dd04dac3 100644 --- a/bigtest.perl +++ b/bigtest.perl @@ -25,7 +25,7 @@ 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")) { diff --git a/datatypes.h b/datatypes.h index 7e2ac9b3..5fa528dd 100644 --- a/datatypes.h +++ b/datatypes.h @@ -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 15037ad7..5a40feb1 100644 --- 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 to C. Returns +the number of characters required to store the name (which can be +larger than C, 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 #define TT_CHC 5 +#ifdef FTXPOST +#include +#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 index 00000000..7dd627c2 --- /dev/null +++ b/fontfiles/ExistenceTest.afm @@ -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 index 00000000..46a98745 Binary files /dev/null and b/fontfiles/ExistenceTest.pfb differ diff --git a/fontfiles/ExistenceTest.sfd b/fontfiles/ExistenceTest.sfd new file mode 100644 index 00000000..012568a4 --- /dev/null +++ b/fontfiles/ExistenceTest.sfd @@ -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 index 00000000..a4a7e568 Binary files /dev/null and b/fontfiles/ExistenceTest.ttf differ diff --git a/freetyp2.c b/freetyp2.c index 0b237910..d64757d5 100644 --- a/freetyp2.c +++ b/freetyp2.c @@ -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 9ca74a3c..1acd9f83 100644 --- 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 diff --git a/lib/Imager/Engines.pod b/lib/Imager/Engines.pod index 5a33036a..17914006 100644 --- a/lib/Imager/Engines.pod +++ b/lib/Imager/Engines.pod @@ -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. For details on the virtual machine used to transform the images, see L. + # generate a colorful spiral + # requires that Parse::RecDescent be installed + my $newimg = Imager::transform2({ + width => 160, height=>160, + expr => <{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 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 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 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 index 00000000..f39c8649 --- /dev/null +++ b/lib/Imager/Font/BBox.pm @@ -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 + +=head1 SEE ALSO + +Imager(3), Imager::Font(3) + +=cut + +1; + diff --git a/lib/Imager/Font/FreeType2.pm b/lib/Imager/Font/FreeType2.pm index f22a7239..bcf108dd 100644 --- a/lib/Imager/Font/FreeType2.pm +++ b/lib/Imager/Font/FreeType2.pm @@ -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__ diff --git a/lib/Imager/Font/Truetype.pm b/lib/Imager/Font/Truetype.pm index 3b985042..ebb5ce61 100644 --- a/lib/Imager/Font/Truetype.pm +++ b/lib/Imager/Font/Truetype.pm @@ -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__ diff --git a/lib/Imager/Font/Type1.pm b/lib/Imager/Font/Type1.pm index 78e78a49..bae89699 100644 --- a/lib/Imager/Font/Type1.pm +++ b/lib/Imager/Font/Type1.pm @@ -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 index 00000000..b962a4cd --- /dev/null +++ b/lib/Imager/Font/Wrap.pm @@ -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, C, C, C and C options. + +You can simply calculate space usage by setting C to C, +or set C to see how much text can fit within the given +C. + +=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 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 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 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. + +Uses a simplistic text model, which is why there's no hyphenation, and +no tabs. + +=head1 AUTHOR + +Tony Cook + +=head1 SEE ALSO + +Imager(3), Imager::Font(3) + +=cut diff --git a/samples/transform.pl b/samples/transform.pl new file mode 100644 index 00000000..793c504f --- /dev/null +++ b/samples/transform.pl @@ -0,0 +1,14 @@ +#perl -w +use Imager; + +# generate a colorful spiral +my $newimg = Imager::transform2({ + width => 160, height=>160, + expr => <write(file=>'transform1.ppm'); diff --git a/samples/transform1.ppm b/samples/transform1.ppm new file mode 100644 index 00000000..b8c5918c Binary files /dev/null and b/samples/transform1.ppm differ diff --git a/t/t30t1font.t b/t/t30t1font.t index cd689751..22b12642 100644 --- a/t/t30t1font.t +++ b/t/t30t1font.t @@ -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(); diff --git a/t/t35ttfont.t b/t/t35ttfont.t index 65b69411..c2dc3dfd 100644 --- a/t/t35ttfont.t +++ b/t/t35ttfont.t @@ -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"); diff --git a/t/t36oofont.t b/t/t36oofont.t index 7cc80b3e..cab30fe7 100644 --- a/t/t36oofont.t +++ b/t/t36oofont.t @@ -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); diff --git a/t/t38ft2font.t b/t/t38ft2font.t index e78187d3..0ffd1415 100644 --- a/t/t38ft2font.t +++ b/t/t38ft2font.t @@ -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 index 00000000..8483b5ed --- /dev/null +++ b/t/t80texttools.t @@ -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 = <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"); +} diff --git a/t/testtools.pl b/t/testtools.pl index 997e8312..ac5cbaf3 100644 --- a/t/testtools.pl +++ b/t/testtools.pl @@ -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;