From 3e02d5aff85babc3ffbfcf52313f2108e313aa23 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 13:46:43 +0200 Subject: Adding upstream version 2.12.1. Signed-off-by: Daniel Baumann --- library/Icinga/Chart/Primitive/Animatable.php | 43 ++++ library/Icinga/Chart/Primitive/Animation.php | 87 ++++++++ library/Icinga/Chart/Primitive/Canvas.php | 140 ++++++++++++ library/Icinga/Chart/Primitive/Circle.php | 84 +++++++ library/Icinga/Chart/Primitive/Drawable.php | 22 ++ library/Icinga/Chart/Primitive/Line.php | 103 +++++++++ library/Icinga/Chart/Primitive/Path.php | 187 ++++++++++++++++ library/Icinga/Chart/Primitive/PieSlice.php | 307 ++++++++++++++++++++++++++ library/Icinga/Chart/Primitive/RawElement.php | 43 ++++ library/Icinga/Chart/Primitive/Rect.php | 119 ++++++++++ library/Icinga/Chart/Primitive/Styleable.php | 161 ++++++++++++++ library/Icinga/Chart/Primitive/Text.php | 184 +++++++++++++++ 12 files changed, 1480 insertions(+) create mode 100644 library/Icinga/Chart/Primitive/Animatable.php create mode 100644 library/Icinga/Chart/Primitive/Animation.php create mode 100644 library/Icinga/Chart/Primitive/Canvas.php create mode 100644 library/Icinga/Chart/Primitive/Circle.php create mode 100644 library/Icinga/Chart/Primitive/Drawable.php create mode 100644 library/Icinga/Chart/Primitive/Line.php create mode 100644 library/Icinga/Chart/Primitive/Path.php create mode 100644 library/Icinga/Chart/Primitive/PieSlice.php create mode 100644 library/Icinga/Chart/Primitive/RawElement.php create mode 100644 library/Icinga/Chart/Primitive/Rect.php create mode 100644 library/Icinga/Chart/Primitive/Styleable.php create mode 100644 library/Icinga/Chart/Primitive/Text.php (limited to 'library/Icinga/Chart/Primitive') diff --git a/library/Icinga/Chart/Primitive/Animatable.php b/library/Icinga/Chart/Primitive/Animatable.php new file mode 100644 index 0000000..69ba0e1 --- /dev/null +++ b/library/Icinga/Chart/Primitive/Animatable.php @@ -0,0 +1,43 @@ +animation = $anim; + } + + /** + * Append the animation to the given element + * + * @param DOMElement $dom The element to append the animation to + * @param RenderContext $ctx The context to use for rendering the animation object + */ + protected function appendAnimation(DOMElement $dom, RenderContext $ctx) + { + if ($this->animation) { + $dom->appendChild($this->animation->toSvg($ctx)); + } + } +} diff --git a/library/Icinga/Chart/Primitive/Animation.php b/library/Icinga/Chart/Primitive/Animation.php new file mode 100644 index 0000000..e620fa7 --- /dev/null +++ b/library/Icinga/Chart/Primitive/Animation.php @@ -0,0 +1,87 @@ +attribute = $attribute; + $this->from = $from; + $this->to = $to; + $this->duration = $duration; + $this->begin = $begin; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + + $animate = $ctx->getDocument()->createElement('animate'); + $animate->setAttribute('attributeName', $this->attribute); + $animate->setAttribute('attributeType', 'XML'); + $animate->setAttribute('from', $this->from); + $animate->setAttribute('to', $this->to); + $animate->setAttribute('begin', $this->begin . 's'); + $animate->setAttribute('dur', $this->duration . 's'); + $animate->setAttribute('fill', "freeze"); + + return $animate; + } +} diff --git a/library/Icinga/Chart/Primitive/Canvas.php b/library/Icinga/Chart/Primitive/Canvas.php new file mode 100644 index 0000000..32f06bf --- /dev/null +++ b/library/Icinga/Chart/Primitive/Canvas.php @@ -0,0 +1,140 @@ +rect = $rect; + $this->name = $name; + } + + /** + * Convert this canvas to a clipPath element + */ + public function toClipPath() + { + $this->isClipPath = true; + } + + /** + * Return the layout of this canvas + * + * @return LayoutBox + */ + public function getLayout() + { + return $this->rect; + } + + /** + * Add an element to this canvas + * + * @param Drawable $child + */ + public function addElement(Drawable $child) + { + $this->children[] = $child; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $doc = $ctx->getDocument(); + if ($this->isClipPath) { + $outer = $doc->createElement('defs'); + $innerContainer = $element = $doc->createElement('clipPath'); + $outer->appendChild($element); + } else { + $outer = $element = $doc->createElement('g'); + $innerContainer = $doc->createElement('g'); + $innerContainer->setAttribute('x', 0); + $innerContainer->setAttribute('y', 0); + $innerContainer->setAttribute('id', $this->name . '_inner'); + $innerContainer->setAttribute('transform', $this->rect->getInnerTransform($ctx)); + $element->appendChild($innerContainer); + } + + $element->setAttribute('id', $this->name); + foreach ($this->children as $child) { + $innerContainer->appendChild($child->toSvg($ctx)); + } + + if (isset($this->ariaRole)) { + $outer->setAttribute('role', $this->ariaRole); + } + return $outer; + } + + /** + * Set the aria role used to determine the meaning of this canvas in the accessibility tree + * + * The role 'presentation' will indicate that the purpose of this canvas is entirely decorative, while the role + * 'img' will indicate that the canvas contains an image, with a possible title or a description. For other + * possible roles, see http://www.w3.org/TR/wai-aria/roles + * + * @param $role string The aria role to set + */ + public function setAriaRole($role) + { + $this->ariaRole = $role; + } +} diff --git a/library/Icinga/Chart/Primitive/Circle.php b/library/Icinga/Chart/Primitive/Circle.php new file mode 100644 index 0000000..f98ffac --- /dev/null +++ b/library/Icinga/Chart/Primitive/Circle.php @@ -0,0 +1,84 @@ +x = $x; + $this->y = $y; + $this->radius = $radius; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $coords = $ctx->toAbsolute($this->x, $this->y); + $circle = $ctx->getDocument()->createElement('circle'); + $circle->setAttribute('cx', Format::formatSVGNumber($coords[0])); + $circle->setAttribute('cy', Format::formatSVGNumber($coords[1])); + $circle->setAttribute('r', $this->radius); + + $id = $this->id ?? uniqid('circle-'); + $circle->setAttribute('id', $id); + $this->setId($id); + + $this->applyAttributes($circle); + + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $circle->appendChild( + $circle->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + return $circle; + } +} diff --git a/library/Icinga/Chart/Primitive/Drawable.php b/library/Icinga/Chart/Primitive/Drawable.php new file mode 100644 index 0000000..5b4355c --- /dev/null +++ b/library/Icinga/Chart/Primitive/Drawable.php @@ -0,0 +1,22 @@ +xStart = $x1; + $this->xEnd = $x2; + $this->yStart = $y1; + $this->yEnd = $y2; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $doc = $ctx->getDocument(); + list($x1, $y1) = $ctx->toAbsolute($this->xStart, $this->yStart); + list($x2, $y2) = $ctx->toAbsolute($this->xEnd, $this->yEnd); + $line = $doc->createElement('line'); + $line->setAttribute('x1', Format::formatSVGNumber($x1)); + $line->setAttribute('x2', Format::formatSVGNumber($x2)); + $line->setAttribute('y1', Format::formatSVGNumber($y1)); + $line->setAttribute('y2', Format::formatSVGNumber($y2)); + + $id = $this->id ?? uniqid('line-'); + $line->setAttribute('id', $id); + $this->setId($id); + + $this->applyAttributes($line); + + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $line->appendChild( + $line->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + return $line; + } +} diff --git a/library/Icinga/Chart/Primitive/Path.php b/library/Icinga/Chart/Primitive/Path.php new file mode 100644 index 0000000..b9d5f7b --- /dev/null +++ b/library/Icinga/Chart/Primitive/Path.php @@ -0,0 +1,187 @@ +append($points); + } + + /** + * Append a single point or an array of points to this path + * + * @param array $points Either a single [x, y] point or an array of x, y points + * + * @return $this Fluid interface + */ + public function append(array $points) + { + if (count($points) === 0) { + return $this; + } + if (!is_array($points[0])) { + $points = array($points); + } + $this->points = array_merge($this->points, $points); + return $this; + } + + /** + * Prepend a single point or an array of points to this path + * + * @param array $points Either a single [x, y] point or an array of x, y points + * + * @return $this Fluid interface + */ + public function prepend(array $points) + { + if (count($points) === 0) { + return $this; + } + if (!is_array($points[0])) { + $points = array($points); + } + $this->points = array_merge($points, $this->points); + return $this; + } + + /** + * Set this path to be discrete + * + * @param boolean $bool True to draw discrete or false to draw straight lines between points + * + * @return $this Fluid interface + */ + public function setDiscrete($bool) + { + $this->discrete = $bool; + return $this; + } + + /** + * Mark this path as containing absolute coordinates + * + * @return $this Fluid interface + */ + public function toAbsolute() + { + $this->isAbsolute = true; + return $this; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $doc = $ctx->getDocument(); + $group = $doc->createElement('g'); + + $pathDescription = ''; + $tpl = self::TPL_MOVE; + $lastPoint = null; + foreach ($this->points as $point) { + if (!$this->isAbsolute) { + $point = $ctx->toAbsolute($point[0], $point[1]); + } + $point[0] = Format::formatSVGNumber($point[0]); + $point[1] = Format::formatSVGNumber($point[1]); + if ($lastPoint && $this->discrete) { + $pathDescription .= sprintf($tpl, $point[0], $lastPoint[1]); + } + $pathDescription .= vsprintf($tpl, $point); + $lastPoint = $point; + $tpl = self::TPL_STRAIGHT; + } + + $path = $doc->createElement('path'); + + $id = $this->id ?? uniqid('path-'); + $path->setAttribute('id', $id); + $this->setId($id); + + $path->setAttribute('d', $pathDescription); + + $this->applyAttributes($path); + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $path->appendChild( + $path->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + $group->appendChild($path); + return $group; + } +} diff --git a/library/Icinga/Chart/Primitive/PieSlice.php b/library/Icinga/Chart/Primitive/PieSlice.php new file mode 100644 index 0000000..f898435 --- /dev/null +++ b/library/Icinga/Chart/Primitive/PieSlice.php @@ -0,0 +1,307 @@ +x = $this->y = $this->radius = $radius; + + $this->startRadian = M_PI * $percentStart/50; + $this->endRadian = M_PI * ($percent + $percentStart)/50; + } + + /** + * Create the path for the pie slice + * + * @param int $x The x position of the pie slice + * @param int $y The y position of the pie slice + * @param int $r The absolute radius of the pie slice + * + * @return string A SVG path string + */ + private function getPieSlicePath($x, $y, $r) + { + // The coordinate system is mirrored on the Y axis, so we have to flip cos and sin + $xStart = $x + ($r * sin($this->startRadian)); + $yStart = $y - ($r * cos($this->startRadian)); + + if ($this->endRadian - $this->startRadian == 2*M_PI) { + // To draw a full circle, adjust arc endpoint by a small (unvisible) value + $this->endRadian -= 0.001; + $pathString = 'M ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart); + } else { + // Start at the center of the pieslice + $pathString = 'M ' . $x . ' ' . $y; + // Draw a straight line to the upper part of the arc + $pathString .= ' L ' . Format::formatSVGNumber($xStart) . ' ' . Format::formatSVGNumber($yStart); + } + + // Instead of directly connecting the upper part of the arc (leaving a triangle), draw a bow with the radius + $pathString .= ' A ' . Format::formatSVGNumber($r) . ' ' . Format::formatSVGNumber($r); + // These are the flags for the bow, see the SVG path documentation for details + // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + $pathString .= ' 0 ' . (($this->endRadian - $this->startRadian > M_PI) ? '1' : '0 ') . ' 1'; + + // xEnd and yEnd are the lower point of the arc + $xEnd = $x + ($r * sin($this->endRadian)); + $yEnd = $y - ($r * cos($this->endRadian)); + $pathString .= ' ' . Format::formatSVGNumber($xEnd) . ' ' . Format::formatSVGNumber($yEnd); + + return $pathString; + } + + /** + * Draw the label handler and the text for this pie slice + * + * @param RenderContext $ctx The rendering context to use for coordinate translation + * @param int $r The radius of the pie in absolute coordinates + * + * @return DOMElement The group DOMElement containing the handle and label + */ + private function drawDescriptionLabel(RenderContext $ctx, $r) + { + $group = $ctx->getDocument()->createElement('g'); + $rOuter = ($ctx->xToAbsolute($this->outerCaptionBound) + $ctx->yToAbsolute($this->outerCaptionBound)) / 2; + $addOffset = $rOuter - $r ; + if ($addOffset < 0) { + $addOffset = 0; + } + list($x, $y) = $ctx->toAbsolute($this->x, $this->y); + $midRadius = $this->startRadian + ($this->endRadian - $this->startRadian) / 2; + list($offsetX, $offsetY) = $ctx->toAbsolute($this->captionOffset, $this->captionOffset); + + $midX = $x + intval(($offsetX + $r)/2 * sin($midRadius)); + $midY = $y - intval(($offsetY + $r)/2 * cos($midRadius)); + + // Draw the handle + $path = new Path(array($midX, $midY)); + + $midX += ($addOffset + $r/3) * ($midRadius > M_PI ? -1 : 1); + $path->append(array($midX, $midY))->toAbsolute(); + + $midX += intval($r/2 * sin(M_PI/9)) * ($midRadius > M_PI ? -1 : 1); + $midY -= intval($r/2 * cos(M_PI/3)) * ($midRadius < M_PI*1.4 && $midRadius > M_PI/3 ? -1 : 1); + + if ($ctx->yToRelative($midY) > 100) { + $midY = $ctx->yToAbsolute(100); + } elseif ($ctx->yToRelative($midY) < 0) { + $midY = $ctx->yToAbsolute($ctx->yToRelative(100+$midY)); + } + + $path->append(array($midX , $midY)); + $rel = $ctx->toRelative($midX, $midY); + + // Draw the text box + $text = new Text($rel[0]+1.5, $rel[1], $this->caption); + $text->setFontSize('5em'); + $text->setAlignment(($midRadius > M_PI ? Text::ALIGN_END : Text::ALIGN_START)); + + $group->appendChild($path->toSvg($ctx)); + $group->appendChild($text->toSvg($ctx)); + + return $group; + } + + /** + * Set the x position of the pie slice + * + * @param int $x The new x position + * + * @return $this Fluid interface + */ + public function setX($x) + { + $this->x = $x; + return $this; + } + + /** + * Set the y position of the pie slice + * + * @param int $y The new y position + * + * @return $this Fluid interface + */ + public function setY($y) + { + $this->y = $y; + return $this; + } + + /** + * Set a root element to be used for drawing labels + * + * @param DOMElement $group The label group + * + * @return $this Fluid interface + */ + public function setLabelGroup(DOMElement $group) + { + $this->labelGroup = $group; + return $this; + } + + /** + * Set the caption for this label + * + * @param string $caption The caption for this element + * + * @return $this Fluid interface + */ + public function setCaption($caption) + { + $this->caption = $caption; + return $this; + } + + /** + * Set the internal offset of the caption handle + * + * @param int $offset The offset for the caption handle + * + * @return $this Fluid interface + */ + public function setCaptionOffset($offset) + { + $this->captionOffset = $offset; + return $this; + } + + /** + * Set the minimum radius to be used for drawing labels + * + * @param int $bound The offset for the caption text + * + * @return $this Fluid interface + */ + public function setOuterCaptionBound($bound) + { + $this->outerCaptionBound = $bound; + return $this; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $doc = $ctx->getDocument(); + $group = $doc->createElement('g'); + $r = ($ctx->xToAbsolute($this->radius) + $ctx->yToAbsolute($this->radius)) / 2; + list($x, $y) = $ctx->toAbsolute($this->x, $this->y); + + $slicePath = $doc->createElement('path'); + + $slicePath->setAttribute('d', $this->getPieSlicePath($x, $y, $r)); + $slicePath->setAttribute('data-icinga-graph-type', 'pieslice'); + + $id = $this->id ?? uniqid('slice-'); + $slicePath->setAttribute('id', $id); + $this->setId($id); + + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $slicePath->appendChild( + $slicePath->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + $this->applyAttributes($slicePath); + $group->appendChild($slicePath); + if ($this->caption != "") { + $lblGroup = ($this->labelGroup ? $this->labelGroup : $group); + $lblGroup->appendChild($this->drawDescriptionLabel($ctx, $r)); + } + return $group; + } +} diff --git a/library/Icinga/Chart/Primitive/RawElement.php b/library/Icinga/Chart/Primitive/RawElement.php new file mode 100644 index 0000000..721b6e0 --- /dev/null +++ b/library/Icinga/Chart/Primitive/RawElement.php @@ -0,0 +1,43 @@ +domEl = $el; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + return $this->domEl; + } +} diff --git a/library/Icinga/Chart/Primitive/Rect.php b/library/Icinga/Chart/Primitive/Rect.php new file mode 100644 index 0000000..0c0835f --- /dev/null +++ b/library/Icinga/Chart/Primitive/Rect.php @@ -0,0 +1,119 @@ +x = $x; + $this->y = $y; + $this->width = $width; + $this->height = $height; + } + + /** + * Call to let the rectangle keep the ratio + */ + public function keepRatio() + { + $this->keepRatio = true; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + $doc = $ctx->getDocument(); + $rect = $doc->createElement('rect'); + + list($x, $y) = $ctx->toAbsolute($this->x, $this->y); + if ($this->keepRatio) { + $ctx->keepRatio(); + } + list($width, $height) = $ctx->toAbsolute($this->width, $this->height); + if ($this->keepRatio) { + $ctx->ignoreRatio(); + } + $rect->setAttribute('x', Format::formatSVGNumber($x)); + $rect->setAttribute('y', Format::formatSVGNumber($y)); + $rect->setAttribute('width', Format::formatSVGNumber($width)); + $rect->setAttribute('height', Format::formatSVGNumber($height)); + + $id = $this->id ?? uniqid('rect-'); + $rect->setAttribute('id', $id); + $this->setId($id); + + $this->applyAttributes($rect); + $this->appendAnimation($rect, $ctx); + + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $rect->appendChild( + $rect->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + return $rect; + } +} diff --git a/library/Icinga/Chart/Primitive/Styleable.php b/library/Icinga/Chart/Primitive/Styleable.php new file mode 100644 index 0000000..15025bf --- /dev/null +++ b/library/Icinga/Chart/Primitive/Styleable.php @@ -0,0 +1,161 @@ + + */ + public $additionalStyle = []; + + /** + * The id of this element + * + * @var ?string + */ + public $id = null; + + /** + * Additional attributes to be set + * + * @var array + */ + public $attributes = array(); + + /** + * Set the stroke width for this drawable + * + * @param int|float $width The stroke with unit + * + * @return $this Fluid interface + */ + public function setStrokeWidth($width) + { + $this->strokeWidth = $width; + return $this; + } + + /** + * Set the color for the stroke or none for no stroke + * + * @param string $color The color to set for the stroke + * + * @return $this Fluid interface + */ + public function setStrokeColor($color) + { + $this->strokeColor = $color ? $color : 'none'; + return $this; + } + + /** + * Set additional styles for this drawable + * + * @param array $styles The styles to set additionally + * + * @return $this Fluid interface + */ + public function setAdditionalStyle($styles) + { + $this->additionalStyle = $styles; + return $this; + } + + /** + * Set the fill for this styleable + * + * @param string $color The color to use for filling or null to use no fill + * + * @return $this Fluid interface + */ + public function setFill($color = null) + { + $this->fill = $color ? $color : 'none'; + return $this; + } + + /** + * Set the id for this element + * + * @param string $id The id to set for this element + * + * @return $this Fluid interface + */ + public function setId($id) + { + $this->id = $id; + + return $this; + } + + /** + * Return the ruleset used for styling the DOMNode + * + * @return Style A ruleset containing styles + */ + public function getStyle() + { + $styles = $this->additionalStyle; + $styles['fill'] = $this->fill; + $styles['stroke'] = $this->strokeColor; + $styles['stroke-width'] = (string) $this->strokeWidth; + + return (new Style()) + ->setNonce(Csp::getStyleNonce()) + ->add("#$this->id", $styles); + } + + /** + * Add an additional attribute to this element + */ + public function setAttribute($key, $value) + { + $this->attributes[$key] = $value; + } + + /** + * Apply attribute to a DOMElement + * + * @param DOMElement $el Element to apply attributes + */ + protected function applyAttributes(DOMElement $el) + { + foreach ($this->attributes as $name => $value) { + $el->setAttribute($name, $value); + } + } +} diff --git a/library/Icinga/Chart/Primitive/Text.php b/library/Icinga/Chart/Primitive/Text.php new file mode 100644 index 0000000..f6bf365 --- /dev/null +++ b/library/Icinga/Chart/Primitive/Text.php @@ -0,0 +1,184 @@ +x = $x; + $this->y = $y; + $this->text = $text; + $this->fontSize = $fontSize; + + $this->setAdditionalStyle([ + 'font-size' => $this->fontSize, + 'font-family' => 'Ubuntu, Calibri, Trebuchet MS, Helvetica, Verdana, sans-serif', + 'font-weight' => $this->fontWeight, + 'font-stretch' => $this->fontStretch, + 'font-style' => 'normal', + 'text-anchor' => $this->alignment + ]); + } + + /** + * Set the font size of the svg text element + * + * @param string $size The font size including a unit + * + * @return $this Fluid interface + */ + public function setFontSize($size) + { + $this->fontSize = $size; + return $this; + } + + /** + * Set the text alignment with one of the ALIGN_* constants + * + * @param String $align Value how to align + * + * @return $this Fluid interface + */ + public function setAlignment($align) + { + $this->alignment = $align; + return $this; + } + + /** + * Set the weight of the current font + * + * @param string $weight The weight of the string + * + * @return $this Fluid interface + */ + public function setFontWeight($weight) + { + $this->fontWeight = $weight; + return $this; + } + + /** + * Create the SVG representation from this Drawable + * + * @param RenderContext $ctx The context to use for rendering + * + * @return DOMElement The SVG Element + */ + public function toSvg(RenderContext $ctx) + { + list($x, $y) = $ctx->toAbsolute($this->x, $this->y); + $text = $ctx->getDocument()->createElement('text'); + $text->setAttribute('x', Format::formatSVGNumber($x - 15)); + + $id = $this->id ?? uniqid('text-'); + $text->setAttribute('id', $id); + $this->setId($id); + + $text->setAttribute('y', Format::formatSVGNumber($y)); + $text->appendChild(new DOMText($this->text)); + + $style = new DOMDocument(); + $style->loadHTML($this->getStyle()); + + $text->appendChild( + $text->ownerDocument->importNode( + $style->getElementsByTagName('style')->item(0), + true + ) + ); + + return $text; + } +} -- cgit v1.2.3