4 use Test::More tests => 160;
6 # Before `make install' is performed this script should be runnable with
7 # `make test'. After `make install' it should work as `perl test.pl'
9 ######################### We start with some black magic to print on failure.
11 # Change 1..1 below to 1..last_test_to_print .
12 # (It may become useful if the test is moved to ./t subdirectory.)
14 BEGIN { use_ok(Imager => ':all') }
16 init_log("testout/t38ft2font.log",2);
18 my @base_color = (64, 255, 64);
22 i_has_format("ft2") or skip("no freetype2 library found", 159);
26 my $fontname=$ENV{'TTFONTTEST'}||'./fontfiles/dodge.ttf';
28 -f $fontname or skip("cannot find fontfile $fontname", 159);
31 my $bgcolor=i_color_new(255,0,0,0);
32 my $overlay=Imager::ImgRaw::new(200,70,3);
34 my $ttraw=Imager::Font::FreeType2::i_ft2_new($fontname, 0);
36 $ttraw or print Imager::_error_as_msg(),"\n";
37 ok($ttraw, "loaded raw font");
39 my @bbox=Imager::Font::FreeType2::i_ft2_bbox($ttraw, 50.0, 0, 'XMCLH', 0);
40 print "#bbox @bbox\n";
42 is(@bbox, 8, "i_ft2_bbox() returns 8 values");
44 ok(Imager::Font::FreeType2::i_ft2_cp($ttraw,$overlay,5,50,1,50.0,50, 'XMCLH',1,1, 0, 0), "drawn to channel");
45 i_line($overlay,0,50,100,50,$bgcolor,1);
47 open(FH,">testout/t38ft2font.ppm") || die "cannot open testout/t38ft2font.ppm\n";
49 my $IO = Imager::io_new_fd(fileno(FH));
50 ok(i_writeppm_wiol($overlay, $IO), "saved image");
53 $bgcolor=i_color_set($bgcolor,200,200,200,0);
54 my $backgr=Imager::ImgRaw::new(500,300,3);
57 ok(Imager::Font::FreeType2::i_ft2_text($ttraw,$backgr,100,150,NC(255, 64, 64),200.0,50, 'MAW',1,1,0, 0), "drew MAW");
58 Imager::Font::FreeType2::i_ft2_settransform($ttraw, [0.9659, 0.2588, 0, -0.2588, 0.9659, 0 ]);
59 ok(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");
60 i_line($backgr, 0,150, 499, 150, NC(0, 0, 255),1);
62 open(FH,">testout/t38ft2font2.ppm") || die "cannot open testout/t38ft2font.ppm\n";
64 $IO = Imager::io_new_fd(fileno(FH));
65 ok(i_writeppm_wiol($backgr,$IO), "saved second image");
68 my $oof = Imager::Font->new(file=>$fontname, type=>'ft2', 'index'=>0);
70 ok($oof, "loaded OO font");
72 my $im = Imager->new(xsize=>400, ysize=>250);
74 ok($im->string(font=>$oof,
79 color=>NC(255, 128, 255),
81 align=>0), "drawn through OO interface");
82 ok($oof->transform(matrix=>[1, 0.1, 0, 0, 1, 0]),
83 "set matrix via OO interface");
84 ok($im->string(font=>$oof,
92 align=>1), "drawn transformed through OO");
93 use Imager::Matrix2d ':handy';
94 ok($oof->transform(matrix=>m2d_rotate(degrees=>-30)),
95 "set transform from m2d module");
96 ok($im->string(font=>$oof,
102 color=>NC(255,255,0),
104 align=>0, vlayout=>0), "drawn first rotated");
106 ok($im->string(font=>$oof,
114 align=>0, vlayout=>0), "drawn second rotated");
116 $oof->transform(matrix=>m2d_identity());
117 $oof->hinting(hinting=>1);
120 # the test font (dodge.ttf) only supports one character above 0xFF that
121 # I can see, 0x2010 HYPHEN (which renders the same as 0x002D HYPHEN MINUS)
122 # an attempt at utf8 support
123 # first attempt to use native perl UTF8
126 skip("no native UTF8 support in this version of perl", 1)
129 # we need to do this in eval to prevent compile time errors in older
131 eval q{$text = "A\x{2010}A"}; # A, HYPHEN, A in our test font
132 #$text = "A".chr(0x2010)."A"; # this one works too
133 unless (ok($im->string(font=>$oof,
139 aa=>1), "drawn UTF natively")) {
140 print "# ",$im->errstr,"\n";
144 # an attempt using emulation of UTF8
145 my $text = pack("C*", 0x41, 0xE2, 0x80, 0x90, 0x41);
146 #my $text = "A\xE2\x80\x90\x41\x{2010}";
147 #substr($text, -1, 0) = '';
148 unless (ok($im->string(font=>$oof,
153 color=>NC(255,128,0),
155 utf8=>1), "drawn UTF emulated")) {
156 print "# ",$im->errstr,"\n";
160 # well it was - it demostrates what happens when you combine
161 # transformations and font hinting
162 for my $steps (0..39) {
163 $oof->transform(matrix=>m2d_rotate(degrees=>-$steps+5));
164 # demonstrates why we disable hinting on a doing a transform
165 # if the following line is enabled then the 0 degrees output sticks
167 # $oof->hinting(hinting=>1);
168 $im->string(font=>$oof,
173 color=>NC(255, $steps * 5, 200-$steps * 5),
178 $im->write(file=>'testout/t38_oo.ppm')
179 or print "# could not save OO output: ",$im->errstr,"\n";
181 my (@got) = $oof->has_chars(string=>"\x01H");
182 ok(@got == 2, "has_chars returned 2 items");
183 ok(!$got[0], "have no chr(1)");
184 ok($got[1], "have 'H'");
185 ok($oof->has_chars(string=>"H\x01") eq "\x01\x00",
186 "scalar has_chars()");
188 print "# OO bounding boxes\n";
189 @bbox = $oof->bounding_box(string=>"hello", size=>30);
190 my $bbox = $oof->bounding_box(string=>"hello", size=>30);
192 is(@bbox, 8, "list bbox returned 8 items");
193 ok($bbox->isa('Imager::Font::BBox'), "scalar bbox returned right class");
194 ok($bbox->start_offset == $bbox[0], "start_offset");
195 ok($bbox->end_offset == $bbox[2], "end_offset");
196 ok($bbox->global_ascent == $bbox[3], "global_ascent");
197 ok($bbox->global_descent == $bbox[1], "global_descent");
198 ok($bbox->ascent == $bbox[5], "ascent");
199 ok($bbox->descent == $bbox[4], "descent");
200 ok($bbox->advance_width == $bbox[6], "advance_width");
202 print "# aligned text output\n";
203 my $alimg = Imager->new(xsize=>300, ysize=>300);
204 $alimg->box(color=>'40FF40', filled=>1);
206 $oof->transform(matrix=>m2d_identity());
207 $oof->hinting(hinting=>1);
209 align_test('left', 'top', 10, 10, $oof, $alimg);
210 align_test('start', 'top', 10, 40, $oof, $alimg);
211 align_test('center', 'top', 150, 70, $oof, $alimg);
212 align_test('end', 'top', 290, 100, $oof, $alimg);
213 align_test('right', 'top', 290, 130, $oof, $alimg);
215 align_test('center', 'top', 150, 160, $oof, $alimg);
216 align_test('center', 'center', 150, 190, $oof, $alimg);
217 align_test('center', 'bottom', 150, 220, $oof, $alimg);
218 align_test('center', 'baseline', 150, 250, $oof, $alimg);
220 ok($alimg->write(file=>'testout/t38aligned.ppm'),
221 "saving aligned output image");
223 my $exfont = Imager::Font->new(file=>'fontfiles/ExistenceTest.ttf',
227 ok($exfont, "loaded existence font") or
228 skip("couldn't load test font", 11);
230 # the test font is known to have a shorter advance width for that char
231 my @bbox = $exfont->bounding_box(string=>"/", size=>100);
232 is(@bbox, 8, "should be 8 entries");
233 isnt($bbox[6], $bbox[2], "different advance width");
234 my $bbox = $exfont->bounding_box(string=>"/", size=>100);
235 ok($bbox->pos_width != $bbox->advance_width, "OO check");
237 cmp_ok($bbox->right_bearing, '<', 0, "check right bearing");
239 cmp_ok($bbox->display_width, '>', $bbox->advance_width,
240 "check display width (roughly)");
242 # check with a char that fits inside the box
243 $bbox = $exfont->bounding_box(string=>"!", size=>100);
244 print "# pos width ", $bbox->pos_width, "\n";
245 is($bbox->pos_width, $bbox->advance_width,
246 "check backwards compatibility");
247 cmp_ok($bbox->left_bearing, '>', 0, "left bearing positive");
248 cmp_ok($bbox->right_bearing, '>', 0, "right bearing positive");
249 cmp_ok($bbox->display_width, '<', $bbox->advance_width,
250 "display smaller than advance");
253 # make sure the number of tests on each branch match
254 if (Imager::Font::FreeType2::i_ft2_can_face_name()) {
255 my $facename = Imager::Font::FreeType2::i_ft2_face_name($exfont->{id});
256 print "# face name '$facename'\n";
257 ok($facename eq 'ExistenceTest', "test face name");
258 $facename = $exfont->face_name;
259 ok($facename eq 'ExistenceTest', "test face name OO");
262 # make sure we get the error we expect
263 my $facename = Imager::Font::FreeType2::i_ft2_face_name($exfont->{id});
264 my ($msg) = Imager::_error_as_msg();
265 ok(!defined($facename), "test face name not supported");
267 ok(scalar($msg =~ /or later required/), "test face name not supported");
273 Imager::Font::FreeType2->can_glyph_names
274 or skip("FT2 compiled without glyph names support", 9);
276 # FT2 considers POST tables in TTF fonts unreliable, so use
277 # a type 1 font, see below for TTF test
278 my $exfont = Imager::Font->new(file=>'fontfiles/ExistenceTest.pfb',
282 ok($exfont, "load Type 1 via FT2")
283 or skip("couldn't load type 1 with FT2", 8);
285 Imager::Font::FreeType2::i_ft2_glyph_name($exfont->{id}, "!J/");
287 #print Dumper \@glyph_names;
288 is($glyph_names[0], 'exclam', "check exclam name");
289 ok(!defined($glyph_names[1]), "check for no J name");
290 is($glyph_names[2], 'slash', "check slash name");
293 @glyph_names = $exfont->glyph_names(string=>"!J/");
294 is($glyph_names[0], 'exclam', "check exclam name OO");
295 ok(!defined($glyph_names[1]), "check for no J name OO");
296 is($glyph_names[2], 'slash', "check slash name OO");
298 # make sure a missing string parameter is handled correctly
300 $exfont->glyph_names();
302 is($@, "", "correct error handling");
303 cmp_ok(Imager->errstr, '=~', qr/no string parameter/, "error message");
306 # freetype 2 considers truetype glyph name tables unreliable
307 # due to some specific fonts, supplying reliable_only=>0 bypasses
308 # that check and lets us get the glyph names even for truetype fonts
309 # so we can test this stuff <sigh>
310 # we can't use ExistenceTest.ttf since that's generated with
311 # AppleStandardEncoding since the same .sfd needs to generate
312 # a .pfb file, NameTest.ttf uses a Unicode encoding
314 # we were using an unsigned char to store a unicode character
315 # https://rt.cpan.org/Ticket/Display.html?id=7949
316 $exfont = Imager::Font->new(file=>'fontfiles/NameTest.ttf',
320 ok($exfont, "load TTF via FT2")
321 or skip("could not load TTF with FT2", 1);
322 my $text = pack("C*", 0xE2, 0x80, 0x90); # "\x{2010}" as utf-8
323 my @names = $exfont->glyph_names(string=>$text,
324 utf8=>1, reliable_only=>0);
325 is($names[0], "hyphentwo", "check utf8 glyph name");
329 # check that error codes are translated correctly
330 my $errfont = Imager::Font->new(file=>"t/t38ft2font.t", type=>"ft2");
331 is($errfont, undef, "new font vs non font");
332 cmp_ok(Imager->errstr, '=~', qr/unknown file format/, "check error message");
334 # Multiple Master tests
335 # we check a non-MM font errors correctly
336 print "# check that the methods act correctly for a non-MM font\n";
337 ok(!$exfont->is_mm, "exfont not MM");
338 ok((() = $exfont->mm_axes) == 0, "exfont has no MM axes");
339 cmp_ok(Imager->errstr, '=~', qr/no multiple masters/,
340 "and returns correct error when we ask");
341 ok(!$exfont->set_mm_coords(coords=>[0, 0]), "fail setting axis on exfont");
342 cmp_ok(Imager->errstr, '=~', qr/no multiple masters/,
343 "and returns correct error when we ask");
345 # try a MM font now - test font only has A defined
346 print "# Try a multiple master font\n";
347 my $mmfont = Imager::Font->new(file=>"fontfiles/MMOne.pfb", type=>"ft2",
348 color=>"white", aa=>1, size=>60);
349 ok($mmfont, "loaded MM font");
350 ok($mmfont->is_mm, "font is multiple master");
351 my @axes = $mmfont->mm_axes;
352 is(@axes, 2, "check we got both axes");
353 is($axes[0][0], "Weight", "name of first axis");
354 is($axes[0][1], 50, "min for first axis");
355 is($axes[0][2], 999, "max for first axis");
356 is($axes[1][0], "Slant", "name of second axis");
357 is($axes[1][1], 0, "min for second axis");
358 is($axes[1][2], 999, "max for second axis");
359 my $mmim = Imager->new(xsize=>200, ysize=>200);
360 $mmim->string(font=>$mmfont, x=>0, 'y'=>50, text=>"A");
361 ok($mmfont->set_mm_coords(coords=>[ 700, 0 ]), "set to bold, unsloped");
362 $mmim->string(font=>$mmfont, x=>0, 'y'=>100, text=>"A", color=>'blue');
363 my @weights = qw(50 260 525 760 999);
364 my @slants = qw(0 333 666 999);
365 for my $windex (0 .. $#weights) {
366 my $weight = $weights[$windex];
367 for my $sindex (0 .. $#slants) {
368 my $slant = $slants[$sindex];
369 $mmfont->set_mm_coords(coords=>[ $weight, $slant ]);
370 $mmim->string(font=>$mmfont, x=>30+32*$windex, 'y'=>50+45*$sindex,
375 ok($mmim->write(file=>"testout/t38mm.ppm"), "save MM output");
378 { print "# alignment tests\n";
379 my $font = Imager::Font->new(file=>'fontfiles/ImUgly.ttf', type=>'ft2');
380 ok($font, "loaded deffont OO")
381 or skip("could not load font:".Imager->errstr, 4);
382 my $im = Imager->new(xsize=>140, ysize=>150);
389 $im->line(x1=>0, y1=>40, x2=>139, y2=>40, color=>'blue');
390 $im->line(x1=>0, y1=>90, x2=>139, y2=>90, color=>'blue');
391 $im->line(x1=>0, y1=>110, x2=>139, y2=>110, color=>'blue');
392 for my $args ([ x=>5, text=>"A", color=>"white" ],
393 [ x=>40, text=>"y", color=>"white" ],
394 [ x=>75, text=>"A", channel=>1 ],
395 [ x=>110, text=>"y", channel=>1 ]) {
396 ok($im->string(%common, @$args, 'y'=>40), "A no alignment");
397 ok($im->string(%common, @$args, 'y'=>90, align=>1), "A align=1");
398 ok($im->string(%common, @$args, 'y'=>110, align=>0), "A align=0");
400 ok($im->write(file=>'testout/t38align.ppm'), "save align image");
404 { # outputting a space in non-AA could either crash
406 my $font = Imager::Font->new(file=>'fontfiles/ImUgly.ttf', type=>'ft2');
407 my $im = Imager->new(xsize => 100, ysize => 100);
408 ok($im->string(x => 10, y => 10, string => "test space", aa => 0,
409 color => '#FFF', size => 8, font => $font),
410 "draw space non-antialiased (color)");
411 ok($im->string(x => 10, y => 50, string => "test space", aa => 0,
412 channel => 0, size => 8, font => $font),
413 "draw space non-antialiased (channel)");
418 my ($h, $v, $x, $y, $f, $img) = @_;
420 my @pos = $f->align(halign=>$h, valign=>$v, 'x'=>$x, 'y'=>$y,
421 image=>$img, size=>15, color=>'FFFFFF',
422 string=>"x$h ${v}y", channel=>1, aa=>1);
423 @pos = $img->align_string(halign=>$h, valign=>$v, 'x'=>$x, 'y'=>$y,
424 font=>$f, size=>15, color=>'FF99FF',
425 string=>"x$h ${v}y", aa=>1);
426 if (ok(@pos == 4, "$h $v aligned output")) {
428 my $cx = int(($pos[0] + $pos[2]) / 2);
429 my $cy = int(($pos[1] + $pos[3]) / 2);
431 print "# @pos cx $cx cy $cy\n";
432 okmatchcolor($img, $cx, $pos[1]-1, @base_color, "outer top edge");
433 okmatchcolor($img, $cx, $pos[3], @base_color, "outer bottom edge");
434 okmatchcolor($img, $pos[0]-1, $cy, @base_color, "outer left edge");
435 okmatchcolor($img, $pos[2], $cy, @base_color, "outer right edge");
437 okmismatchcolor($img, $cx, $pos[1], @base_color, "inner top edge");
438 okmismatchcolor($img, $cx, $pos[3]-1, @base_color, "inner bottom edge");
439 okmismatchcolor($img, $pos[0], $cy, @base_color, "inner left edge");
440 # okmismatchcolor($img, $pos[2]-1, $cy, @base_color, "inner right edge");
441 # XXX: This gets triggered by a freetype2 bug I think
442 # $ rpm -qa | grep freetype
447 cross($img, $x, $y, 'FF0000');
448 cross($img, $cx, $pos[1]-1, '0000FF');
449 cross($img, $cx, $pos[3], '0000FF');
450 cross($img, $pos[0]-1, $cy, '0000FF');
451 cross($img, $pos[2], $cy, '0000FF');
454 SKIP: { skip("couldn't draw text", 7) };
459 my ($img, $x, $y, $r, $g, $b, $about) = @_;
461 my $c = $img->getpixel('x'=>$x, 'y'=>$y);
462 my ($fr, $fg, $fb) = $c->rgba;
463 ok($fr == $r && $fg == $g && $fb == $b,
464 "want ($r,$g,$b) found ($fr,$fg,$fb)\@($x,$y) $about");
467 sub okmismatchcolor {
468 my ($img, $x, $y, $r, $g, $b, $about) = @_;
470 my $c = $img->getpixel('x'=>$x, 'y'=>$y);
471 my ($fr, $fg, $fb) = $c->rgba;
472 ok($fr != $r || $fg != $g || $fb != $b,
473 "don't want ($r,$g,$b) found ($fr,$fg,$fb)\@($x,$y) $about");
477 my ($img, $x, $y, $color) = @_;
479 $img->setpixel('x'=>[$x, $x, $x, $x, $x, $x-2, $x-1, $x+1, $x+2],
480 'y'=>[$y-2, $y-1, $y, $y+1, $y+2, $y, $y, $y, $y],