Closed tumur1 closed 1 month ago
Thanks for reporting. I will look into this.
I do not know what the issue is, but from some tests I made with three different Japanese fonts when I look at the output from imageftbbox
for the first font the bounding box is the correct size but there is a significant difference in the bounding box x position when outputting Japanese and Latin characters in the same Japanese font. However, for the second and third fonts I tried the positioning was more accurate but still not exactly aligned. The output from the Imagick driver is slightly more accurate but not exact.
GD Driver
Imagick Driver
$image->text('W', 250, 200, function (FontFactory $font) {
$font->filename('./ちはや純.ttf');
$font->size(250);
$font->color('#fff');
$font->align('center');
});
$image->text('ム', 250, 200, function (FontFactory $font) {
$font->filename('./ちはや純.ttf');
$font->size(250);
$font->color('#f00');
$font->align('center');
});
$image->text('W', 250, 300, function (FontFactory $font) {
$font->filename('./mini-wakuwaku.otf');
$font->size(150);
$font->color('#fff');
$font->align('center');
});
$image->text('ム', 250, 300, function (FontFactory $font) {
$font->filename('./mini-wakuwaku.otf');
$font->size(150);
$font->color('#f00');
$font->align('center');
});
$image->text('W', 250, 450, function (FontFactory $font) {
$font->filename('./Tryk2kp.ttc');
$font->size(150);
$font->color('#fff');
$font->align('center');
});
$image->text('ム', 250, 450, function (FontFactory $font) {
$font->filename('./Tryk2kp.ttc');
$font->size(150);
$font->color('#f00');
$font->align('center');
});
I've also done a few more tests and unfortunately I don't know where to locate the problem.
However, I suspect that the font used is the deciding factor here.
A test I did with version 2 and "Arial Unicode MS" as the font did not show a correctly centered result either. Neither with version 2 nor with version 3.
@tumur1 What font did you use in your example?
$manager = new ImageManager(['driver' => 'gd']);
$image = $manager->canvas(200, 200, '666');
$image->line(100, 0, 100, 200, function ($line) {
$line->color('333');
});
$image->line(0, 100, 200, 100, function ($line) {
$line->color('333');
});
$image->text('ゥ', 100, 100, function ($font) {
$font->file('./ArialUnicodeMS.ttf');
$font->size(130);
$font->color('fff');
$font->align('center');
$font->valign('middle');
});
$image = ImageManager::gd()
->create(200, 200)
->fill('666');
$image->drawLine(function ($line) {
$line->color('333');
$line->from(100, 0);
$line->to(100, 200);
});
$image->drawLine(function ($line) {
$line->color('333');
$line->from(0, 100);
$line->to(200, 100);
});
$image->text('ゥ', 100, 100, function ($font) {
$font->file('./ArialUnicodeMS.ttf');
$font->size(130);
$font->color('fff');
$font->align('center');
$font->valign('middle');
});
Looking at the output from the Imagick::queryFontMetrics
function, it seems that with some Japanese fonts the textWidth value of Japanese characters is significantly greater than the same character's boundingBox value, and there is too much space between characters if you output 2 or more. The closer the textWidth value is to the characterWidth value (the font size specified by the user), the more likely it is that a character will be centered correctly.
The boundingBox values are very accurate but can only be used for single characters, so a possible solution might be to split the text string into separate characters, total the width of all the bounding boxes, and include a calculation for the letter spacing (kerning).
I have created a test script using the standard Imagick functions that seems to horizontally center the text correctly:
In my test script I am using the originX value from the Imagick::queryFontMetrics
function which seems to be more accurate than the textWidth value. Latin fonts seem to be horizontally centered correctly as well.
The code I used to generate this image was:
$width = 500;
$height = 500;
$image = new Imagick();
$image->setAntiAlias(true);
$image->newImage($width, $height, new ImagickPixel('#444'));
$draw = new ImagickDraw();
// draw center target lines
$draw->setStrokeColor('#f00');
$draw->line($width / 2, 0, $width / 2, $height);
$draw->line(0, $height / 2, $width, $height / 2);
// define text and retrieve font metrics
$draw->setFont('./Tryk2kp.ttc');
$draw->setFontSize(400);
$text = 'ム';
$metrics = $image->queryFontMetrics($draw, $text);
$baseline = $metrics['boundingBox']['y2'];
$character_width = $metrics['boundingBox']['x2'] - $metrics['boundingBox']['x1'];
$text_height = $metrics['ascender'] + $metrics['descender'];
// draw rectangle with a size of $metrics['originX']
$draw->setStrokeColor('#f00');
$draw->setFillColor('transparent');
$draw->rectangle(
($width / 2) - ($metrics['originX'] / 2),
($height / 2) - ($text_height / 2),
($width / 2) - ($metrics['originX'] / 2) + $metrics['originX'],
($height / 2) - ($text_height / 2) + $text_height
);
// draw rectangle with a size of $metrics['boundingBox']['x2'] - $metrics['boundingBox']['x1']
$draw->setStrokeColor('#f00');
$draw->setFillColor('transparent');
$draw->rectangle(
($width / 2) - ($character_width / 2),
($height / 2) - ($text_height / 2),
($width / 2) - ($character_width / 2) + $character_width,
($height / 2) - ($text_height / 2) + $text_height
);
// draw text
$draw->setFillColor('#f00');
$x = ($width / 2) - ($metrics['originX'] / 2);
$y = ($height / 2) + ($text_height / 2);
$image->annotateImage(
$draw,
$x,
$y,
0,
$text
);
// define text and retrieve font metrics
$draw->setFont('./mini-wakuwaku.otf');
$draw->setFontSize(90);
$text = 'アイウエオ';
$metrics = $image->queryFontMetrics($draw, $text);
$baseline = $metrics['boundingBox']['y2'];
$character_width = $metrics['boundingBox']['x2'] - $metrics['boundingBox']['x1'];
$text_height = $metrics['ascender'] + $metrics['descender'];
// draw rectangle with a size of $metrics['originX']
$draw->setStrokeColor('#ff0');
$draw->setFillColor('transparent');
$draw->rectangle(
($width / 2) - ($metrics['originX'] / 2),
($height / 2) - ($text_height / 2),
($width / 2) - ($metrics['originX'] / 2) + $metrics['originX'],
($height / 2) - ($text_height / 2) + $text_height
);
// draw rectangle with a size of $metrics['boundingBox']['x2'] - $metrics['boundingBox']['x1']
$draw->setStrokeColor('#ff0');
$draw->setFillColor('transparent');
$draw->rectangle(
($width / 2) - ($character_width / 2),
($height / 2) - ($text_height / 2),
($width / 2) - ($character_width / 2) + $character_width,
($height / 2) - ($text_height / 2) + $text_height
);
// draw text
$draw->setFillColor('#ff0');
$x = ($width / 2) - ($metrics['originX'] / 2);
$y = ($height / 2) + ($text_height / 2);
$image->annotateImage(
$draw,
$x,
$y,
0,
$text
);
$image->drawImage($draw);
$image->setImageFormat('png');
header("Content-Type: image/png");
echo $image;
As it stands now, Imagick seems to be working without any problems.
I did another comparison with just GD (without Intervention Image). This script draws characters (latin and japanese for comparision) and visualizes the coordinate of the "box size". Here you can see that the box is drawn correctly with a latin character. With a Japanese character you can see clear deviations. This could be a bug in GD.
define('CHAR', 'ゥ');
define('COLOR_GREY', 14540253);
define('COLOR_BLACK', 0);
define('FONT_SIZE', 140);
define('FONT_ANGLE', 0);
define('FONT_FILE', './ArialUnicodeMS.ttf');
// create gdimage
$gd = imagecreatetruecolor(400, 400);
// fill with grey
imagefill($gd, 0, 0, COLOR_GREY);
// draw grid
imageline($gd, 200, 0, 200, 400, COLOR_BLACK);
imageline($gd, 0, 200, 400, 200, COLOR_BLACK);
// read box size
$box = imageftbbox(
FONT_SIZE,
FONT_ANGLE,
FONT_FILE,
CHAR
);
// map box size to text position (200x200)
$box = array_map(function ($point) {
return $point + 200;
}, $box);
// draw box
imagepolygon(
$gd,
$box,
COLOR_BLACK,
);
// draw text
imagettftext(
$gd,
FONT_SIZE,
FONT_ANGLE,
200,
200,
COLOR_BLACK,
FONT_FILE,
CHAR
);
// output
header('content-type: image/png');
imagepng($gd);
exit;
I have created another test script using standard GD functions that seems to horizontally center the text correctly:
I noticed in the src/Drivers/Gd/FontProcessor.php
script that the text width is calculated as follows:
intval(abs($box[4] - $box[0]))
However, if I do this
intval(abs($box[4]) + abs($box[0]))
then (in my test script) I get the correct text width. The text width is also correct for Latin fonts.
This also applies with the height calculation. Currently the code to do this is
intval(abs($box[5] - $box[1]))
but if I change the code to
intval(abs($box[5]) + abs($box[1]))
then the vertical centering is also correct for Japanese characters.
The code I used to generate the first image was:
$width = 500;
$height = 500;
$image = imagecreatetruecolor($width, $height);
$black = imagecolorallocate($image, hexdec(44), hexdec(44), hexdec(44));
$red = imagecolorallocate($image, 255, 0, 0);
$yellow = imagecolorallocate($image, 255, 255, 0);
// draw center target lines
imageline($image, $width / 2, 0, $width / 2, $height, $red);
imageline($image, 0, $height / 2, $width, $height / 2, $red);
// define text and retrieve font metrics (bounding box only)
$font = './Tryk2kp.ttc';
$font_size = (int) 200;
$text = 'ム';
$box = imageftbbox($font_size, 0, $font, $text);
$text_width = intval(abs($box[4]) + abs($box[0]));
$text_height = intval(abs($box[5]) + abs($box[1]));
// draw rectangle with a size of abs($box[4]) + abs($box[0])
imagerectangle(
$image,
(int) ($width / 2) - (int) ($text_width / 2),
(int) ($height / 2) - (int) ($text_height / 2),
(int) ($width / 2) - (int) ($text_width / 2) + (int) $text_width,
(int) ($height / 2) - (int) ($text_height / 2) + $text_height,
$red
);
// draw text
$x = (int) ($width / 2) - (int) ($text_width / 2);
$y = ($height / 2) + ($text_height / 2);
imagefttext($image, $font_size, 0, (int) $x, (int) $y, $red, $font, $text);
// define text and retrieve font metrics (bounding box only)
$font = './mini-wakuwaku.otf';
$font_size = (int) 50;
$text = 'アイウエオ';
$box = imageftbbox($font_size, 0, $font, $text);
$text_width = intval(abs($box[4]) + abs($box[0]));
$text_height = intval(abs($box[5]) + abs($box[1]));
// draw rectangle with a size of abs($box[4]) + abs($box[0])
imagerectangle(
$image,
(int) ($width / 2) - (int) ($text_width / 2),
(int) ($height / 2) - (int) ($text_height / 2),
(int) ($width / 2) - (int) ($text_width / 2) + (int) $text_width,
(int) ($height / 2) - (int) ($text_height / 2) + $text_height,
$yellow
);
// draw text
$x = (int) ($width / 2) - (int) ($text_width / 2);
$y = ($height / 2) + ($text_height / 2);
imagefttext($image, $font_size, 0, (int) $x, (int) $y, $yellow, $font, $text);
header('Content-Type: image/png');
imagepng($image);
imagedestroy($image);
@olivervogel I am using the Noto_Sans_CJK-Bold font. It seems to be on the GD driver. When I switched the driver to Imagick, the problem was solved. [My server did not support Imagick before, but it has been upgraded now]. Additionally, I have attached the font I used. Feel free to close the issue if no further investigation is needed. font.zip
GD:
Imagick:
I found the error. The result of imageftbbox
contains an offset that is not included in the calculation of Intervention Image. For Latin fonts this does not seem to make a difference, but for Japanese (and probably also other non-latin) fonts the offset is needed to render the position correctly.
Top-Left corner is 0, -10
array(8) {
[0]=>
int(0)
[1]=>
int(0)
[2]=>
int(39)
[3]=>
int(0)
[4]=>
int(39)
[5]=>
int(-10)
[6]=>
int(0)
[7]=>
int(-10)
}
Top-Left corner is at 29, -91 (offset of 29 is currently disregarded).
array(8) {
[0]=>
int(29)
[1]=>
int(-8)
[2]=>
int(131)
[3]=>
int(-8)
[4]=>
int(131)
[5]=>
int(-91)
[6]=>
int(29)
[7]=>
int(-91)
}
This issue has been fixed as of version 3.7.0.
I'm using the library to create user icons with the first letter of their name. In version 2, placing the first letter on the canvas worked perfectly and it was centered. However, after updating to version 3, the centering is no longer working for Japanese characters (English characters seem to be unaffected).
Code Example v2:
v3:
Result: v2![v2](https://github.com/Intervention/image/assets/123346464/36cedd46-7d24-4401-a88a-c61a4ada7ab0)
v3![v3](https://github.com/Intervention/image/assets/123346464/5dbba479-d52b-462a-848f-eb379bc4fed6)
For example, it's small(ゥ)character
Environment (please complete the following information):
PHP Version: PHP 8.2.9
OS: Centos
Intervention Image Version: v2
v3
![image](https://github.com/Intervention/image/assets/123346464/bb2b7709-22fa-49fe-8cee-67c30800a169)
GD or Imagick: GD