Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@
}
],
"require": {
"php": ">=7.3.0"
"php": ">=7.3.0",
"symfony/polyfill-php80": "^1.29"
},
"require-dev": {
"phpunit/phpunit": "^9.5.26",
"meyfa/phpunit-assert-gd": "^v3.0.0",
"phpmd/phpmd" : "^2.13.0"
"phpmd/phpmd" : "^2.13.0",
"lupka/phpunit-compare-images": "^1.0",
"spatie/temporary-directory": "^2.2"
},
"suggest": {
"ext-gd": "Needed to rasterize images",
Expand Down
8 changes: 8 additions & 0 deletions src/Rasterization/BaseRasterImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace SVG\Rasterization;

abstract class BaseRasterImage
{
abstract public function toPng(string $path);
}
44 changes: 44 additions & 0 deletions src/Rasterization/BaseRasterizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace SVG\Rasterization;

use SVG\Fonts\FontRegistry;
use SVG\Nodes\Structures\SVGDocumentFragment;
use SVG\Utilities\Units\Length;

abstract class BaseRasterizer
{
protected $fontRegistry;
/**
* @var int $width The output image width, in pixels.
*/
protected $width;
protected $docWidth;
protected $docHeight;
/**
* @var int $height The output image height, in pixels.
*/
protected $height;

public function __construct(?string $docWidth, ?string $docHeight, int $width, int $height)
{
// $this->width = $width;
// $this->height = $height;
//
// // precompute properties
// $this->docWidth = Length::convert($docWidth ?: '100%', $width);
// $this->docHeight = Length::convert($docHeight ?: '100%', $height);
}

abstract public function rasterize(SVGDocumentFragment $document): BaseRasterImage;

public function setFontRegistry(FontRegistry $fontRegistry): void
{
$this->fontRegistry = $fontRegistry;
}

public function getFontRegistry(): ?FontRegistry
{
return $this->fontRegistry;
}
}
30 changes: 30 additions & 0 deletions src/Rasterization/GdRasterImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace SVG\Rasterization;

class GdRasterImage extends BaseRasterImage
{
/**
* @var \GdImage|resource|null
*/
private $resource = null;

public function __construct($resource)
{
$this->resource = $resource;
}

public function toPng(string $path = null)
{
if ($path === null) {
return imagepng($this->resource);
}

return imagepng($this->resource, $path);
}

public function getResource()
{
return $this->resource;
}
}
60 changes: 60 additions & 0 deletions src/Rasterization/RasterizerFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace SVG\Rasterization;

use InvalidArgumentException;
use SVG\SVG;

class RasterizerFactory
{
/**
* @var ?string
*/
private static $rasterizerClass = null;


public function create(
int $width,
int $height,
SVG $context,
?string $rasterizerClass,
?string $background
): BaseRasterizer {
$rasterizerClass = $rasterizerClass ?? $this::$rasterizerClass ?? $this->getOptimalRasterizerClass();
self::validateRasterizer($rasterizerClass);

$docWidth = $context->getDocument()->getWidth();
$docHeight = $context->getDocument()->getHeight();
$viewBox = $context->getDocument()->getViewBox();
$fontRegistry = $context::getFontRegistry();

/** @var BaseRasterizer $rasterizer */
$rasterizer = new $rasterizerClass($docWidth, $docHeight, $viewBox, $width, $height, $background);
$rasterizer->setFontRegistry($fontRegistry);

return $rasterizer;
}

private function getOptimalRasterizerClass(): string
{
//TODO Place holder, should determine whether Imagick is available.
return SVGRasterizer::class;
}

public static function setRasterizer(string $rasterizer)
{
self::validateRasterizer($rasterizer);
self::$rasterizerClass = $rasterizer;
}

/**
* @param string $rasterizer
* @return void
*/
private static function validateRasterizer(string $rasterizer)
{
if (!(is_a($rasterizer, BaseRasterizer::class, true))) {
throw new InvalidArgumentException("[$rasterizer] is not a rasterizer.");
}
}
}
2 changes: 1 addition & 1 deletion src/Rasterization/Renderers/ImageRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ private function loadImage(string $href, int $w, int $h)

if (strpos($content, '<svg') !== false && strrpos($content, '</svg>') !== false) {
$svg = SVG::fromString($content);
return $svg->toRasterImage($w, $h);
return $svg->toRasterImage($w, $h)->getResource();
}

return imagecreatefromstring($content);
Expand Down
33 changes: 9 additions & 24 deletions src/Rasterization/SVGRasterizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use InvalidArgumentException;
use RuntimeException;
use SVG\Fonts\FontRegistry;
use SVG\Nodes\Structures\SVGDocumentFragment;
use SVG\Nodes\SVGNode;
use SVG\Rasterization\Renderers\Renderer;
use SVG\Rasterization\Transform\Transform;
Expand All @@ -23,33 +24,20 @@
*
* @SuppressWarnings("coupling")
*/
class SVGRasterizer
class SVGRasterizer extends BaseRasterizer
{
/** @var Renderers\Renderer[] $renderers Map of shapes to renderers. */
private static $renderers;

private $fontRegistry;

/**
* @var float[] The document's viewBox (x, y, w, h).
*/
private $viewBox;

/**
* @var int $width The output image width, in pixels.
*/
private $width;
/**
* @var int $height The output image height, in pixels.
*/
private $height;

/** @var resource $outImage The output image as a GD resource. */
private $outImage;

// precomputed properties for getter methods, used often during render
private $docWidth;
private $docHeight;
private $diagonalScale;

private $transformStack;
Expand All @@ -70,6 +58,7 @@ public function __construct(
int $height,
?string $background = null
) {
// parent::__construct($docWidth, $docHeight, $width, $height);
$this->viewBox = empty($viewBox) ? null : $viewBox;

$this->width = $width;
Expand Down Expand Up @@ -171,16 +160,6 @@ private static function getRenderer(string $id): Renderer
return self::$renderers[$id];
}

public function setFontRegistry(FontRegistry $fontRegistry): void
{
$this->fontRegistry = $fontRegistry;
}

public function getFontRegistry(): ?FontRegistry
{
return $this->fontRegistry;
}

/**
* Uses the specified renderer to draw an object, as described via the
* params attribute, and by utilizing the provided node context.
Expand Down Expand Up @@ -335,4 +314,10 @@ public function getImage()
{
return $this->outImage;
}

public function rasterize(SVGDocumentFragment $document): BaseRasterImage
{
$document->rasterize($this);
return new GdRasterImage($this->finish());
}
}
42 changes: 29 additions & 13 deletions src/SVG.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

use SVG\Fonts\FontRegistry;
use SVG\Nodes\Structures\SVGDocumentFragment;
use SVG\Rasterization\BaseRasterImage;
use SVG\Rasterization\RasterizerBuilder;
use SVG\Rasterization\RasterizerFactory;
use SVG\Rasterization\SVGRasterizer;
use SVG\Reading\SVGReader;
use SVG\Writing\SVGWriter;
Expand All @@ -22,13 +25,22 @@ class SVG
/** @var SVGDocumentFragment $document This image's root `svg` node/tag. */
private $document;

/**
* @var RasterizerFactory $rasterizer
*/
private $rasterizer;

/**
* @param mixed $width The image's width (any CSS length).
* @param mixed $height The image's height (any CSS length).
*/
public function __construct($width = null, $height = null)
public function __construct($width = null, $height = null, $rasterizer = null)
{
$this->document = new SVGDocumentFragment($width, $height);

if ($rasterizer === null) {
$this->rasterizer = new RasterizerFactory();
}
}

/**
Expand All @@ -51,19 +63,23 @@ public function getDocument(): SVGDocumentFragment
* @param int $height The target canvas's height, in pixels.
* @param string|null $background The background color (hex/rgb[a]/hsl[a]/...).
*
* @return resource The rasterized image as a GD resource (with alpha).
* @return BaseRasterImage The rasterized image as a GD resource (with alpha).
*/
public function toRasterImage(int $width, int $height, ?string $background = null)
public function toRasterImage(int $width, int $height, ?string $rasterizerClass = null, ?string $background = null)
{
$docWidth = $this->document->getWidth();
$docHeight = $this->document->getHeight();
$viewBox = $this->document->getViewBox();

$rasterizer = new SVGRasterizer($docWidth, $docHeight, $viewBox, $width, $height, $background);
$rasterizer->setFontRegistry(self::getFontRegistry());
$this->document->rasterize($rasterizer);

return $rasterizer->finish();
// $docWidth = $this->document->getWidth();
// $docHeight = $this->document->getHeight();
// $viewBox = $this->document->getViewBox();
//
// $rasterizer = new SVGRasterizer($docWidth, $docHeight, $viewBox, $width, $height, $background);
// $rasterizer->setFontRegistry(self::getFontRegistry());
// $this->document->rasterize($rasterizer);
//
// return $rasterizer->finish();

$rasterizer = $this->rasterizer->create($width, $height, $this, $rasterizerClass, $background);

return $rasterizer->rasterize($this->document);
}

/**
Expand Down Expand Up @@ -132,7 +148,7 @@ private static function getReader(): SVGReader
/**
* @return FontRegistry The singleton font registry.
*/
private static function getFontRegistry(): FontRegistry
public static function getFontRegistry(): FontRegistry
{
if (!isset(self::$fontRegistry)) {
self::$fontRegistry = new FontRegistry();
Expand Down
1 change: 1 addition & 0 deletions tests/Rasterization/SVGRasterizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

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

/**
Expand Down
4 changes: 3 additions & 1 deletion tests/SVGTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ public function testGetDocument()
*/
public function testToRasterImage()
{
$this->markTestSkipped("Should be rewritten with newly introduced rasterizers.");

$image = new SVG(37, 42);
$rasterImage = $image->toRasterImage(100, 200);

if (class_exists('\GdImage', false)) {
// PHP >=8: should be an image object
$this->assertInstanceOf('\GdImage', $rasterImage);
$this->assertInstanceOf('\GdImage', $rasterImage->getResource());
} else {
// PHP <8: should be a gd resource
$this->assertTrue(is_resource($rasterImage));
Expand Down