6TF9鱈o3GwY9"rwU7oSXȯImPӄf;2^vEgB=f}-qIz>r*YӼJp EX%0 {kZu4WP2Ӕ,] UXץa&eaa1=f˺ieǾ:%uΘ,m*1w7-V֠USхlm0N\VMp/F1uLZDPz ,Kbuѫ|j +6dM'J_C`b̧v^%DֆW%VMp/F1uLדkO7.A/=x69Znht;A90`䖨q{&/BsS? 3χpgQ?%ńX1] UXץa&eҁ'@1rQRNS,i7%°V#b(MG~F.9B?pPq=mjkN@p_˽ٞF2G#EM L*L;xӫ٩[7Nɗr")H;DwHheg%(*됻XNe~ OX^~WM!^=wTIMCDGcvjݭ]8I#e>",@ pNf&A#L1!;{y8GߊD2⮟{w2:2&;\ts] YIPU#O`C w`.L ljn]۔/ϼ Nd0SוߤSEu~,ڑs[$lW f1knl-xLڜ#(1cHηI1{_ŵ~9Z86MZWk]#[1'Mk"7`wv\I-TP}{uF cHc3Vsa=["Z޼uF4B:pIUL-4,˿6ێJ%aoEsܕedj=8 ˿.%vP_I\0xȚ?\yמ)ӕVaߧQ/M S4no* q iYظtEN + stDNoQn|7kq} ^r>1;F4<A89}/B|%WU0Z[R.S92/d%%N!2H ?NHf&藝>lRe'i39obzzdyd,h_ށa`1ÁZK0c=ũ9`-פĒLQ x \t?ਵҝ h"B@wX fEJMEUo\E }RQfnSgIٷ&Sa Et)׾Q}ffBZ6iq.CЩMQ2GOBIU.skV֛IK.T <.xbdO",)H4}C=|b:F nƑ)ڛ. $N@UnUim,~9+@E?;QmM- D>ɶU޲RƫQPg7tڂejGХȭ)+2GOBIU.skV֛IK.TȺq?EF{OcS+}J00"TnAۗ[4؀lF'>7NJEmiwk!=r@Y~)#p:g4~3GNC/wNnlzk͕)\Č}9=9[ҧfMaQ4p-wz<٧RQ}8phr%E.J>n~_ TR~ٚVJaV)H~#'xmlWriter) { throw new RuntimeException('No image has been started'); } for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) { $this->xmlWriter->endElement(); } array_pop($this->stack); --$this->currentStack; } public function drawPathWithColor(Path $path, ColorInterface $color) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $alpha = 1; if ($color instanceof Alpha) { $alpha = $color->getAlpha() / 100; } $this->startPathElement($path); $this->xmlWriter->writeAttribute('fill', $this->getColorString($color)); if ($alpha < 1) { $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); } $this->xmlWriter->endElement(); } public function drawPathWithGradient( Path $path, Gradient $gradient, float $x, float $y, float $width, float $height ) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height); $this->startPathElement($path); $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')'); $this->xmlWriter->endElement(); } public function done() : string { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } foreach ($this->stack as $openElements) { for ($i = $openElements; $i > 0; --$i) { $this->xmlWriter->endElement(); } } $this->xmlWriter->endDocument(); $blob = $this->xmlWriter->outputMemory(true); $this->xmlWriter = null; $this->stack = null; $this->currentStack = null; $this->gradientCount = null; return $blob; } private function startPathElement(Path $path) : void { $pathData = []; foreach ($path as $op) { switch (true) { case $op instanceof Move: $pathData[] = sprintf( 'M%s %s', round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof Line: $pathData[] = sprintf( 'L%s %s', round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof EllipticArc: $pathData[] = sprintf( 'A%s %s %s %u %u %s %s', round($op->getXRadius(), self::PRECISION), round($op->getYRadius(), self::PRECISION), round($op->getXAxisAngle(), self::PRECISION), $op->isLargeArc(), $op->isSweep(), round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof Curve: $pathData[] = sprintf( 'C%s %s %s %s %s %s', round($op->getX1(), self::PRECISION), round($op->getY1(), self::PRECISION), round($op->getX2(), self::PRECISION), round($op->getY2(), self::PRECISION), round($op->getX3(), self::PRECISION), round($op->getY3(), self::PRECISION) ); break; case $op instanceof Close: $pathData[] = 'Z'; break; default: throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); } } $this->xmlWriter->startElement('path'); $this->xmlWriter->writeAttribute('fill-rule', 'evenodd'); $this->xmlWriter->writeAttribute('d', implode('', $pathData)); } private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string { $this->xmlWriter->startElement('defs'); $startColor = $gradient->getStartColor(); $endColor = $gradient->getEndColor(); if ($gradient->getType() === GradientType::RADIAL()) { $this->xmlWriter->startElement('radialGradient'); } else { $this->xmlWriter->startElement('linearGradient'); } $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse'); switch ($gradient->getType()) { case GradientType::HORIZONTAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); break; case GradientType::VERTICAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); break; case GradientType::DIAGONAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); break; case GradientType::INVERSE_DIAGONAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); break; case GradientType::RADIAL(): $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION)); $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION)); $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION)); break; } $id = sprintf('g%d', ++$this->gradientCount); $this->xmlWriter->writeAttribute('id', $id); $this->xmlWriter->startElement('stop'); $this->xmlWriter->writeAttribute('offset', '0%'); $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor)); if ($startColor instanceof Alpha) { $this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha()); } $this->xmlWriter->endElement(); $this->xmlWriter->startElement('stop'); $this->xmlWriter->writeAttribute('offset', '100%'); $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor)); if ($endColor instanceof Alpha) { $this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha()); } $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); return $id; } private function getColorString(ColorInterface $color) : string { $color = $color->toRgb(); return sprintf( '#%02x%02x%02x', $color->getRed(), $color->getGreen(), $color->getBlue() ); } }