documentElement; $viewBox = $root->getAttribute('viewBox'); $widthAttr = $root->getAttribute('width'); $heightAttr = $root->getAttribute('height'); $width = null; $height = null; if ($viewBox !== '') { // SVG spec allows whitespace or comma separators. $parts = preg_split('/[\s,]+/', trim($viewBox)) ?: []; if (isset($parts[2]) && is_numeric($parts[2])) { $width = (int)(float)$parts[2]; } if (isset($parts[3]) && is_numeric($parts[3])) { $height = (int)(float)$parts[3]; } } if ($widthAttr !== '') { if (is_numeric($widthAttr)) { $width = (int)(float)$widthAttr; } elseif ($width === null) { // Unit like "mm", "cm", "%" - stripped because without an // output device (dpi) we cannot translate to pixels. $width = (int)$widthAttr; } } if ($heightAttr !== '') { if (is_numeric($heightAttr)) { $height = (int)(float)$heightAttr; } elseif ($height === null) { $height = (int)$heightAttr; } } return new ImageDimension( max(0, $width ?? self::DEFAULT_DIMENSION), max(0, $height ?? self::DEFAULT_DIMENSION), ); } /** * Serialize the document as plain XML markup of its root element, * without an `` prolog. */ public function toXml(SvgDocument $document): string { return (string)$document->saveXML($document->documentElement); } /** * Serialize the document as inline HTML5-ready markup. * * Emits only the root `` element (no `` prolog) and * applies the clean-ups needed when inlining SVG into an HTML * document: * * - drops `xmlns="http://www.w3.org/2000/svg"` (HTML5 auto-places * `` in the SVG namespace), * - drops the legacy `version` attribute, * - synthesizes a `viewBox` from `width`/`height` when missing so * the SVG scales with CSS instead of rendering at intrinsic * pixel size. * * Operates on a detached clone; the passed document is not mutated. */ public function toInlineMarkup(SvgDocument $document): string { $clone = new \DOMDocument(); $clone->appendChild($clone->importNode($document->documentElement, true)); /** @var \DOMElement $root */ $root = $clone->documentElement; $root->removeAttributeNS('http://www.w3.org/2000/svg', ''); if ($root->hasAttribute('version')) { $root->removeAttribute('version'); } if (!$root->hasAttribute('viewBox') && $root->hasAttribute('width') && $root->hasAttribute('height') ) { $root->setAttribute( 'viewBox', sprintf('0 0 %d %d', (int)$root->getAttribute('width'), (int)$root->getAttribute('height')), ); } return (string)$clone->saveXML($root); } /** * Wrap the source SVG in an outer `` that carries the crop viewBox * and target dimensions. The passed document is not mutated, a new * {@see SvgDocument} is returned. */ public function cropScale(SvgDocument $document, Area $cropArea, ImageDimension $targetDimension): SvgDocument { $offsetLeft = (int)$cropArea->getOffsetLeft(); $offsetTop = (int)$cropArea->getOffsetTop(); // Rounding matches ImageDimension's width/height calculation. $newWidth = (int)round($cropArea->getWidth()); $newHeight = (int)round($cropArea->getHeight()); $sourceRoot = $document->documentElement; $intrinsic = $this->getDimensions($document); $wrapper = new SvgDocument('1.0'); $wrapper->preserveWhiteSpace = true; $wrapper->formatOutput = true; // Deep-copy the source tree into the wrapper document so we never // mutate the passed document. /** @var \DOMElement $innerSvg */ $innerSvg = $wrapper->importNode($sourceRoot, true); // Ensure the inner carries width/height; without them the // crop cannot render correctly when the file is embedded via . if ($sourceRoot->getAttribute('width') === '') { $innerSvg->setAttribute('width', (string)$intrinsic->getWidth()); $innerSvg->setAttribute('data-manipulated-width', 'true'); } if ($sourceRoot->getAttribute('height') === '') { $innerSvg->setAttribute('height', (string)$intrinsic->getHeight()); $innerSvg->setAttribute('data-manipulated-height', 'true'); } $outerSvg = $wrapper->createElement('svg'); $outerSvg->setAttribute('xmlns', 'http://www.w3.org/2000/svg'); $outerSvg->setAttribute('viewBox', $offsetLeft . ' ' . $offsetTop . ' ' . $newWidth . ' ' . $newHeight); $outerSvg->setAttribute('width', (string)$targetDimension->getWidth()); $outerSvg->setAttribute('height', (string)$targetDimension->getHeight()); // Propagate preserveAspectRatio from the inner root onto the wrapper. $preserveAspectRatio = $sourceRoot->getAttribute('preserveAspectRatio'); if ($preserveAspectRatio !== '') { $outerSvg->setAttribute('preserveAspectRatio', $preserveAspectRatio); } $outerSvg->appendChild($innerSvg); $wrapper->appendChild($outerSvg); return $wrapper; } }