Skip to content

Commit

Permalink
fix: Allow renderer short-circuit if unable to render anything (#200)
Browse files Browse the repository at this point in the history
The text renderer would previously throw in some circumstances if no
font was available. With this patch, its `prepareRenderParams()` method
can return `null` in these cases, aborting the render for that text
element without causing a crash.

This also simplifies RectRenderer, which doesn't need an "empty" state
anymore.
  • Loading branch information
meyfa authored Jan 22, 2023
1 parent 3b1d664 commit 35d6c36
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/Rasterization/Renderers/EllipseRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class EllipseRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
{
$cx = $options['cx'];
$cy = $options['cy'];
Expand Down
2 changes: 1 addition & 1 deletion src/Rasterization/Renderers/LineRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class LineRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
{
$x1 = $options['x1'];
$y1 = $options['y1'];
Expand Down
17 changes: 10 additions & 7 deletions src/Rasterization/Renderers/MultiPassRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ public function render(SVGRasterizer $rasterizer, array $options, SVGNode $conte
$transform = $rasterizer->getCurrentTransform();

$params = $this->prepareRenderParams($options, $transform, $rasterizer->getFontRegistry());
if (!isset($params)) {
return;
}

$paintOrder = self::getPaintOrder($context);
foreach ($paintOrder as $paint) {
Expand Down Expand Up @@ -75,19 +78,19 @@ private function paintFill(SVGRasterizer $rasterizer, SVGNode $context, $params)
}

/**
* Converts the options array into a new parameters array that the render
* methods can make more sense of.
* Converts the options array into a new parameters array that the render methods can make more sense of.
*
* Specifically, the intention is to allow subclasses to outsource coordinate translation, approximation of curves
* and the like to this method rather than dealing with it in the render methods. This shall encourage single passes
* over the input data (for performance reasons).
*
* Specifically, the intention is to allow subclasses to outsource
* coordinate translation, approximation of curves and the like to this
* method rather than dealing with it in the render methods. This shall
* encourage single passes over the input data (for performance reasons).
* If this method determines that rendering isn't possible (e.g. because the shape is empty), it shall return null.
*
* @param array $options The associative array of raw options.
* @param Transform $transform The coordinate transform to apply, to go from user to output coordinates.
* @param FontRegistry|null $fontRegistry The font registry to use for text rendering.
*
* @return array The new associative array of computed render parameters.
* @return array|null The new associative array of computed render parameters, if there is something to render.
*/
abstract protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry);

Expand Down
2 changes: 1 addition & 1 deletion src/Rasterization/Renderers/PathRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PathRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
{
$approximator = new PathApproximator($transform);
$approximator->approximate($options['commands']);
Expand Down
2 changes: 1 addition & 1 deletion src/Rasterization/Renderers/PolygonRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PolygonRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
{
$points = [];
foreach ($options['points'] as $point) {
Expand Down
13 changes: 2 additions & 11 deletions src/Rasterization/Renderers/RectRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ class RectRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): ?array
{
$w = $options['width'];
$h = $options['height'];
$transform->resize($w, $h);

if ($w <= 0 || $h <= 0) {
return ['empty' => true];
return null;
}

$x1 = $options['x'];
Expand All @@ -54,7 +54,6 @@ protected function prepareRenderParams(array $options, Transform $transform, ?Fo
}

return [
'empty' => false,
'x1' => $x1,
'y1' => $y1,
'x2' => $x1 + $w - 1,
Expand All @@ -69,10 +68,6 @@ protected function prepareRenderParams(array $options, Transform $transform, ?Fo
*/
protected function renderFill($image, $params, int $color): void
{
if ($params['empty']) {
return;
}

if ($params['rx'] != 0 || $params['ry'] != 0) {
$this->renderFillRounded($image, $params, $color);
return;
Expand Down Expand Up @@ -133,10 +128,6 @@ private function renderFillRounded($image, array $params, int $color): void
*/
protected function renderStroke($image, $params, int $color, float $strokeWidth): void
{
if ($params['empty']) {
return;
}

imagesetthickness($image, round($strokeWidth));

if ($params['rx'] != 0 || $params['ry'] != 0) {
Expand Down
6 changes: 5 additions & 1 deletion src/Rasterization/Renderers/TextRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class TextRenderer extends MultiPassRenderer
/**
* @inheritdoc
*/
protected function prepareRenderParams(array $options, Transform $transform, ?FontRegistry $fontRegistry): array
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'];
Expand All @@ -41,6 +41,10 @@ protected function prepareRenderParams(array $options, Transform $transform, ?Fo
}
}

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

// text-anchor
$anchorOffset = 0;
if ($options['anchor'] === 'middle' || $options['anchor'] === 'end') {
Expand Down
37 changes: 37 additions & 0 deletions tests/Rasterization/Renderers/TextRendererTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace SVG\Rasterization\Renderers;

use AssertGD\GDSimilarityConstraint;
use SVG\Rasterization\SVGRasterizer;

/**
* @requires extension gd
* @covers \SVG\Rasterization\Renderers\TextRenderer
*
* @SuppressWarnings(PHPMD)
*/
class TextRendererTest extends \PHPUnit\Framework\TestCase
{
public function testShouldNotFailWithoutRegisteredFont()
{
$obj = new TextRenderer();

$context = $this->getMockForAbstractClass('\SVG\Nodes\SVGNode');

$rasterizer = new SVGRasterizer('40px', '80px', null, 4, 8);
$obj->render($rasterizer, [
'x' => 10,
'y' => 10,
'fontFamily' => 'Roboto',
'fontWeight' => 'normal',
'fontStyle' => 'normal',
'fontSize' => 16,
'anchor' => 'middle',
'text' => 'foo',
], $context);
$img = $rasterizer->finish();

$this->assertThat($img, new GDSimilarityConstraint('./tests/images/empty-4x8.png'));
}
}

0 comments on commit 35d6c36

Please sign in to comment.