and at least mention it in the docs beyond the examples
- document the values for the read() and write() method type
parameter
+ - support UTF8 text with Freetype 1.x
=================================================================
undef_int
-i_tt_text(handle,im,xb,yb,cl,points,str,len,smooth)
+i_tt_text(handle,im,xb,yb,cl,points,str_sv,len_ignored,smooth,utf8)
Imager::Font::TT handle
Imager::ImgRaw im
int xb
int yb
Imager::Color cl
float points
- char* str
- int len
+ SV * str_sv
+ int len_ignored
int smooth
+ int utf8
+ PREINIT:
+ char *str;
+ STRLEN len;
+ CODE:
+#ifdef SvUTF8
+ if (SvUTF8(str_sv))
+ utf8 = 1;
+#endif
+ str = SvPV(str_sv, len);
+ RETVAL = i_tt_text(handle, im, xb, yb, cl, points, str,
+ len, smooth, utf8);
+ OUTPUT:
+ RETVAL
undef_int
-i_tt_cp(handle,im,xb,yb,channel,points,str,len,smooth)
+i_tt_cp(handle,im,xb,yb,channel,points,str_sv,len_ignored,smooth,utf8)
Imager::Font::TT handle
Imager::ImgRaw im
int xb
int yb
int channel
float points
- char* str
- int len
+ SV * str_sv
+ int len_ignored
int smooth
-
+ int utf8
+ PREINIT:
+ char *str;
+ STRLEN len;
+ CODE:
+#ifdef SvUTF8
+ if (SvUTF8(str_sv))
+ utf8 = 1;
+#endif
+ str = SvPV(str_sv, len);
+ RETVAL = i_tt_cp(handle, im, xb, yb, channel, points, str, len,
+ smooth, utf8);
+ OUTPUT:
+ RETVAL
undef_int
-i_tt_bbox(handle,point,str,len)
+i_tt_bbox(handle,point,str_sv,len_ignored, utf8)
Imager::Font::TT handle
float point
- char* str
- int len
+ SV* str_sv
+ int len_ignored
+ int utf8
PREINIT:
int cords[6],rc;
+ char * str;
+ STRLEN len;
PPCODE:
- if ((rc=i_tt_bbox(handle,point,str,len,cords))) {
+#ifdef SvUTF8
+ if (SvUTF8(ST(2)))
+ utf8 = 1;
+#endif
+ str = SvPV(str_sv, len);
+ if ((rc=i_tt_bbox(handle,point,str,len,cords, utf8))) {
EXTEND(SP, 4);
PUSHs(sv_2mortal(newSViv(cords[0])));
PUSHs(sv_2mortal(newSViv(cords[1])));
#endif /* HAVE_LIBT1 */
+/* Truetype font support */
+#ifdef HAVE_LIBTT
+#include <freetype.h>
+#define TT_CHC 5
+/* convert a code point into an index in the glyph cache */
+#define TT_HASH(x) ((x) & 0xFF)
+typedef struct i_glyph_entry_ {
+ TT_Glyph glyph;
+ unsigned long ch;
+} i_tt_glyph_entry;
+#define TT_NOCHAR (~0UL)
+struct TT_Instancehandle_ {
+ TT_Instance instance;
+ TT_Instance_Metrics imetrics;
+ TT_Glyph_Metrics gmetrics[256];
+ i_tt_glyph_entry glyphs[256];
+ int smooth;
+ int ptsize;
+ int order;
+};
+typedef struct TT_Instancehandle_ TT_Instancehandle;
-/* Truetype font support */
-
-#ifdef HAVE_LIBTT
-
+struct TT_Fonthandle_ {
+ TT_Face face;
+ TT_Face_Properties properties;
+ TT_Instancehandle instanceh[TT_CHC];
+ TT_CharMap char_map;
+};
/* Defines */
static void i_tt_done_raster_map( TT_Raster_Map *bit );
static void i_tt_clear_raster_map( TT_Raster_Map* bit );
static void i_tt_blit_or( TT_Raster_Map *dst, TT_Raster_Map *src,int x_off, int y_off );
-static int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned char j );
-static void i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int x_off, int y_off, int smooth );
-static void i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char* txt, int len, int smooth );
+static int i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j );
+static void
+i_tt_render_glyph( TT_Glyph glyph, TT_Glyph_Metrics* gmetrics,
+ TT_Raster_Map *bit, TT_Raster_Map *small_bit,
+ int x_off, int y_off, int smooth );
+static int
+i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit,
+ TT_Raster_Map *small_bit, int cords[6],
+ char const* txt, int len, int smooth, int utf8 );
static void i_tt_dump_raster_map2( i_img* im, TT_Raster_Map* bit, int xb, int yb, i_color *cl, int smooth );
static void i_tt_dump_raster_map_channel( i_img* im, TT_Raster_Map* bit, int xb, int yb, int channel, int smooth );
-static int i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char* txt, int len, int smooth );
-static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6] );
+static int
+i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6],
+ float points, char const* txt, int len, int smooth, int utf8 );
+static undef_int i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6], int utf8 );
/* static globals needed */
int i,idx;
TT_Error error;
- mm_log((1,"i_tt_get_instance(handle 0x%X, points %d, smooth %d)\n",handle,points,smooth));
+ mm_log((1,"i_tt_get_instance(handle 0x%X, points %d, smooth %d)\n",
+ handle,points,smooth));
if (smooth == -1) { /* Smooth doesn't matter for this search */
- for(i=0;i<TT_CHC;i++) if (handle->instanceh[i].ptsize==points) {
- mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
- return i;
+ for(i=0;i<TT_CHC;i++) {
+ if (handle->instanceh[i].ptsize==points) {
+ mm_log((1,"i_tt_get_instance: in cache - (non selective smoothing search) returning %d\n",i));
+ return i;
+ }
}
smooth=1; /* We will be adding a font - add it as smooth then */
} else { /* Smooth doesn't matter for this search */
- for(i=0;i<TT_CHC;i++) if (handle->instanceh[i].ptsize==points && handle->instanceh[i].smooth==smooth) {
- mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
- return i;
+ for(i=0;i<TT_CHC;i++) {
+ if (handle->instanceh[i].ptsize == points
+ && handle->instanceh[i].smooth == smooth) {
+ mm_log((1,"i_tt_get_instance: in cache returning %d\n",i));
+ return i;
+ }
}
}
/* Found the instance in the cache - return the cache index */
- for(idx=0;idx<TT_CHC;idx++) if (!(handle->instanceh[idx].order)) break; /* find the lru item */
+ for(idx=0;idx<TT_CHC;idx++) {
+ if (!(handle->instanceh[idx].order)) break; /* find the lru item */
+ }
mm_log((1,"i_tt_get_instance: lru item is %d\n",idx));
- mm_log((1,"i_tt_get_instance: lru pointer 0x%X\n",USTRCT(handle->instanceh[idx].instance) ));
+ mm_log((1,"i_tt_get_instance: lru pointer 0x%X\n",
+ USTRCT(handle->instanceh[idx].instance) ));
if ( USTRCT(handle->instanceh[idx].instance) ) {
mm_log((1,"i_tt_get_instance: freeing lru item from cache %d\n",idx));
- /* Free cached glyphs */
+ /* Free cached glyphs */
for(i=0;i<256;i++)
- if ( USTRCT(handle->instanceh[idx].glyphs[i]) )
- TT_Done_Glyph( handle->instanceh[idx].glyphs[i] );
+ if ( USTRCT(handle->instanceh[idx].glyphs[i].glyph) )
+ TT_Done_Glyph( handle->instanceh[idx].glyphs[i].glyph );
- for(i=0;i<256;i++) USTRCT(handle->instanceh[idx].glyphs[i])=NULL;
- TT_Done_Instance( handle->instanceh[idx].instance ); /* Free instance if needed */
-
+ for(i=0;i<256;i++) {
+ handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
+ USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
+ }
+
+ /* Free instance if needed */
+ TT_Done_Instance( handle->instanceh[idx].instance );
}
/* create and initialize instance */
handle->instanceh[idx].smooth=smooth;
TT_Get_Instance_Metrics( handle->instanceh[idx].instance, &(handle->instanceh[idx].imetrics) );
- /* Zero the memory for the glyph storage so they are not thought as cached if they haven't been cached
- since this new font was loaded */
+ /* Zero the memory for the glyph storage so they are not thought as
+ cached if they haven't been cached since this new font was loaded */
- for(i=0;i<256;i++) USTRCT(handle->instanceh[idx].glyphs[i])=NULL;
+ for(i=0;i<256;i++) {
+ handle->instanceh[idx].glyphs[i].ch = TT_NOCHAR;
+ USTRCT(handle->instanceh[idx].glyphs[i].glyph)=NULL;
+ }
return idx;
}
/* load the typeface */
error = TT_Open_Face( engine, fontname, &handle->face );
if ( error ) {
- if ( error == TT_Err_Could_Not_Open_File ) { mm_log((1, "Could not find/open %s.\n", fontname )) }
- else { mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname, error )); }
+ if ( error == TT_Err_Could_Not_Open_File ) {
+ mm_log((1, "Could not find/open %s.\n", fontname ));
+ }
+ else {
+ mm_log((1, "Error while opening %s, error code = 0x%x.\n",fontname,
+ error ));
+ }
return NULL;
}
TT_Get_Face_Properties( handle->face, &(handle->properties) );
+
/* First, look for a Unicode charmap */
-
n = handle->properties.num_CharMaps;
USTRCT( handle->char_map )=NULL; /* Invalidate character map */
for ( i = 0; i < n; i++ ) {
TT_Get_CharMap_ID( handle->face, i, &platform, &encoding );
- if ( (platform == 3 && encoding == 1 ) || (platform == 0 && encoding == 0 ) ) {
- mm_log((2,"i_tt_new - found char map platform %u encoding %u\n", platform, encoding));
+ if ( (platform == 3 && encoding == 1 )
+ || (platform == 0 && encoding == 0 ) ) {
+ mm_log((2,"i_tt_new - found char map platform %u encoding %u\n",
+ platform, encoding));
TT_Get_CharMap( handle->face, i, &(handle->char_map) );
break;
}
static
int
-i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned char j) { /* FIXME: Check if unsigned char is enough */
+i_tt_get_glyph( TT_Fonthandle *handle, int inst, unsigned long j) {
unsigned short load_flags, code;
TT_Error error;
- mm_log((1, "i_tt_get_glyph(handle 0x%X, inst %d, j %d (%c))\n",handle,inst,j,j));
- mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));
+ mm_log((1, "i_tt_get_glyph(handle 0x%X, inst %d, j %d (%c))\n",
+ handle,inst,j, ((j >= ' ' && j <= '~') ? j : '.')));
+
+ /*mm_log((1, "handle->instanceh[inst].glyphs[j]=0x%08X\n",handle->instanceh[inst].glyphs[j] ));*/
- if ( TT_VALID(handle->instanceh[inst].glyphs[j]) ) {
+ if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)
+ && handle->instanceh[inst].glyphs[TT_HASH(j)].ch == j) {
mm_log((1,"i_tt_get_glyph: %d in cache\n",j));
return 1;
}
+
+ if ( TT_VALID(handle->instanceh[inst].glyphs[TT_HASH(j)].glyph) ) {
+ /* clean up the entry */
+ TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+ USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+ handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
+ }
/* Ok - it wasn't cached - try to get it in */
load_flags = TTLOAD_SCALE_GLYPH;
if ( code >= handle->properties.num_Glyphs ) code = 0;
} else code = TT_Char_Index( handle->char_map, j );
- if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[j])) )
+ if ( (error = TT_New_Glyph( handle->face, &handle->instanceh[inst].glyphs[TT_HASH(j)].glyph)) ) {
mm_log((1, "Cannot allocate and load glyph: error 0x%x.\n", error ));
- if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[j], code, load_flags)) )
+ return 0;
+ }
+ if ( (error = TT_Load_Glyph( handle->instanceh[inst].instance, handle->instanceh[inst].glyphs[TT_HASH(j)].glyph, code, load_flags)) ) {
mm_log((1, "Cannot allocate and load glyph: error 0x%x.\n", error ));
-
+ /* Don't leak */
+ TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+ USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+ return 0;
+ }
+
/* At this point the glyph should be allocated and loaded */
+ handle->instanceh[inst].glyphs[TT_HASH(j)].ch = j;
+
/* Next get the glyph metrics */
-
- error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[j], &handle->instanceh[inst].gmetrics[j] );
- mm_log((1, "TT_Get_Glyph_Metrics: error 0x%x.\n", error ));
+ error = TT_Get_Glyph_Metrics( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph,
+ &handle->instanceh[inst].gmetrics[TT_HASH(j)] );
+ if (error) {
+ mm_log((1, "TT_Get_Glyph_Metrics: error 0x%x.\n", error ));
+ TT_Done_Glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph );
+ USTRCT( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph ) = NULL;
+ handle->instanceh[inst].glyphs[TT_HASH(j)].ch = TT_NOCHAR;
+ return 0;
+ }
+
return 1;
}
*/
static
-void
-i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char* txt, int len, int smooth ) {
- unsigned char j;
+int
+i_tt_render_all_glyphs( TT_Fonthandle *handle, int inst, TT_Raster_Map *bit, TT_Raster_Map *small_bit, int cords[6], char const* txt, int len, int smooth, int utf8 ) {
+ unsigned long j;
int i;
TT_F26Dot6 x,y;
x=-cords[0]; /* FIXME: If you font is antialiased this should be expanded by one to allow for aa expansion and the allocation too - do before passing here */
y=-cords[4];
- for ( i = 0; i < len; i++ ) {
- j = txt[i];
- if ( !i_tt_get_glyph(handle,inst,j) ) continue;
- i_tt_render_glyph( handle->instanceh[inst].glyphs[j], &handle->instanceh[inst].gmetrics[j], bit, small_bit, x, y, smooth );
- x += handle->instanceh[inst].gmetrics[j].advance / 64;
+ while (len) {
+ if (utf8) {
+ j = i_utf8_advance(&txt, &len);
+ if (j == ~0UL) {
+ i_push_error(0, "invalid UTF8 character");
+ return 0;
+ }
+ }
+ else {
+ j = (unsigned char)*txt++;
+ --len;
+ }
+ if ( !i_tt_get_glyph(handle,inst,j) )
+ continue;
+ i_tt_render_glyph( handle->instanceh[inst].glyphs[TT_HASH(j)].glyph,
+ &handle->instanceh[inst].gmetrics[TT_HASH(j)], bit,
+ small_bit, x, y, smooth );
+ x += handle->instanceh[inst].gmetrics[TT_HASH(j)].advance / 64;
}
+
+ return 1;
}
static
int
-i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char* txt, int len, int smooth ) {
+i_tt_rasterize( TT_Fonthandle *handle, TT_Raster_Map *bit, int cords[6], float points, char const* txt, int len, int smooth, int utf8 ) {
int inst;
int width, height;
TT_Raster_Map small_bit;
}
/* calculate bounding box */
- i_tt_bbox_inst( handle, inst, txt, len, cords );
+ i_tt_bbox_inst( handle, inst, txt, len, cords, 0 );
width = cords[2]-cords[0];
height = cords[5]-cords[4];
i_tt_clear_raster_map( bit );
if ( smooth ) i_tt_init_raster_map( &small_bit, handle->instanceh[inst].imetrics.x_ppem + 32, height, smooth );
- i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len, smooth );
+ i_tt_render_all_glyphs( handle, inst, bit, &small_bit, cords, txt, len,
+ smooth, utf8 );
/* ascent = ( handle->properties.horizontal->Ascender * handle->instanceh[inst].imetrics.y_ppem ) / handle->properties.header->Units_Per_EM; */
/*
-=item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth)
+=item i_tt_cp(handle, im, xb, yb, channel, points, txt, len, smooth, utf8)
Interface to text rendering into a single channel in an image
*/
undef_int
-i_tt_cp( TT_Fonthandle *handle, i_img *im, int xb, int yb, int channel, float points, char* txt, int len, int smooth ) {
+i_tt_cp( TT_Fonthandle *handle, i_img *im, int xb, int yb, int channel, float points, char const* txt, int len, int smooth, int utf8 ) {
int cords[6];
int ascent, st_offset;
TT_Raster_Map bit;
- if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth ) ) return 0;
+ if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
ascent=cords[5];
st_offset=cords[0];
/*
-=item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth)
+=item i_tt_text(handle, im, xb, yb, cl, points, txt, len, smooth, utf8)
Interface to text rendering in a single color onto an image
*/
undef_int
-i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char* txt, int len, int smooth) {
+i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char const* txt, int len, int smooth, int utf8) {
int cords[6];
int ascent, st_offset;
TT_Raster_Map bit;
- if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth ) ) return 0;
+ if (! i_tt_rasterize( handle, &bit, cords, points, txt, len, smooth, utf8 ) ) return 0;
ascent=cords[5];
st_offset=cords[0];
/*
-=item i_tt_bbox_inst(handle, inst, txt, len, cords)
+=item i_tt_bbox_inst(handle, inst, txt, len, cords, utf8)
Function to get texts bounding boxes given the instance of the font (internal)
static
undef_int
-i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6] ) {
+i_tt_bbox_inst( TT_Fonthandle *handle, int inst ,const char *txt, int len, int cords[6], int utf8 ) {
int i, upm, ascent, descent, gascent, gdescent, width, casc, cdesc, first, start;
- unsigned int j;
+ unsigned long j;
unsigned char *ustr;
ustr=(unsigned char*)txt;
- mm_log((1,"i_tt_box_inst(handle 0x%X,inst %d,txt '%.*s', len %d)\n",handle,inst,len,txt,len));
+ mm_log((1,"i_tt_box_inst(handle 0x%X,inst %d,txt '%.*s', len %d, utf8 %d)\n",handle,inst,len,txt,len, utf8));
upm = handle->properties.header->Units_Per_EM;
gascent = ( handle->properties.horizontal->Ascender * handle->instanceh[inst].imetrics.y_ppem + upm - 1) / upm;
mm_log((1, "i_tt_box_inst: gascent=%d gdescent=%d\n", gascent, gdescent));
first=1;
- for ( i = 0; i < len; ++i ) {
- j = ustr[i];
+ while (len) {
+ if (utf8) {
+ j = i_utf8_advance(&txt, &len);
+ if (j == ~0UL) {
+ i_push_error(0, "invalid UTF8 character");
+ return 0;
+ }
+ }
+ else {
+ j = (unsigned char)*txt++;
+ --len;
+ }
if ( i_tt_get_glyph(handle,inst,j) ) {
- TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + j;
+ TT_Glyph_Metrics *gm = handle->instanceh[inst].gmetrics + TT_HASH(j);
width += gm->advance / 64;
casc = (gm->bbox.yMax+63) / 64;
cdesc = (gm->bbox.yMin-63) / 64;
/*
-=item i_tt_bbox(handle, points, txt, len, cords)
+=item i_tt_bbox(handle, points, txt, len, cords, utf8)
Interface to get a strings bounding box
*/
undef_int
-i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6]) {
+i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6], int utf8) {
int inst;
- mm_log((1,"i_tt_box(handle 0x%X,points %f,txt '%.*s', len %d)\n",handle,points,len,txt,len));
+ mm_log((1,"i_tt_box(handle 0x%X,points %f,txt '%.*s', len %d, utf8 %d)\n",handle,points,len,txt,len, utf8));
if ( (inst=i_tt_get_instance(handle,points,-1)) < 0) {
mm_log((1,"i_tt_text: get instance failed\n"));
return 0;
}
- return i_tt_bbox_inst(handle, inst, txt, len, cords);
+ return i_tt_bbox_inst(handle, inst, txt, len, cords, utf8);
}
#include FT_FREETYPE_H
static void ft2_push_message(int code);
-static unsigned long utf8_advance(char **p, int *len);
static FT_Library library;
*/
int
i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
- char *text, int len, int *bbox, int utf8) {
+ char const *text, int len, int *bbox, int utf8) {
FT_Error error;
int width;
int index;
while (len) {
unsigned long c;
if (utf8) {
- c = utf8_advance(&text, &len);
+ c = i_utf8_advance(&text, &len);
if (c == ~0UL) {
i_push_error(0, "invalid UTF8 character");
return 0;
*/
int
i_ft2_bbox_r(FT2_Fonthandle *handle, double cheight, double cwidth,
- char *text, int len, int vlayout, int utf8, int *bbox) {
+ char const *text, int len, int vlayout, int utf8, int *bbox) {
FT_Error error;
int width;
int index;
while (len) {
unsigned long c;
if (utf8) {
- c = utf8_advance(&text, &len);
+ c = i_utf8_advance(&text, &len);
if (c == ~0UL) {
i_push_error(0, "invalid UTF8 character");
return 0;
*/
int
i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty, i_color *cl,
- double cheight, double cwidth, char *text, int len, int align,
+ double cheight, double cwidth, char const *text, int len, int align,
int aa, int vlayout, int utf8) {
FT_Error error;
int index;
while (len) {
unsigned long c;
if (utf8) {
- c = utf8_advance(&text, &len);
+ c = i_utf8_advance(&text, &len);
if (c == ~0UL) {
i_push_error(0, "invalid UTF8 character");
return 0;
*/
i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty, int channel,
- double cheight, double cwidth, char *text, int len, int align,
+ double cheight, double cwidth, char const *text, int len, int align,
int aa, int vlayout, int utf8) {
int bbox[8];
i_img *work;
=cut
*/
-int i_ft2_has_chars(FT2_Fonthandle *handle, char *text, int len, int utf8,
- char *out) {
+int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len,
+ int utf8, char *out) {
int count = 0;
mm_log((1, "i_ft2_check_chars(handle %p, text %p, len %d, utf8 %d)\n",
handle, text, len, utf8));
unsigned long c;
int index;
if (utf8) {
- c = utf8_advance(&text, &len);
+ c = i_utf8_advance(&text, &len);
if (c == ~0UL) {
i_push_error(0, "invalid UTF8 character");
return 0;
return 1;
}
-struct utf8_size {
- int mask, expect;
- int size;
-};
-
-struct utf8_size utf8_sizes[] =
-{
- { 0x80, 0x00, 1 },
- { 0xE0, 0xC0, 2 },
- { 0xF0, 0xE0, 3 },
- { 0xF8, 0xF0, 4 },
-};
-
-/*
-=item utf8_advance(char **p, int *len)
-
-Retreive a UTF8 character from the stream.
-
-Modifies *p and *len to indicate the consumed characters.
-
-This doesn't support the extended UTF8 encoding used by later versions
-of Perl.
-
-=cut
-*/
-
-unsigned long utf8_advance(char **p, int *len) {
- unsigned char c;
- int i, ci, clen = 0;
- unsigned char codes[3];
- if (*len == 0)
- return ~0UL;
- c = *(*p)++; --*len;
-
- for (i = 0; i < sizeof(utf8_sizes)/sizeof(*utf8_sizes); ++i) {
- if ((c & utf8_sizes[i].mask) == utf8_sizes[i].expect) {
- clen = utf8_sizes[i].size;
- }
- }
- if (clen == 0 || *len < clen-1) {
- --*p; ++*len;
- return ~0UL;
- }
-
- /* check that each character is well formed */
- i = 1;
- ci = 0;
- while (i < clen) {
- if (((*p)[ci] & 0xC0) != 0x80) {
- --*p; ++*len;
- return ~0UL;
- }
- codes[ci] = (*p)[ci];
- ++ci; ++i;
- }
- *p += clen-1; *len -= clen-1;
- if (c & 0x80) {
- if ((c & 0xE0) == 0xC0) {
- return ((c & 0x1F) << 6) + (codes[0] & 0x3F);
- }
- else if ((c & 0xF0) == 0xE0) {
- return ((c & 0x0F) << 12) | ((codes[0] & 0x3F) << 6) | (codes[1] & 0x3f);
- }
- else if ((c & 0xF8) == 0xF0) {
- return ((c & 0x07) << 18) | ((codes[0] & 0x3F) << 12)
- | ((codes[1] & 0x3F) << 6) | (codes[2] & 0x3F);
- }
- else {
- *p -= clen; *len += clen;
- return ~0UL;
- }
- }
- else {
- return c;
- }
-}
-
/*
=back
#ifdef HAVE_LIBTT
-#include <freetype.h>
-#define TT_CHC 5
-
-struct TT_Instancehandle_ {
- TT_Instance instance;
- TT_Instance_Metrics imetrics;
- TT_Glyph_Metrics gmetrics[256];
- TT_Glyph glyphs[256];
- int smooth;
- int ptsize;
- int order;
-};
-
-typedef struct TT_Instancehandle_ TT_Instancehandle;
-
-struct TT_Fonthandle_ {
- TT_Face face;
- TT_Face_Properties properties;
- TT_Instancehandle instanceh[TT_CHC];
- TT_CharMap char_map;
-};
+struct TT_Fonthandle_;
typedef struct TT_Fonthandle_ TT_Fonthandle;
-
-
undef_int i_init_tt( void );
TT_Fonthandle* i_tt_new(char *fontname);
void i_tt_destroy( TT_Fonthandle *handle );
-undef_int i_tt_cp( TT_Fonthandle *handle,i_img *im,int xb,int yb,int channel,float points,char* txt,int len,int smooth);
-undef_int i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char* txt, int len, int smooth);
-undef_int i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6]);
-
+undef_int i_tt_cp( TT_Fonthandle *handle,i_img *im,int xb,int yb,int channel,float points,char const* txt,int len,int smooth, int utf8);
+undef_int i_tt_text( TT_Fonthandle *handle, i_img *im, int xb, int yb, i_color *cl, float points, char const* txt, int len, int smooth, int utf8);
+undef_int i_tt_bbox( TT_Fonthandle *handle, float points,char *txt,int len,int cords[6], int utf8);
#endif /* End of freetype headers */
extern int i_ft2_settransform(FT2_Fonthandle *handle, double *matrix);
extern int i_ft2_sethinting(FT2_Fonthandle *handle, int hinting);
extern int i_ft2_bbox(FT2_Fonthandle *handle, double cheight, double cwidth,
- char *text, int len, int *bbox, int utf8);
+ char const *text, int len, int *bbox, int utf8);
extern int i_ft2_text(FT2_Fonthandle *handle, i_img *im, int tx, int ty,
i_color *cl, double cheight, double cwidth,
- char *text, int len, int align, int aa, int vlayout,
- int utf8);
+ char const *text, int len, int align, int aa,
+ int vlayout, int utf8);
extern int i_ft2_cp(FT2_Fonthandle *handle, i_img *im, int tx, int ty,
int channel, double cheight, double cwidth,
- char *text, int len, int align, int aa, int vlayout,
+ char const *text, int len, int align, int aa, int vlayout,
int utf8);
-extern int i_ft2_has_chars(FT2_Fonthandle *handle, char *text, int len,
+extern int i_ft2_has_chars(FT2_Fonthandle *handle, char const *text, int len,
int utf8, char *work);
#endif
#undef max
#endif
+extern unsigned long i_utf8_advance(char const **p, int *len);
+
/* XXX Shouldn't these go away? */
int i_min(int a,int b);
if (a>b) return a; else return b;
}
+
+struct utf8_size {
+ int mask, expect;
+ int size;
+};
+
+struct utf8_size utf8_sizes[] =
+{
+ { 0x80, 0x00, 1 },
+ { 0xE0, 0xC0, 2 },
+ { 0xF0, 0xE0, 3 },
+ { 0xF8, 0xF0, 4 },
+};
+
+/*
+=item utf8_advance(char **p, int *len)
+
+Retreive a UTF8 character from the stream.
+
+Modifies *p and *len to indicate the consumed characters.
+
+This doesn't support the extended UTF8 encoding used by later versions
+of Perl.
+
+=cut
+*/
+
+unsigned long i_utf8_advance(char const **p, int *len) {
+ unsigned char c;
+ int i, ci, clen = 0;
+ unsigned char codes[3];
+ if (*len == 0)
+ return ~0UL;
+ c = *(*p)++; --*len;
+
+ for (i = 0; i < sizeof(utf8_sizes)/sizeof(*utf8_sizes); ++i) {
+ if ((c & utf8_sizes[i].mask) == utf8_sizes[i].expect) {
+ clen = utf8_sizes[i].size;
+ }
+ }
+ if (clen == 0 || *len < clen-1) {
+ --*p; ++*len;
+ return ~0UL;
+ }
+
+ /* check that each character is well formed */
+ i = 1;
+ ci = 0;
+ while (i < clen) {
+ if (((*p)[ci] & 0xC0) != 0x80) {
+ --*p; ++*len;
+ return ~0UL;
+ }
+ codes[ci] = (*p)[ci];
+ ++ci; ++i;
+ }
+ *p += clen-1; *len -= clen-1;
+ if (c & 0x80) {
+ if ((c & 0xE0) == 0xC0) {
+ return ((c & 0x1F) << 6) + (codes[0] & 0x3F);
+ }
+ else if ((c & 0xF0) == 0xE0) {
+ return ((c & 0x0F) << 12) | ((codes[0] & 0x3F) << 6) | (codes[1] & 0x3f);
+ }
+ else if ((c & 0xF8) == 0xF0) {
+ return ((c & 0x07) << 18) | ((codes[0] & 0x3F) << 12)
+ | ((codes[1] & 0x3F) << 6) | (codes[2] & 0x3F);
+ }
+ else {
+ *p -= clen; *len += clen;
+ return ~0UL;
+ }
+ }
+ else {
+ return c;
+ }
+}
+
sub _draw {
my $self = shift;
my %input = @_;
+
+ # note that the string length parameter is ignored and calculated in
+ # XS with SvPV(), since we want the number of bytes rather than the
+ # number of characters, which is what we'd get in perl for a UTF8
+ # encoded string in 5.6 and later
+
if ( exists $input{channel} ) {
Imager::i_tt_cp($self->{id},$input{image}{IMG},
$input{'x'}, $input{'y'}, $input{channel}, $input{size},
- $input{string}, length($input{string}),$input{aa});
+ $input{string}, length($input{string}),$input{aa},
+ $input{utf8});
} else {
Imager::i_tt_text($self->{id}, $input{image}{IMG},
$input{'x'}, $input{'y'}, $input{color},
$input{size}, $input{string},
- length($input{string}), $input{aa});
+ length($input{string}), $input{aa}, $input{utf8});
}
}
my $self = shift;
my %input = @_;
return Imager::i_tt_bbox($self->{id}, $input{size},
- $input{string}, length($input{string}));
+ $input{string}, length($input{string}),
+ $input{utf8});
}
+sub utf8 { 1 }
+
1;
__END__
# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)
-
-BEGIN { $| = 1; print "1..3\n"; }
+use strict;
+my $loaded;
+BEGIN { $| = 1; print "1..23\n"; }
END {print "not ok 1\n" unless $loaded;}
use Imager qw(:all);
+require "t/testtools.pl";
$loaded = 1;
-print "ok 1\n";
+
+okx(1, "Loaded");
init_log("testout/t35ttfont.log",2);
-sub skip {
- print "ok 2 # skip\n";
- print "ok 3 # skip\n";
+unless (i_has_format("tt")) {
+ skipx(22, "freetype 1.x unavailable or disabled");
malloc_state();
- exit(0);
+ exit;
}
-
-if (!(i_has_format("tt")) ) { skip(); }
print "# has tt\n";
-$fontname=$ENV{'TTFONTTEST'}||'./fontfiles/dodge.ttf';
+my $deffont = './fontfiles/dodge.ttf';
+my $fontname=$ENV{'TTFONTTEST'} || $deffont;
if (! -f $fontname) {
print "# cannot find fontfile for truetype test $fontname\n";
i_init_fonts();
# i_tt_set_aa(1);
-$bgcolor = i_color_new(255,0,0,0);
-$overlay = Imager::ImgRaw::new(200,70,3);
+my $bgcolor = i_color_new(255,0,0,0);
+my $overlay = Imager::ImgRaw::new(200,70,3);
-$ttraw = Imager::i_tt_new($fontname);
+my $ttraw = Imager::i_tt_new($fontname);
+okx($ttraw, "create font");
#use Data::Dumper;
#warn Dumper($ttraw);
-@bbox = i_tt_bbox($ttraw,50.0,'XMCLH',5);
+my @bbox = i_tt_bbox($ttraw,50.0,'XMCLH',5,0);
+okx(@bbox == 6, "bounding box");
print "#bbox: ($bbox[0], $bbox[1]) - ($bbox[2], $bbox[3])\n";
-i_tt_cp($ttraw,$overlay,5,50,1,50.0,'XMCLH',5,1);
+okx(i_tt_cp($ttraw,$overlay,5,50,1,50.0,'XMCLH',5,1,0), "cp output");
i_draw($overlay,0,50,100,50,$bgcolor);
open(FH,">testout/t35ttfont.ppm") || die "cannot open testout/t35ttfont.ppm\n";
binmode(FH);
-$IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($overlay, $IO);
+my $IO = Imager::io_new_fd( fileno(FH) );
+okx(i_writeppm_wiol($overlay, $IO), "save t35ttfont.ppm");
close(FH);
-print "ok 2\n";
-
$bgcolor=i_color_set($bgcolor,200,200,200,0);
-$backgr=Imager::ImgRaw::new(500,300,3);
+my $backgr=Imager::ImgRaw::new(500,300,3);
# i_tt_set_aa(2);
-i_tt_text($ttraw,$backgr,100,120,$bgcolor,50.0,'test',4,1);
+okx(i_tt_text($ttraw,$backgr,100,120,$bgcolor,50.0,'test',4,1,0),
+ "normal output");
my $ugly = Imager::i_tt_new("./fontfiles/ImUgly.ttf");
-i_tt_text($ugly, $backgr,100, 80, $bgcolor, 14, 'g%g', 3, 1);
-i_tt_text($ugly, $backgr,150, 80, $bgcolor, 14, 'delta', 5, 1);
+okx($ugly, "create ugly font");
+# older versions were dropping the bottom of g and the right of a
+okx(i_tt_text($ugly, $backgr,100, 80, $bgcolor, 14, 'g%g', 3, 1, 0),
+ "draw g%g");
+okx(i_tt_text($ugly, $backgr,150, 80, $bgcolor, 14, 'delta', 5, 1, 0),
+ "draw delta");
i_draw($backgr,0,20,499,20,i_color_new(0,127,0,0));
-i_tt_text($ttraw, $backgr, 20, 20, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1);
-i_tt_text($ttraw, $backgr, 20, 50, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1);
-
+okx(i_tt_text($ttraw, $backgr, 20, 20, $bgcolor, 14, 'abcdefghijklmnopqrstuvwxyz{|}', 29, 1, 0), "alphabet");
+okx(i_tt_text($ttraw, $backgr, 20, 50, $bgcolor, 14, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 26, 1, 0), "ALPHABET");
+
+# UTF8 tests
+# for perl < 5.6 we can hand-encode text
+# the following is "A\x{2010}A"
+#
+my $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
+my $alttext = "A-A";
+
+my @utf8box = i_tt_bbox($ttraw, 50.0, $text, length($text), 1);
+okx(@utf8box == 6, "utf8 bbox element count");
+my @base = i_tt_bbox($ttraw, 50.0, $alttext, length($alttext), 0);
+okx(@base == 6, "alt bbox element count");
+my $maxdiff = $fontname eq $deffont ? 0 : $base[2] / 3;
+print "# (@utf8box vs @base)\n";
+okx(abs($utf8box[2] - $base[2]) <= $maxdiff,
+ "compare box sizes $utf8box[2] vs $base[2] (maxerror $maxdiff)");
+
+# hand-encoded UTF8 drawing
+okx(i_tt_text($ttraw, $backgr, 200, 80, $bgcolor, 14, $text, length($text), 1, 1), "draw hand-encoded UTF8");
+
+okx(i_tt_cp($ttraw, $backgr, 250, 80, 1, 14, $text, length($text), 1, 1),
+ "cp hand-encoded UTF8");
+
+# ok, try native perl UTF8 if available
+if ($] >= 5.006) {
+ my $text;
+ # we need to do this in eval to prevent compile time errors in older
+ # versions
+ eval q{$text = "A\x{2010}A"}; # A, HYPHEN, A in our test font
+ #$text = "A".chr(0x2010)."A"; # this one works too
+ okx(i_tt_text($ttraw, $backgr, 300, 80, $bgcolor, 14, $text, 0, 1, 0),
+ "draw UTF8");
+ okx(i_tt_cp($ttraw, $backgr, 350, 80, 0, 14, $text, 0, 1, 0),
+ "cp UTF8");
+ @utf8box = i_tt_bbox($ttraw, 50.0, $text, length($text), 0);
+ okx(@utf8box == 6, "native utf8 bbox element count");
+ okx(abs($utf8box[2] - $base[2]) <= $maxdiff,
+ "compare box sizes native $utf8box[2] vs $base[2] (maxerror $maxdiff)");
+ eval q{$text = "A\x{0905}\x{0906}\x{0103}A"}; # Devanagari
+ okx(i_tt_text($ugly, $backgr, 100, 160, $bgcolor, 36, $text, 0, 1, 0),
+ "more complex output");
+}
+else {
+ skipx(5, "perl too old to test native UTF8 support");
+}
open(FH,">testout/t35ttfont2.ppm") || die "cannot open testout/t35ttfont.ppm\n";
binmode(FH);
$IO = Imager::io_new_fd( fileno(FH) );
-i_writeppm_wiol($backgr, $IO);
+okx(i_writeppm_wiol($backgr, $IO), "save t35ttfont2.ppm");
close(FH);
-print "ok 3\n";
-
+okx(1, "end of code");
# Change 1..1 below to 1..last_test_to_print .
# (It may become useful if the test is moved to ./t subdirectory.)
-BEGIN { $| = 1; print "1..7\n"; }
-END {print "not ok 1\n" unless $::loaded;}
+my $loaded;
+BEGIN { $| = 1; print "1..13\n"; }
+END {print "not ok 1\n" unless $loaded;}
use Imager;
-$::loaded=1;
-print "ok 1\n";
+require "t/testtools.pl";
+$loaded=1;
+okx(1, "loaded");
init_log("testout/t36oofont.log", 1);
my $red=Imager::Color->new(205, 92, 92, 255);
die $Imager::ERRSTR unless $red;
-
-
if (i_has_format("t1") and -f $fontname_pfb) {
my $img=Imager->new(xsize=>300, ysize=>100) or die "$Imager::ERRSTR\n";
my $font=Imager::Font->new(file=>$fontname_pfb,size=>25)
or die $img->{ERRSTR};
- print "ok 2\n";
-
- $img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100)
- or die $img->{ERRSTR};
-
- print "ok 3\n";
+ okx(1, "created font");
+ okx($img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100),
+ "draw text");
$img->line(x1=>0, x2=>300, y1=>50, y2=>50, color=>$green);
my $text="LLySja";
my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
- print @bbox ? '' : 'not ', "ok 4\n";
+ okx(@bbox == 6, "bounding box list length");
$img->box(box=>\@bbox, color=>$green);
- $img->write(file=>"testout/t36oofont1.ppm", type=>'pnm')
- or die "cannot write to testout/t36oofont1.ppm: $img->{ERRSTR}\n";
+ okx($img->write(file=>"testout/t36oofont1.ppm", type=>'pnm'),
+ "write t36oofont1.ppm")
+ or print "# ",$img->errstr,"\n";
} else {
- print "ok 2 # skip\n";
- print "ok 3 # skip\n";
- print "ok 4 # skip\n";
+ skipx(4, "T1lib missing or disabled");
}
if (i_has_format("tt") and -f $fontname_tt) {
my $font=Imager::Font->new(file=>$fontname_tt,size=>25)
or die $img->{ERRSTR};
- print "ok 5\n";
+ okx(1, "create TT font object");
- $img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100)
- or die $img->{ERRSTR};
-
- print "ok 6\n";
+ okx($img->string(font=>$font, text=>"XMCLH", 'x'=>100, 'y'=>100),
+ "draw text");
$img->line(x1=>0, x2=>300, y1=>50, y2=>50, color=>$green);
my $text="LLySja";
my @bbox=$font->bounding_box(string=>$text, 'x'=>0, 'y'=>50);
- print @bbox ? '' : 'not ', "ok 7\n";
+ okx(@bbox == 6, "bbox list size");
$img->box(box=>\@bbox, color=>$green);
- $img->write(file=>"testout/t36oofont2.ppm", type=>'pnm')
- or die "cannot write to testout/t36oofont2.ppm: $img->{ERRSTR}\n";
+ $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
+ okx($img->string(font=>$font, text=>$text, 'x'=>100, 'y'=>50, utf8=>1),
+ "draw hand-encoded UTF8 text");
+
+ if($] >= 5.006) {
+ eval q{$text = "A\x{2010}A"};
+ okx($img->string(font=>$font, text=>$text, 'x'=>200, 'y'=>50),
+ "draw native UTF8 text");
+ }
+ else {
+ skipx(1, "perl too old for native utf8");
+ }
+
+ okx($img->write(file=>"testout/t36oofont2.ppm", type=>'pnm'),
+ "write t36oofont2.ppm")
+ or print "# ", $img->errstr,"\n";
+
+ okx($font->utf8, "make sure utf8 method returns true");
} else {
- print "ok 5 # skip\n";
- print "ok 6 # skip\n";
- print "ok 7 # skip\n";
+ skipx(7, "FT1.x missing or disabled");
}
+okx(1, "end");
# this doesn't need a new namespace - I hope
use Imager qw(:all);
+use vars qw($TESTNUM);
+
+$TESTNUM = 1;
sub test_img {
my $green=i_color_new(0,255,0,255);
print "ok $_ # skip $why\n" for $testnum ... $testnum+$count-1;
}
+sub skipx {
+ my ($count, $why) = @_;
+
+ skipn($TESTNUM, $count, $why);
+ $TESTNUM += $count;
+}
+
+sub okx {
+ my ($ok, $comment) = @_;
+
+ return okn($TESTNUM++, $ok, $comment);
+}
+
+sub okn {
+ my ($num, $ok, $comment) = @_;
+
+ if ($ok) {
+ print "ok $num # $comment\n";
+ }
+ else {
+ print "not ok $num # $comment\n";
+ }
+
+ return $ok;
+}
+
1;