Passed
Push — complex-text-wrapping ( 722a44...30da31 )
by Burhan
02:01
created

FieldRendererComplexText::renderLine()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 56
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6.6676

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 6
eloc 30
c 3
b 0
f 0
nc 8
nop 4
dl 0
loc 56
ccs 25
cts 34
cp 0.7352
crap 6.6676
rs 8.8177

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Graze\CiffRenderer\Renderer\FieldRenderer;
4
5
use Graze\CiffRenderer\Parser\FieldParser\FieldParserComplexText;
6
use Graze\CiffRenderer\Parser\FieldParser\FieldParserInterface;
7
use Intervention\Image\Gd\Font;
8
use Intervention\Image\ImageManager;
9
10
class FieldRendererComplexText implements FieldRendererInterface
11
{
12
    /**
13
     * @var float
14
     */
15
    const LETTER_SPACING_ADJUSTMENT = 0.97;
16
17
    /**
18
     * @var Font
19
     */
20
    private $font;
21
22
    /**
23
     * @param Font $font
24
     */
25 2
    public function __construct(Font $font)
26
    {
27 2
        $this->font = $font;
28 2
    }
29
30
    /**
31
     * @param ImageManager $imageManager
32
     * @param FieldParserInterface $parser
33
     * @param null|callable $fontResolver
34
     * @param null|callable $graphicResolver
35
     * @return \Intervention\Image\Image
36
     */
37 1
    public function render(
38
        ImageManager $imageManager,
39
        FieldParserInterface $parser,
40
        callable $fontResolver = null,
41
        callable $graphicResolver = null
42
    ) {
43 1
        $fontPath = $fontResolver($parser->getFontFace());
44
45
        // Input may be multiline so grab the raw lines but preserve empty lines by injecting spaces
46 1
        $rawLines = explode("\n", str_replace("\n\n", "\n \n", $parser->getText()));
47 1
        $lines = [];
48 1
        foreach ($rawLines as $rawLine) {
49
            // Pass each raw line through the wrapping function
50 1
            $lines = array_merge($lines, explode("\n", $this->wrap($parser->getFontSize(), $fontPath, $rawLine, $parser->getWidth())));
51 1
        }
52
53 1
        $renderedTexts = [];
54 1
        $width = 0;
55 1
        $height = 0;
56
57 1
        foreach ($lines as $line) {
58 1
            $renderedText = $this->renderLine($imageManager, $parser, $fontPath, $line);
59 1
            if (is_null($renderedText)) {
60
                continue;
61
            }
62
63
            // store dimensions ready to create a canvas
64 1
            $width = max($width, $renderedText->getWidth());
65 1
            $height += $renderedText->getHeight();
66
67 1
            $renderedTexts[] = $renderedText;
68 1
        }
69
70 1
        if (empty($renderedTexts)) {
71
            // nothing to render
72
            return null;
73
        }
74
75
        // create a canvas to add each line of text to
76 1
        $canvas = $imageManager->canvas($width, $height);
77
78 1
        $offsetX = 0;
79 1
        $offsetY = 0;
80 1
        foreach ($renderedTexts as $renderedText) {
81
            // add the text to the canvas
82 1
            $canvas->insert($renderedText, 'top-left', (int) $offsetX, (int) $offsetY);
83
84
            // increase the offset for the next line
85 1
            $offsetY += $renderedText->getHeight();
86 1
        }
87
88 1
        $orientation = $parser->getOrientation();
89 1
        if ($orientation) {
90 1
            $canvas->rotate(360 - $orientation);
91 1
        }
92
93 1
        return $canvas;
94
    }
95
96
    /**
97
     * @param ImageManager $imageManager
98
     * @param FieldParserInterface $parser
99
     * @param string $fontPath
100
     * @param string $text
101
     * @return \Intervention\Image\Image
102
     */
103 1
    private function renderLine(ImageManager $imageManager, FieldParserInterface $parser, $fontPath, $text)
104
    {
105
        // create the text here so it can be measured
106 1
        $this->font->text($text);
107
108 1
        $this->font->file($fontPath);
109 1
        $this->font->size($parser->getFontSize());
110 1
        $this->font->color('#000');
111 1
        $this->font->align($parser->getTextAlignment());
112 1
        $this->font->valign('top');
113
114 1
        $size = $this->font->getBoxSize();
115
116
        // If we are trying to render a single space, it will have zero height and be skipped. Temporarily use the
117
        // dimensions of the letter 'l' to ensure it isn't skipped.
118 1
        if ($text == ' ') {
119
            $this->font->text('l');
120
            $size = $this->font->getBoxSize();
121
            $this->font->text($text);
122
        }
123
124
        // if text consists of invisible chars, measured size will be zero, nothing to print
125 1
        if ($size['width'] < 1 || $size['height'] < 1) {
126
            return null;
127
        }
128
129 1
        $fontCallback = function (&$font) {
130
            // override the font created by Canvas::text()
131 1
            $font = $this->font;
132 1
        };
133
134 1
        $canvas = $imageManager->canvas($parser->getWidth(), $size['height']);
135
136
        // figure out the correct reference point for this alignment
137 1
        switch ($parser->getTextAlignment()) {
138 1
            case FieldParserComplexText::ALIGN_CENTRE:
139
                $posX = round($parser->getWidth()/2);
140
                break;
141
142 1
            case FieldParserComplexText::ALIGN_RIGHT:
143
                $posX = $parser->getWidth();
144
                break;
145
146 1
            default:
147 1
                $posX = 0;
148 1
                break;
149 1
        }
150
151
        // no need to pass the text in here as we override the font in the callback
152 1
        $canvas->text('', $posX, 0, $fontCallback);
0 ignored issues
show
Bug introduced by
It seems like $posX can also be of type double; however, parameter $x of Intervention\Image\Image::text() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

152
        $canvas->text('', /** @scrutinizer ignore-type */ $posX, 0, $fontCallback);
Loading history...
153
154
        // attempt to correct differences between this and Clarisoft
155 1
        $width = (int) round($parser->getWidth() * self::LETTER_SPACING_ADJUSTMENT);
156 1
        $canvas->resize($width, $size['height']);
157
158 1
        return $canvas;
159
    }
160
161
    /**
162
     * Wrap the given text such that it doesn't exceed the given width using the given font and size.
163
     *
164
     * Derived from https://www.php.net/manual/en/function.imagettftext.php#89505
165
     *
166
     * @param string $fontSize
167
     * @param string $fontFace
168
     * @param string $string
169
     * @param int $width
170
     * @return string
171
     */
172 1
    private function wrap($fontSize, $fontFace, $string, $width)
173
    {
174
        // Font size needs to be scaled a bit to render to the same width as in Clarisoft.
175 1
        $fontSize = $fontSize * 0.8;
176 1
        $wrappedString = "";
177
178 1
        if ($string == ' ') {
179
            return $string;
180
        }
181
182 1
        $words = explode(' ', $string);
183
184 1
        foreach ($words as $word) {
185 1
            $teststring = $wrappedString=="" ? $word : $wrappedString.' '.$word;
186 1
            $testbox = imagettfbbox($fontSize, 0, $fontFace, $teststring);
0 ignored issues
show
Unused Code introduced by
The call to Graze\CiffRenderer\Rende...Renderer\imagettfbbox() has too many arguments starting with $fontSize. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
            $testbox = /** @scrutinizer ignore-call */ imagettfbbox($fontSize, 0, $fontFace, $teststring);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
187
            // Check that adding the new word does not exceed the width we want.
188
            // This isn't exact for various (unknown) reasons, so there is a 10 pixel buffer.
189 1
            if ($testbox[2] > ($width+10)) {
190
                $wrappedString.=($wrappedString=="" ? "" : "\n").$word;
191
            } else {
192 1
                $wrappedString.=($wrappedString=="" ? "" : ' ').$word;
193
            }
194 1
        }
195
196 1
        return $wrappedString;
197
    }
198
199
    /**
200
     * @return FieldRendererInterface
201
     */
202 1
    public static function factory()
203
    {
204 1
        return new static(
205 1
            new Font()
206 1
        );
207
    }
208
}
209