meyfa/php-svg

View on GitHub
src/Rasterization/Renderers/TextRenderer.php

Summary

Maintainability
A
1 hr
Test Coverage
F
15%
<?php

namespace SVG\Rasterization\Renderers;

use SVG\Fonts\FontRegistry;
use SVG\Rasterization\Transform\Transform;

/**
 * This renderer can draw basic text.
 *
 * Options:
 * - float x: the x coordinate of the text
 * - float y: the y coordinate of the baseline
 * - string anchor: the anchor point (start|middle|end) for x coordinate
 * - float fontSize: the font size
 * - string fontFamily: the font family
 * - string fontStyle: the font style (normal|italic|oblique)
 * - string fontWeight: the font weight (normal|bold|bolder|lighter|(number))
 * - string text: the text to draw
 */
class TextRenderer extends MultiPassRenderer
{
    /**
     * @inheritdoc
     */
    protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
    {
        // this assumes there is no rotation or skew, but that's fine, we can't deal with that anyway
        $size1 = $options['fontSize'];
        $size2 = $size1;
        $transform->resize($size1, $size2);
        $size = min($size1, $size2);

        $fontPath = null;
        if (isset($fontRegistry)) {
            $isItalic = $options['fontStyle'] === 'italic' || $options['fontStyle'] === 'oblique';
            $weight = self::resolveFontWeight($options['fontWeight']);
            $matchingFont = $fontRegistry->findMatchingFont($options['fontFamily'], $isItalic, $weight);
            if ($matchingFont !== null) {
                $fontPath = $matchingFont->getPath();
            }
        }

        if (!isset($fontPath)) {
            return null;
        }

        // text-anchor
        $anchorOffset = 0;
        if ($options['anchor'] === 'middle' || $options['anchor'] === 'end') {
            $width = self::calculateTextWidth($options['text'], $fontPath, $size);
            $anchorOffset = $options['anchor'] === 'middle' ? ($width / 2) : $width;
        }

        $x = $options['x'];
        $y = $options['y'];
        $transform->map($x, $y);

        return [
            'x'         => $x - $anchorOffset,
            'y'         => $y,
            'size'      => $size,
            'fontPath'  => $fontPath,
            'text'      => $options['text'],
        ];
    }

    /**
     * @inheritdoc
     */
    protected function renderFill($image, $params, int $color): void
    {
        imagettftext(
            $image,
            $params['size'],
            0,
            $params['x'],
            $params['y'],
            $color,
            $params['fontPath'],
            $params['text']
        );
    }

    /**
     * @inheritdoc
     */
    protected function renderStroke($image, $params, int $color, float $strokeWidth): void
    {
        $x = $params['x'];
        $y = $params['y'];
        $px = $strokeWidth;

        for ($c1 = ($x - abs($px)); $c1 <= ($x + abs($px)); $c1++) {
            for ($c2 = ($y - abs($px)); $c2 <= ($y + abs($px)); $c2++) {
                imagettftext($image, $params['size'], 0, $c1, $c2, $color, $params['fontPath'], $params['text']);
            }
        }
    }

    /**
     * Compute the width, in pixels, of the given text.
     *
     * @param string $text The text to measure.
     * @param string $fontFile The font file path.
     * @param float $size The font size in pixels.
     *
     * @return float The width in pixels.
     */
    private static function calculateTextWidth(string $text, string $fontFile, float $size): float
    {
        // note for future: imagettfbbox is unable to calculate height properly.
        // width should be fine though.

        $box = imagettfbbox($size, 0, $fontFile, $text);

        $minX = min($box[0], $box[2], $box[4], $box[6]);
        $maxX = max($box[0], $box[2], $box[4], $box[6]);

        return abs($maxX - $minX);
    }

    private static function resolveFontWeight($weight): int
    {
        // TODO implement "bolder" and "lighter"

        if (is_numeric($weight)) {
            return (int) $weight;
        } elseif ($weight === 'bold') {
            return 700;
        }

        return 400;
    }
}