*/ private array $elements = []; public function __construct( private readonly Dimensions $dimensions, private readonly ?ViewBox $viewBox = null, private ?string $title = null, private ?string $description = null, private readonly ?FileStorage $fileStorage = null ) { } /** * Add rectangle to canvas */ public function rect( Position $position, Dimensions $dimensions, Fill $fill, ?Stroke $stroke = null, ?Radius $rx = null, ?Radius $ry = null, ?Transform $transform = null, ?string $id = null, ?string $class = null ): self { $this->elements[] = new RectElement( $position, $dimensions, $fill, $stroke, $rx, $ry, $transform, $id, $class ); return $this; } /** * Add circle to canvas */ public function circle( Position $center, Radius $radius, Fill $fill, ?Stroke $stroke = null, ?Transform $transform = null, ?string $id = null, ?string $class = null ): self { $this->elements[] = new CircleElement( $center, $radius, $fill, $stroke, $transform, $id, $class ); return $this; } /** * Add line to canvas */ public function line( Position $start, Position $end, Stroke $stroke, ?Transform $transform = null, ?string $id = null, ?string $class = null ): self { $this->elements[] = new LineElement( $start, $end, $stroke, $transform, $id, $class ); return $this; } /** * Add text to canvas */ public function text( string $content, Position $position, TextStyle $style, ?Transform $transform = null, ?string $id = null, ?string $class = null ): self { $this->elements[] = new TextElement( new TextContent($content), $position, $style, $transform, $id, $class ); return $this; } /** * Add group to canvas */ public function group( array $children, ?Transform $transform = null, ?string $id = null, ?string $class = null ): self { $this->elements[] = new GroupElement( $children, $transform, $id, $class ); return $this; } /** * Add custom element to canvas */ public function element(SvgElement $element): self { $this->elements[] = $element; return $this; } /** * Set title for accessibility */ public function withTitle(string $title): self { $this->title = $title; return $this; } /** * Set description for accessibility */ public function withDescription(string $description): self { $this->description = $description; return $this; } /** * Get all elements */ public function getElements(): array { return $this->elements; } /** * Clear all elements */ public function clear(): self { $this->elements = []; return $this; } /** * Get element count */ public function count(): int { return count($this->elements); } /** * Render complete SVG document */ public function toSvg(): string { $svgAttributes = [ 'width' => (string) $this->dimensions->width, 'height' => (string) $this->dimensions->height, 'xmlns' => 'http://www.w3.org/2000/svg', ]; if ($this->viewBox !== null) { $svgAttributes['viewBox'] = $this->viewBox->toSvgAttribute(); } $attributeString = $this->buildAttributeString($svgAttributes); $svg = "\n"; $svg .= "\n"; // Add accessibility elements if ($this->title !== null) { $svg .= " " . htmlspecialchars($this->title, ENT_XML1, 'UTF-8') . "\n"; } if ($this->description !== null) { $svg .= " " . htmlspecialchars($this->description, ENT_XML1, 'UTF-8') . "\n"; } // Add all elements foreach ($this->elements as $element) { $svg .= " " . $element->toSvg() . "\n"; } $svg .= ""; return $svg; } /** * Render to inline SVG (without XML declaration) */ public function toInlineSvg(): string { $svg = $this->toSvg(); // Remove XML declaration return preg_replace('/<\?xml[^?]*\?>\s*/', '', $svg) ?? $svg; } /** * Export to file using Framework's FileStorage */ public function toFile(string $filepath): void { $storage = $this->fileStorage ?? new FileStorage(); $storage->put($filepath, $this->toSvg()); } private function buildAttributeString(array $attributes): string { $parts = []; foreach ($attributes as $key => $value) { $parts[] = sprintf('%s="%s"', $key, htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8')); } return implode(' ', $parts); } }