#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
#!perl -w
use strict;
use lib 't';
-use Test::More tests => 32;
+use Test::More tests => 38;
BEGIN { use_ok(Imager => ':all') }
++$|;
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';
}
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");
+ }
}
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
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;
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);
}
}
- 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));
}
}
- 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;
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;
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;
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];
}
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;
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;
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];
}
=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;
SIZE sz;
HBITMAP bm, oldBm;
LPVOID bits;
-
+ int wide_count;
+ LPWSTR wide_text;
+
dc = GetDC(NULL);
set_logfont(face, size, &lf);
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));
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 {
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);
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;
}
/*
+=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.