From d8546e4fc7eb5650ec8bda2d3e1ca3a3215733bc Mon Sep 17 00:00:00 2001 From: duncte123 Date: Tue, 2 Jun 2020 09:25:17 +0200 Subject: [PATCH] Add code from stil/gd-text#30 --- composer.json | 2 +- src/Box.php | 168 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 137 insertions(+), 33 deletions(-) diff --git a/composer.json b/composer.json index 8d10738..7576570 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "A class drawing multiline and aligned text on pictures. Uses GD extension.", "license": "MIT", "require": { - "php": ">=5.3", + "php": ">=7.1", "ext-gd": "*" }, "require-dev": { diff --git a/src/Box.php b/src/Box.php index 3721a50..2f8bc77 100644 --- a/src/Box.php +++ b/src/Box.php @@ -1,4 +1,5 @@ strokeColor = $color; } - + /** * @param int $v Stroke size in *pixels* */ @@ -130,16 +131,16 @@ public function setStrokeSize($v) } /** - * @param Color $color Shadow color - * @param int $xShift Relative shadow position in pixels. Positive values move shadow to right, negative to left. - * @param int $yShift Relative shadow position in pixels. Positive values move shadow to bottom, negative to up. + * @param Color $color Shadow color + * @param int $xShift Relative shadow position in pixels. Positive values move shadow to right, negative to left. + * @param int $yShift Relative shadow position in pixels. Positive values move shadow to bottom, negative to up. */ public function setTextShadow(Color $color, $xShift, $yShift) { - $this->textShadow = array( + $this->textShadow = [ 'color' => $color, - 'offset' => new Point($xShift, $yShift) - ); + 'offset' => new Point($xShift, $yShift), + ]; } /** @@ -152,6 +153,7 @@ public function setBackgroundColor(Color $color) /** * Allows to customize spacing between lines. + * * @param float $v Height of the single text line, in percents, proportionally to font size */ public function setLineHeight($v) @@ -169,13 +171,14 @@ public function setBaseline($v) /** * Sets text alignment inside textbox + * * @param string $x Horizontal alignment. Allowed values are: left, center, right. * @param string $y Vertical alignment. Allowed values are: top, center, bottom. */ public function setTextAlign($x = 'left', $y = 'top') { - $xAllowed = array('left', 'right', 'center'); - $yAllowed = array('top', 'bottom', 'center'); + $xAllowed = ['left', 'right', 'center']; + $yAllowed = ['top', 'bottom', 'center']; if (!in_array($x, $xAllowed)) { throw new \InvalidArgumentException('Invalid horizontal alignement value was specified.'); @@ -191,9 +194,10 @@ public function setTextAlign($x = 'left', $y = 'top') /** * Sets textbox position and dimensions - * @param int $x Distance in pixels from left edge of image. - * @param int $y Distance in pixels from top edge of image. - * @param int $width Width of texbox in pixels. + * + * @param int $x Distance in pixels from left edge of image. + * @param int $y Distance in pixels from top edge of image. + * @param int $width Width of texbox in pixels. * @param int $height Height of textbox in pixels. */ public function setBox($x, $y, $width, $height) @@ -219,9 +223,91 @@ public function setTextWrapping($textWrapping) /** * Draws the text on the picture. + * + * @param string $text Text to draw. May contain newline characters. + * + * @return Rectangle Area that cover the drawn text + */ + public function draw(string $text): Rectangle + { + return $this->drawText($text, true); + } + + /** + * Draws the text on the picture, fitting it to the current box + * + * @param string $text Text to draw. May contain newline characters. + * @param int $precision Increment or decrement of font size. The lower this value, the slower this method. + * @param int $maxFontSize The maximum size that the font is allowed to be. + * @param int $minFontSize The minimum size that the font is allowed to be. + * @param int|null $usedFontSize The font size that was used + * + * @return Rectangle Area that cover the drawn text + */ + public function drawFitFontSize(string $text, int $precision, int $maxFontSize = -1, int $minFontSize = -1, ?int &$usedFontSize = null) + { + $initialFontSize = $this->fontSize; + + $usedFontSize = $this->fontSize; + $rectangle = $this->calculate($text); + + if ($rectangle->getHeight() > $this->box->getHeight() || $rectangle->getWidth() > $this->box->getWidth()) { + // Decrement font size + do { + $this->setFontSize($usedFontSize); + $rectangle = $this->calculate($text); + + $usedFontSize -= $precision; + } while (($minFontSize == -1 || $usedFontSize > $minFontSize) && + ($rectangle->getHeight() > $this->box->getHeight() || $rectangle->getWidth() > $this->box->getWidth())); + + $usedFontSize += $precision; + } else { + // Increment font size + do { + $this->setFontSize($usedFontSize); + $rectangle = $this->calculate($text); + + $usedFontSize += $precision; + } while (($maxFontSize > 0 && $usedFontSize < $maxFontSize) + && $rectangle->getHeight() < $this->box->getHeight() + && $rectangle->getWidth() < $this->box->getWidth()); + + $usedFontSize -= $precision * 2; + } + $this->setFontSize($usedFontSize); + + $rectangle = $this->drawText($text, true); + + // Restore initial font size + $this->setFontSize($initialFontSize); + + return $rectangle; + } + + /** + * Get the area that will cover the given text + * + * @param string $text The text to calculate the rectangle for. + * + * @return Rectangle + */ + public function calculate(string $text) + { + return $this->drawText($text, false); + } + + /** + * Draws the text on the picture. + * + * Modified from https://stackoverflow.com/a/52799317/4807235 to better fit the text if there are no spaces + * * @param string $text Text to draw. May contain newline characters. + * @param bool $draw If we should draw the text on the canvas + * + * @return Rectangle */ - public function draw($text) + public function drawText(string $text, bool $draw): Rectangle { if (!isset($this->fontFace)) { throw new \InvalidArgumentException('No path to font file has been specified.'); @@ -229,7 +315,7 @@ public function draw($text) switch ($this->textWrapping) { case TextWrapping::NoWrap: - $lines = array($text); + $lines = [$text]; break; case TextWrapping::WrapWithOverflow: default: @@ -246,7 +332,7 @@ public function draw($text) } $lineHeightPx = $this->lineHeight * $this->fontSize; - $textHeight = count($lines) * $lineHeightPx; + $textHeight = \count($lines) * $lineHeightPx; switch ($this->alignY) { case VerticalAlignment::Center: @@ -261,6 +347,10 @@ public function draw($text) } $n = 0; + + $drawnX = $drawnY = PHP_INT_MAX; + $drawnH = $drawnW = 0; + foreach ($lines as $line) { $box = $this->calculateBox($line); switch ($this->alignX) { @@ -280,7 +370,7 @@ public function draw($text) $xMOD = $this->box->getX() + $xAlign; $yMOD = $this->box->getY() + $yAlign + $yShift + ($n * $lineHeightPx); - if ($line && $this->backgroundColor) { + if ($draw && $line && $this->backgroundColor) { // Marks whole texbox area with given background-color $backgroundHeight = $this->fontSize; @@ -304,43 +394,54 @@ public function draw($text) $box->getWidth(), $lineHeightPx ), - new Color(rand(1, 180), rand(1, 180), rand(1, 180)) + new Color(\rand(1, 180), \rand(1, 180), \rand(1, 180)) ); } - if ($this->textShadow !== false) { + if ($draw) { + if ($this->textShadow !== false) { + $this->drawInternal( + new Point( + $xMOD + $this->textShadow['offset']->getX(), + $yMOD + $this->textShadow['offset']->getY() + ), + $this->textShadow['color'], + $line + ); + } + + $this->strokeText($xMOD, $yMOD, $line); $this->drawInternal( new Point( - $xMOD + $this->textShadow['offset']->getX(), - $yMOD + $this->textShadow['offset']->getY() + $xMOD, + $yMOD ), - $this->textShadow['color'], + $this->fontColor, $line ); } - $this->strokeText($xMOD, $yMOD, $line); - $this->drawInternal( - new Point( - $xMOD, - $yMOD - ), - $this->fontColor, - $line - ); + $drawnX = \min($xMOD, $drawnX); + $drawnY = \min($this->box->getY() + $yAlign + ($n * $lineHeightPx), $drawnY); + $drawnW = \max($drawnW, $box->getWidth()); + $drawnH += $lineHeightPx; $n++; } + + return new Rectangle($drawnX, $drawnY, $drawnW, $drawnH); } /** * Splits overflowing text into array of strings. + * * @param string $text + * * @return string[] */ protected function wrapTextWithOverflow($text) { - $lines = array(); + $lines = []; // Split text explicitly into lines by \n, \r\n and \r $explicitLines = preg_split('/\n|\r\n?/', $text); foreach ($explicitLines as $line) { @@ -358,6 +459,7 @@ protected function wrapTextWithOverflow($text) } $lines[] = $line; } + return $lines; } @@ -383,14 +485,16 @@ protected function drawFilledRectangle(Rectangle $rect, Color $color) /** * Returns the bounding box of a text. + * * @param string $text + * * @return Rectangle */ protected function calculateBox($text) { $bounds = imagettfbbox($this->getFontSizeInPoints(), 0, $this->fontFace, $text); - $xLeft = $bounds[0]; // (lower|upper) left corner, X position + $xLeft = $bounds[0]; // (lower|upper) left corner, X position $xRight = $bounds[2]; // (lower|upper) right corner, X position $yLower = $bounds[1]; // lower (left|right) corner, Y position $yUpper = $bounds[5]; // upper (left|right) corner, Y position