Created
November 30, 2023 11:47
-
-
Save garvinhicking/257d4920da4da0c0e1c3fba3a0b313c9 to your computer and use it in GitHub Desktop.
SVG crop dummy implementation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>SVG comparison</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<style> | |
* { | |
box-sizing: border-box; | |
font-family: sans-serif; | |
} | |
.container { | |
max-width: 1000px; | |
margin: auto; | |
border: 1px solid black; | |
} | |
figure { | |
margin: 0; | |
} | |
.orange-border, | |
img { | |
border: 3px solid orange; | |
} | |
.red-border, | |
img.image-embed-proc-item { | |
border: 3px solid red; | |
} | |
.blue-border, | |
img.image-embed-raw-item { | |
border: 3px solid blue; | |
} | |
.width-100-height-auto img { | |
width: 100%; | |
height: auto; | |
} | |
.max-width-100-height-auto img { | |
max-width: 100%; | |
height: auto; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h2> | |
Legend: | |
</h2> | |
<p class="orange-border">Orange Border: Using #svgView(viewBox()) URI fragment. Breaks because of "relative" cropping."</p> | |
<p class="blue-border">Blue Border: Using SVG with baked-in viewBox and unmanipulated SVG (no width/height injection). Breaks because of unapplied cropping when missing width/height</p> | |
<p class="red-border">Red Border: Using SVG with baked-in viewBox. Works?</p> | |
<h2> | |
Solution: | |
</h2> | |
<ul> | |
<li>We need "baked-in" viewBoxes. Per viewBox crop one processed file will need to be generated.</li> | |
<li>We want to use IMG tags, because CSS styling is easier and less-breaking than introducing to style a SVG tag. Also, this way we can use processed files as background images.</li> | |
<li>The processed file creates a SVG wrapper around the original input file.</li> | |
<li>We can NOT use the inline IMAGE reference to the original image, because browser's security models will not resolve it. The same reason prevents using src:data...</li> | |
<li>The original input file is manipulated to receive width/height attributes, because otherwise the cropping will not be relative to the image dimensions.</li> | |
<li>Check <a href="proc.txt">proc.txt</a> to see the file processing logic.</li> | |
<li>Check the RED BORDER images. Those should be usable.</li> | |
</ul> | |
<h2> | |
No CSS width or height (plain html) | |
</h2> | |
<div> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG with width and height set | |
</figcaption> | |
</figure> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo-nodim.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG without width and height attributes | |
</figcaption> | |
</figure> | |
</div> | |
<h2> | |
CSS width: 100% height: auto | |
</h2> | |
<div class="width-100-height-auto"> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG with width and height set | |
</figcaption> | |
</figure> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo-nodim.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG without width and height attributes | |
</figcaption> | |
</figure> | |
</div> | |
<h2> | |
CSS max-width: 100% height: auto | |
</h2> | |
<div class="max-width-100-height-auto"> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG with width and height set | |
</figcaption> | |
</figure> | |
<figure class="image"> | |
<img class="image-embed-item" src="proc.php?f=logo-nodim.svg#svgView(viewBox(0,13,85,64))" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-proc-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64" width="85" height="64" loading="lazy" alt="" /> | |
<img class="image-embed-raw-item" src="proc.php?f=logo-nodim.svg&viewbox=0,13,85,64&raw=1" width="85" height="64" loading="lazy" alt="" /> | |
<figcaption class="image-caption"> | |
SVG without width and height attributes | |
</figcaption> | |
</figure> | |
</div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
ini_set('display_errors', 'on'); | |
error_reporting(E_ALL); | |
// Takes an input file, creates a wrapping SVG file. | |
// The inner SVG element needs to have width/height set. | |
// The outer SVG element can have a crop definition, also width/height. | |
// The resulting element can still be scaled with different width/height | |
// attributes to the <img> element (or via CSS). | |
// Input parameters: | |
// ?f=filename [filename] | |
// &viewbox=A,B,X,Y [crop definition] | |
// &raw [if set, skips manipulation (debugging/examples)] | |
// Sanitize | |
$svgBase = __DIR__ . '/' . preg_replace('/[^a-z0-9\.\-_]+/i', '', basename($_REQUEST['f'])); | |
if (!file_exists($svgBase) || !is_file($svgBase)) { | |
die('Computer says no.'); | |
} | |
$GLOBALS['manipulateSvg'] = true; | |
if (isset($_REQUEST['raw'])) { | |
$GLOBALS['manipulateSvg'] = false; | |
} | |
// Helper to retrieve the original file's dimensions. Preference on viewBox="" attribute. | |
function operateSvgDimensions($svg): array { | |
// A default used when SVG neither uses width, height or viewBox | |
// Files falling back to this are probably broken. | |
$width = $height = 100; | |
$svgViewBox = ($svg->documentElement->getAttribute('viewBox') ?? ''); | |
$svgWidth = ($svg->documentElement->getAttribute('width') ?? ''); | |
$svgHeight = ($svg->documentElement->getAttribute('height') ?? ''); | |
$svgInvalid = ($svg->documentElement->getAttribute('invalid') ?? ''); | |
// The ideal attribute to receive the dimensions from is the viewBox attribute. | |
// It's guaranteed to contain pixel sizes (however, fractions are possible). | |
if ($svgViewBox !== '') { | |
$viewBoxParts = explode(' ', (string)$svgViewBox); | |
$width = (string)$viewBoxParts[2]; | |
$height = (string)$viewBoxParts[3]; | |
} else { | |
// viewBox attribute missing, need to check if width/height exists | |
if ($svgWidth !== '') { | |
// Check: What could happen if width is specified with percentage or other relative units? | |
$width = (string)$svgWidth; | |
} | |
if ($svgHeight !== '') { | |
// Check: What could happen if height is specified with percentage or other relative units? | |
$width = (string)$svgHeight; | |
} | |
} | |
// Ensure that the deduced width and height settings are attributes on the original <svg> | |
// If those would be missing, cropping cannot successfully be applied when getting embedded in <img>! | |
if ($GLOBALS['manipulateSvg']) { | |
if ($svgWidth === '') { | |
$svg->documentElement->setAttribute('width', $width); | |
$svg->documentElement->setAttribute('data-manipulated-width', 'true'); | |
} | |
if ($svgHeight === '') { | |
$svg->documentElement->setAttribute('height', $height); | |
$svg->documentElement->setAttribute('data-manipulated-height', 'true'); | |
} | |
} | |
return [$width, $height]; | |
} | |
// Parse GET viewbox parameter | |
function getViewbox(string $viewbox): array { | |
$parts = explode(',', $viewbox); | |
$returnParts = []; | |
foreach($parts as $part) { | |
$part = (string)trim($part); | |
if (strlen($part) == 0) continue; | |
$returnParts[] = preg_replace('/[^0-9\.]/', '', (string)$part); | |
} | |
return $returnParts; | |
} | |
header('Content-Type: image/svg+xml'); | |
header('Content-Disposition: inline; filename="processed-wrapped.svg"'); | |
// Load original SVG | |
$svg = new DOMDocument(); | |
$svg->load($svgBase); | |
// Create a fresh wrapping <svg> tag | |
$newSvgDocument = new DOMDocument('1.0'); | |
$newSvgDocument->preserveWhiteSpace = true; | |
$newSvgDocument->formatOutput = true; | |
$newSvgElement = $newSvgDocument->createElement('svg'); | |
$newSvgElement->setAttribute('xmlns', 'http://www.w3.org/2000/svg'); | |
// See if cropping needs to be applied. | |
$originalDimensions = operateSvgDimensions($svg); | |
if (isset($_REQUEST['viewbox'])) { | |
// Yes: Cropped to specified arguments. | |
$viewboxparts = getViewbox($_REQUEST['viewbox']); | |
$newSvgElement->setAttribute('viewBox', implode(' ', $viewboxparts)); | |
$newSvgElement->setAttribute('width', $viewboxparts[2]); | |
$newSvgElement->setAttribute('height', $viewboxparts[3]); | |
} else { | |
// No: Uncropped full image, deduced from the parent. | |
$newSvgElement->setAttribute('viewBox', '0 0 ' . implode(' ', $originalDimensions)); | |
$newSvgElement->setAttribute('width', $originalDimensions[0]); | |
$newSvgElement->setAttribute('height', $originalDimensions[1]); | |
} | |
// Profit? | |
$newSvgElement->setAttribute('preserveAspectRatio', 'xMidYMid meet'); | |
// Import the existing root <svg> element | |
$importedSvg = $newSvgDocument->importNode($svg->documentElement, true); | |
// Append the existing SVG content to the new <svg> element | |
foreach ($svg->documentElement->childNodes as $child) { | |
$importedNode = $newSvgDocument->importNode($child, true); | |
$importedSvg->appendChild($importedNode); | |
} | |
// Stitch together the wrapper plus the old root element plus children | |
$newSvgElement->appendChild($importedSvg); | |
$newSvgDocument->appendChild($newSvgElement); | |
// For now we output it dynamically. An actual implementation would | |
// statically write this of course. | |
echo $newSvgDocument->saveXML(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment