Passed
Push — complex-text-wrapping ( cc49b2...d7c802 )
by Burhan
03:56
created

FieldRendererComplexText::render()   B

Complexity

Conditions 7
Paths 30

Size

Total Lines 57
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 30.7665

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 27
nc 30
nop 4
dl 0
loc 57
c 3
b 0
f 0
cc 7
ccs 6
cts 28
cp 0.2143
crap 30.7665
rs 8.5546

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
        }
52
53
        $renderedTexts = [];
54
        $width = 0;
55
        $height = 0;
56
57
        foreach ($lines as $line) {
58
            $renderedText = $this->renderLine($imageManager, $parser, $fontPath, $line);
59
            if (is_null($renderedText)) {
60
                continue;
61
            }
62
63
            // store dimensions ready to create a canvas
64
            $width = max($width, $renderedText->getWidth());
65
            $height += $renderedText->getHeight();
66
67
            $renderedTexts[] = $renderedText;
68
        }
69
70
        if (empty($renderedTexts)) {
71
            // nothing to render
72
            return null;
73
        }
74
75
        // create a canvas to add each line of text to
76
        $canvas = $imageManager->canvas($width, $height);
77
78
        $offsetX = 0;
79
        $offsetY = 0;
80
        foreach ($renderedTexts as $renderedText) {
81
            // add the text to the canvas
82
            $canvas->insert($renderedText, 'top-left', (int) $offsetX, (int) $offsetY);
83
84
            // increase the offset for the next line
85
            $offsetY += $renderedText->getHeight();
86
        }
87
88
        $orientation = $parser->getOrientation();
89
        if ($orientation) {
90
            $canvas->rotate(360 - $orientation);
91
        }
92
93
        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
    private function renderLine(ImageManager $imageManager, FieldParserInterface $parser, $fontPath, $text)
104
    {
105
        // create the text here so it can be measured
106
        $this->font->text($text);
107
108
        $this->font->file($fontPath);
109
        $this->font->size($parser->getFontSize());
110
        $this->font->color('#000');
111
        $this->font->align($parser->getTextAlignment());
112
        $this->font->valign('top');
113
114
        $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
        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
        if ($size['width'] < 1 || $size['height'] < 1) {
126
            return null;
127
        }
128
129
        $fontCallback = function (&$font) {
130
            // override the font created by Canvas::text()
131
            $font = $this->font;
132
        };
133
134
        $canvas = $imageManager->canvas($parser->getWidth(), $size['height']);
135
136
        // figure out the correct reference point for this alignment
137
        switch ($parser->getTextAlignment()) {
138
            case FieldParserComplexText::ALIGN_CENTRE:
139
                $posX = round($parser->getWidth()/2);
140
                break;
141
142
            case FieldParserComplexText::ALIGN_RIGHT:
143
                $posX = $parser->getWidth();
144
                break;
145
146
            default:
147
                $posX = 0;
148
                break;
149
        }
150
151
        // no need to pass the text in here as we override the font in the callback
152
        $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
        $width = (int) round($parser->getWidth() * self::LETTER_SPACING_ADJUSTMENT);
156
        $canvas->resize($width, $size['height']);
157
158
        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
     * @param string $fontSize
164
     * @param string $fontFace
165
     * @param string $string
166
     * @param int $width
167
     * @return string
168
     */
169 1
    private function wrap($fontSize, $fontFace, $string, $width){
170
        // Font size needs to be scaled a bit to render to the same width as in Clarisoft.
171 1
        $fontSize = $fontSize * 0.8;
172 1
        $wrappedString = "";
173
174 1
        if ($string == ' ') {
175
            return $string;
176
        }
177
178 1
        $words = explode(' ', $string);
179
180 1
        foreach ($words as $word){
181
182 1
            $teststring = $wrappedString=="" ? $word : $wrappedString.' '.$word;
183 1
            $testbox = imagettfbbox($fontSize, 0, $fontFace, $teststring);
184
            // Check that adding the new word does not exceed the width we want.
185
            // This isn't exact for various (unknown) reasons, so there is a 10 pixel buffer.
186
            if ( $testbox[2] > ($width+10) ){
187
                $wrappedString.=($wrappedString=="" ? "" : "\n").$word;
188
            } else {
189
                $wrappedString.=($wrappedString=="" ? "" : ' ').$word;
190
            }
191
        }
192
193
        return $wrappedString;
194
    }
195
196
    /**
197
     * @return FieldRendererInterface
198
     */
199 1
    public static function factory()
200
    {
201 1
        return new static(
202 1
            new Font()
203
        );
204
    }
205
}
206