Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve use handling #111

Merged
merged 3 commits into from
Feb 23, 2024
Merged
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
13 changes: 11 additions & 2 deletions src/Svg/Document.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Svg\Tag\Polyline;
use Svg\Tag\Rect;
use Svg\Tag\Stop;
use Svg\Tag\Symbol;
use Svg\Tag\Text;
use Svg\Tag\StyleTag;
use Svg\Tag\UseTag;
Expand Down Expand Up @@ -322,10 +323,14 @@ private function _tagStart($parser, $name, $attributes)
break;

case 'g':
case 'symbol':
$tag = new Group($this, $name);
break;

case 'symbol':
$this->inDefs = true;
$tag = new Symbol($this, $name);
break;

case 'clippath':
$tag = new ClipPath($this, $name);
break;
Expand Down Expand Up @@ -378,6 +383,11 @@ function _tagEnd($parser, $name)
$this->inDefs = false;
return;

case 'symbol':
$this->inDefs = false;
$tag = array_pop($this->stack);
break;

case 'svg':
case 'path':
case 'rect':
Expand All @@ -393,7 +403,6 @@ function _tagEnd($parser, $name)
case 'style':
case 'text':
case 'g':
case 'symbol':
case 'clippath':
case 'use':
case 'a':
Expand Down
91 changes: 91 additions & 0 deletions src/Svg/Tag/AbstractTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,97 @@ protected function applyTransform($attributes)
}
}

/**
* Apply a viewBox transform to the element
*
* @param array $attributes
*/
protected function applyViewbox($attributes) {
if (!isset($attributes["viewbox"])) {
return;
}

$surface = $this->document->getSurface();
$viewBox = preg_split('/[\s,]+/is', trim($attributes['viewbox']));
if (count($viewBox) != 4) {
return;
}

// Computing the equivalent transform of an SVG viewport
// https://svgwg.org/svg2-draft/coords.html#ComputingAViewportsTransform

// 1. Let vb-x, vb-y, vb-width, vb-height be the min-x, min-y, width and height values of the viewBox attribute respectively.
[$vbX, $vbY, $vbWidth, $vbHeight] = $viewBox;

if ($vbWidth < 0 || $vbHeight < 0) {
return;
}

// correct solution is to not render, for now scaling to 0 below
//if ($vbWidth == 0 || $vbHeight == 0) {
//}

// 2. Let e-x, e-y, e-width, e-height be the position and size of the element respectively.
$eX = $attributes["x"] ?? 0;
$eY = $attributes["y"] ?? 0;
$eWidth = $attributes["width"] ?? $this->document->getWidth();
$eHeight = $attributes["height"] ?? $this->document->getHeight();

// 3. Let align be the align value of preserveAspectRatio, or 'xMidYMid' if preserveAspectRatio is not defined.
$preserveAspectRatio = explode(" ", $attributes["preserveAspectRatio"] ?? "xMidYMid meet");
$align = $preserveAspectRatio[0];

// 4. Let meetOrSlice be the meetOrSlice value of preserveAspectRatio, or 'meet' if preserveAspectRatio is not defined or if meetOrSlice is missing from this value.
$meetOrSlice = $meetOrSlice ?? "meet";

// 5. Initialize scale-x to e-width/vb-width.
$scaleX = $vbWidth == 0 ? 0 : ($eWidth / $vbWidth);

// 6. Initialize scale-y to e-height/vb-height.
$scaleY = $vbHeight == 0 ? 0 : ($eHeight / $vbHeight);

// 7. If align is not 'none' and meetOrSlice is 'meet', set the larger of scale-x and scale-y to the smaller.
if ($align !== "none" && $meetOrSlice === "meet") {
$scaleX = min($scaleX, $scaleY);
$scaleY = min($scaleX, $scaleY);
}

// 8. Otherwise, if align is not 'none' and meetOrSlice is 'slice', set the smaller of scale-x and scale-y to the larger.
elseif ($align !== "none" && $meetOrSlice === "slice") {
$scaleX = max($scaleX, $scaleY);
$scaleY = max($scaleX, $scaleY);
}

// 9. Initialize translate-x to e-x - (vb-x * scale-x).
$translateX = $eX - ($vbX * $scaleX);

// 10. Initialize translate-y to e-y - (vb-y * scale-y)
$translateY = $eY - ($vbY * $scaleY);

// 11. If align contains 'xMid', add (e-width - vb-width * scale-x) / 2 to translate-x.
if (strpos($align, "xMid") !== false) {
$translateX += ($eWidth - $vbWidth * $scaleX) / 2;
}

// 12. If align contains 'xMax', add (e-width - vb-width * scale-x) to translate-x.
if (strpos($align, "xMax") !== false) {
$translateX += ($eWidth - $vbWidth * $scaleX);
}

// 13. If align contains 'yMid', add (e-height - vb-height * scale-y) / 2 to translate-y.
if (strpos($align, "yMid") !== false) {
$translateX += ($eHeight - $vbHeight * $scaleY) / 2;
}

// 14. If align contains 'yMax', add (e-height - vb-height * scale-y) to translate-y.
if (strpos($align, "yMid") !== false) {
$translateX += ($eHeight - $vbHeight * $scaleY);
}

$surface->translate($translateX, $translateY);
$surface->scale($scaleX, $scaleY);
}

/**
* Convert the given size for the context of this current tag.
* Takes a pixel-based reference, which is usually specific to the context of the size,
Expand Down
34 changes: 34 additions & 0 deletions src/Svg/Tag/Symbol.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @package php-svg-lib
* @link http://github.com/PhenX/php-svg-lib
* @author Fabien Ménager <[email protected]>
* @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html
*/

namespace Svg\Tag;

use Svg\Style;

class Symbol extends AbstractTag
{
protected function before($attributes)
{
$surface = $this->document->getSurface();

$surface->save();

$style = $this->makeStyle($attributes);

$this->setStyle($style);
$surface->setStyle($style);

$this->applyViewbox($attributes);
$this->applyTransform($attributes);
}

protected function after()
{
$this->document->getSurface()->restore();
}
}
50 changes: 30 additions & 20 deletions src/Svg/Tag/UseTag.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ protected function before($attributes)
$link = $attributes["href"] ?? $attributes["xlink:href"];
$this->reference = $document->getDef($link);

if ($this->reference) {
$this->reference->before($attributes);
}

$surface = $document->getSurface();
$surface->save();

Expand All @@ -63,11 +59,6 @@ protected function after() {
return;
}
parent::after();

if ($this->reference) {
$this->reference->after();
}

$this->getDocument()->getSurface()->restore();
}

Expand All @@ -84,19 +75,22 @@ public function handle($attributes)
return;
}

$originalAttributes = array_merge($this->reference->attributes);
$originalStyle = $this->reference->getStyle();
$mergedAttributes = $this->reference->attributes;
$attributesToNotMerge = ['x', 'y', 'width', 'height', 'href', 'xlink:href', 'id'];
$attributesToNotMerge = ['x', 'y', 'width', 'height', 'href', 'xlink:href', 'id', 'style'];
foreach ($attributes as $attrKey => $attrVal) {
if (!in_array($attrKey, $attributesToNotMerge) && !isset($mergedAttributes[$attrKey])) {
$mergedAttributes[$attrKey] = $attrVal;
}
}
$mergedAttributes['style'] = ($attributes['style'] ?? '') . ';' . ($mergedAttributes['style'] ?? '');

$this->reference->handle($mergedAttributes);
$this->_handle($this->reference, $mergedAttributes);

foreach ($this->reference->children as $_child) {
$_attributes = array_merge($_child->attributes, $mergedAttributes);
$_child->handle($_attributes);
$this->reference->attributes = $originalAttributes;
if ($originalStyle !== null) {
$this->reference->setStyle($originalStyle);
}
}

Expand All @@ -107,16 +101,32 @@ public function handleEnd()
return;
}

if ($this->reference) {
$this->_handleEnd($this->reference);
}

parent::handleEnd();
}

if (!$this->reference) {
return;
private function _handle($tag, $attributes) {
$tag->handle($attributes);
foreach ($tag->children as $child) {
$originalAttributes = array_merge($child->attributes);
$originalStyle = $child->getStyle();
$mergedAttributes = $child->attributes;
$mergedAttributes['style'] = ($attributes['style'] ?? '') . ';' . ($mergedAttributes['style'] ?? '');
$this->_handle($child, $mergedAttributes);
$child->attributes = $originalAttributes;
if ($originalStyle !== null) {
$child->setStyle($originalStyle);
}
}
}

$this->reference->handleEnd();

foreach ($this->reference->children as $_child) {
$_child->handleEnd();
private function _handleEnd($tag) {
foreach ($tag->children as $child) {
$this->_handleEnd($child);
}
$tag->handleEnd();
}
}
Loading