From bd4f550c29eb95bf841523fe3ed143a77a79674c Mon Sep 17 00:00:00 2001 From: Tony Cook Date: Thu, 19 Oct 2006 12:28:40 +0000 Subject: [PATCH] Add UTF8 support to the Win32 font driver http://rt.cpan.org/Ticket/Display.html?id=22166 --- Imager.xs | 46 +++++++++--- imager.h | 6 +- lib/Imager/Font/Win32.pm | 12 ++-- t/t37w32font.t | 32 ++++++++- win32.c | 150 ++++++++++++++++++++++++++++++++------- 5 files changed, 200 insertions(+), 46 deletions(-) diff --git a/Imager.xs b/Imager.xs index 82ae7efe..da7d9512 100644 --- a/Imager.xs +++ b/Imager.xs @@ -4192,51 +4192,77 @@ i_tags_count(im) #ifdef HAVE_WIN32 void -i_wf_bbox(face, size, text) +i_wf_bbox(face, size, text_sv, utf8=0) char *face int size - char *text + SV *text_sv + int utf8 PREINIT: int cords[BOUNDING_BOX_COUNT]; int rc, i; + char const *text; + STRLEN text_len; PPCODE: - if (rc = i_wf_bbox(face, size, text, strlen(text), cords)) { + text = SvPV(text_sv, text_len); +#ifdef SvUTF8 + if (SvUTF8(text_sv)) + utf8 = 1; +#endif + if (rc = i_wf_bbox(face, size, text, text_len, cords, utf8)) { EXTEND(SP, rc); for (i = 0; i < rc; ++i) PUSHs(sv_2mortal(newSViv(cords[i]))); } undef_int -i_wf_text(face, im, tx, ty, cl, size, text, align, aa) +i_wf_text(face, im, tx, ty, cl, size, text_sv, align, aa, utf8 = 0) char *face Imager::ImgRaw im int tx int ty Imager::Color cl int size - char *text + SV *text_sv int align int aa + int utf8 + PREINIT: + char const *text; + STRLEN text_len; CODE: - RETVAL = i_wf_text(face, im, tx, ty, cl, size, text, strlen(text), - align, aa); + text = SvPV(text_sv, text_len); +#ifdef SvUTF8 + if (SvUTF8(text_sv)) + utf8 = 1; +#endif + RETVAL = i_wf_text(face, im, tx, ty, cl, size, text, text_len, + align, aa, utf8); OUTPUT: RETVAL undef_int -i_wf_cp(face, im, tx, ty, channel, size, text, align, aa) +i_wf_cp(face, im, tx, ty, channel, size, text_sv, align, aa, utf8 = 0) char *face Imager::ImgRaw im int tx int ty int channel int size - char *text + SV *text_sv int align int aa + int utf8 + PREINIT: + char const *text; + STRLEN text_len; CODE: + text = SvPV(text_sv, text_len); +#ifdef SvUTF8 + if (SvUTF8(text_sv)) + utf8 = 1; +#endif RETVAL = i_wf_cp(face, im, tx, ty, channel, size, text, strlen(text), - align, aa); + align, aa, utf8); OUTPUT: RETVAL diff --git a/imager.h b/imager.h index e5997805..613777b8 100644 --- a/imager.h +++ b/imager.h @@ -276,11 +276,11 @@ i_ft2_set_mm_coords(FT2_Fonthandle *handle, int coord_count, const long *coords) #ifdef WIN32 -extern int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbox); +extern int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbox, int utf8); extern int i_wf_text(const char *face, i_img *im, int tx, int ty, const i_color *cl, - int size, const char *text, int len, int align, int aa); + int size, const char *text, int len, int align, int aa, int utf8); extern int i_wf_cp(const char *face, i_img *im, int tx, int ty, int channel, - int size, const char *text, int len, int align, int aa); + int size, const char *text, int len, int align, int aa, int utf8); extern int i_wf_addfont(char const *file); #endif diff --git a/lib/Imager/Font/Win32.pm b/lib/Imager/Font/Win32.pm index 9220f871..790d9ae8 100644 --- a/lib/Imager/Font/Win32.pm +++ b/lib/Imager/Font/Win32.pm @@ -3,7 +3,7 @@ use strict; use vars qw(@ISA $VERSION); @ISA = qw(Imager::Font); -$VERSION = "1.004"; +$VERSION = "1.005"; # called by Imager::Font::new() # since Win32's HFONTs include the size information this @@ -17,7 +17,7 @@ sub new { sub _bounding_box { my ($self, %opts) = @_; - my @bbox = Imager::i_wf_bbox($self->{face}, $opts{size}, $opts{string}); + my @bbox = Imager::i_wf_bbox($self->{face}, $opts{size}, $opts{string}, $opts{utf8}); } sub _draw { @@ -27,16 +27,20 @@ sub _draw { if (exists $input{channel}) { Imager::i_wf_cp($self->{face}, $input{image}{IMG}, $input{x}, $input{'y'}, $input{channel}, $input{size}, - $input{string}, $input{align}, $input{aa}); + $input{string}, $input{align}, $input{aa}, $input{utf8}); } else { Imager::i_wf_text($self->{face}, $input{image}{IMG}, $input{x}, $input{'y'}, $input{color}, $input{size}, - $input{string}, $input{align}, $input{aa}); + $input{string}, $input{align}, $input{aa}, $input{utf8}); } } +sub utf8 { + return 1; +} + 1; __END__ diff --git a/t/t37w32font.t b/t/t37w32font.t index 4619a2c2..c6a91b5c 100644 --- a/t/t37w32font.t +++ b/t/t37w32font.t @@ -1,7 +1,7 @@ #!perl -w use strict; use lib 't'; -use Test::More tests => 32; +use Test::More tests => 38; BEGIN { use_ok(Imager => ':all') } ++$|; @@ -9,7 +9,7 @@ init_log("testout/t37w32font.log",1); SKIP: { - i_has_format('w32') or skip("no MS Windows", 31); + i_has_format('w32') or skip("no MS Windows", 37); print "# has w32\n"; my $fontname=$ENV{'TTFONTTEST'} || 'Times New Roman Bold'; @@ -130,5 +130,33 @@ SKIP: } ok($im->write(file=>'testout/t37align.ppm'), "save align image"); } + { print "# utf 8 support\n"; + my $font = Imager::Font->new(face => "Arial"); + ok($font, "created font"); + my $im = Imager->new(xsize => 100, ysize => 100); + ok($im->string(string => "\xE2\x98\xBA", size => 80, aa => 1, utf8 => 1, + color => "white", font => $font, x => 5, y => 80), + "draw in utf8 (hand encoded)") + or print "# ", $im->errstr, "\n"; + ok($im->write(file=>'testout/t37utf8.ppm'), "save utf8 image"); + # native perl utf8 + # Win32 only supported on 5.6+ + # since this gets compiled even on older perls we need to be careful + # creating the string + my $text; + eval q{$text = "\x{263A}"}; # A, HYPHEN, A in our test font + my $im2 = Imager->new(xsize => 100, ysize => 100); + ok($im2->string(string => $text, size => 80, aa => 1, + color => 'white', font => $font, x => 5, y => 80), + "draw in utf8 (perl utf8)") + or print "# ", $im->errstr, "\n"; + ok($im2->write(file=>'testout/t37utf8b.ppm'), "save utf8 image"); + is(Imager::i_img_diff($im->{IMG}, $im2->{IMG}), 0, + "check result is the same"); + + # bounding box + cmp_ok($font->bounding_box(string=>$text, size => 80)->advance_width, '<', 100, + "check we only get width of single char rather than 3"); + } } diff --git a/win32.c b/win32.c index f03b9141..cfc75e53 100644 --- a/win32.c +++ b/win32.c @@ -14,8 +14,8 @@ win32.c - implements some win32 specific code, specifically Win32 font support. if (i_wf_bbox(facename, size, text, text_len, bbox)) { // we have the bbox } - i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa); - i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa) + i_wf_text(face, im, tx, ty, cl, size, text, len, align, aa, utf8); + i_wf_cp(face, im, tx, ty, channel, size, text, len, align, aa, utf8) =head1 DESCRIPTION @@ -31,17 +31,19 @@ An Imager interface to font output using the Win32 GDI. static void set_logfont(const char *face, int size, LOGFONT *lf); static LPVOID render_text(const char *face, int size, const char *text, int length, int aa, - HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm); + HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm, int utf8); +static LPWSTR utf8_to_wide_string(char const *text, int text_len, int *wide_chars); /* -=item i_wf_bbox(face, size, text, length, bbox) +=item i_wf_bbox(face, size, text, length, bbox, utf8) Calculate a bounding box for the text. =cut */ -int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbox) { +int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbox, + int utf8) { LOGFONT lf; HFONT font, oldFont; HDC dc; @@ -49,11 +51,12 @@ int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbo TEXTMETRIC tm; ABC first, last; GLYPHMETRICS gm; - int i; MAT2 mat; int ascent, descent, max_ascent = -size, min_descent = size; + const char *workp; + int work_len; - mm_log((1, "i_wf_bbox(face %s, size %d, text %p, length %d, bbox %p)\n", face, size, text, length, bbox)); + mm_log((1, "i_wf_bbox(face %s, size %d, text %p, length %d, bbox %p, utf8 %d)\n", face, size, text, length, bbox, utf8)); set_logfont(face, size, &lf); font = CreateFontIndirect(&lf); @@ -69,14 +72,30 @@ int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbo } } - for (i = 0; i < length; ++i) { - unsigned char c = text[i]; - unsigned char cp = c > '~' ? '.' : c < ' ' ? '.' : c; + workp = text; + work_len = 0; + while (work_len > 0) { + unsigned long c; + unsigned char cp; + + if (utf8) { + c = i_utf8_advance(&workp, &work_len); + if (c == ~0UL) { + i_push_error(0, "invalid UTF8 character"); + return 0; + } + } + else { + c = (unsigned char)*text++; + --work_len; + } + + cp = c > '~' ? '.' : c < ' ' ? '.' : c; memset(&mat, 0, sizeof(mat)); mat.eM11.value = 1; mat.eM22.value = 1; - if (GetGlyphOutline(dc, c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) { + if (GetGlyphOutline(dc, (UINT)c, GGO_METRICS, &gm, 0, NULL, &mat) != GDI_ERROR) { mm_log((2, " glyph '%c' (%02x): bbx (%u,%u) org (%d,%d) inc(%d,%d)\n", cp, c, gm.gmBlackBoxX, gm.gmBlackBoxY, gm.gmptGlyphOrigin.x, gm.gmptGlyphOrigin.y, gm.gmCellIncX, gm.gmCellIncY)); @@ -91,12 +110,31 @@ int i_wf_bbox(const char *face, int size, const char *text, int length, int *bbo } } - if (!GetTextExtentPoint32(dc, text, length, &sz) - || !GetTextMetrics(dc, &tm)) { - SelectObject(dc, oldFont); - ReleaseDC(NULL, dc); - DeleteObject(font); - return 0; + if (utf8) { + int wide_chars; + LPWSTR wide_text = utf8_to_wide_string(text, length, &wide_chars); + + if (!wide_text) + return 0; + + if (!GetTextExtentPoint32W(dc, wide_text, wide_chars, &sz) + || !GetTextMetrics(dc, &tm)) { + SelectObject(dc, oldFont); + ReleaseDC(NULL, dc); + DeleteObject(font); + return 0; + } + + myfree(wide_text); + } + else { + if (!GetTextExtentPoint32(dc, text, length, &sz) + || !GetTextMetrics(dc, &tm)) { + SelectObject(dc, oldFont); + ReleaseDC(NULL, dc); + DeleteObject(font); + return 0; + } } bbox[BBOX_GLOBAL_DESCENT] = tm.tmDescent; bbox[BBOX_DESCENT] = min_descent == size ? tm.tmDescent : min_descent; @@ -141,7 +179,7 @@ Draws the text in the given color. int i_wf_text(const char *face, i_img *im, int tx, int ty, const i_color *cl, int size, - const char *text, int len, int align, int aa) { + const char *text, int len, int align, int aa, int utf8) { unsigned char *bits; HBITMAP bm; SIZE sz; @@ -151,7 +189,7 @@ i_wf_text(const char *face, i_img *im, int tx, int ty, const i_color *cl, int si TEXTMETRIC tm; int top; - bits = render_text(face, size, text, len, aa, &bm, &sz, &tm); + bits = render_text(face, size, text, len, aa, &bm, &sz, &tm, utf8); if (!bits) return 0; @@ -164,7 +202,7 @@ i_wf_text(const char *face, i_img *im, int tx, int ty, const i_color *cl, int si else { int bbox[BOUNDING_BOX_COUNT]; - i_wf_bbox(face, size, text, len, bbox); + i_wf_bbox(face, size, text, len, bbox, utf8); top -= tm.tmAscent - bbox[BBOX_ASCENT]; } @@ -196,7 +234,7 @@ Draws the text in the given channel. int i_wf_cp(const char *face, i_img *im, int tx, int ty, int channel, int size, - const char *text, int len, int align, int aa) { + const char *text, int len, int align, int aa, int utf8) { unsigned char *bits; HBITMAP bm; SIZE sz; @@ -205,7 +243,7 @@ i_wf_cp(const char *face, i_img *im, int tx, int ty, int channel, int size, TEXTMETRIC tm; int top; - bits = render_text(face, size, text, len, aa, &bm, &sz, &tm); + bits = render_text(face, size, text, len, aa, &bm, &sz, &tm, utf8); if (!bits) return 0; @@ -218,7 +256,7 @@ i_wf_cp(const char *face, i_img *im, int tx, int ty, int channel, int size, else { int bbox[BOUNDING_BOX_COUNT]; - i_wf_bbox(face, size, text, len, bbox); + i_wf_bbox(face, size, text, len, bbox, utf8); top -= tm.tmAscent - bbox[BBOX_ASCENT]; } @@ -296,7 +334,7 @@ native greyscale bitmaps. =cut */ static LPVOID render_text(const char *face, int size, const char *text, int length, int aa, - HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm) { + HBITMAP *pbm, SIZE *psz, TEXTMETRIC *tm, int utf8) { BITMAPINFO bmi; BITMAPINFOHEADER *bmih = &bmi.bmiHeader; HDC dc, bmpDc; @@ -305,7 +343,9 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng SIZE sz; HBITMAP bm, oldBm; LPVOID bits; - + int wide_count; + LPWSTR wide_text; + dc = GetDC(NULL); set_logfont(face, size, &lf); @@ -316,12 +356,22 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng lf.lfQuality = aa ? ANTIALIASED_QUALITY : NONANTIALIASED_QUALITY; #endif + if (utf8) { + wide_text = utf8_to_wide_string(text, length, &wide_count); + } + else { + wide_text = NULL; + } + bmpDc = CreateCompatibleDC(dc); if (bmpDc) { font = CreateFontIndirect(&lf); if (font) { oldFont = SelectObject(bmpDc, font); - GetTextExtentPoint32(bmpDc, text, length, &sz); + if (utf8) + GetTextExtentPoint32W(bmpDc, wide_text, wide_count, &sz); + else + GetTextExtentPoint32(bmpDc, text, length, &sz); GetTextMetrics(bmpDc, tm); memset(&bmi, 0, sizeof(bmi)); @@ -343,7 +393,12 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng oldBm = SelectObject(bmpDc, bm); SetTextColor(bmpDc, RGB(255, 255, 255)); SetBkColor(bmpDc, RGB(0, 0, 0)); - TextOut(bmpDc, 0, 0, text, length); + if (utf8) { + TextOutW(bmpDc, 0, 0, wide_text, wide_count); + } + else { + TextOut(bmpDc, 0, 0, text, length); + } SelectObject(bmpDc, oldBm); } else { @@ -353,12 +408,16 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng DeleteObject(font); DeleteDC(bmpDc); ReleaseDC(NULL, dc); + if (wide_text) + myfree(wide_text); return NULL; } SelectObject(bmpDc, oldFont); DeleteObject(font); } else { + if (wide_text) + myfree(wide_text); i_push_errorf(0, "Could not create logical font: %ld", GetLastError()); DeleteDC(bmpDc); @@ -368,11 +427,16 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng DeleteDC(bmpDc); } else { + if (wide_text) + myfree(wide_text); i_push_errorf(0, "Could not create rendering DC: %ld", GetLastError()); ReleaseDC(NULL, dc); return NULL; } + if (wide_text) + myfree(wide_text); + ReleaseDC(NULL, dc); *pbm = bm; @@ -382,6 +446,38 @@ static LPVOID render_text(const char *face, int size, const char *text, int leng } /* +=item utf8_to_wide_string(text, text_len, wide_chars) + +=cut +*/ + +static +LPWSTR +utf8_to_wide_string(char const *text, int text_len, int *wide_chars) { + int wide_count = MultiByteToWideChar(CP_UTF8, 0, text, text_len, NULL, 0); + LPWSTR result; + + if (wide_count < 0) { + i_push_errorf(0, "Could not convert utf8: %ld", GetLastError()); + return NULL; + } + ++wide_count; + result = mymalloc(sizeof(WCHAR) * wide_count); + if (MultiByteToWideChar(CP_UTF8, 0, text, text_len, result, wide_count) < 0) { + i_push_errorf(0, "Could not convert utf8: %ld", GetLastError()); + return NULL; + } + + result[wide_count-1] = (WCHAR)'\0'; + *wide_chars = wide_count - 1; + + return result; +} + + +/* +=back + =head1 BUGS Should really use a structure so we can set more attributes. -- 2.39.2