Passed
Push — master ( 6e26c0...38e037 )
by Dan
02:58
created

BuildFromUrlCommand   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 262
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 10

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 31
c 1
b 1
f 0
lcom 2
cbo 10
dl 0
loc 262
rs 9.8

8 Methods

Rating   Name   Duplication   Size   Complexity  
B configure() 0 38 1
C execute() 0 82 8
A getFont() 0 12 3
A getPlacer() 0 17 4
B getColorGenerator() 0 24 6
A getFontSizeGenerator() 0 16 4
A render() 0 11 2
A output() 0 13 3
1
<?php
2
3
namespace SixtyNine\Cloud\Command;
4
5
use Imagine\Image\ImageInterface;
6
use SixtyNine\Cloud\Builder\CloudBuilder;
7
use SixtyNine\Cloud\Builder\FiltersBuilder;
8
use SixtyNine\Cloud\Builder\PalettesBuilder;
9
use SixtyNine\Cloud\Builder\WordsListBuilder;
10
use SixtyNine\Cloud\Color\ColorGeneratorInterface;
11
use SixtyNine\Cloud\Color\RandomColorGenerator;
12
use SixtyNine\Cloud\Color\RotateColorGenerator;
13
use SixtyNine\Cloud\Factory\FontsFactory;
14
use SixtyNine\Cloud\Factory\PlacerFactory;
15
use SixtyNine\Cloud\FontSize\BoostFontSizeGenerator;
16
use SixtyNine\Cloud\FontSize\DimFontSizeGenerator;
17
use SixtyNine\Cloud\FontSize\FontSizeGeneratorInterface;
18
use SixtyNine\Cloud\FontSize\LinearFontSizeGenerator;
19
use SixtyNine\Cloud\Placer\PlacerInterface;
20
use SixtyNine\Cloud\Renderer\CloudRenderer;
21
use Symfony\Component\Console\Command\Command;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
27
class BuildFromUrlCommand extends Command
28
{
29
    protected $allowedOutputFormats = array('gif', 'jpeg', 'png');
30
    protected $allowedFontSizeBoosts = array('linear', 'dim', 'boost');
31
    protected $allowedPaletteTypes = array('cycle', 'random');
32
33
    protected function configure()
34
    {
35
        $this
36
            ->setName('cloud:from-url')
37
            ->setDescription('Create a cloud from a URL')
38
            ->addArgument('url', InputArgument::REQUIRED, 'The URL for the words')
39
            // Filters options
40
            ->addOption('case', null, InputOption::VALUE_OPTIONAL, 'Change case filter type (uppercase, lowercase, ucfirst)')
41
            ->addOption('max-word-count', null, InputOption::VALUE_OPTIONAL, 'Maximum number of words', 100)
42
            ->addOption('min-word-length', null, InputOption::VALUE_OPTIONAL, 'Minimumal word length', 5)
43
            ->addOption('max-word-length', null, InputOption::VALUE_OPTIONAL, 'Maximal word length', 10)
44
            ->addOption('no-remove-numbers', null, InputOption::VALUE_NONE, 'Disable the remove numbers filter')
45
            ->addOption('no-remove-trailing', null, InputOption::VALUE_NONE, 'Disable the remove trailing characters filter')
46
            ->addOption('no-remove-unwanted', null, InputOption::VALUE_NONE, 'Disable the remove unwanted characters filter')
47
            // WordsList options
48
            ->addOption('vertical-probability', null, InputOption::VALUE_OPTIONAL, 'The percentage probability of having vertical words (0-100)', 50)
49
            ->addOption('palette', null, InputOption::VALUE_OPTIONAL, 'The name of the palette used to color words')
50
            ->addOption('palette-type', null, InputOption::VALUE_OPTIONAL, 'The way the palette colors are used (cycle, random)', 'cycle')
51
            ->addOption('palettes-file', null, InputOption::VALUE_OPTIONAL, 'Optional path to the fonts, if omitted, defaults to <base>/fonts')
52
            ->addOption('sort-by', null, InputOption::VALUE_OPTIONAL, 'Words sorting field (text, count, angle)')
53
            ->addOption('sort-order', null, InputOption::VALUE_OPTIONAL, 'Words sorting order (asc, desc)')
54
            // Cloud options
55
            ->addOption('background-color', null, InputOption::VALUE_OPTIONAL, 'Background color of the cloud', '#FFFFFF')
56
            ->addOption('placer', null, InputOption::VALUE_OPTIONAL, 'Word placer to use')
57
            ->addOption('font', null, InputOption::VALUE_OPTIONAL, 'Font to use to draw the cloud')
58
            ->addOption('width', null, InputOption::VALUE_OPTIONAL, 'Width of the cloud', 800)
59
            ->addOption('height', null, InputOption::VALUE_OPTIONAL, 'Height of the cloud', 600)
60
            ->addOption('font-size-boost', null, InputOption::VALUE_OPTIONAL, 'Minimal font size (linear, dim, boost)', 'linear')
61
            ->addOption('min-font-size', null, InputOption::VALUE_OPTIONAL, 'Minimal font size', 12)
62
            ->addOption('max-font-size', null, InputOption::VALUE_OPTIONAL, 'Maximal font size', 64)
63
            // Other options
64
            ->addOption('save-to-file', null, InputOption::VALUE_OPTIONAL, 'If set to a file name, the output will be saved there')
65
            ->addOption('format', null, InputOption::VALUE_OPTIONAL, 'Output format (gif, jpeg, png)', 'png')
66
            ->addOption('fonts-path', null, InputOption::VALUE_OPTIONAL, 'Optional path to the fonts, if omitted, defaults to <base>/fonts')
67
            ->addOption('render-usher', null, InputOption::VALUE_NONE, 'Enable the rendering of the words usher')
68
            ->addOption('render-boxes', null, InputOption::VALUE_NONE, 'Enable the rendering of the words bounding boxes')
69
        ;
70
    }
71
72
    protected function execute(InputInterface $input, OutputInterface $output)
73
    {
74
        // Build the filters
75
        $filtersBuilder = FiltersBuilder::create()
76
            ->setMinLength($input->getOption('min-word-length'))
77
            ->setMaxLength($input->getOption('max-word-length'))
78
            ->setRemoveNumbers(!$input->getOption('no-remove-numbers'))
79
            ->setRemoveUnwanted(!$input->getOption('no-remove-unwanted'))
80
            ->setRemoveTrailing(!$input->getOption('no-remove-trailing'))
81
        ;
82
83
        $case = $input->getOption('case');
84
        if ($case && in_array($case, $filtersBuilder->getAllowedCase())) {
85
            $filtersBuilder->setCase($case);
86
        }
87
88
        // Create a placer
89
        $placerName = $this->getPlacer($input->getOption('placer'));
90
        $placer = PlacerFactory::getInstance()->getPlacer(
91
            $placerName,
92
            $input->getOption('width'),
93
            $input->getOption('height')
94
        );
95
96
        // Get the font file
97
        $fontsPath = $input->getOption('fonts-path')
98
            ? realpath($input->getOption('fonts-path'))
99
            : constant('BASE_PATH') . '/fonts'
100
        ;
101
        $factory = FontsFactory::create($fontsPath);
102
        $font = $this->getFont($factory, $input->getOption('font'));
103
104
        // Create the list builder
105
        $listBuilder = WordsListBuilder::create()
106
            ->setMaxWords($input->getOption('max-word-count'))
107
            ->setFilters($filtersBuilder->build())
108
            ->randomizeOrientation($input->getOption('vertical-probability'))
109
            ->importUrl($input->getArgument('url'))
110
        ;
111
112
        $sortBy = $input->getOption('sort-by');
113
        $sortOrder = $input->getOption('sort-order');
114
115
        if ($sortBy && $sortOrder) {
116
            $listBuilder->sort($sortBy, $sortOrder);
117
        }
118
119
        // Apply a color generator if needed
120
        $colorGenerator = $this->getColorGenerator(
121
            $input->getOption('palette'),
122
            $input->getOption('palette-type'),
123
            $input->getOption('palettes-file')
124
        );
125
126
        if ($colorGenerator) {
127
            $listBuilder->randomizeColors($colorGenerator);
128
        }
129
130
        // Build the list
131
        $list = $listBuilder->build('list');
132
133
        // Create a cloud builder
134
        $cloudBuilder = CloudBuilder::create($factory)
135
            ->setBackgroundColor($input->getOption('background-color'))
136
            ->setDimension($input->getOption('width'), $input->getOption('height'))
137
            ->setFont($font)
138
            ->setFontSizes($input->getOption('min-font-size'), $input->getOption('max-font-size'))
139
            ->setPlacer($placerName)
140
            ->setSizeGenerator($this->getFontSizeGenerator($input->getOption('font-size-boost')))
141
            ->useList($list)
142
        ;
143
144
        // Render the cloud and show the bounding boxes and the usher if needed
145
        $image = $this->render(
146
            $cloudBuilder,
147
            $factory,
148
            $input->getOption('render-usher') ? $placer : null,
0 ignored issues
show
Bug introduced by
It seems like $input->getOption('rende...sher') ? $placer : null can also be of type object; however, SixtyNine\Cloud\Command\...romUrlCommand::render() does only seem to accept null|object<SixtyNine\Cl...Placer\PlacerInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
149
            $input->getOption('render-boxes')
150
        );
151
152
        $this->output($image, $input->getOption('format'), $input->getOption('save-to-file'));
153
    }
154
155
    /**
156
     * @param FontsFactory $factory
157
     * @param string $font
158
     * @return string
159
     * @throws \InvalidArgumentException
160
     */
161
    protected function getFont(FontsFactory $factory, $font)
162
    {
163
        if ($font) {
164
            return $font;
165
        }
166
167
        if (0 === count($factory->getFonts())) {
168
            throw new \InvalidArgumentException('No font file found');
169
        }
170
171
        return $factory->getFonts()[0];
172
    }
173
174
    /**
175
     * @param string $name
176
     * @return PlacerInterface
177
     * @throws \InvalidArgumentException
178
     */
179
    protected function getPlacer($name)
180
    {
181
        $availablePlacers = PlacerFactory::getInstance()->getPlacersNames();
182
183
        if ($name) {
184
            if (!in_array($name, $availablePlacers)) {
185
                throw new \InvalidArgumentException('Word placer not found: ' . $name);
186
            }
187
            return $name;
188
        }
189
190
        if (!count($availablePlacers)) {
191
            throw new \InvalidArgumentException('No word placers available');
192
        }
193
194
        return $availablePlacers[0];
195
    }
196
197
    /**
198
     * @param string $paletteName
199
     * @param string $paletteType
200
     * @param string $palettesFile
201
     * @return bool|ColorGeneratorInterface
202
     * @throws \InvalidArgumentException
203
     */
204
    protected function getColorGenerator($paletteName, $paletteType, $palettesFile = null)
205
    {
206
        if ($paletteName && $paletteType) {
207
208
            if (!in_array($paletteType, $this->allowedPaletteTypes)) {
209
                throw new \InvalidArgumentException('Palette type must be either "cycle" or "random"');
210
            }
211
212
            $file = $palettesFile
213
                ? $palettesFile
214
                : __DIR__ . '/../Resources/palettes.yml'
215
            ;
216
            $paletteBuilder = PalettesBuilder::create()->importPalettes($file);
217
218
            $palette = $paletteBuilder->getNamedPalette($paletteName);
219
            $generatorClass = ($paletteType === 'cycle')
220
                ? RotateColorGenerator::class
221
                : RandomColorGenerator::class
222
            ;
223
            return new $generatorClass($palette);
224
        }
225
226
        return false;
227
    }
228
229
    /**
230
     * @param string $type
231
     * @return FontSizeGeneratorInterface
232
     * @throws \InvalidArgumentException
233
     */
234
    protected function getFontSizeGenerator($type = 'linear')
235
    {
236
        if (!in_array($type, $this->allowedFontSizeBoosts)) {
237
            throw new \InvalidArgumentException('Invalid font size boost: ' . $type);
238
        }
239
240
        $generatorClass = LinearFontSizeGenerator::class;
241
        if ($type === 'dim') {
242
            $generatorClass = DimFontSizeGenerator::class;
243
        }
244
        if ($type === 'boost') {
245
            $generatorClass = BoostFontSizeGenerator::class;
246
        }
247
248
        return new $generatorClass();
249
    }
250
251
    /**
252
     * @param CloudBuilder $cloudBuilder
253
     * @param FontsFactory $factory
254
     * @param PlacerInterface $placer
255
     * @param bool $renderBoxes
256
     * @return \Imagine\Gd\Image|ImageInterface
257
     */
258
    protected function render(CloudBuilder $cloudBuilder, FontsFactory $factory, PlacerInterface $placer = null, $renderBoxes = false)
259
    {
260
        $renderer = new CloudRenderer();
261
        $image = $renderer->render($cloudBuilder->build(), $factory, $renderBoxes);
262
263
        if ($placer) {
264
            $renderer->renderUsher($image, $placer, '#FF0000');
265
        }
266
267
        return $image;
268
    }
269
    /**
270
     * @param ImageInterface $image
271
     * @param string $outputFormat
272
     * @param string $outputFile
273
     * @throws \InvalidArgumentException
274
     */
275
    protected function output(ImageInterface $image, $outputFormat, $outputFile = null)
276
    {
277
        if (!in_array($outputFormat, $this->allowedOutputFormats)) {
278
            throw new \InvalidArgumentException('Invalid output format: ' . $outputFormat);
279
        }
280
281
        if ($outputFile) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $outputFile of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
282
            $image->save($outputFile, array('format' => $outputFormat));
283
            return;
284
        }
285
286
        echo $image->get($outputFormat);
287
    }
288
}
289