PKUZAQsrc/autoload.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ spl_autoload_register(function($class) { if (0 === strpos($class, "Svg")) { $file = str_replace('\\', DIRECTORY_SEPARATOR, $class); $file = realpath(__DIR__ . DIRECTORY_SEPARATOR . $file . '.php'); if (file_exists($file)) { include_once $file; } } });PKUZˮ`DDsrc/Svg/Tag/RadialGradient.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class RadialGradient extends AbstractTag { public function start($attributes) { } } PKUZ! src/Svg/Tag/UseTag.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class UseTag extends AbstractTag { protected $x = 0; protected $y = 0; protected $width; protected $height; /** @var AbstractTag */ protected $reference; protected function before($attributes) { if (isset($attributes['x'])) { $this->x = $attributes['x']; } if (isset($attributes['y'])) { $this->y = $attributes['y']; } if (isset($attributes['width'])) { $this->width = $attributes['width']; } if (isset($attributes['height'])) { $this->height = $attributes['height']; } parent::before($attributes); $document = $this->getDocument(); $link = $attributes["xlink:href"]; $this->reference = $document->getDef($link); if ($this->reference) { $this->reference->before($attributes); } $surface = $document->getSurface(); $surface->save(); $surface->translate($this->x, $this->y); } protected function after() { parent::after(); if ($this->reference) { $this->reference->after(); } $this->getDocument()->getSurface()->restore(); } public function handle($attributes) { parent::handle($attributes); if (!$this->reference) { return; } $attributes = array_merge($this->reference->attributes, $attributes); $this->reference->handle($attributes); foreach ($this->reference->children as $_child) { $_attributes = array_merge($_child->attributes, $attributes); $_child->handle($_attributes); } } public function handleEnd() { parent::handleEnd(); if (!$this->reference) { return; } $this->reference->handleEnd(); foreach ($this->reference->children as $_child) { $_child->handleEnd(); } } } PKUZ<::src/Svg/Tag/Stop.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Stop extends AbstractTag { public function start($attributes) { } } PKUZ a6src/Svg/Tag/Shape.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Style; class Shape extends AbstractTag { protected function before($attributes) { $surface = $this->document->getSurface(); $surface->save(); $style = $this->makeStyle($attributes); $this->setStyle($style); $surface->setStyle($style); $this->applyTransform($attributes); } protected function after() { $surface = $this->document->getSurface(); if ($this->hasShape) { $style = $surface->getStyle(); $fill = $style->fill && is_array($style->fill); $stroke = $style->stroke && is_array($style->stroke); if ($fill) { if ($stroke) { $surface->fillStroke(); } else { // if (is_string($style->fill)) { // /** @var LinearGradient|RadialGradient $gradient */ // $gradient = $this->getDocument()->getDef($style->fill); // // var_dump($gradient->getStops()); // } $surface->fill(); } } elseif ($stroke) { $surface->stroke(); } else { $surface->endPath(); } } $surface->restore(); } } PKUZ9src/Svg/Tag/AbstractTag.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Document; use Svg\Style; abstract class AbstractTag { /** @var Document */ protected $document; public $tagName; /** @var Style */ protected $style; protected $attributes = array(); protected $hasShape = true; /** @var self[] */ protected $children = array(); public function __construct(Document $document, $tagName) { $this->document = $document; $this->tagName = $tagName; } public function getDocument(){ return $this->document; } /** * @return Group|null */ public function getParentGroup() { $stack = $this->getDocument()->getStack(); for ($i = count($stack)-2; $i >= 0; $i--) { $tag = $stack[$i]; if ($tag instanceof Group || $tag instanceof Document) { return $tag; } } return null; } public function handle($attributes) { $this->attributes = $attributes; if (!$this->getDocument()->inDefs) { $this->before($attributes); $this->start($attributes); } } public function handleEnd() { if (!$this->getDocument()->inDefs) { $this->end(); $this->after(); } } protected function before($attributes) { } protected function start($attributes) { } protected function end() { } protected function after() { } public function getAttributes() { return $this->attributes; } protected function setStyle(Style $style) { $this->style = $style; if ($style->display === "none") { $this->hasShape = false; } } /** * @return Style */ public function getStyle() { return $this->style; } /** * Make a style object from the tag and its attributes * * @param array $attributes * * @return Style */ protected function makeStyle($attributes) { $style = new Style(); $style->inherit($this); $style->fromStyleSheets($this, $attributes); $style->fromAttributes($attributes); return $style; } protected function applyTransform($attributes) { if (isset($attributes["transform"])) { $surface = $this->document->getSurface(); $transform = $attributes["transform"]; $match = array(); preg_match_all( '/(matrix|translate|scale|rotate|skewX|skewY)\((.*?)\)/is', $transform, $match, PREG_SET_ORDER ); $transformations = array(); if (count($match[0])) { foreach ($match as $_match) { $arguments = preg_split('/[ ,]+/', $_match[2]); array_unshift($arguments, $_match[1]); $transformations[] = $arguments; } } foreach ($transformations as $t) { switch ($t[0]) { case "matrix": $surface->transform($t[1], $t[2], $t[3], $t[4], $t[5], $t[6]); break; case "translate": $surface->translate($t[1], isset($t[2]) ? $t[2] : 0); break; case "scale": $surface->scale($t[1], isset($t[2]) ? $t[2] : $t[1]); break; case "rotate": if (isset($t[2])) { $t[3] = isset($t[3]) ? $t[3] : 0; $surface->translate($t[2], $t[3]); $surface->rotate($t[1]); $surface->translate(-$t[2], -$t[3]); } else { $surface->rotate($t[1]); } break; case "skewX": $surface->skewX($t[1]); break; case "skewY": $surface->skewY($t[1]); break; } } } } } PKUZpsrc/Svg/Tag/Polygon.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Polygon extends Shape { public function start($attributes) { $tmp = array(); preg_match_all('/([\-]*[0-9\.]+)/', $attributes['points'], $tmp); $points = $tmp[0]; $count = count($points); $surface = $this->document->getSurface(); list($x, $y) = $points; $surface->moveTo($x, $y); for ($i = 2; $i < $count; $i += 2) { $x = $points[$i]; $y = $points[$i + 1]; $surface->lineTo($x, $y); } $surface->closePath(); } } PKUZ3:src/Svg/Tag/ClipPath.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Style; class ClipPath extends AbstractTag { protected function before($attributes) { $surface = $this->document->getSurface(); $surface->save(); $style = $this->makeStyle($attributes); $this->setStyle($style); $surface->setStyle($style); $this->applyTransform($attributes); } protected function after() { $this->document->getSurface()->restore(); } } PKUZx1 ttsrc/Svg/Tag/Text.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Text extends Shape { protected $x = 0; protected $y = 0; protected $text = ""; public function start($attributes) { $document = $this->document; $height = $this->document->getHeight(); $this->y = $height; if (isset($attributes['x'])) { $this->x = $attributes['x']; } if (isset($attributes['y'])) { $this->y = $height - $attributes['y']; } $document->getSurface()->transform(1, 0, 0, -1, 0, $height); } public function end() { $surface = $this->document->getSurface(); $x = $this->x; $y = $this->y; $style = $surface->getStyle(); $surface->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight); switch ($style->textAnchor) { case "middle": $width = $surface->measureText($this->text); $x -= $width / 2; break; case "end": $width = $surface->measureText($this->text); $x -= $width; break; } $surface->fillText($this->getText(), $x, $y); } protected function after() { $this->document->getSurface()->restore(); } public function appendText($text) { $this->text .= $text; } public function getText() { return trim($this->text); } } PKUZ=`src/Svg/Tag/Image.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Image extends AbstractTag { protected $x = 0; protected $y = 0; protected $width = 0; protected $height = 0; protected $href = null; protected function before($attributes) { parent::before($attributes); $surface = $this->document->getSurface(); $surface->save(); $this->applyTransform($attributes); } public function start($attributes) { $document = $this->document; $height = $this->document->getHeight(); $this->y = $height; if (isset($attributes['x'])) { $this->x = $attributes['x']; } if (isset($attributes['y'])) { $this->y = $height - $attributes['y']; } if (isset($attributes['width'])) { $this->width = $attributes['width']; } if (isset($attributes['height'])) { $this->height = $attributes['height']; } if (isset($attributes['xlink:href'])) { $this->href = $attributes['xlink:href']; } $document->getSurface()->transform(1, 0, 0, -1, 0, $height); $document->getSurface()->drawImage($this->href, $this->x, $this->y, $this->width, $this->height); } protected function after() { $this->document->getSurface()->restore(); } } PKUZKsrc/Svg/Tag/Circle.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Circle extends Shape { protected $cx = 0; protected $cy = 0; protected $r; public function start($attributes) { if (isset($attributes['cx'])) { $this->cx = $attributes['cx']; } if (isset($attributes['cy'])) { $this->cy = $attributes['cy']; } if (isset($attributes['r'])) { $this->r = $attributes['r']; } $this->document->getSurface()->circle($this->cx, $this->cy, $this->r); } } PKUZW WWsrc/Svg/Tag/Rect.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Rect extends Shape { protected $x = 0; protected $y = 0; protected $width = 0; protected $height = 0; protected $rx = 0; protected $ry = 0; public function start($attributes) { if (isset($attributes['x'])) { $this->x = $attributes['x']; } if (isset($attributes['y'])) { $this->y = $attributes['y']; } if (isset($attributes['width'])) { if ('%' === substr($attributes['width'], -1)) { $factor = substr($attributes['width'], 0, -1) / 100; $this->width = $this->document->getWidth() * $factor; } else { $this->width = $attributes['width']; } } if (isset($attributes['height'])) { if ('%' === substr($attributes['height'], -1)) { $factor = substr($attributes['height'], 0, -1) / 100; $this->height = $this->document->getHeight() * $factor; } else { $this->height = $attributes['height']; } } if (isset($attributes['rx'])) { $this->rx = $attributes['rx']; } if (isset($attributes['ry'])) { $this->ry = $attributes['ry']; } $this->document->getSurface()->rect($this->x, $this->y, $this->width, $this->height, $this->rx, $this->ry); } } PKUZ*Tsrc/Svg/Tag/Group.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Style; class Group extends AbstractTag { protected function before($attributes) { $surface = $this->document->getSurface(); $surface->save(); $style = $this->makeStyle($attributes); $this->setStyle($style); $surface->setStyle($style); $this->applyTransform($attributes); } protected function after() { $this->document->getSurface()->restore(); } } PKUZg>  src/Svg/Tag/LinearGradient.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Gradient; use Svg\Style; class LinearGradient extends AbstractTag { protected $x1; protected $y1; protected $x2; protected $y2; /** @var Gradient\Stop[] */ protected $stops = array(); public function start($attributes) { parent::start($attributes); if (isset($attributes['x1'])) { $this->x1 = $attributes['x1']; } if (isset($attributes['y1'])) { $this->y1 = $attributes['y1']; } if (isset($attributes['x2'])) { $this->x2 = $attributes['x2']; } if (isset($attributes['y2'])) { $this->y2 = $attributes['y2']; } } public function getStops() { if (empty($this->stops)) { foreach ($this->children as $_child) { if ($_child->tagName != "stop") { continue; } $_stop = new Gradient\Stop(); $_attributes = $_child->attributes; // Style if (isset($_attributes["style"])) { $_style = Style::parseCssStyle($_attributes["style"]); if (isset($_style["stop-color"])) { $_stop->color = Style::parseColor($_style["stop-color"]); } if (isset($_style["stop-opacity"])) { $_stop->opacity = max(0, min(1.0, $_style["stop-opacity"])); } } // Attributes if (isset($_attributes["offset"])) { $_stop->offset = $_attributes["offset"]; } if (isset($_attributes["stop-color"])) { $_stop->color = Style::parseColor($_attributes["stop-color"]); } if (isset($_attributes["stop-opacity"])) { $_stop->opacity = max(0, min(1.0, $_attributes["stop-opacity"])); } $this->stops[] = $_stop; } } return $this->stops; } } PKUZosrc/Svg/Tag/Ellipse.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Ellipse extends Shape { protected $cx = 0; protected $cy = 0; protected $rx = 0; protected $ry = 0; public function start($attributes) { parent::start($attributes); if (isset($attributes['cx'])) { $this->cx = $attributes['cx']; } if (isset($attributes['cy'])) { $this->cy = $attributes['cy']; } if (isset($attributes['rx'])) { $this->rx = $attributes['rx']; } if (isset($attributes['ry'])) { $this->ry = $attributes['ry']; } $this->document->getSurface()->ellipse($this->cx, $this->cy, $this->rx, $this->ry, 0, 0, 360, false); } } PKUZzsrc/Svg/Tag/Anchor.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Anchor extends Group { } PKUZ0?ؔNNsrc/Svg/Tag/Path.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Svg\Surface\SurfaceInterface; class Path extends Shape { // kindly borrowed from fabric.util.parsePath. /* @see https://github.com/fabricjs/fabric.js/blob/master/src/util/path.js#L664 */ const NUMBER_PATTERN = '([-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)\s*'; const COMMA_PATTERN = '(?:\s+,?\s*|,\s*)?'; const FLAG_PATTERN = '([01])'; const ARC_REGEXP = '/' . self::NUMBER_PATTERN . self::COMMA_PATTERN . self::NUMBER_PATTERN . self::COMMA_PATTERN . self::NUMBER_PATTERN . self::COMMA_PATTERN . self::FLAG_PATTERN . self::COMMA_PATTERN . self::FLAG_PATTERN . self::COMMA_PATTERN . self::NUMBER_PATTERN . self::COMMA_PATTERN . self::NUMBER_PATTERN . '/'; static $commandLengths = array( 'm' => 2, 'l' => 2, 'h' => 1, 'v' => 1, 'c' => 6, 's' => 4, 'q' => 4, 't' => 2, 'a' => 7, ); static $repeatedCommands = array( 'm' => 'l', 'M' => 'L', ); public static function parse(string $commandSequence): array { $commands = array(); preg_match_all('/([MZLHVCSQTAmzlhvcsqta])([eE ,\-.\d]+)*/', $commandSequence, $commands, PREG_SET_ORDER); $path = array(); foreach ($commands as $c) { if (count($c) == 3) { $commandLower = strtolower($c[1]); // arcs have special flags that apparently don't require spaces. if ($commandLower === 'a' && preg_match_all(static::ARC_REGEXP, $c[2], $matches)) { $numberOfMatches = count($matches[0]); for ($k = 0; $k < $numberOfMatches; ++$k) { $path[] = [ $c[1], $matches[1][$k], $matches[2][$k], $matches[3][$k], $matches[4][$k], $matches[5][$k], $matches[6][$k], $matches[7][$k], ]; } continue; } $arguments = array(); preg_match_all('/([-+]?((\d+\.\d+)|((\d+)|(\.\d+)))(?:e[-+]?\d+)?)/i', $c[2], $arguments, PREG_PATTERN_ORDER); $item = $arguments[0]; if ( isset(self::$commandLengths[$commandLower]) && ($commandLength = self::$commandLengths[$commandLower]) && count($item) > $commandLength ) { $repeatedCommand = isset(self::$repeatedCommands[$c[1]]) ? self::$repeatedCommands[$c[1]] : $c[1]; $command = $c[1]; for ($k = 0, $klen = count($item); $k < $klen; $k += $commandLength) { $_item = array_slice($item, $k, $k + $commandLength); array_unshift($_item, $command); $path[] = $_item; $command = $repeatedCommand; } } else { array_unshift($item, $c[1]); $path[] = $item; } } else { $item = array($c[1]); $path[] = $item; } } return $path; } public function start($attributes) { if (!isset($attributes['d'])) { $this->hasShape = false; return; } $path = static::parse($attributes['d']); $surface = $this->document->getSurface(); // From https://github.com/kangax/fabric.js/blob/master/src/shapes/path.class.js $current = null; // current instruction $previous = null; $subpathStartX = 0; $subpathStartY = 0; $x = 0; // current x $y = 0; // current y $controlX = 0; // current control point x $controlY = 0; // current control point y $tempX = null; $tempY = null; $tempControlX = null; $tempControlY = null; $l = 0; //-((this.width / 2) + $this.pathOffset.x), $t = 0; //-((this.height / 2) + $this.pathOffset.y), $methodName = null; foreach ($path as $current) { switch ($current[0]) { // first letter case 'l': // lineto, relative $x += $current[1]; $y += $current[2]; $surface->lineTo($x + $l, $y + $t); break; case 'L': // lineto, absolute $x = $current[1]; $y = $current[2]; $surface->lineTo($x + $l, $y + $t); break; case 'h': // horizontal lineto, relative $x += $current[1]; $surface->lineTo($x + $l, $y + $t); break; case 'H': // horizontal lineto, absolute $x = $current[1]; $surface->lineTo($x + $l, $y + $t); break; case 'v': // vertical lineto, relative $y += $current[1]; $surface->lineTo($x + $l, $y + $t); break; case 'V': // verical lineto, absolute $y = $current[1]; $surface->lineTo($x + $l, $y + $t); break; case 'm': // moveTo, relative $x += $current[1]; $y += $current[2]; $subpathStartX = $x; $subpathStartY = $y; $surface->moveTo($x + $l, $y + $t); break; case 'M': // moveTo, absolute $x = $current[1]; $y = $current[2]; $subpathStartX = $x; $subpathStartY = $y; $surface->moveTo($x + $l, $y + $t); break; case 'c': // bezierCurveTo, relative $tempX = $x + $current[5]; $tempY = $y + $current[6]; $controlX = $x + $current[3]; $controlY = $y + $current[4]; $surface->bezierCurveTo( $x + $current[1] + $l, // x1 $y + $current[2] + $t, // y1 $controlX + $l, // x2 $controlY + $t, // y2 $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; break; case 'C': // bezierCurveTo, absolute $x = $current[5]; $y = $current[6]; $controlX = $current[3]; $controlY = $current[4]; $surface->bezierCurveTo( $current[1] + $l, $current[2] + $t, $controlX + $l, $controlY + $t, $x + $l, $y + $t ); break; case 's': // shorthand cubic bezierCurveTo, relative // transform to absolute x,y $tempX = $x + $current[3]; $tempY = $y + $current[4]; if (!preg_match('/[CcSs]/', $previous[0])) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point $controlX = $x; $controlY = $y; } else { // calculate reflection of previous control points $controlX = 2 * $x - $controlX; $controlY = 2 * $y - $controlY; } $surface->bezierCurveTo( $controlX + $l, $controlY + $t, $x + $current[1] + $l, $y + $current[2] + $t, $tempX + $l, $tempY + $t ); // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." $controlX = $x + $current[1]; $controlY = $y + $current[2]; $x = $tempX; $y = $tempY; break; case 'S': // shorthand cubic bezierCurveTo, absolute $tempX = $current[3]; $tempY = $current[4]; if (!preg_match('/[CcSs]/', $previous[0])) { // If there is no previous command or if the previous command was not a C, c, S, or s, // the control point is coincident with the current point $controlX = $x; $controlY = $y; } else { // calculate reflection of previous control points $controlX = 2 * $x - $controlX; $controlY = 2 * $y - $controlY; } $surface->bezierCurveTo( $controlX + $l, $controlY + $t, $current[1] + $l, $current[2] + $t, $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; // set control point to 2nd one of this command // "... the first control point is assumed to be // the reflection of the second control point on // the previous command relative to the current point." $controlX = $current[1]; $controlY = $current[2]; break; case 'q': // quadraticCurveTo, relative // transform to absolute x,y $tempX = $x + $current[3]; $tempY = $y + $current[4]; $controlX = $x + $current[1]; $controlY = $y + $current[2]; $surface->quadraticCurveTo( $controlX + $l, $controlY + $t, $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; break; case 'Q': // quadraticCurveTo, absolute $tempX = $current[3]; $tempY = $current[4]; $surface->quadraticCurveTo( $current[1] + $l, $current[2] + $t, $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; $controlX = $current[1]; $controlY = $current[2]; break; case 't': // shorthand quadraticCurveTo, relative // transform to absolute x,y $tempX = $x + $current[1]; $tempY = $y + $current[2]; if (preg_match("/[QqTt]/", $previous[0])) { // If there is no previous command or if the previous command was not a Q, q, T or t, // assume the control point is coincident with the current point $controlX = $x; $controlY = $y; } else { if ($previous[0] === 't') { // calculate reflection of previous control points for t $controlX = 2 * $x - $tempControlX; $controlY = 2 * $y - $tempControlY; } else { if ($previous[0] === 'q') { // calculate reflection of previous control points for q $controlX = 2 * $x - $controlX; $controlY = 2 * $y - $controlY; } } } $tempControlX = $controlX; $tempControlY = $controlY; $surface->quadraticCurveTo( $controlX + $l, $controlY + $t, $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; $controlX = $x + $current[1]; $controlY = $y + $current[2]; break; case 'T': $tempX = $current[1]; $tempY = $current[2]; // calculate reflection of previous control points $controlX = 2 * $x - $controlX; $controlY = 2 * $y - $controlY; $surface->quadraticCurveTo( $controlX + $l, $controlY + $t, $tempX + $l, $tempY + $t ); $x = $tempX; $y = $tempY; break; case 'a': // TODO: optimize this $this->drawArc( $surface, $x + $l, $y + $t, array( $current[1], $current[2], $current[3], $current[4], $current[5], $current[6] + $x + $l, $current[7] + $y + $t ) ); $x += $current[6]; $y += $current[7]; break; case 'A': // TODO: optimize this $this->drawArc( $surface, $x + $l, $y + $t, array( $current[1], $current[2], $current[3], $current[4], $current[5], $current[6] + $l, $current[7] + $t ) ); $x = $current[6]; $y = $current[7]; break; case 'z': case 'Z': $x = $subpathStartX; $y = $subpathStartY; $surface->closePath(); break; } $previous = $current; } } function drawArc(SurfaceInterface $surface, $fx, $fy, $coords) { $rx = $coords[0]; $ry = $coords[1]; $rot = $coords[2]; $large = $coords[3]; $sweep = $coords[4]; $tx = $coords[5]; $ty = $coords[6]; $segs = array( array(), array(), array(), array(), ); $segsNorm = $this->arcToSegments($tx - $fx, $ty - $fy, $rx, $ry, $large, $sweep, $rot); for ($i = 0, $len = count($segsNorm); $i < $len; $i++) { $segs[$i][0] = $segsNorm[$i][0] + $fx; $segs[$i][1] = $segsNorm[$i][1] + $fy; $segs[$i][2] = $segsNorm[$i][2] + $fx; $segs[$i][3] = $segsNorm[$i][3] + $fy; $segs[$i][4] = $segsNorm[$i][4] + $fx; $segs[$i][5] = $segsNorm[$i][5] + $fy; call_user_func_array(array($surface, "bezierCurveTo"), $segs[$i]); } } function arcToSegments($toX, $toY, $rx, $ry, $large, $sweep, $rotateX) { $th = $rotateX * M_PI / 180; $sinTh = sin($th); $cosTh = cos($th); $fromX = 0; $fromY = 0; $rx = abs($rx); $ry = abs($ry); $px = -$cosTh * $toX * 0.5 - $sinTh * $toY * 0.5; $py = -$cosTh * $toY * 0.5 + $sinTh * $toX * 0.5; $rx2 = $rx * $rx; $ry2 = $ry * $ry; $py2 = $py * $py; $px2 = $px * $px; $pl = $rx2 * $ry2 - $rx2 * $py2 - $ry2 * $px2; $root = 0; if ($pl < 0) { $s = sqrt(1 - $pl / ($rx2 * $ry2)); $rx *= $s; $ry *= $s; } else { $root = ($large == $sweep ? -1.0 : 1.0) * sqrt($pl / ($rx2 * $py2 + $ry2 * $px2)); } $cx = $root * $rx * $py / $ry; $cy = -$root * $ry * $px / $rx; $cx1 = $cosTh * $cx - $sinTh * $cy + $toX * 0.5; $cy1 = $sinTh * $cx + $cosTh * $cy + $toY * 0.5; $mTheta = $this->calcVectorAngle(1, 0, ($px - $cx) / $rx, ($py - $cy) / $ry); $dtheta = $this->calcVectorAngle(($px - $cx) / $rx, ($py - $cy) / $ry, (-$px - $cx) / $rx, (-$py - $cy) / $ry); if ($sweep == 0 && $dtheta > 0) { $dtheta -= 2 * M_PI; } else { if ($sweep == 1 && $dtheta < 0) { $dtheta += 2 * M_PI; } } // $Convert $into $cubic $bezier $segments <= 90deg $segments = ceil(abs($dtheta / M_PI * 2)); $result = array(); $mDelta = $dtheta / $segments; $mT = 8 / 3 * sin($mDelta / 4) * sin($mDelta / 4) / sin($mDelta / 2); $th3 = $mTheta + $mDelta; for ($i = 0; $i < $segments; $i++) { $result[$i] = $this->segmentToBezier( $mTheta, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY ); $fromX = $result[$i][4]; $fromY = $result[$i][5]; $mTheta = $th3; $th3 += $mDelta; } return $result; } function segmentToBezier($th2, $th3, $cosTh, $sinTh, $rx, $ry, $cx1, $cy1, $mT, $fromX, $fromY) { $costh2 = cos($th2); $sinth2 = sin($th2); $costh3 = cos($th3); $sinth3 = sin($th3); $toX = $cosTh * $rx * $costh3 - $sinTh * $ry * $sinth3 + $cx1; $toY = $sinTh * $rx * $costh3 + $cosTh * $ry * $sinth3 + $cy1; $cp1X = $fromX + $mT * (-$cosTh * $rx * $sinth2 - $sinTh * $ry * $costh2); $cp1Y = $fromY + $mT * (-$sinTh * $rx * $sinth2 + $cosTh * $ry * $costh2); $cp2X = $toX + $mT * ($cosTh * $rx * $sinth3 + $sinTh * $ry * $costh3); $cp2Y = $toY + $mT * ($sinTh * $rx * $sinth3 - $cosTh * $ry * $costh3); return array( $cp1X, $cp1Y, $cp2X, $cp2Y, $toX, $toY ); } function calcVectorAngle($ux, $uy, $vx, $vy) { $ta = atan2($uy, $ux); $tb = atan2($vy, $vx); if ($tb >= $ta) { return $tb - $ta; } else { return 2 * M_PI - ($ta - $tb); } } } PKUZ)bDsrc/Svg/Tag/Line.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Line extends Shape { protected $x1 = 0; protected $y1 = 0; protected $x2 = 0; protected $y2 = 0; public function start($attributes) { if (isset($attributes['x1'])) { $this->x1 = $attributes['x1']; } if (isset($attributes['y1'])) { $this->y1 = $attributes['y1']; } if (isset($attributes['x2'])) { $this->x2 = $attributes['x2']; } if (isset($attributes['y2'])) { $this->y2 = $attributes['y2']; } $surface = $this->document->getSurface(); $surface->moveTo($this->x1, $this->y1); $surface->lineTo($this->x2, $this->y2); } } PKUZisrc/Svg/Tag/Polyline.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; class Polyline extends Shape { public function start($attributes) { $tmp = array(); preg_match_all('/([\-]*[0-9\.]+)/', $attributes['points'], $tmp); $points = $tmp[0]; $count = count($points); $surface = $this->document->getSurface(); list($x, $y) = $points; $surface->moveTo($x, $y); for ($i = 2; $i < $count; $i += 2) { $x = $points[$i]; $y = $points[$i + 1]; $surface->lineTo($x, $y); } } } PKUZ=1csrc/Svg/Tag/StyleTag.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Tag; use Sabberworm\CSS; class StyleTag extends AbstractTag { protected $text = ""; public function end() { $parser = new CSS\Parser($this->text); $this->document->appendStyleSheet($parser->parse()); } public function appendText($text) { $this->text .= $text; } } PKUZM(88src/Svg/Gradient/Stop.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Gradient; class Stop { public $offset; public $color; public $opacity = 1.0; } PKUZ\S%%src/Svg/Document.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg; use Svg\Surface\SurfaceInterface; use Svg\Tag\AbstractTag; use Svg\Tag\Anchor; use Svg\Tag\Circle; use Svg\Tag\Ellipse; use Svg\Tag\Group; use Svg\Tag\ClipPath; use Svg\Tag\Image; use Svg\Tag\Line; use Svg\Tag\LinearGradient; use Svg\Tag\Path; use Svg\Tag\Polygon; use Svg\Tag\Polyline; use Svg\Tag\Rect; use Svg\Tag\Stop; use Svg\Tag\Text; use Svg\Tag\StyleTag; use Svg\Tag\UseTag; class Document extends AbstractTag { protected $filename; public $inDefs = false; protected $x; protected $y; protected $width; protected $height; protected $subPathInit; protected $pathBBox; protected $viewBox; /** @var SurfaceInterface */ protected $surface; /** @var AbstractTag[] */ protected $stack = array(); /** @var AbstractTag[] */ protected $defs = array(); /** @var \Sabberworm\CSS\CSSList\Document[] */ protected $styleSheets = array(); public function loadFile($filename) { $this->filename = $filename; } protected function initParser() { $parser = xml_parser_create("utf-8"); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); xml_set_element_handler( $parser, array($this, "_tagStart"), array($this, "_tagEnd") ); xml_set_character_data_handler( $parser, array($this, "_charData") ); return $parser; } public function __construct() { } /** * @return SurfaceInterface */ public function getSurface() { return $this->surface; } public function getStack() { return $this->stack; } public function getWidth() { return $this->width; } public function getHeight() { return $this->height; } public function getDimensions() { $rootAttributes = null; $parser = xml_parser_create("utf-8"); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false); xml_set_element_handler( $parser, function ($parser, $name, $attributes) use (&$rootAttributes) { if ($name === "svg" && $rootAttributes === null) { $attributes = array_change_key_case($attributes, CASE_LOWER); $rootAttributes = $attributes; } }, function ($parser, $name) {} ); $fp = fopen($this->filename, "r"); while ($line = fread($fp, 8192)) { xml_parse($parser, $line, false); if ($rootAttributes !== null) { break; } } xml_parser_free($parser); return $this->handleSizeAttributes($rootAttributes); } public function handleSizeAttributes($attributes){ if ($this->width === null) { if (isset($attributes["width"])) { $width = Style::convertSize($attributes["width"], 400); $this->width = $width; } if (isset($attributes["height"])) { $height = Style::convertSize($attributes["height"], 300); $this->height = $height; } if (isset($attributes['viewbox'])) { $viewBox = preg_split('/[\s,]+/is', trim($attributes['viewbox'])); if (count($viewBox) == 4) { $this->x = $viewBox[0]; $this->y = $viewBox[1]; if (!$this->width) { $this->width = $viewBox[2]; } if (!$this->height) { $this->height = $viewBox[3]; } } } } return array( 0 => $this->width, 1 => $this->height, "width" => $this->width, "height" => $this->height, ); } public function getDocument(){ return $this; } /** * Append a style sheet * * @param \Sabberworm\CSS\CSSList\Document $stylesheet */ public function appendStyleSheet($stylesheet) { $this->styleSheets[] = $stylesheet; } /** * Get the document style sheets * * @return \Sabberworm\CSS\CSSList\Document[] */ public function getStyleSheets() { return $this->styleSheets; } protected function before($attributes) { $surface = $this->getSurface(); $style = new DefaultStyle(); $style->inherit($this); $style->fromAttributes($attributes); $this->setStyle($style); $surface->setStyle($style); } public function render(SurfaceInterface $surface) { $this->inDefs = false; $this->surface = $surface; $parser = $this->initParser(); if ($this->x || $this->y) { $surface->translate(-$this->x, -$this->y); } $fp = fopen($this->filename, "r"); while ($line = fread($fp, 8192)) { xml_parse($parser, $line, false); } xml_parse($parser, "", true); xml_parser_free($parser); } protected function svgOffset($attributes) { $this->attributes = $attributes; $this->handleSizeAttributes($attributes); } public function getDef($id) { $id = ltrim($id, "#"); return isset($this->defs[$id]) ? $this->defs[$id] : null; } private function _tagStart($parser, $name, $attributes) { $this->x = 0; $this->y = 0; $tag = null; $attributes = array_change_key_case($attributes, CASE_LOWER); switch (strtolower($name)) { case 'defs': $this->inDefs = true; return; case 'svg': if (count($this->attributes)) { $tag = new Group($this, $name); } else { $tag = $this; $this->svgOffset($attributes); } break; case 'path': $tag = new Path($this, $name); break; case 'rect': $tag = new Rect($this, $name); break; case 'circle': $tag = new Circle($this, $name); break; case 'ellipse': $tag = new Ellipse($this, $name); break; case 'image': $tag = new Image($this, $name); break; case 'line': $tag = new Line($this, $name); break; case 'polyline': $tag = new Polyline($this, $name); break; case 'polygon': $tag = new Polygon($this, $name); break; case 'lineargradient': $tag = new LinearGradient($this, $name); break; case 'radialgradient': $tag = new LinearGradient($this, $name); break; case 'stop': $tag = new Stop($this, $name); break; case 'style': $tag = new StyleTag($this, $name); break; case 'a': $tag = new Anchor($this, $name); break; case 'g': case 'symbol': $tag = new Group($this, $name); break; case 'clippath': $tag = new ClipPath($this, $name); break; case 'use': $tag = new UseTag($this, $name); break; case 'text': $tag = new Text($this, $name); break; case 'desc': return; } if ($tag) { if (isset($attributes["id"])) { $this->defs[$attributes["id"]] = $tag; } else { /** @var AbstractTag $top */ $top = end($this->stack); if ($top && $top != $tag) { $top->children[] = $tag; } } $this->stack[] = $tag; $tag->handle($attributes); } } function _charData($parser, $data) { $stack_top = end($this->stack); if ($stack_top instanceof Text || $stack_top instanceof StyleTag) { $stack_top->appendText($data); } } function _tagEnd($parser, $name) { /** @var AbstractTag $tag */ $tag = null; switch (strtolower($name)) { case 'defs': $this->inDefs = false; return; case 'svg': case 'path': case 'rect': case 'circle': case 'ellipse': case 'image': case 'line': case 'polyline': case 'polygon': case 'radialgradient': case 'lineargradient': case 'stop': case 'style': case 'text': case 'g': case 'symbol': case 'clippath': case 'use': case 'a': $tag = array_pop($this->stack); break; } if (!$this->inDefs && $tag) { $tag->handleEnd(); } } } PKUZ7src/Svg/DefaultStyle.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg; class DefaultStyle extends Style { public $color = ''; public $opacity = 1.0; public $display = 'inline'; public $fill = 'black'; public $fillOpacity = 1.0; public $fillRule = 'nonzero'; public $stroke = 'none'; public $strokeOpacity = 1.0; public $strokeLinecap = 'butt'; public $strokeLinejoin = 'miter'; public $strokeMiterlimit = 4; public $strokeWidth = 1.0; public $strokeDasharray = 0; public $strokeDashoffset = 0; } PKUZ~RR$src/Svg/Surface/SurfaceInterface.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Surface; use Svg\Style; /** * Interface Surface, like CanvasRenderingContext2D * * @package Svg */ interface SurfaceInterface { public function save(); public function restore(); // transformations (default transform is the identity matrix) public function scale($x, $y); public function rotate($angle); public function translate($x, $y); public function transform($a, $b, $c, $d, $e, $f); // path ends public function beginPath(); public function closePath(); public function fill(); public function stroke(); public function endPath(); public function fillStroke(); public function clip(); // text (see also the CanvasDrawingStyles interface) public function fillText($text, $x, $y, $maxWidth = null); public function strokeText($text, $x, $y, $maxWidth = null); public function measureText($text); // drawing images public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null); // paths public function lineTo($x, $y); public function moveTo($x, $y); public function quadraticCurveTo($cpx, $cpy, $x, $y); public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y); public function arcTo($x1, $y1, $x2, $y2, $radius); public function circle($x, $y, $radius); public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false); public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise); // Rectangle public function rect($x, $y, $w, $h, $rx = 0, $ry = 0); public function fillRect($x, $y, $w, $h); public function strokeRect($x, $y, $w, $h); public function setStyle(Style $style); /** * @return Style */ public function getStyle(); public function setFont($family, $style, $weight); } PKUZ"NN"src/Svg/Surface/SurfaceGmagick.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Surface; use Svg\Style; class SurfaceGmagick implements SurfaceInterface { const DEBUG = false; /** @var \GmagickDraw */ private $canvas; private $width; private $height; /** @var Style */ private $style; public function __construct($w, $h) { if (self::DEBUG) { echo __FUNCTION__ . "\n"; } $this->width = $w; $this->height = $h; $canvas = new \GmagickDraw(); $this->canvas = $canvas; } function out() { if (self::DEBUG) { echo __FUNCTION__ . "\n"; } $image = new \Gmagick(); $image->newimage($this->width, $this->height); $image->drawimage($this->canvas); $tmp = tempnam(sys_get_temp_dir(), "gm"); $image->write($tmp); return file_get_contents($tmp); } public function save() { if (self::DEBUG) { echo __FUNCTION__ . "\n"; } $this->canvas->save(); } public function restore() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->restore(); } public function scale($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->scale($x, $y); } public function rotate($angle) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->rotate($angle); } public function translate($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->translate($x, $y); } public function transform($a, $b, $c, $d, $e, $f) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->concat($a, $b, $c, $d, $e, $f); } public function beginPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement beginPath() method. } public function closePath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->closepath(); } public function fillStroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fill_stroke(); } public function clip() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->clip(); } public function fillText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->set_text_pos($x, $y); $this->canvas->show($text); } public function strokeText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement drawImage() method. } public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; if (strpos($image, "data:") === 0) { $data = substr($image, strpos($image, ";") + 1); if (strpos($data, "base64") === 0) { $data = base64_decode(substr($data, 7)); } $image = tempnam(sys_get_temp_dir(), "svg"); file_put_contents($image, $data); } $img = $this->canvas->load_image("auto", $image, ""); $sy = $sy - $sh; $this->canvas->fit_image($img, $sx, $sy, 'boxsize={' . "$sw $sh" . '} fitmethod=entire'); } public function lineTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->lineto($x, $y); } public function moveTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->moveto($x, $y); } public function quadraticCurveTo($cpx, $cpy, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement quadraticCurveTo() method. } public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->curveto($cp1x, $cp1y, $cp2x, $cp2y, $x, $y); } public function arcTo($x1, $y1, $x2, $y2, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; } public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->arc($x, $y, $radius, $startAngle, $endAngle); } public function circle($x, $y, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->circle($x, $y, $radius); } public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->ellipse($x, $y, $radiusX, $radiusY); } public function fillRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->fill(); } public function rect($x, $y, $w, $h, $rx = 0, $ry = 0) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->rect($x, $y, $w, $h); } public function fill() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fill(); } public function strokeRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->stroke(); } public function stroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->stroke(); } public function endPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; //$this->canvas->endPath(); } public function measureText($text) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $style = $this->getStyle(); $font = $this->getFont($style->fontFamily, $style->fontStyle); return $this->canvas->stringwidth($text, $font, $this->getStyle()->fontSize); } public function getStyle() { if (self::DEBUG) echo __FUNCTION__ . "\n"; return $this->style; } public function setStyle(Style $style) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->style = $style; $canvas = $this->canvas; if (is_array($style->stroke) && $stroke = $style->stroke) { $canvas->setcolor("stroke", "rgb", $stroke[0] / 255, $stroke[1] / 255, $stroke[2] / 255, null); } if (is_array($style->fill) && $fill = $style->fill) { // $canvas->setcolor("fill", "rgb", $fill[0] / 255, $fill[1] / 255, $fill[2] / 255, null); } $opts = array(); if ($style->strokeWidth > 0.000001) { $opts[] = "linewidth=$style->strokeWidth"; } if (in_array($style->strokeLinecap, array("butt", "round", "projecting"))) { $opts[] = "linecap=$style->strokeLinecap"; } if (in_array($style->strokeLinejoin, array("miter", "round", "bevel"))) { $opts[] = "linejoin=$style->strokeLinejoin"; } $canvas->set_graphics_option(implode(" ", $opts)); $font = $this->getFont($style->fontFamily, $style->fontStyle); $canvas->setfont($font, $style->fontSize); } private function getFont($family, $style) { $map = array( "serif" => "Times", "sans-serif" => "Helvetica", "fantasy" => "Symbol", "cursive" => "serif", "monospance" => "Courier", ); $family = strtolower($family); if (isset($map[$family])) { $family = $map[$family]; } return $this->canvas->load_font($family, "unicode", "fontstyle=$style"); } public function setFont($family, $style, $weight) { // TODO: Implement setFont() method. } } PKUZ==--!src/Svg/Surface/SurfacePDFLib.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Surface; use Svg\Style; use Svg\Document; class SurfacePDFLib implements SurfaceInterface { const DEBUG = false; private $canvas; private $width; private $height; /** @var Style */ private $style; public function __construct(Document $doc, $canvas = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $dimensions = $doc->getDimensions(); $w = $dimensions["width"]; $h = $dimensions["height"]; if (!$canvas) { $canvas = new \PDFlib(); /* all strings are expected as utf8 */ $canvas->set_option("stringformat=utf8"); $canvas->set_option("errorpolicy=return"); /* open new PDF file; insert a file name to create the PDF on disk */ if ($canvas->begin_document("", "") == 0) { die("Error: " . $canvas->get_errmsg()); } $canvas->set_info("Creator", "PDFlib starter sample"); $canvas->set_info("Title", "starter_graphics"); $canvas->begin_page_ext($w, $h, ""); } // Flip PDF coordinate system so that the origin is in // the top left rather than the bottom left $canvas->setmatrix( 1, 0, 0, -1, 0, $h ); $this->width = $w; $this->height = $h; $this->canvas = $canvas; } function out() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->end_page_ext(""); $this->canvas->end_document(""); return $this->canvas->get_buffer(); } public function save() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->save(); } public function restore() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->restore(); } public function scale($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->scale($x, $y); } public function rotate($angle) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->rotate($angle); } public function translate($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->translate($x, $y); } public function transform($a, $b, $c, $d, $e, $f) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->concat($a, $b, $c, $d, $e, $f); } public function beginPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement beginPath() method. } public function closePath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->closepath(); } public function fillStroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fill_stroke(); } public function clip() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->clip(); } public function fillText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->set_text_pos($x, $y); $this->canvas->show($text); } public function strokeText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement drawImage() method. } public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; if (strpos($image, "data:") === 0) { $data = substr($image, strpos($image, ";") + 1); if (strpos($data, "base64") === 0) { $data = base64_decode(substr($data, 7)); } } else { $data = file_get_contents($image); } $image = tempnam(sys_get_temp_dir(), "svg"); file_put_contents($image, $data); $img = $this->canvas->load_image("auto", $image, ""); $sy = $sy - $sh; $this->canvas->fit_image($img, $sx, $sy, 'boxsize={' . "$sw $sh" . '} fitmethod=entire'); unlink($image); } public function lineTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->lineto($x, $y); } public function moveTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->moveto($x, $y); } public function quadraticCurveTo($cpx, $cpy, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; // FIXME not accurate $this->canvas->curveTo($cpx, $cpy, $cpx, $cpy, $x, $y); } public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->curveto($cp1x, $cp1y, $cp2x, $cp2y, $x, $y); } public function arcTo($x1, $y1, $x2, $y2, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; } public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->arc($x, $y, $radius, $startAngle, $endAngle); } public function circle($x, $y, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->circle($x, $y, $radius); } public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->ellipse($x, $y, $radiusX, $radiusY); } public function fillRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->fill(); } public function rect($x, $y, $w, $h, $rx = 0, $ry = 0) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $canvas = $this->canvas; if ($rx <= 0.000001/* && $ry <= 0.000001*/) { $canvas->rect($x, $y, $w, $h); return; } /* Define a path for a rectangle with corners rounded by a given radius. * Start from the lower left corner and proceed counterclockwise. */ $canvas->moveto($x + $rx, $y); /* Start of the arc segment in the lower right corner */ $canvas->lineto($x + $w - $rx, $y); /* Arc segment in the lower right corner */ $canvas->arc($x + $w - $rx, $y + $rx, $rx, 270, 360); /* Start of the arc segment in the upper right corner */ $canvas->lineto($x + $w, $y + $h - $rx ); /* Arc segment in the upper right corner */ $canvas->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90); /* Start of the arc segment in the upper left corner */ $canvas->lineto($x + $rx, $y + $h); /* Arc segment in the upper left corner */ $canvas->arc($x + $rx, $y + $h - $rx, $rx, 90, 180); /* Start of the arc segment in the lower left corner */ $canvas->lineto($x , $y + $rx); /* Arc segment in the lower left corner */ $canvas->arc($x + $rx, $y + $rx, $rx, 180, 270); } public function fill() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fill(); } public function strokeRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->stroke(); } public function stroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->stroke(); } public function endPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->endPath(); } public function measureText($text) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $style = $this->getStyle(); $font = $this->getFont($style->fontFamily, $style->fontStyle); return $this->canvas->stringwidth($text, $font, $this->getStyle()->fontSize); } public function getStyle() { if (self::DEBUG) echo __FUNCTION__ . "\n"; return $this->style; } public function setStyle(Style $style) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->style = $style; $canvas = $this->canvas; if ($stroke = $style->stroke && is_array($style->stroke)) { $canvas->setcolor( "stroke", "rgb", $stroke[0] / 255, $stroke[1] / 255, $stroke[2] / 255, null ); } if ($fill = $style->fill && is_array($style->fill)) { $canvas->setcolor( "fill", "rgb", $fill[0] / 255, $fill[1] / 255, $fill[2] / 255, null ); } if ($fillRule = strtolower($style->fillRule)) { $map = array( "nonzero" => "winding", "evenodd" => "evenodd", ); if (isset($map[$fillRule])) { $fillRule = $map[$fillRule]; $canvas->set_parameter("fillrule", $fillRule); } } $opts = array(); if ($style->strokeWidth > 0.000001) { $opts[] = "linewidth=$style->strokeWidth"; } if (in_array($style->strokeLinecap, array("butt", "round", "projecting"))) { $opts[] = "linecap=$style->strokeLinecap"; } if (in_array($style->strokeLinejoin, array("miter", "round", "bevel"))) { $opts[] = "linejoin=$style->strokeLinejoin"; } $canvas->set_graphics_option(implode(" ", $opts)); $opts = array(); $opacity = $style->opacity; if ($opacity !== null && $opacity < 1.0) { $opts[] = "opacityfill=$opacity"; $opts[] = "opacitystroke=$opacity"; } else { $fillOpacity = $style->fillOpacity; if ($fillOpacity !== null && $fillOpacity < 1.0) { $opts[] = "opacityfill=$fillOpacity"; } $strokeOpacity = $style->strokeOpacity; if ($strokeOpacity !== null && $strokeOpacity < 1.0) { $opts[] = "opacitystroke=$strokeOpacity"; } } if (count($opts)) { $gs = $canvas->create_gstate(implode(" ", $opts)); $canvas->set_gstate($gs); } $font = $this->getFont($style->fontFamily, $style->fontStyle); if ($font) { $canvas->setfont($font, $style->fontSize); } } private function getFont($family, $style) { $map = array( "serif" => "Times", "sans-serif" => "Helvetica", "fantasy" => "Symbol", "cursive" => "Times", "monospace" => "Courier", "arial" => "Helvetica", "verdana" => "Helvetica", ); $family = strtolower($family); if (isset($map[$family])) { $family = $map[$family]; } return $this->canvas->load_font($family, "unicode", "fontstyle=$style"); } public function setFont($family, $style, $weight) { // TODO: Implement setFont() method. } } PKUZ,ppsrc/Svg/Surface/CPdf.phpnuW+A * @author Orion Richardson * @author Helmut Tischer * @author Ryan H. Masten * @author Brian Sweeney * @author Fabien Ménager * @license Public Domain http://creativecommons.org/licenses/publicdomain/ * @package Cpdf */ namespace Svg\Surface; class CPdf { /** * @var integer The current number of pdf objects in the document */ public $numObj = 0; /** * @var array This array contains all of the pdf objects, ready for final assembly */ public $objects = array(); /** * @var integer The objectId (number within the objects array) of the document catalog */ public $catalogId; /** * @var array Array carrying information about the fonts that the system currently knows about * Used to ensure that a font is not loaded twice, among other things */ public $fonts = array(); /** * @var string The default font metrics file to use if no other font has been loaded. * The path to the directory containing the font metrics should be included */ public $defaultFont = './fonts/Helvetica.afm'; /** * @string A record of the current font */ public $currentFont = ''; /** * @var string The current base font */ public $currentBaseFont = ''; /** * @var integer The number of the current font within the font array */ public $currentFontNum = 0; /** * @var integer */ public $currentNode; /** * @var integer Object number of the current page */ public $currentPage; /** * @var integer Object number of the currently active contents block */ public $currentContents; /** * @var integer Number of fonts within the system */ public $numFonts = 0; /** * @var integer Number of graphic state resources used */ private $numStates = 0; /** * @var array Current color for fill operations, defaults to inactive value, * all three components should be between 0 and 1 inclusive when active */ public $currentColor = null; /** * @var string Fill rule (nonzero or evenodd) */ public $fillRule = "nonzero"; /** * @var array Current color for stroke operations (lines etc.) */ public $currentStrokeColor = null; /** * @var string Current style that lines are drawn in */ public $currentLineStyle = ''; /** * @var array Current line transparency (partial graphics state) */ public $currentLineTransparency = array("mode" => "Normal", "opacity" => 1.0); /** * array Current fill transparency (partial graphics state) */ public $currentFillTransparency = array("mode" => "Normal", "opacity" => 1.0); /** * @var array An array which is used to save the state of the document, mainly the colors and styles * it is used to temporarily change to another state, the change back to what it was before */ public $stateStack = array(); /** * @var integer Number of elements within the state stack */ public $nStateStack = 0; /** * @var integer Number of page objects within the document */ public $numPages = 0; /** * @var array Object Id storage stack */ public $stack = array(); /** * @var integer Number of elements within the object Id storage stack */ public $nStack = 0; /** * an array which contains information about the objects which are not firmly attached to pages * these have been added with the addObject function */ public $looseObjects = array(); /** * array contains infomation about how the loose objects are to be added to the document */ public $addLooseObjects = array(); /** * @var integer The objectId of the information object for the document * this contains authorship, title etc. */ public $infoObject = 0; /** * @var integer Number of images being tracked within the document */ public $numImages = 0; /** * @var array An array containing options about the document * it defaults to turning on the compression of the objects */ public $options = array('compression' => true); /** * @var integer The objectId of the first page of the document */ public $firstPageId; /** * @var float Used to track the last used value of the inter-word spacing, this is so that it is known * when the spacing is changed. */ public $wordSpaceAdjust = 0; /** * @var float Used to track the last used value of the inter-letter spacing, this is so that it is known * when the spacing is changed. */ public $charSpaceAdjust = 0; /** * @var integer The object Id of the procset object */ public $procsetObjectId; /** * @var array Store the information about the relationship between font families * this used so that the code knows which font is the bold version of another font, etc. * the value of this array is initialised in the constructor function. */ public $fontFamilies = array(); /** * @var string Folder for php serialized formats of font metrics files. * If empty string, use same folder as original metrics files. * This can be passed in from class creator. * If this folder does not exist or is not writable, Cpdf will be **much** slower. * Because of potential trouble with php safe mode, folder cannot be created at runtime. */ public $fontcache = ''; /** * @var integer The version of the font metrics cache file. * This value must be manually incremented whenever the internal font data structure is modified. */ public $fontcacheVersion = 6; /** * @var string Temporary folder. * If empty string, will attempty system tmp folder. * This can be passed in from class creator. * Only used for conversion of gd images to jpeg images. */ public $tmp = ''; /** * @var string Track if the current font is bolded or italicised */ public $currentTextState = ''; /** * @var string Messages are stored here during processing, these can be selected afterwards to give some useful debug information */ public $messages = ''; /** * @var string The ancryption array for the document encryption is stored here */ public $arc4 = ''; /** * @var integer The object Id of the encryption information */ public $arc4_objnum = 0; /** * @var string The file identifier, used to uniquely identify a pdf document */ public $fileIdentifier = ''; /** * @var boolean A flag to say if a document is to be encrypted or not */ public $encrypted = false; /** * @var string The encryption key for the encryption of all the document content (structure is not encrypted) */ public $encryptionKey = ''; /** * @var array Array which forms a stack to keep track of nested callback functions */ public $callback = array(); /** * @var integer The number of callback functions in the callback array */ public $nCallback = 0; /** * @var array Store label->id pairs for named destinations, these will be used to replace internal links * done this way so that destinations can be defined after the location that links to them */ public $destinations = array(); /** * @var array Store the stack for the transaction commands, each item in here is a record of the values of all the * publiciables within the class, so that the user can rollback at will (from each 'start' command) * note that this includes the objects array, so these can be large. */ public $checkpoint = ''; /** * @var array Table of Image origin filenames and image labels which were already added with o_image(). * Allows to merge identical images */ public $imagelist = array(); /** * @var boolean Whether the text passed in should be treated as Unicode or just local character set. */ public $isUnicode = false; /** * @var string the JavaScript code of the document */ public $javascript = ''; /** * @var boolean whether the compression is possible */ protected $compressionReady = false; /** * @var array Current page size */ protected $currentPageSize = array("width" => 0, "height" => 0); /** * @var array All the chars that will be required in the font subsets */ protected $stringSubsets = array(); /** * @var string The target internal encoding */ static protected $targetEncoding = 'iso-8859-1'; /** * @var array The list of the core fonts */ static protected $coreFonts = array( 'courier', 'courier-bold', 'courier-oblique', 'courier-boldoblique', 'helvetica', 'helvetica-bold', 'helvetica-oblique', 'helvetica-boldoblique', 'times-roman', 'times-bold', 'times-italic', 'times-bolditalic', 'symbol', 'zapfdingbats' ); /** * Class constructor * This will start a new document * * @param array $pageSize Array of 4 numbers, defining the bottom left and upper right corner of the page. first two are normally zero. * @param boolean $isUnicode Whether text will be treated as Unicode or not. * @param string $fontcache The font cache folder * @param string $tmp The temporary folder */ function __construct($pageSize = array(0, 0, 612, 792), $isUnicode = false, $fontcache = '', $tmp = '') { $this->isUnicode = $isUnicode; $this->fontcache = $fontcache; $this->tmp = ($tmp === '') ? sys_get_temp_dir() : $tmp; $this->newDocument($pageSize); $this->compressionReady = function_exists('gzcompress'); if (in_array('Windows-1252', mb_list_encodings())) { self::$targetEncoding = 'Windows-1252'; } // also initialize the font families that are known about already $this->setFontFamily('init'); // $this->fileIdentifier = md5('xxxxxxxx'.time()); } /** * Document object methods (internal use only) * * There is about one object method for each type of object in the pdf document * Each function has the same call list ($id,$action,$options). * $id = the object ID of the object, or what it is to be if it is being created * $action = a string specifying the action to be performed, though ALL must support: * 'new' - create the object with the id $id * 'out' - produce the output for the pdf object * $options = optional, a string or array containing the various parameters for the object * * These, in conjunction with the output function are the ONLY way for output to be produced * within the pdf 'file'. */ /** * Destination object, used to specify the location for the user to jump to, presently on opening */ protected function o_destination($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'destination', 'info' => array()); $tmp = ''; switch ($options['type']) { case 'XYZ': case 'FitR': $tmp = ' ' . $options['p3'] . $tmp; case 'FitH': case 'FitV': case 'FitBH': case 'FitBV': $tmp = ' ' . $options['p1'] . ' ' . $options['p2'] . $tmp; case 'Fit': case 'FitB': $tmp = $options['type'] . $tmp; $this->objects[$id]['info']['string'] = $tmp; $this->objects[$id]['info']['page'] = $options['page']; } break; case 'out': $tmp = $o['info']; $res = "\n$id 0 obj\n" . '[' . $tmp['page'] . ' 0 R /' . $tmp['string'] . "]\nendobj"; return $res; } } /** * set the viewer preferences */ protected function o_viewerPreferences($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'viewerPreferences', 'info' => array()); break; case 'add': foreach ($options as $k => $v) { switch ($k) { case 'HideToolbar': case 'HideMenubar': case 'HideWindowUI': case 'FitWindow': case 'CenterWindow': case 'NonFullScreenPageMode': case 'Direction': $o['info'][$k] = $v; break; } } break; case 'out': $res = "\n$id 0 obj\n<< "; foreach ($o['info'] as $k => $v) { $res .= "\n/$k $v"; } $res .= "\n>>\n"; return $res; } } /** * define the document catalog, the overall controller for the document */ protected function o_catalog($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'catalog', 'info' => array()); $this->catalogId = $id; break; case 'outlines': case 'pages': case 'openHere': case 'javascript': $o['info'][$action] = $options; break; case 'viewerPreferences': if (!isset($o['info']['viewerPreferences'])) { $this->numObj++; $this->o_viewerPreferences($this->numObj, 'new'); $o['info']['viewerPreferences'] = $this->numObj; } $vp = $o['info']['viewerPreferences']; $this->o_viewerPreferences($vp, 'add', $options); break; case 'out': $res = "\n$id 0 obj\n<< /Type /Catalog"; foreach ($o['info'] as $k => $v) { switch ($k) { case 'outlines': $res .= "\n/Outlines $v 0 R"; break; case 'pages': $res .= "\n/Pages $v 0 R"; break; case 'viewerPreferences': $res .= "\n/ViewerPreferences $v 0 R"; break; case 'openHere': $res .= "\n/OpenAction $v 0 R"; break; case 'javascript': $res .= "\n/Names <>"; break; } } $res .= " >>\nendobj"; return $res; } } /** * object which is a parent to the pages in the document */ protected function o_pages($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'pages', 'info' => array()); $this->o_catalog($this->catalogId, 'pages', $id); break; case 'page': if (!is_array($options)) { // then it will just be the id of the new page $o['info']['pages'][] = $options; } else { // then it should be an array having 'id','rid','pos', where rid=the page to which this one will be placed relative // and pos is either 'before' or 'after', saying where this page will fit. if (isset($options['id']) && isset($options['rid']) && isset($options['pos'])) { $i = array_search($options['rid'], $o['info']['pages']); if (isset($o['info']['pages'][$i]) && $o['info']['pages'][$i] == $options['rid']) { // then there is a match // make a space switch ($options['pos']) { case 'before': $k = $i; break; case 'after': $k = $i + 1; break; default: $k = -1; break; } if ($k >= 0) { for ($j = count($o['info']['pages']) - 1; $j >= $k; $j--) { $o['info']['pages'][$j + 1] = $o['info']['pages'][$j]; } $o['info']['pages'][$k] = $options['id']; } } } } break; case 'procset': $o['info']['procset'] = $options; break; case 'mediaBox': $o['info']['mediaBox'] = $options; // which should be an array of 4 numbers $this->currentPageSize = array('width' => $options[2], 'height' => $options[3]); break; case 'font': $o['info']['fonts'][] = array('objNum' => $options['objNum'], 'fontNum' => $options['fontNum']); break; case 'extGState': $o['info']['extGStates'][] = array('objNum' => $options['objNum'], 'stateNum' => $options['stateNum']); break; case 'xObject': $o['info']['xObjects'][] = array('objNum' => $options['objNum'], 'label' => $options['label']); break; case 'out': if (count($o['info']['pages'])) { $res = "\n$id 0 obj\n<< /Type /Pages\n/Kids ["; foreach ($o['info']['pages'] as $v) { $res .= "$v 0 R\n"; } $res .= "]\n/Count " . count($this->objects[$id]['info']['pages']); if ((isset($o['info']['fonts']) && count($o['info']['fonts'])) || isset($o['info']['procset']) || (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) ) { $res .= "\n/Resources <<"; if (isset($o['info']['procset'])) { $res .= "\n/ProcSet " . $o['info']['procset'] . " 0 R"; } if (isset($o['info']['fonts']) && count($o['info']['fonts'])) { $res .= "\n/Font << "; foreach ($o['info']['fonts'] as $finfo) { $res .= "\n/F" . $finfo['fontNum'] . " " . $finfo['objNum'] . " 0 R"; } $res .= "\n>>"; } if (isset($o['info']['xObjects']) && count($o['info']['xObjects'])) { $res .= "\n/XObject << "; foreach ($o['info']['xObjects'] as $finfo) { $res .= "\n/" . $finfo['label'] . " " . $finfo['objNum'] . " 0 R"; } $res .= "\n>>"; } if (isset($o['info']['extGStates']) && count($o['info']['extGStates'])) { $res .= "\n/ExtGState << "; foreach ($o['info']['extGStates'] as $gstate) { $res .= "\n/GS" . $gstate['stateNum'] . " " . $gstate['objNum'] . " 0 R"; } $res .= "\n>>"; } $res .= "\n>>"; if (isset($o['info']['mediaBox'])) { $tmp = $o['info']['mediaBox']; $res .= "\n/MediaBox [" . sprintf( '%.3F %.3F %.3F %.3F', $tmp[0], $tmp[1], $tmp[2], $tmp[3] ) . ']'; } } $res .= "\n >>\nendobj"; } else { $res = "\n$id 0 obj\n<< /Type /Pages\n/Count 0\n>>\nendobj"; } return $res; } } /** * define the outlines in the doc, empty for now */ protected function o_outlines($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'outlines', 'info' => array('outlines' => array())); $this->o_catalog($this->catalogId, 'outlines', $id); break; case 'outline': $o['info']['outlines'][] = $options; break; case 'out': if (count($o['info']['outlines'])) { $res = "\n$id 0 obj\n<< /Type /Outlines /Kids ["; foreach ($o['info']['outlines'] as $v) { $res .= "$v 0 R "; } $res .= "] /Count " . count($o['info']['outlines']) . " >>\nendobj"; } else { $res = "\n$id 0 obj\n<< /Type /Outlines /Count 0 >>\nendobj"; } return $res; } } /** * an object to hold the font description */ protected function o_font($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array( 't' => 'font', 'info' => array( 'name' => $options['name'], 'fontFileName' => $options['fontFileName'], 'SubType' => 'Type1' ) ); $fontNum = $this->numFonts; $this->objects[$id]['info']['fontNum'] = $fontNum; // deal with the encoding and the differences if (isset($options['differences'])) { // then we'll need an encoding dictionary $this->numObj++; $this->o_fontEncoding($this->numObj, 'new', $options); $this->objects[$id]['info']['encodingDictionary'] = $this->numObj; } else { if (isset($options['encoding'])) { // we can specify encoding here switch ($options['encoding']) { case 'WinAnsiEncoding': case 'MacRomanEncoding': case 'MacExpertEncoding': $this->objects[$id]['info']['encoding'] = $options['encoding']; break; case 'none': break; default: $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; break; } } else { $this->objects[$id]['info']['encoding'] = 'WinAnsiEncoding'; } } if ($this->fonts[$options['fontFileName']]['isUnicode']) { // For Unicode fonts, we need to incorporate font data into // sub-sections that are linked from the primary font section. // Look at o_fontGIDtoCID and o_fontDescendentCID functions // for more informaiton. // // All of this code is adapted from the excellent changes made to // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) $toUnicodeId = ++$this->numObj; $this->o_contents($toUnicodeId, 'new', 'raw'); $this->objects[$id]['info']['toUnicode'] = $toUnicodeId; $stream = <<> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfrange <0000> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end EOT; $res = "<>\n"; $res .= "stream\n" . $stream . "endstream"; $this->objects[$toUnicodeId]['c'] = $res; $cidFontId = ++$this->numObj; $this->o_fontDescendentCID($cidFontId, 'new', $options); $this->objects[$id]['info']['cidFont'] = $cidFontId; } // also tell the pages node about the new font $this->o_pages($this->currentNode, 'font', array('fontNum' => $fontNum, 'objNum' => $id)); break; case 'add': foreach ($options as $k => $v) { switch ($k) { case 'BaseFont': $o['info']['name'] = $v; break; case 'FirstChar': case 'LastChar': case 'Widths': case 'FontDescriptor': case 'SubType': $this->addMessage('o_font ' . $k . " : " . $v); $o['info'][$k] = $v; break; } } // pass values down to descendent font if (isset($o['info']['cidFont'])) { $this->o_fontDescendentCID($o['info']['cidFont'], 'add', $options); } break; case 'out': if ($this->fonts[$this->objects[$id]['info']['fontFileName']]['isUnicode']) { // For Unicode fonts, we need to incorporate font data into // sub-sections that are linked from the primary font section. // Look at o_fontGIDtoCID and o_fontDescendentCID functions // for more informaiton. // // All of this code is adapted from the excellent changes made to // transform FPDF to TCPDF (http://tcpdf.sourceforge.net/) $res = "\n$id 0 obj\n<objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'fontDescriptor', 'info' => $options); break; case 'out': $res = "\n$id 0 obj\n<< /Type /FontDescriptor\n"; foreach ($o['info'] as $label => $value) { switch ($label) { case 'Ascent': case 'CapHeight': case 'Descent': case 'Flags': case 'ItalicAngle': case 'StemV': case 'AvgWidth': case 'Leading': case 'MaxWidth': case 'MissingWidth': case 'StemH': case 'XHeight': case 'CharSet': if (mb_strlen($value, '8bit')) { $res .= "/$label $value\n"; } break; case 'FontFile': case 'FontFile2': case 'FontFile3': $res .= "/$label $value 0 R\n"; break; case 'FontBBox': $res .= "/$label [$value[0] $value[1] $value[2] $value[3]]\n"; break; case 'FontName': $res .= "/$label /$value\n"; break; } } $res .= ">>\nendobj"; return $res; } } /** * the font encoding */ protected function o_fontEncoding($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': // the options array should contain 'differences' and maybe 'encoding' $this->objects[$id] = array('t' => 'fontEncoding', 'info' => $options); break; case 'out': $res = "\n$id 0 obj\n<< /Type /Encoding\n"; if (!isset($o['info']['encoding'])) { $o['info']['encoding'] = 'WinAnsiEncoding'; } if ($o['info']['encoding'] !== 'none') { $res .= "/BaseEncoding /" . $o['info']['encoding'] . "\n"; } $res .= "/Differences \n["; $onum = -100; foreach ($o['info']['differences'] as $num => $label) { if ($num != $onum + 1) { // we cannot make use of consecutive numbering $res .= "\n$num /$label"; } else { $res .= " /$label"; } $onum = $num; } $res .= "\n]\n>>\nendobj"; return $res; } } /** * a descendent cid font, needed for unicode fonts */ protected function o_fontDescendentCID($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'fontDescendentCID', 'info' => $options); // we need a CID system info section $cidSystemInfoId = ++$this->numObj; $this->o_contents($cidSystemInfoId, 'new', 'raw'); $this->objects[$id]['info']['cidSystemInfo'] = $cidSystemInfoId; $res = "<objects[$cidSystemInfoId]['c'] = $res; // and a CID to GID map $cidToGidMapId = ++$this->numObj; $this->o_fontGIDtoCIDMap($cidToGidMapId, 'new', $options); $this->objects[$id]['info']['cidToGidMap'] = $cidToGidMapId; break; case 'add': foreach ($options as $k => $v) { switch ($k) { case 'BaseFont': $o['info']['name'] = $v; break; case 'FirstChar': case 'LastChar': case 'MissingWidth': case 'FontDescriptor': case 'SubType': $this->addMessage("o_fontDescendentCID $k : $v"); $o['info'][$k] = $v; break; } } // pass values down to cid to gid map $this->o_fontGIDtoCIDMap($o['info']['cidToGidMap'], 'add', $options); break; case 'out': $res = "\n$id 0 obj\n"; $res .= "<fonts[$o['info']['fontFileName']]['CIDWidths'])) { $cid_widths = &$this->fonts[$o['info']['fontFileName']]['CIDWidths']; $w = ''; foreach ($cid_widths as $cid => $width) { $w .= "$cid [$width] "; } $res .= "/W [$w]\n"; } $res .= "/CIDToGIDMap " . $o['info']['cidToGidMap'] . " 0 R\n"; $res .= ">>\n"; $res .= "endobj"; return $res; } } /** * a font glyph to character map, needed for unicode fonts */ protected function o_fontGIDtoCIDMap($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'fontGIDtoCIDMap', 'info' => $options); break; case 'out': $res = "\n$id 0 obj\n"; $fontFileName = $o['info']['fontFileName']; $tmp = $this->fonts[$fontFileName]['CIDtoGID'] = base64_decode($this->fonts[$fontFileName]['CIDtoGID']); $compressed = isset($this->fonts[$fontFileName]['CIDtoGID_Compressed']) && $this->fonts[$fontFileName]['CIDtoGID_Compressed']; if (!$compressed && isset($o['raw'])) { $res .= $tmp; } else { $res .= "<<"; if (!$compressed && $this->compressionReady && $this->options['compression']) { // then implement ZLIB based compression on this content stream $compressed = true; $tmp = gzcompress($tmp, 6); } if ($compressed) { $res .= "\n/Filter /FlateDecode"; } $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream"; } $res .= "\nendobj"; return $res; } } /** * the document procset, solves some problems with printing to old PS printers */ protected function o_procset($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'procset', 'info' => array('PDF' => 1, 'Text' => 1)); $this->o_pages($this->currentNode, 'procset', $id); $this->procsetObjectId = $id; break; case 'add': // this is to add new items to the procset list, despite the fact that this is considered // obselete, the items are required for printing to some postscript printers switch ($options) { case 'ImageB': case 'ImageC': case 'ImageI': $o['info'][$options] = 1; break; } break; case 'out': $res = "\n$id 0 obj\n["; foreach ($o['info'] as $label => $val) { $res .= "/$label "; } $res .= "]\nendobj"; return $res; } } /** * define the document information */ protected function o_info($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->infoObject = $id; $date = 'D:' . @date('Ymd'); $this->objects[$id] = array( 't' => 'info', 'info' => array( 'Creator' => 'R and OS php pdf writer, http://www.ros.co.nz', 'CreationDate' => $date ) ); break; case 'Title': case 'Author': case 'Subject': case 'Keywords': case 'Creator': case 'Producer': case 'CreationDate': case 'ModDate': case 'Trapped': $o['info'][$action] = $options; break; case 'out': if ($this->encrypted) { $this->encryptInit($id); } $res = "\n$id 0 obj\n<<\n"; foreach ($o['info'] as $k => $v) { $res .= "/$k ("; if ($this->encrypted) { $v = $this->ARC4($v); } // dates must be outputted as-is, without Unicode transformations elseif (!in_array($k, array('CreationDate', 'ModDate'))) { $v = $this->filterText($v); } $res .= $v; $res .= ")\n"; } $res .= ">>\nendobj"; return $res; } } /** * an action object, used to link to URLS initially */ protected function o_action($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': if (is_array($options)) { $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => $options['type']); } else { // then assume a URI action $this->objects[$id] = array('t' => 'action', 'info' => $options, 'type' => 'URI'); } break; case 'out': if ($this->encrypted) { $this->encryptInit($id); } $res = "\n$id 0 obj\n<< /Type /Action"; switch ($o['type']) { case 'ilink': if (!isset($this->destinations[(string)$o['info']['label']])) { break; } // there will be an 'label' setting, this is the name of the destination $res .= "\n/S /GoTo\n/D " . $this->destinations[(string)$o['info']['label']] . " 0 R"; break; case 'URI': $res .= "\n/S /URI\n/URI ("; if ($this->encrypted) { $res .= $this->filterText($this->ARC4($o['info']), true, false); } else { $res .= $this->filterText($o['info'], true, false); } $res .= ")"; break; } $res .= "\n>>\nendobj"; return $res; } } /** * an annotation object, this will add an annotation to the current page. * initially will support just link annotations */ protected function o_annotation($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': // add the annotation to the current page $pageId = $this->currentPage; $this->o_page($pageId, 'annot', $id); // and add the action object which is going to be required switch ($options['type']) { case 'link': $this->objects[$id] = array('t' => 'annotation', 'info' => $options); $this->numObj++; $this->o_action($this->numObj, 'new', $options['url']); $this->objects[$id]['info']['actionId'] = $this->numObj; break; case 'ilink': // this is to a named internal link $label = $options['label']; $this->objects[$id] = array('t' => 'annotation', 'info' => $options); $this->numObj++; $this->o_action($this->numObj, 'new', array('type' => 'ilink', 'label' => $label)); $this->objects[$id]['info']['actionId'] = $this->numObj; break; } break; case 'out': $res = "\n$id 0 obj\n<< /Type /Annot"; switch ($o['info']['type']) { case 'link': case 'ilink': $res .= "\n/Subtype /Link"; break; } $res .= "\n/A " . $o['info']['actionId'] . " 0 R"; $res .= "\n/Border [0 0 0]"; $res .= "\n/H /I"; $res .= "\n/Rect [ "; foreach ($o['info']['rect'] as $v) { $res .= sprintf("%.4F ", $v); } $res .= "]"; $res .= "\n>>\nendobj"; return $res; } } /** * a page object, it also creates a contents object to hold its contents */ protected function o_page($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->numPages++; $this->objects[$id] = array( 't' => 'page', 'info' => array( 'parent' => $this->currentNode, 'pageNum' => $this->numPages ) ); if (is_array($options)) { // then this must be a page insertion, array should contain 'rid','pos'=[before|after] $options['id'] = $id; $this->o_pages($this->currentNode, 'page', $options); } else { $this->o_pages($this->currentNode, 'page', $id); } $this->currentPage = $id; //make a contents object to go with this page $this->numObj++; $this->o_contents($this->numObj, 'new', $id); $this->currentContents = $this->numObj; $this->objects[$id]['info']['contents'] = array(); $this->objects[$id]['info']['contents'][] = $this->numObj; $match = ($this->numPages % 2 ? 'odd' : 'even'); foreach ($this->addLooseObjects as $oId => $target) { if ($target === 'all' || $match === $target) { $this->objects[$id]['info']['contents'][] = $oId; } } break; case 'content': $o['info']['contents'][] = $options; break; case 'annot': // add an annotation to this page if (!isset($o['info']['annot'])) { $o['info']['annot'] = array(); } // $options should contain the id of the annotation dictionary $o['info']['annot'][] = $options; break; case 'out': $res = "\n$id 0 obj\n<< /Type /Page"; $res .= "\n/Parent " . $o['info']['parent'] . " 0 R"; if (isset($o['info']['annot'])) { $res .= "\n/Annots ["; foreach ($o['info']['annot'] as $aId) { $res .= " $aId 0 R"; } $res .= " ]"; } $count = count($o['info']['contents']); if ($count == 1) { $res .= "\n/Contents " . $o['info']['contents'][0] . " 0 R"; } else { if ($count > 1) { $res .= "\n/Contents [\n"; // reverse the page contents so added objects are below normal content //foreach (array_reverse($o['info']['contents']) as $cId) { // Back to normal now that I've got transparency working --Benj foreach ($o['info']['contents'] as $cId) { $res .= "$cId 0 R\n"; } $res .= "]"; } } $res .= "\n>>\nendobj"; return $res; } } /** * the contents objects hold all of the content which appears on pages */ protected function o_contents($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array('t' => 'contents', 'c' => '', 'info' => array()); if (mb_strlen($options, '8bit') && intval($options)) { // then this contents is the primary for a page $this->objects[$id]['onPage'] = $options; } else { if ($options === 'raw') { // then this page contains some other type of system object $this->objects[$id]['raw'] = 1; } } break; case 'add': // add more options to the decleration foreach ($options as $k => $v) { $o['info'][$k] = $v; } case 'out': $tmp = $o['c']; $res = "\n$id 0 obj\n"; if (isset($this->objects[$id]['raw'])) { $res .= $tmp; } else { $res .= "<<"; if ($this->compressionReady && $this->options['compression']) { // then implement ZLIB based compression on this content stream $res .= " /Filter /FlateDecode"; $tmp = gzcompress($tmp, 6); } if ($this->encrypted) { $this->encryptInit($id); $tmp = $this->ARC4($tmp); } foreach ($o['info'] as $k => $v) { $res .= "\n/$k $v"; } $res .= "\n/Length " . mb_strlen($tmp, '8bit') . " >>\nstream\n$tmp\nendstream"; } $res .= "\nendobj"; return $res; } } protected function o_embedjs($id, $action) { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array( 't' => 'embedjs', 'info' => array( 'Names' => '[(EmbeddedJS) ' . ($id + 1) . ' 0 R]' ) ); break; case 'out': $res = "\n$id 0 obj\n<< "; foreach ($o['info'] as $k => $v) { $res .= "\n/$k $v"; } $res .= "\n>>\nendobj"; return $res; } } protected function o_javascript($id, $action, $code = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': $this->objects[$id] = array( 't' => 'javascript', 'info' => array( 'S' => '/JavaScript', 'JS' => '(' . $this->filterText($code) . ')', ) ); break; case 'out': $res = "\n$id 0 obj\n<< "; foreach ($o['info'] as $k => $v) { $res .= "\n/$k $v"; } $res .= "\n>>\nendobj"; return $res; } } /** * an image object, will be an XObject in the document, includes description and data */ protected function o_image($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': // make the new object $this->objects[$id] = array('t' => 'image', 'data' => &$options['data'], 'info' => array()); $info =& $this->objects[$id]['info']; $info['Type'] = '/XObject'; $info['Subtype'] = '/Image'; $info['Width'] = $options['iw']; $info['Height'] = $options['ih']; if (isset($options['masked']) && $options['masked']) { $info['SMask'] = ($this->numObj - 1) . ' 0 R'; } if (!isset($options['type']) || $options['type'] === 'jpg') { if (!isset($options['channels'])) { $options['channels'] = 3; } switch ($options['channels']) { case 1: $info['ColorSpace'] = '/DeviceGray'; break; case 4: $info['ColorSpace'] = '/DeviceCMYK'; break; default: $info['ColorSpace'] = '/DeviceRGB'; break; } if ($info['ColorSpace'] === '/DeviceCMYK') { $info['Decode'] = '[1 0 1 0 1 0 1 0]'; } $info['Filter'] = '/DCTDecode'; $info['BitsPerComponent'] = 8; } else { if ($options['type'] === 'png') { $info['Filter'] = '/FlateDecode'; $info['DecodeParms'] = '<< /Predictor 15 /Colors ' . $options['ncolor'] . ' /Columns ' . $options['iw'] . ' /BitsPerComponent ' . $options['bitsPerComponent'] . '>>'; if ($options['isMask']) { $info['ColorSpace'] = '/DeviceGray'; } else { if (mb_strlen($options['pdata'], '8bit')) { $tmp = ' [ /Indexed /DeviceRGB ' . (mb_strlen($options['pdata'], '8bit') / 3 - 1) . ' '; $this->numObj++; $this->o_contents($this->numObj, 'new'); $this->objects[$this->numObj]['c'] = $options['pdata']; $tmp .= $this->numObj . ' 0 R'; $tmp .= ' ]'; $info['ColorSpace'] = $tmp; if (isset($options['transparency'])) { $transparency = $options['transparency']; switch ($transparency['type']) { case 'indexed': $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] '; $info['Mask'] = $tmp; break; case 'color-key': $tmp = ' [ ' . $transparency['r'] . ' ' . $transparency['r'] . $transparency['g'] . ' ' . $transparency['g'] . $transparency['b'] . ' ' . $transparency['b'] . ' ] '; $info['Mask'] = $tmp; break; } } } else { if (isset($options['transparency'])) { $transparency = $options['transparency']; switch ($transparency['type']) { case 'indexed': $tmp = ' [ ' . $transparency['data'] . ' ' . $transparency['data'] . '] '; $info['Mask'] = $tmp; break; case 'color-key': $tmp = ' [ ' . $transparency['r'] . ' ' . $transparency['r'] . ' ' . $transparency['g'] . ' ' . $transparency['g'] . ' ' . $transparency['b'] . ' ' . $transparency['b'] . ' ] '; $info['Mask'] = $tmp; break; } } $info['ColorSpace'] = '/' . $options['color']; } } $info['BitsPerComponent'] = $options['bitsPerComponent']; } } // assign it a place in the named resource dictionary as an external object, according to // the label passed in with it. $this->o_pages($this->currentNode, 'xObject', array('label' => $options['label'], 'objNum' => $id)); // also make sure that we have the right procset object for it. $this->o_procset($this->procsetObjectId, 'add', 'ImageC'); break; case 'out': $tmp = &$o['data']; $res = "\n$id 0 obj\n<<"; foreach ($o['info'] as $k => $v) { $res .= "\n/$k $v"; } if ($this->encrypted) { $this->encryptInit($id); $tmp = $this->ARC4($tmp); } $res .= "\n/Length " . mb_strlen($tmp, '8bit') . ">>\nstream\n$tmp\nendstream\nendobj"; return $res; } } /** * graphics state object */ protected function o_extGState($id, $action, $options = "") { static $valid_params = array( "LW", "LC", "LC", "LJ", "ML", "D", "RI", "OP", "op", "OPM", "Font", "BG", "BG2", "UCR", "TR", "TR2", "HT", "FL", "SM", "SA", "BM", "SMask", "CA", "ca", "AIS", "TK" ); if ($action !== "new") { $o = &$this->objects[$id]; } switch ($action) { case "new": $this->objects[$id] = array('t' => 'extGState', 'info' => $options); // Tell the pages about the new resource $this->numStates++; $this->o_pages($this->currentNode, 'extGState', array("objNum" => $id, "stateNum" => $this->numStates)); break; case "out": $res = "\n$id 0 obj\n<< /Type /ExtGState\n"; foreach ($o["info"] as $k => $v) { if (!in_array($k, $valid_params)) { continue; } $res .= "/$k $v\n"; } $res .= ">>\nendobj"; return $res; } } /** * encryption object. */ protected function o_encryption($id, $action, $options = '') { if ($action !== 'new') { $o = &$this->objects[$id]; } switch ($action) { case 'new': // make the new object $this->objects[$id] = array('t' => 'encryption', 'info' => $options); $this->arc4_objnum = $id; // figure out the additional paramaters required $pad = chr(0x28) . chr(0xBF) . chr(0x4E) . chr(0x5E) . chr(0x4E) . chr(0x75) . chr(0x8A) . chr(0x41) . chr(0x64) . chr(0x00) . chr(0x4E) . chr(0x56) . chr(0xFF) . chr(0xFA) . chr(0x01) . chr(0x08) . chr(0x2E) . chr(0x2E) . chr(0x00) . chr(0xB6) . chr(0xD0) . chr(0x68) . chr(0x3E) . chr(0x80) . chr(0x2F) . chr(0x0C) . chr(0xA9) . chr(0xFE) . chr(0x64) . chr(0x53) . chr(0x69) . chr(0x7A); $len = mb_strlen($options['owner'], '8bit'); if ($len > 32) { $owner = substr($options['owner'], 0, 32); } else { if ($len < 32) { $owner = $options['owner'] . substr($pad, 0, 32 - $len); } else { $owner = $options['owner']; } } $len = mb_strlen($options['user'], '8bit'); if ($len > 32) { $user = substr($options['user'], 0, 32); } else { if ($len < 32) { $user = $options['user'] . substr($pad, 0, 32 - $len); } else { $user = $options['user']; } } $tmp = $this->md5_16($owner); $okey = substr($tmp, 0, 5); $this->ARC4_init($okey); $ovalue = $this->ARC4($user); $this->objects[$id]['info']['O'] = $ovalue; // now make the u value, phew. $tmp = $this->md5_16( $user . $ovalue . chr($options['p']) . chr(255) . chr(255) . chr(255) . $this->fileIdentifier ); $ukey = substr($tmp, 0, 5); $this->ARC4_init($ukey); $this->encryptionKey = $ukey; $this->encrypted = true; $uvalue = $this->ARC4($pad); $this->objects[$id]['info']['U'] = $uvalue; $this->encryptionKey = $ukey; // initialize the arc4 array break; case 'out': $res = "\n$id 0 obj\n<<"; $res .= "\n/Filter /Standard"; $res .= "\n/V 1"; $res .= "\n/R 2"; $res .= "\n/O (" . $this->filterText($o['info']['O'], true, false) . ')'; $res .= "\n/U (" . $this->filterText($o['info']['U'], true, false) . ')'; // and the p-value needs to be converted to account for the twos-complement approach $o['info']['p'] = (($o['info']['p'] ^ 255) + 1) * -1; $res .= "\n/P " . ($o['info']['p']); $res .= "\n>>\nendobj"; return $res; } } /** * ARC4 functions * A series of function to implement ARC4 encoding in PHP */ /** * calculate the 16 byte version of the 128 bit md5 digest of the string */ function md5_16($string) { $tmp = md5($string); $out = ''; for ($i = 0; $i <= 30; $i = $i + 2) { $out .= chr(hexdec(substr($tmp, $i, 2))); } return $out; } /** * initialize the encryption for processing a particular object */ function encryptInit($id) { $tmp = $this->encryptionKey; $hex = dechex($id); if (mb_strlen($hex, '8bit') < 6) { $hex = substr('000000', 0, 6 - mb_strlen($hex, '8bit')) . $hex; } $tmp .= chr(hexdec(substr($hex, 4, 2))) . chr(hexdec(substr($hex, 2, 2))) . chr( hexdec(substr($hex, 0, 2)) ) . chr(0) . chr(0); $key = $this->md5_16($tmp); $this->ARC4_init(substr($key, 0, 10)); } /** * initialize the ARC4 encryption */ function ARC4_init($key = '') { $this->arc4 = ''; // setup the control array if (mb_strlen($key, '8bit') == 0) { return; } $k = ''; while (mb_strlen($k, '8bit') < 256) { $k .= $key; } $k = substr($k, 0, 256); for ($i = 0; $i < 256; $i++) { $this->arc4 .= chr($i); } $j = 0; for ($i = 0; $i < 256; $i++) { $t = $this->arc4[$i]; $j = ($j + ord($t) + ord($k[$i])) % 256; $this->arc4[$i] = $this->arc4[$j]; $this->arc4[$j] = $t; } } /** * ARC4 encrypt a text string */ function ARC4($text) { $len = mb_strlen($text, '8bit'); $a = 0; $b = 0; $c = $this->arc4; $out = ''; for ($i = 0; $i < $len; $i++) { $a = ($a + 1) % 256; $t = $c[$a]; $b = ($b + ord($t)) % 256; $c[$a] = $c[$b]; $c[$b] = $t; $k = ord($c[(ord($c[$a]) + ord($c[$b])) % 256]); $out .= chr(ord($text[$i]) ^ $k); } return $out; } /** * functions which can be called to adjust or add to the document */ /** * add a link in the document to an external URL */ function addLink($url, $x0, $y0, $x1, $y1) { $this->numObj++; $info = array('type' => 'link', 'url' => $url, 'rect' => array($x0, $y0, $x1, $y1)); $this->o_annotation($this->numObj, 'new', $info); } /** * add a link in the document to an internal destination (ie. within the document) */ function addInternalLink($label, $x0, $y0, $x1, $y1) { $this->numObj++; $info = array('type' => 'ilink', 'label' => $label, 'rect' => array($x0, $y0, $x1, $y1)); $this->o_annotation($this->numObj, 'new', $info); } /** * set the encryption of the document * can be used to turn it on and/or set the passwords which it will have. * also the functions that the user will have are set here, such as print, modify, add */ function setEncryption($userPass = '', $ownerPass = '', $pc = array()) { $p = bindec("11000000"); $options = array('print' => 4, 'modify' => 8, 'copy' => 16, 'add' => 32); foreach ($pc as $k => $v) { if ($v && isset($options[$k])) { $p += $options[$k]; } else { if (isset($options[$v])) { $p += $options[$v]; } } } // implement encryption on the document if ($this->arc4_objnum == 0) { // then the block does not exist already, add it. $this->numObj++; if (mb_strlen($ownerPass) == 0) { $ownerPass = $userPass; } $this->o_encryption($this->numObj, 'new', array('user' => $userPass, 'owner' => $ownerPass, 'p' => $p)); } } /** * should be used for internal checks, not implemented as yet */ function checkAllHere() { } /** * return the pdf stream as a string returned from the function */ function output($debug = false) { if ($debug) { // turn compression off $this->options['compression'] = false; } if ($this->javascript) { $this->numObj++; $js_id = $this->numObj; $this->o_embedjs($js_id, 'new'); $this->o_javascript(++$this->numObj, 'new', $this->javascript); $id = $this->catalogId; $this->o_catalog($id, 'javascript', $js_id); } if ($this->arc4_objnum) { $this->ARC4_init($this->encryptionKey); } $this->checkAllHere(); $xref = array(); $content = '%PDF-1.3'; $pos = mb_strlen($content, '8bit'); foreach ($this->objects as $k => $v) { $tmp = 'o_' . $v['t']; $cont = $this->$tmp($k, 'out'); $content .= $cont; $xref[] = $pos; $pos += mb_strlen($cont, '8bit'); } $content .= "\nxref\n0 " . (count($xref) + 1) . "\n0000000000 65535 f \n"; foreach ($xref as $p) { $content .= str_pad($p, 10, "0", STR_PAD_LEFT) . " 00000 n \n"; } $content .= "trailer\n<<\n/Size " . (count($xref) + 1) . "\n/Root 1 0 R\n/Info $this->infoObject 0 R\n"; // if encryption has been applied to this document then add the marker for this dictionary if ($this->arc4_objnum > 0) { $content .= "/Encrypt $this->arc4_objnum 0 R\n"; } if (mb_strlen($this->fileIdentifier, '8bit')) { $content .= "/ID[<$this->fileIdentifier><$this->fileIdentifier>]\n"; } // account for \n added at start of xref table $pos++; $content .= ">>\nstartxref\n$pos\n%%EOF\n"; return $content; } /** * intialize a new document * if this is called on an existing document results may be unpredictable, but the existing document would be lost at minimum * this function is called automatically by the constructor function */ private function newDocument($pageSize = array(0, 0, 612, 792)) { $this->numObj = 0; $this->objects = array(); $this->numObj++; $this->o_catalog($this->numObj, 'new'); $this->numObj++; $this->o_outlines($this->numObj, 'new'); $this->numObj++; $this->o_pages($this->numObj, 'new'); $this->o_pages($this->numObj, 'mediaBox', $pageSize); $this->currentNode = 3; $this->numObj++; $this->o_procset($this->numObj, 'new'); $this->numObj++; $this->o_info($this->numObj, 'new'); $this->numObj++; $this->o_page($this->numObj, 'new'); // need to store the first page id as there is no way to get it to the user during // startup $this->firstPageId = $this->currentContents; } /** * open the font file and return a php structure containing it. * first check if this one has been done before and saved in a form more suited to php * note that if a php serialized version does not exist it will try and make one, but will * require write access to the directory to do it... it is MUCH faster to have these serialized * files. */ private function openFont($font) { // assume that $font contains the path and file but not the extension $pos = strrpos($font, '/'); if ($pos === false) { $dir = './'; $name = $font; } else { $dir = substr($font, 0, $pos + 1); $name = substr($font, $pos + 1); } $fontcache = $this->fontcache; if ($fontcache == '') { $fontcache = $dir; } //$name filename without folder and extension of font metrics //$dir folder of font metrics //$fontcache folder of runtime created php serialized version of font metrics. // If this is not given, the same folder as the font metrics will be used. // Storing and reusing serialized versions improves speed much $this->addMessage("openFont: $font - $name"); if (!$this->isUnicode || in_array(mb_strtolower(basename($name)), self::$coreFonts)) { $metrics_name = "$name.afm"; } else { $metrics_name = "$name.ufm"; } $cache_name = "$metrics_name.php"; $this->addMessage("metrics: $metrics_name, cache: $cache_name"); if (file_exists($fontcache . $cache_name)) { $this->addMessage("openFont: php file exists $fontcache$cache_name"); $this->fonts[$font] = require($fontcache . $cache_name); if (!isset($this->fonts[$font]['_version_']) || $this->fonts[$font]['_version_'] != $this->fontcacheVersion) { // if the font file is old, then clear it out and prepare for re-creation $this->addMessage('openFont: clear out, make way for new version.'); $this->fonts[$font] = null; unset($this->fonts[$font]); } } else { $old_cache_name = "php_$metrics_name"; if (file_exists($fontcache . $old_cache_name)) { $this->addMessage( "openFont: php file doesn't exist $fontcache$cache_name, creating it from the old format" ); $old_cache = file_get_contents($fontcache . $old_cache_name); file_put_contents($fontcache . $cache_name, 'openFont($font); } } if (!isset($this->fonts[$font]) && file_exists($dir . $metrics_name)) { // then rebuild the php_.afm file from the .afm file $this->addMessage("openFont: build php file from $dir$metrics_name"); $data = array(); // 20 => 'space' $data['codeToName'] = array(); // Since we're not going to enable Unicode for the core fonts we need to use a font-based // setting for Unicode support rather than a global setting. $data['isUnicode'] = (strtolower(substr($metrics_name, -3)) !== 'afm'); $cidtogid = ''; if ($data['isUnicode']) { $cidtogid = str_pad('', 256 * 256 * 2, "\x00"); } $file = file($dir . $metrics_name); foreach ($file as $rowA) { $row = trim($rowA); $pos = strpos($row, ' '); if ($pos) { // then there must be some keyword $key = substr($row, 0, $pos); switch ($key) { case 'FontName': case 'FullName': case 'FamilyName': case 'PostScriptName': case 'Weight': case 'ItalicAngle': case 'IsFixedPitch': case 'CharacterSet': case 'UnderlinePosition': case 'UnderlineThickness': case 'Version': case 'EncodingScheme': case 'CapHeight': case 'XHeight': case 'Ascender': case 'Descender': case 'StdHW': case 'StdVW': case 'StartCharMetrics': case 'FontHeightOffset': // OAR - Added so we can offset the height calculation of a Windows font. Otherwise it's too big. $data[$key] = trim(substr($row, $pos)); break; case 'FontBBox': $data[$key] = explode(' ', trim(substr($row, $pos))); break; //C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ; case 'C': // Found in AFM files $bits = explode(';', trim($row)); $dtmp = array(); foreach ($bits as $bit) { $bits2 = explode(' ', trim($bit)); if (mb_strlen($bits2[0], '8bit') == 0) { continue; } if (count($bits2) > 2) { $dtmp[$bits2[0]] = array(); for ($i = 1; $i < count($bits2); $i++) { $dtmp[$bits2[0]][] = $bits2[$i]; } } else { if (count($bits2) == 2) { $dtmp[$bits2[0]] = $bits2[1]; } } } $c = (int)$dtmp['C']; $n = $dtmp['N']; $width = floatval($dtmp['WX']); if ($c >= 0) { if ($c != hexdec($n)) { $data['codeToName'][$c] = $n; } $data['C'][$c] = $width; } else { $data['C'][$n] = $width; } if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { $data['MissingWidth'] = $width; } break; // U 827 ; WX 0 ; N squaresubnosp ; G 675 ; case 'U': // Found in UFM files if (!$data['isUnicode']) { break; } $bits = explode(';', trim($row)); $dtmp = array(); foreach ($bits as $bit) { $bits2 = explode(' ', trim($bit)); if (mb_strlen($bits2[0], '8bit') === 0) { continue; } if (count($bits2) > 2) { $dtmp[$bits2[0]] = array(); for ($i = 1; $i < count($bits2); $i++) { $dtmp[$bits2[0]][] = $bits2[$i]; } } else { if (count($bits2) == 2) { $dtmp[$bits2[0]] = $bits2[1]; } } } $c = (int)$dtmp['U']; $n = $dtmp['N']; $glyph = $dtmp['G']; $width = floatval($dtmp['WX']); if ($c >= 0) { // Set values in CID to GID map if ($c >= 0 && $c < 0xFFFF && $glyph) { $cidtogid[$c * 2] = chr($glyph >> 8); $cidtogid[$c * 2 + 1] = chr($glyph & 0xFF); } if ($c != hexdec($n)) { $data['codeToName'][$c] = $n; } $data['C'][$c] = $width; } else { $data['C'][$n] = $width; } if (!isset($data['MissingWidth']) && $c == -1 && $n === '.notdef') { $data['MissingWidth'] = $width; } break; case 'KPX': break; // don't include them as they are not used yet //KPX Adieresis yacute -40 $bits = explode(' ', trim($row)); $data['KPX'][$bits[1]][$bits[2]] = $bits[3]; break; } } } if ($this->compressionReady && $this->options['compression']) { // then implement ZLIB based compression on CIDtoGID string $data['CIDtoGID_Compressed'] = true; $cidtogid = gzcompress($cidtogid, 6); } $data['CIDtoGID'] = base64_encode($cidtogid); $data['_version_'] = $this->fontcacheVersion; $this->fonts[$font] = $data; //Because of potential trouble with php safe mode, expect that the folder already exists. //If not existing, this will hit performance because of missing cached results. if (is_dir(substr($fontcache, 0, -1)) && is_writable(substr($fontcache, 0, -1))) { file_put_contents($fontcache . $cache_name, 'fonts[$font])) { $this->addMessage("openFont: no font file found for $font. Do you need to run load_font.php?"); } //pre_r($this->messages); } /** * if the font is not loaded then load it and make the required object * else just make it the current font * the encoding array can contain 'encoding'=> 'none','WinAnsiEncoding','MacRomanEncoding' or 'MacExpertEncoding' * note that encoding='none' will need to be used for symbolic fonts * and 'differences' => an array of mappings between numbers 0->255 and character names. * */ function selectFont($fontName, $encoding = '', $set = true) { $ext = substr($fontName, -4); if ($ext === '.afm' || $ext === '.ufm') { $fontName = substr($fontName, 0, mb_strlen($fontName) - 4); } if (!isset($this->fonts[$fontName])) { $this->addMessage("selectFont: selecting - $fontName - $encoding, $set"); // load the file $this->openFont($fontName); if (isset($this->fonts[$fontName])) { $this->numObj++; $this->numFonts++; $font = &$this->fonts[$fontName]; //$this->numFonts = md5($fontName); $pos = strrpos($fontName, '/'); // $dir = substr($fontName,0,$pos+1); $name = substr($fontName, $pos + 1); $options = array('name' => $name, 'fontFileName' => $fontName); if (is_array($encoding)) { // then encoding and differences might be set if (isset($encoding['encoding'])) { $options['encoding'] = $encoding['encoding']; } if (isset($encoding['differences'])) { $options['differences'] = $encoding['differences']; } } else { if (mb_strlen($encoding, '8bit')) { // then perhaps only the encoding has been set $options['encoding'] = $encoding; } } $fontObj = $this->numObj; $this->o_font($this->numObj, 'new', $options); $font['fontNum'] = $this->numFonts; // if this is a '.afm' font, and there is a '.pfa' file to go with it ( as there // should be for all non-basic fonts), then load it into an object and put the // references into the font object $basefile = $fontName; $fbtype = ''; if (file_exists("$basefile.pfb")) { $fbtype = 'pfb'; } else { if (file_exists("$basefile.ttf")) { $fbtype = 'ttf'; } } $fbfile = "$basefile.$fbtype"; // $pfbfile = substr($fontName,0,strlen($fontName)-4).'.pfb'; // $ttffile = substr($fontName,0,strlen($fontName)-4).'.ttf'; $this->addMessage('selectFont: checking for - ' . $fbfile); // OAR - I don't understand this old check // if (substr($fontName, -4) === '.afm' && strlen($fbtype)) { if ($fbtype) { $adobeFontName = isset($font['PostScriptName']) ? $font['PostScriptName'] : $font['FontName']; // $fontObj = $this->numObj; $this->addMessage("selectFont: adding font file - $fbfile - $adobeFontName"); // find the array of font widths, and put that into an object. $firstChar = -1; $lastChar = 0; $widths = array(); $cid_widths = array(); foreach ($font['C'] as $num => $d) { if (intval($num) > 0 || $num == '0') { if (!$font['isUnicode']) { // With Unicode, widths array isn't used if ($lastChar > 0 && $num > $lastChar + 1) { for ($i = $lastChar + 1; $i < $num; $i++) { $widths[] = 0; } } } $widths[] = $d; if ($font['isUnicode']) { $cid_widths[$num] = $d; } if ($firstChar == -1) { $firstChar = $num; } $lastChar = $num; } } // also need to adjust the widths for the differences array if (isset($options['differences'])) { foreach ($options['differences'] as $charNum => $charName) { if ($charNum > $lastChar) { if (!$font['isUnicode']) { // With Unicode, widths array isn't used for ($i = $lastChar + 1; $i <= $charNum; $i++) { $widths[] = 0; } } $lastChar = $charNum; } if (isset($font['C'][$charName])) { $widths[$charNum - $firstChar] = $font['C'][$charName]; if ($font['isUnicode']) { $cid_widths[$charName] = $font['C'][$charName]; } } } } if ($font['isUnicode']) { $font['CIDWidths'] = $cid_widths; } $this->addMessage('selectFont: FirstChar = ' . $firstChar); $this->addMessage('selectFont: LastChar = ' . $lastChar); $widthid = -1; if (!$font['isUnicode']) { // With Unicode, widths array isn't used $this->numObj++; $this->o_contents($this->numObj, 'new', 'raw'); $this->objects[$this->numObj]['c'] .= '[' . implode(' ', $widths) . ']'; $widthid = $this->numObj; } $missing_width = 500; $stemV = 70; if (isset($font['MissingWidth'])) { $missing_width = $font['MissingWidth']; } if (isset($font['StdVW'])) { $stemV = $font['StdVW']; } else { if (isset($font['Weight']) && preg_match('!(bold|black)!i', $font['Weight'])) { $stemV = 120; } } // load the pfb file, and put that into an object too. // note that pdf supports only binary format type 1 font files, though there is a // simple utility to convert them from pfa to pfb. // FIXME: should we move font subset creation to CPDF::output? See notes in issue #750. if (!$this->isUnicode || $fbtype !== 'ttf' || empty($this->stringSubsets)) { $data = file_get_contents($fbfile); } else { $this->stringSubsets[$fontName][] = 32; // Force space if not in yet $subset = $this->stringSubsets[$fontName]; sort($subset); // Load font $font_obj = Font::load($fbfile); $font_obj->parse(); // Define subset $font_obj->setSubset($subset); $font_obj->reduce(); // Write new font $tmp_name = "$fbfile.tmp." . uniqid(); $font_obj->open($tmp_name, Font_Binary_Stream::modeWrite); $font_obj->encode(array("OS/2")); $font_obj->close(); // Parse the new font to get cid2gid and widths $font_obj = Font::load($tmp_name); // Find Unicode char map table $subtable = null; foreach ($font_obj->getData("cmap", "subtables") as $_subtable) { if ($_subtable["platformID"] == 0 || $_subtable["platformID"] == 3 && $_subtable["platformSpecificID"] == 1) { $subtable = $_subtable; break; } } if ($subtable) { $glyphIndexArray = $subtable["glyphIndexArray"]; $hmtx = $font_obj->getData("hmtx"); unset($glyphIndexArray[0xFFFF]); $cidtogid = str_pad('', max(array_keys($glyphIndexArray)) * 2 + 1, "\x00"); $font['CIDWidths'] = array(); foreach ($glyphIndexArray as $cid => $gid) { if ($cid >= 0 && $cid < 0xFFFF && $gid) { $cidtogid[$cid * 2] = chr($gid >> 8); $cidtogid[$cid * 2 + 1] = chr($gid & 0xFF); } $width = $font_obj->normalizeFUnit(isset($hmtx[$gid]) ? $hmtx[$gid][0] : $hmtx[0][0]); $font['CIDWidths'][$cid] = $width; } $font['CIDtoGID'] = base64_encode(gzcompress($cidtogid)); $font['CIDtoGID_Compressed'] = true; $data = file_get_contents($tmp_name); } else { $data = file_get_contents($fbfile); } $font_obj->close(); unlink($tmp_name); } // create the font descriptor $this->numObj++; $fontDescriptorId = $this->numObj; $this->numObj++; $pfbid = $this->numObj; // determine flags (more than a little flakey, hopefully will not matter much) $flags = 0; if ($font['ItalicAngle'] != 0) { $flags += pow(2, 6); } if ($font['IsFixedPitch'] === 'true') { $flags += 1; } $flags += pow(2, 5); // assume non-sybolic $list = array( 'Ascent' => 'Ascender', 'CapHeight' => 'CapHeight', 'MissingWidth' => 'MissingWidth', 'Descent' => 'Descender', 'FontBBox' => 'FontBBox', 'ItalicAngle' => 'ItalicAngle' ); $fdopt = array( 'Flags' => $flags, 'FontName' => $adobeFontName, 'StemV' => $stemV ); foreach ($list as $k => $v) { if (isset($font[$v])) { $fdopt[$k] = $font[$v]; } } if ($fbtype === 'pfb') { $fdopt['FontFile'] = $pfbid; } else { if ($fbtype === 'ttf') { $fdopt['FontFile2'] = $pfbid; } } $this->o_fontDescriptor($fontDescriptorId, 'new', $fdopt); // embed the font program $this->o_contents($this->numObj, 'new'); $this->objects[$pfbid]['c'] .= $data; // determine the cruicial lengths within this file if ($fbtype === 'pfb') { $l1 = strpos($data, 'eexec') + 6; $l2 = strpos($data, '00000000') - $l1; $l3 = mb_strlen($data, '8bit') - $l2 - $l1; $this->o_contents( $this->numObj, 'add', array('Length1' => $l1, 'Length2' => $l2, 'Length3' => $l3) ); } else { if ($fbtype == 'ttf') { $l1 = mb_strlen($data, '8bit'); $this->o_contents($this->numObj, 'add', array('Length1' => $l1)); } } // tell the font object about all this new stuff $tmp = array( 'BaseFont' => $adobeFontName, 'MissingWidth' => $missing_width, 'Widths' => $widthid, 'FirstChar' => $firstChar, 'LastChar' => $lastChar, 'FontDescriptor' => $fontDescriptorId ); if ($fbtype === 'ttf') { $tmp['SubType'] = 'TrueType'; } $this->addMessage("adding extra info to font.($fontObj)"); foreach ($tmp as $fk => $fv) { $this->addMessage("$fk : $fv"); } $this->o_font($fontObj, 'add', $tmp); } else { $this->addMessage( 'selectFont: pfb or ttf file not found, ok if this is one of the 14 standard fonts' ); } // also set the differences here, note that this means that these will take effect only the //first time that a font is selected, else they are ignored if (isset($options['differences'])) { $font['differences'] = $options['differences']; } } } if ($set && isset($this->fonts[$fontName])) { // so if for some reason the font was not set in the last one then it will not be selected $this->currentBaseFont = $fontName; // the next lines mean that if a new font is selected, then the current text state will be // applied to it as well. $this->currentFont = $this->currentBaseFont; $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; //$this->setCurrentFont(); } return $this->currentFontNum; //return $this->numObj; } /** * sets up the current font, based on the font families, and the current text state * note that this system is quite flexible, a bold-italic font can be completely different to a * italic-bold font, and even bold-bold will have to be defined within the family to have meaning * This function is to be called whenever the currentTextState is changed, it will update * the currentFont setting to whatever the appropriatte family one is. * If the user calls selectFont themselves then that will reset the currentBaseFont, and the currentFont * This function will change the currentFont to whatever it should be, but will not change the * currentBaseFont. */ private function setCurrentFont() { // if (strlen($this->currentBaseFont) == 0){ // // then assume an initial font // $this->selectFont($this->defaultFont); // } // $cf = substr($this->currentBaseFont,strrpos($this->currentBaseFont,'/')+1); // if (strlen($this->currentTextState) // && isset($this->fontFamilies[$cf]) // && isset($this->fontFamilies[$cf][$this->currentTextState])){ // // then we are in some state or another // // and this font has a family, and the current setting exists within it // // select the font, then return it // $nf = substr($this->currentBaseFont,0,strrpos($this->currentBaseFont,'/')+1).$this->fontFamilies[$cf][$this->currentTextState]; // $this->selectFont($nf,'',0); // $this->currentFont = $nf; // $this->currentFontNum = $this->fonts[$nf]['fontNum']; // } else { // // the this font must not have the right family member for the current state // // simply assume the base font $this->currentFont = $this->currentBaseFont; $this->currentFontNum = $this->fonts[$this->currentFont]['fontNum']; // } } /** * function for the user to find out what the ID is of the first page that was created during * startup - useful if they wish to add something to it later. */ function getFirstPageId() { return $this->firstPageId; } /** * add content to the currently active object */ private function addContent($content) { $this->objects[$this->currentContents]['c'] .= $content; } /** * sets the color for fill operations */ function setColor($color, $force = false) { $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); if (!$force && $this->currentColor == $new_color) { return; } if (isset($new_color[3])) { //$this->currentColor = $new_color; $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F k", $this->currentColor)); } else { if (isset($new_color[2])) { //$this->currentColor = $new_color; $this->addContent(vsprintf("\n%.3F %.3F %.3F rg", $new_color)); } } } /** * sets the color for fill operations */ function setFillRule($fillRule) { if (!in_array($fillRule, array("nonzero", "evenodd"))) { return; } $this->fillRule = $fillRule; } /** * sets the color for stroke operations */ function setStrokeColor($color, $force = false) { $new_color = array($color[0], $color[1], $color[2], isset($color[3]) ? $color[3] : null); if (!$force && $this->currentStrokeColor == $new_color) { return; } if (isset($new_color[3])) { //$this->currentStrokeColor = $new_color; $this->addContent(vsprintf("\n%.3F %.3F %.3F %.3F K", $this->currentStrokeColor)); } else { if (isset($new_color[2])) { //$this->currentStrokeColor = $new_color; $this->addContent(vsprintf("\n%.3F %.3F %.3F RG", $new_color)); } } } /** * Set the graphics state for compositions */ function setGraphicsState($parameters) { // Create a new graphics state object // FIXME: should actually keep track of states that have already been created... $this->numObj++; $this->o_extGState($this->numObj, 'new', $parameters); $this->addContent("\n/GS$this->numStates gs"); } /** * Set current blend mode & opacity for lines. * * Valid blend modes are: * * Normal, Multiply, Screen, Overlay, Darken, Lighten, * ColorDogde, ColorBurn, HardLight, SoftLight, Difference, * Exclusion * * @param string $mode the blend mode to use * @param float $opacity 0.0 fully transparent, 1.0 fully opaque */ function setLineTransparency($mode, $opacity) { static $blend_modes = array( "Normal", "Multiply", "Screen", "Overlay", "Darken", "Lighten", "ColorDogde", "ColorBurn", "HardLight", "SoftLight", "Difference", "Exclusion" ); if (!in_array($mode, $blend_modes)) { $mode = "Normal"; } if (is_null($this->currentLineTransparency)) { $this->currentLineTransparency = []; } if ($mode === (key_exists('mode', $this->currentLineTransparency) ? $this->currentLineTransparency['mode'] : '') && $opacity === (key_exists('opacity', $this->currentLineTransparency) ? $this->currentLineTransparency["opacity"] : '')) { return; } $this->currentLineTransparency["mode"] = $mode; $this->currentLineTransparency["opacity"] = $opacity; $options = array( "BM" => "/$mode", "CA" => (float)$opacity ); $this->setGraphicsState($options); } /** * Set current blend mode & opacity for filled objects. * * Valid blend modes are: * * Normal, Multiply, Screen, Overlay, Darken, Lighten, * ColorDogde, ColorBurn, HardLight, SoftLight, Difference, * Exclusion * * @param string $mode the blend mode to use * @param float $opacity 0.0 fully transparent, 1.0 fully opaque */ function setFillTransparency($mode, $opacity) { static $blend_modes = array( "Normal", "Multiply", "Screen", "Overlay", "Darken", "Lighten", "ColorDogde", "ColorBurn", "HardLight", "SoftLight", "Difference", "Exclusion" ); if (!in_array($mode, $blend_modes)) { $mode = "Normal"; } if (is_null($this->currentFillTransparency)) { $this->currentFillTransparency = []; } if ($mode === (key_exists('mode', $this->currentFillTransparency) ? $this->currentFillTransparency['mode'] : '') && $opacity === (key_exists('opacity', $this->currentFillTransparency) ? $this->currentFillTransparency["opacity"] : '')) { return; } $this->currentFillTransparency["mode"] = $mode; $this->currentFillTransparency["opacity"] = $opacity; $options = array( "BM" => "/$mode", "ca" => (float)$opacity, ); $this->setGraphicsState($options); } function lineTo($x, $y) { $this->addContent(sprintf("\n%.3F %.3F l", $x, $y)); } function moveTo($x, $y) { $this->addContent(sprintf("\n%.3F %.3F m", $x, $y)); } /** * draw a bezier curve based on 4 control points */ function curveTo($x1, $y1, $x2, $y2, $x3, $y3) { $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F %.3F %.3F c", $x1, $y1, $x2, $y2, $x3, $y3)); } /** * draw a bezier curve based on 4 control points */ function quadTo($cpx, $cpy, $x, $y) { $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F v", $cpx, $cpy, $x, $y)); } function closePath() { $this->addContent(' h'); } function endPath() { $this->addContent(' n'); } /** * draw an ellipse * note that the part and filled ellipse are just special cases of this function * * draws an ellipse in the current line style * centered at $x0,$y0, radii $r1,$r2 * if $r2 is not set, then a circle is drawn * from $astart to $afinish, measured in degrees, running anti-clockwise from the right hand side of the ellipse. * nSeg is not allowed to be less than 2, as this will simply draw a line (and will even draw a * pretty crappy shape at 2, as we are approximating with bezier curves. */ function ellipse( $x0, $y0, $r1, $r2 = 0, $angle = 0, $nSeg = 8, $astart = 0, $afinish = 360, $close = true, $fill = false, $stroke = true, $incomplete = false ) { if ($r1 == 0) { return; } if ($r2 == 0) { $r2 = $r1; } if ($nSeg < 2) { $nSeg = 2; } $astart = deg2rad((float)$astart); $afinish = deg2rad((float)$afinish); $totalAngle = $afinish - $astart; $dt = $totalAngle / $nSeg; $dtm = $dt / 3; if ($angle != 0) { $a = -1 * deg2rad((float)$angle); $this->addContent( sprintf("\n q %.3F %.3F %.3F %.3F %.3F %.3F cm", cos($a), -sin($a), sin($a), cos($a), $x0, $y0) ); $x0 = 0; $y0 = 0; } $t1 = $astart; $a0 = $x0 + $r1 * cos($t1); $b0 = $y0 + $r2 * sin($t1); $c0 = -$r1 * sin($t1); $d0 = $r2 * cos($t1); if (!$incomplete) { $this->addContent(sprintf("\n%.3F %.3F m ", $a0, $b0)); } for ($i = 1; $i <= $nSeg; $i++) { // draw this bit of the total curve $t1 = $i * $dt + $astart; $a1 = $x0 + $r1 * cos($t1); $b1 = $y0 + $r2 * sin($t1); $c1 = -$r1 * sin($t1); $d1 = $r2 * cos($t1); $this->addContent( sprintf( "\n%.3F %.3F %.3F %.3F %.3F %.3F c", ($a0 + $c0 * $dtm), ($b0 + $d0 * $dtm), ($a1 - $c1 * $dtm), ($b1 - $d1 * $dtm), $a1, $b1 ) ); $a0 = $a1; $b0 = $b1; $c0 = $c1; $d0 = $d1; } if (!$incomplete) { if ($fill) { $this->addContent(' f'); } if ($stroke) { if ($close) { $this->addContent(' s'); // small 's' signifies closing the path as well } else { $this->addContent(' S'); } } } if ($angle != 0) { $this->addContent(' Q'); } } /** * this sets the line drawing style. * width, is the thickness of the line in user units * cap is the type of cap to put on the line, values can be 'butt','round','square' * where the diffference between 'square' and 'butt' is that 'square' projects a flat end past the * end of the line. * join can be 'miter', 'round', 'bevel' * dash is an array which sets the dash pattern, is a series of length values, which are the lengths of the * on and off dashes. * (2) represents 2 on, 2 off, 2 on , 2 off ... * (2,1) is 2 on, 1 off, 2 on, 1 off.. etc * phase is a modifier on the dash pattern which is used to shift the point at which the pattern starts. */ function setLineStyle($width = 1, $cap = '', $join = '', $dash = '', $phase = 0) { // this is quite inefficient in that it sets all the parameters whenever 1 is changed, but will fix another day $string = ''; if ($width > 0) { $string .= sprintf("%.3F w", $width); } $ca = array('butt' => 0, 'round' => 1, 'square' => 2); if (isset($ca[$cap])) { $string .= " $ca[$cap] J"; } $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2); if (isset($ja[$join])) { $string .= " $ja[$join] j"; } if (is_array($dash)) { $string .= ' [ ' . implode(' ', $dash) . " ] $phase d"; } $this->currentLineStyle = $string; $this->addContent("\n$string"); } function rect($x1, $y1, $width, $height) { $this->addContent(sprintf("\n%.3F %.3F %.3F %.3F re", $x1, $y1, $width, $height)); } function stroke() { $this->addContent("\nS"); } function fill() { $this->addContent("\nf".($this->fillRule === "evenodd" ? "*" : "")); } function fillStroke() { $this->addContent("\nb".($this->fillRule === "evenodd" ? "*" : "")); } /** * save the current graphic state */ function save() { $this->addContent("\nq"); } /** * restore the last graphic state */ function restore() { $this->addContent("\nQ"); } /** * scale * * @param float $s_x scaling factor for width as percent * @param float $s_y scaling factor for height as percent * @param float $x Origin abscisse * @param float $y Origin ordinate */ function scale($s_x, $s_y, $x, $y) { $y = $this->currentPageSize["height"] - $y; $tm = array( $s_x, 0, 0, $s_y, $x * (1 - $s_x), $y * (1 - $s_y) ); $this->transform($tm); } /** * translate * * @param float $t_x movement to the right * @param float $t_y movement to the bottom */ function translate($t_x, $t_y) { $tm = array( 1, 0, 0, 1, $t_x, -$t_y ); $this->transform($tm); } /** * rotate * * @param float $angle angle in degrees for counter-clockwise rotation * @param float $x Origin abscisse * @param float $y Origin ordinate */ function rotate($angle, $x, $y) { $y = $this->currentPageSize["height"] - $y; $a = deg2rad($angle); $cos_a = cos($a); $sin_a = sin($a); $tm = array( $cos_a, -$sin_a, $sin_a, $cos_a, $x - $sin_a * $y - $cos_a * $x, $y - $cos_a * $y + $sin_a * $x, ); $this->transform($tm); } /** * skew * * @param float $angle_x * @param float $angle_y * @param float $x Origin abscisse * @param float $y Origin ordinate */ function skew($angle_x, $angle_y, $x, $y) { $y = $this->currentPageSize["height"] - $y; $tan_x = tan(deg2rad($angle_x)); $tan_y = tan(deg2rad($angle_y)); $tm = array( 1, -$tan_y, -$tan_x, 1, $tan_x * $y, $tan_y * $x, ); $this->transform($tm); } /** * apply graphic transformations * * @param array $tm transformation matrix */ function transform($tm) { $this->addContent(vsprintf("\n %.3F %.3F %.3F %.3F %.3F %.3F cm", $tm)); } /** * add a new page to the document * this also makes the new page the current active object */ function newPage($insert = 0, $id = 0, $pos = 'after') { // if there is a state saved, then go up the stack closing them // then on the new page, re-open them with the right setings if ($this->nStateStack) { for ($i = $this->nStateStack; $i >= 1; $i--) { $this->restoreState($i); } } $this->numObj++; if ($insert) { // the id from the ezPdf class is the id of the contents of the page, not the page object itself // query that object to find the parent $rid = $this->objects[$id]['onPage']; $opt = array('rid' => $rid, 'pos' => $pos); $this->o_page($this->numObj, 'new', $opt); } else { $this->o_page($this->numObj, 'new'); } // if there is a stack saved, then put that onto the page if ($this->nStateStack) { for ($i = 1; $i <= $this->nStateStack; $i++) { $this->saveState($i); } } // and if there has been a stroke or fill color set, then transfer them if (isset($this->currentColor)) { $this->setColor($this->currentColor, true); } if (isset($this->currentStrokeColor)) { $this->setStrokeColor($this->currentStrokeColor, true); } // if there is a line style set, then put this in too if (mb_strlen($this->currentLineStyle, '8bit')) { $this->addContent("\n$this->currentLineStyle"); } // the call to the o_page object set currentContents to the present page, so this can be returned as the page id return $this->currentContents; } /** * output the pdf code, streaming it to the browser * the relevant headers are set so that hopefully the browser will recognise it */ function stream($options = '') { // setting the options allows the adjustment of the headers // values at the moment are: // 'Content-Disposition' => 'filename' - sets the filename, though not too sure how well this will // work as in my trial the browser seems to use the filename of the php file with .pdf on the end // 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this header is not included, off by default // this header seems to have caused some problems despite tha fact that it is supposed to solve // them, so I am leaving it off by default. // 'compress' = > 1 or 0 - apply content stream compression, this is on (1) by default // 'Attachment' => 1 or 0 - if 1, force the browser to open a download dialog if (!is_array($options)) { $options = array(); } if (headers_sent()) { die("Unable to stream pdf: headers already sent"); } $debug = empty($options['compression']); $tmp = ltrim($this->output($debug)); header("Cache-Control: private"); header("Content-type: application/pdf"); //FIXME: I don't know that this is sufficient for determining content length (i.e. what about transport compression?) header("Content-Length: " . mb_strlen($tmp, '8bit')); $fileName = (isset($options['Content-Disposition']) ? $options['Content-Disposition'] : 'file.pdf'); if (!isset($options["Attachment"])) { $options["Attachment"] = true; } $attachment = $options["Attachment"] ? "attachment" : "inline"; // detect the character encoding of the incoming file $encoding = mb_detect_encoding($fileName); $fallbackfilename = mb_convert_encoding($fileName, "ISO-8859-1", $encoding); $encodedfallbackfilename = rawurlencode($fallbackfilename); $encodedfilename = rawurlencode($fileName); header( "Content-Disposition: $attachment; filename=" . $encodedfallbackfilename . "; filename*=UTF-8''$encodedfilename" ); if (isset($options['Accept-Ranges']) && $options['Accept-Ranges'] == 1) { //FIXME: Is this the correct value ... spec says 1#range-unit header("Accept-Ranges: " . mb_strlen($tmp, '8bit')); } echo $tmp; flush(); } /** * return the height in units of the current font in the given size */ function getFontHeight($size) { if (!$this->numFonts) { $this->selectFont($this->defaultFont); } $font = $this->fonts[$this->currentFont]; // for the current font, and the given size, what is the height of the font in user units if (isset($font['Ascender']) && isset($font['Descender'])) { $h = $font['Ascender'] - $font['Descender']; } else { $h = $font['FontBBox'][3] - $font['FontBBox'][1]; } // have to adjust by a font offset for Windows fonts. unfortunately it looks like // the bounding box calculations are wrong and I don't know why. if (isset($font['FontHeightOffset'])) { // For CourierNew from Windows this needs to be -646 to match the // Adobe native Courier font. // // For FreeMono from GNU this needs to be -337 to match the // Courier font. // // Both have been added manually to the .afm and .ufm files. $h += (int)$font['FontHeightOffset']; } return $size * $h / 1000; } function getFontXHeight($size) { if (!$this->numFonts) { $this->selectFont($this->defaultFont); } $font = $this->fonts[$this->currentFont]; // for the current font, and the given size, what is the height of the font in user units if (isset($font['XHeight'])) { $xh = $font['Ascender'] - $font['Descender']; } else { $xh = $this->getFontHeight($size) / 2; } return $size * $xh / 1000; } /** * return the font descender, this will normally return a negative number * if you add this number to the baseline, you get the level of the bottom of the font * it is in the pdf user units */ function getFontDescender($size) { // note that this will most likely return a negative value if (!$this->numFonts) { $this->selectFont($this->defaultFont); } //$h = $this->fonts[$this->currentFont]['FontBBox'][1]; $h = $this->fonts[$this->currentFont]['Descender']; return $size * $h / 1000; } /** * filter the text, this is applied to all text just before being inserted into the pdf document * it escapes the various things that need to be escaped, and so on * * @access private */ function filterText($text, $bom = true, $convert_encoding = true) { if (!$this->numFonts) { $this->selectFont($this->defaultFont); } if ($convert_encoding) { $cf = $this->currentFont; if (isset($this->fonts[$cf]) && $this->fonts[$cf]['isUnicode']) { //$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8'); $text = $this->utf8toUtf16BE($text, $bom); } else { //$text = html_entity_decode($text, ENT_QUOTES); $text = mb_convert_encoding($text, self::$targetEncoding, 'UTF-8'); } } // the chr(13) substitution fixes a bug seen in TCPDF (bug #1421290) return strtr($text, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r')); } /** * given a start position and information about how text is to be laid out, calculate where * on the page the text will end */ private function getTextPosition($x, $y, $angle, $size, $wa, $text) { // given this information return an array containing x and y for the end position as elements 0 and 1 $w = $this->getTextWidth($size, $text); // need to adjust for the number of spaces in this text $words = explode(' ', $text); $nspaces = count($words) - 1; $w += $wa * $nspaces; $a = deg2rad((float)$angle); return array(cos($a) * $w + $x, -sin($a) * $w + $y); } /** * Callback method used by smallCaps * * @param array $matches * * @return string */ function toUpper($matches) { return mb_strtoupper($matches[0]); } function concatMatches($matches) { $str = ""; foreach ($matches as $match) { $str .= $match[0]; } return $str; } /** * add text to the document, at a specified location, size and angle on the page */ function registerText($font, $text) { if (!$this->isUnicode || in_array(mb_strtolower(basename($font)), self::$coreFonts)) { return; } if (!isset($this->stringSubsets[$font])) { $this->stringSubsets[$font] = array(); } $this->stringSubsets[$font] = array_unique( array_merge($this->stringSubsets[$font], $this->utf8toCodePointsArray($text)) ); } /** * add text to the document, at a specified location, size and angle on the page */ function addText($x, $y, $size, $text, $angle = 0, $wordSpaceAdjust = 0, $charSpaceAdjust = 0, $smallCaps = false) { if (!$this->numFonts) { $this->selectFont($this->defaultFont); } $text = str_replace(array("\r", "\n"), "", $text); if ($smallCaps) { preg_match_all("/(\P{Ll}+)/u", $text, $matches, PREG_SET_ORDER); $lower = $this->concatMatches($matches); d($lower); preg_match_all("/(\p{Ll}+)/u", $text, $matches, PREG_SET_ORDER); $other = $this->concatMatches($matches); d($other); //$text = preg_replace_callback("/\p{Ll}/u", array($this, "toUpper"), $text); } // if there are any open callbacks, then they should be called, to show the start of the line if ($this->nCallback > 0) { for ($i = $this->nCallback; $i > 0; $i--) { // call each function $info = array( 'x' => $x, 'y' => $y, 'angle' => $angle, 'status' => 'sol', 'p' => $this->callback[$i]['p'], 'nCallback' => $this->callback[$i]['nCallback'], 'height' => $this->callback[$i]['height'], 'descender' => $this->callback[$i]['descender'] ); $func = $this->callback[$i]['f']; $this->$func($info); } } if ($angle == 0) { $this->addContent(sprintf("\nBT %.3F %.3F Td", $x, $y)); } else { $a = deg2rad((float)$angle); $this->addContent( sprintf("\nBT %.3F %.3F %.3F %.3F %.3F %.3F Tm", cos($a), -sin($a), sin($a), cos($a), $x, $y) ); } if ($wordSpaceAdjust != 0 || $wordSpaceAdjust != $this->wordSpaceAdjust) { $this->wordSpaceAdjust = $wordSpaceAdjust; $this->addContent(sprintf(" %.3F Tw", $wordSpaceAdjust)); } if ($charSpaceAdjust != 0 || $charSpaceAdjust != $this->charSpaceAdjust) { $this->charSpaceAdjust = $charSpaceAdjust; $this->addContent(sprintf(" %.3F Tc", $charSpaceAdjust)); } $len = mb_strlen($text); $start = 0; if ($start < $len) { $part = $text; // OAR - Don't need this anymore, given that $start always equals zero. substr($text, $start); $place_text = $this->filterText($part, false); // modify unicode text so that extra word spacing is manually implemented (bug #) $cf = $this->currentFont; if ($this->fonts[$cf]['isUnicode'] && $wordSpaceAdjust != 0) { $space_scale = 1000 / $size; //$place_text = str_replace(' ', ') ( ) '.($this->getTextWidth($size, chr(32), $wordSpaceAdjust)*-75).' (', $place_text); $place_text = str_replace(' ', ' ) ' . (-round($space_scale * $wordSpaceAdjust)) . ' (', $place_text); } $this->addContent(" /F$this->currentFontNum " . sprintf('%.1F Tf ', $size)); $this->addContent(" [($place_text)] TJ"); } $this->addContent(' ET'); // if there are any open callbacks, then they should be called, to show the end of the line if ($this->nCallback > 0) { for ($i = $this->nCallback; $i > 0; $i--) { // call each function $tmp = $this->getTextPosition($x, $y, $angle, $size, $wordSpaceAdjust, $text); $info = array( 'x' => $tmp[0], 'y' => $tmp[1], 'angle' => $angle, 'status' => 'eol', 'p' => $this->callback[$i]['p'], 'nCallback' => $this->callback[$i]['nCallback'], 'height' => $this->callback[$i]['height'], 'descender' => $this->callback[$i]['descender'] ); $func = $this->callback[$i]['f']; $this->$func($info); } } } /** * calculate how wide a given text string will be on a page, at a given size. * this can be called externally, but is also used by the other class functions */ function getTextWidth($size, $text, $word_spacing = 0, $char_spacing = 0) { static $ord_cache = array(); // this function should not change any of the settings, though it will need to // track any directives which change during calculation, so copy them at the start // and put them back at the end. $store_currentTextState = $this->currentTextState; if (!$this->numFonts) { $this->selectFont($this->defaultFont); } $text = str_replace(array("\r", "\n"), "", $text); // converts a number or a float to a string so it can get the width $text = "$text"; // hmm, this is where it all starts to get tricky - use the font information to // calculate the width of each character, add them up and convert to user units $w = 0; $cf = $this->currentFont; $current_font = $this->fonts[$cf]; $space_scale = 1000 / ($size > 0 ? $size : 1); $n_spaces = 0; if ($current_font['isUnicode']) { // for Unicode, use the code points array to calculate width rather // than just the string itself $unicode = $this->utf8toCodePointsArray($text); foreach ($unicode as $char) { // check if we have to replace character if (isset($current_font['differences'][$char])) { $char = $current_font['differences'][$char]; } if (isset($current_font['C'][$char])) { $char_width = $current_font['C'][$char]; // add the character width $w += $char_width; // add additional padding for space if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space $w += $word_spacing * $space_scale; $n_spaces++; } } } // add additionnal char spacing if ($char_spacing != 0) { $w += $char_spacing * $space_scale * (count($unicode) + $n_spaces); } } else { // If CPDF is in Unicode mode but the current font does not support Unicode we need to convert the character set to Windows-1252 if ($this->isUnicode) { $text = mb_convert_encoding($text, 'Windows-1252', 'UTF-8'); } $len = mb_strlen($text, 'Windows-1252'); for ($i = 0; $i < $len; $i++) { $c = $text[$i]; $char = isset($ord_cache[$c]) ? $ord_cache[$c] : ($ord_cache[$c] = ord($c)); // check if we have to replace character if (isset($current_font['differences'][$char])) { $char = $current_font['differences'][$char]; } if (isset($current_font['C'][$char])) { $char_width = $current_font['C'][$char]; // add the character width $w += $char_width; // add additional padding for space if (isset($current_font['codeToName'][$char]) && $current_font['codeToName'][$char] === 'space') { // Space $w += $word_spacing * $space_scale; $n_spaces++; } } } // add additionnal char spacing if ($char_spacing != 0) { $w += $char_spacing * $space_scale * ($len + $n_spaces); } } $this->currentTextState = $store_currentTextState; $this->setCurrentFont(); return $w * $size / 1000; } /** * this will be called at a new page to return the state to what it was on the * end of the previous page, before the stack was closed down * This is to get around not being able to have open 'q' across pages * */ function saveState($pageEnd = 0) { if ($pageEnd) { // this will be called at a new page to return the state to what it was on the // end of the previous page, before the stack was closed down // This is to get around not being able to have open 'q' across pages $opt = $this->stateStack[$pageEnd]; // ok to use this as stack starts numbering at 1 $this->setColor($opt['col'], true); $this->setStrokeColor($opt['str'], true); $this->addContent("\n" . $opt['lin']); // $this->currentLineStyle = $opt['lin']; } else { $this->nStateStack++; $this->stateStack[$this->nStateStack] = array( 'col' => $this->currentColor, 'str' => $this->currentStrokeColor, 'lin' => $this->currentLineStyle ); } $this->save(); } /** * restore a previously saved state */ function restoreState($pageEnd = 0) { if (!$pageEnd) { $n = $this->nStateStack; $this->currentColor = $this->stateStack[$n]['col']; $this->currentStrokeColor = $this->stateStack[$n]['str']; $this->addContent("\n" . $this->stateStack[$n]['lin']); $this->currentLineStyle = $this->stateStack[$n]['lin']; $this->stateStack[$n] = null; unset($this->stateStack[$n]); $this->nStateStack--; } $this->restore(); } /** * make a loose object, the output will go into this object, until it is closed, then will revert to * the current one. * this object will not appear until it is included within a page. * the function will return the object number */ function openObject() { $this->nStack++; $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); // add a new object of the content type, to hold the data flow $this->numObj++; $this->o_contents($this->numObj, 'new'); $this->currentContents = $this->numObj; $this->looseObjects[$this->numObj] = 1; return $this->numObj; } /** * open an existing object for editing */ function reopenObject($id) { $this->nStack++; $this->stack[$this->nStack] = array('c' => $this->currentContents, 'p' => $this->currentPage); $this->currentContents = $id; // also if this object is the primary contents for a page, then set the current page to its parent if (isset($this->objects[$id]['onPage'])) { $this->currentPage = $this->objects[$id]['onPage']; } } /** * close an object */ function closeObject() { // close the object, as long as there was one open in the first place, which will be indicated by // an objectId on the stack. if ($this->nStack > 0) { $this->currentContents = $this->stack[$this->nStack]['c']; $this->currentPage = $this->stack[$this->nStack]['p']; $this->nStack--; // easier to probably not worry about removing the old entries, they will be overwritten // if there are new ones. } } /** * stop an object from appearing on pages from this point on */ function stopObject($id) { // if an object has been appearing on pages up to now, then stop it, this page will // be the last one that could contian it. if (isset($this->addLooseObjects[$id])) { $this->addLooseObjects[$id] = ''; } } /** * after an object has been created, it wil only show if it has been added, using this function. */ function addObject($id, $options = 'add') { // add the specified object to the page if (isset($this->looseObjects[$id]) && $this->currentContents != $id) { // then it is a valid object, and it is not being added to itself switch ($options) { case 'all': // then this object is to be added to this page (done in the next block) and // all future new pages. $this->addLooseObjects[$id] = 'all'; case 'add': if (isset($this->objects[$this->currentContents]['onPage'])) { // then the destination contents is the primary for the page // (though this object is actually added to that page) $this->o_page($this->objects[$this->currentContents]['onPage'], 'content', $id); } break; case 'even': $this->addLooseObjects[$id] = 'even'; $pageObjectId = $this->objects[$this->currentContents]['onPage']; if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 0) { $this->addObject($id); // hacky huh :) } break; case 'odd': $this->addLooseObjects[$id] = 'odd'; $pageObjectId = $this->objects[$this->currentContents]['onPage']; if ($this->objects[$pageObjectId]['info']['pageNum'] % 2 == 1) { $this->addObject($id); // hacky huh :) } break; case 'next': $this->addLooseObjects[$id] = 'all'; break; case 'nexteven': $this->addLooseObjects[$id] = 'even'; break; case 'nextodd': $this->addLooseObjects[$id] = 'odd'; break; } } } /** * return a storable representation of a specific object */ function serializeObject($id) { if (array_key_exists($id, $this->objects)) { return serialize($this->objects[$id]); } } /** * restore an object from its stored representation. returns its new object id. */ function restoreSerializedObject($obj) { $obj_id = $this->openObject(); $this->objects[$obj_id] = unserialize($obj); $this->closeObject(); return $obj_id; } /** * add content to the documents info object */ function addInfo($label, $value = 0) { // this will only work if the label is one of the valid ones. // modify this so that arrays can be passed as well. // if $label is an array then assume that it is key => value pairs // else assume that they are both scalar, anything else will probably error if (is_array($label)) { foreach ($label as $l => $v) { $this->o_info($this->infoObject, $l, $v); } } else { $this->o_info($this->infoObject, $label, $value); } } /** * set the viewer preferences of the document, it is up to the browser to obey these. */ function setPreferences($label, $value = 0) { // this will only work if the label is one of the valid ones. if (is_array($label)) { foreach ($label as $l => $v) { $this->o_catalog($this->catalogId, 'viewerPreferences', array($l => $v)); } } else { $this->o_catalog($this->catalogId, 'viewerPreferences', array($label => $value)); } } /** * extract an integer from a position in a byte stream */ private function getBytes(&$data, $pos, $num) { // return the integer represented by $num bytes from $pos within $data $ret = 0; for ($i = 0; $i < $num; $i++) { $ret *= 256; $ret += ord($data[$pos + $i]); } return $ret; } /** * Check if image already added to pdf image directory. * If yes, need not to create again (pass empty data) */ function image_iscached($imgname) { return isset($this->imagelist[$imgname]); } /** * add a PNG image into the document, from a GD object * this should work with remote files * * @param string $file The PNG file * @param float $x X position * @param float $y Y position * @param float $w Width * @param float $h Height * @param resource $img A GD resource * @param bool $is_mask true if the image is a mask * @param bool $mask true if the image is masked */ function addImagePng($file, $x, $y, $w = 0.0, $h = 0.0, &$img, $is_mask = false, $mask = null) { if (!function_exists("imagepng")) { throw new Exception("The PHP GD extension is required, but is not installed."); } //if already cached, need not to read again if (isset($this->imagelist[$file])) { $data = null; } else { // Example for transparency handling on new image. Retain for current image // $tIndex = imagecolortransparent($img); // if ($tIndex > 0) { // $tColor = imagecolorsforindex($img, $tIndex); // $new_tIndex = imagecolorallocate($new_img, $tColor['red'], $tColor['green'], $tColor['blue']); // imagefill($new_img, 0, 0, $new_tIndex); // imagecolortransparent($new_img, $new_tIndex); // } // blending mode (literal/blending) on drawing into current image. not relevant when not saved or not drawn //imagealphablending($img, true); //default, but explicitely set to ensure pdf compatibility imagesavealpha($img, false/*!$is_mask && !$mask*/); $error = 0; ob_start(); @imagepng($img); $data = ob_get_clean(); if ($data == '') { $error = 1; $errormsg = 'trouble writing file from GD'; } if ($error) { $this->addMessage('PNG error - (' . $file . ') ' . $errormsg); return; } } //End isset($this->imagelist[$file]) (png Duplicate removal) $this->addPngFromBuf($file, $x, $y, $w, $h, $data, $is_mask, $mask); } protected function addImagePngAlpha($file, $x, $y, $w, $h, $byte) { // generate images $img = imagecreatefrompng($file); if ($img === false) { return; } // FIXME The pixel transformation doesn't work well with 8bit PNGs $eight_bit = ($byte & 4) !== 4; $wpx = imagesx($img); $hpx = imagesy($img); imagesavealpha($img, false); // create temp alpha file $tempfile_alpha = tempnam($this->tmp, "cpdf_img_"); @unlink($tempfile_alpha); $tempfile_alpha = "$tempfile_alpha.png"; // create temp plain file $tempfile_plain = tempnam($this->tmp, "cpdf_img_"); @unlink($tempfile_plain); $tempfile_plain = "$tempfile_plain.png"; $imgalpha = imagecreate($wpx, $hpx); imagesavealpha($imgalpha, false); // generate gray scale palette (0 -> 255) for ($c = 0; $c < 256; ++$c) { imagecolorallocate($imgalpha, $c, $c, $c); } // Use PECL gmagick + Graphics Magic to process transparent PNG images if (extension_loaded("gmagick")) { $gmagick = new Gmagick($file); $gmagick->setimageformat('png'); // Get opacity channel (negative of alpha channel) $alpha_channel_neg = clone $gmagick; $alpha_channel_neg->separateimagechannel(Gmagick::CHANNEL_OPACITY); // Negate opacity channel $alpha_channel = new Gmagick(); $alpha_channel->newimage($wpx, $hpx, "#FFFFFF", "png"); $alpha_channel->compositeimage($alpha_channel_neg, Gmagick::COMPOSITE_DIFFERENCE, 0, 0); $alpha_channel->separateimagechannel(Gmagick::CHANNEL_RED); $alpha_channel->writeimage($tempfile_alpha); // Cast to 8bit+palette $imgalpha_ = imagecreatefrompng($tempfile_alpha); imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); imagedestroy($imgalpha_); imagepng($imgalpha, $tempfile_alpha); // Make opaque image $color_channels = new Gmagick(); $color_channels->newimage($wpx, $hpx, "#FFFFFF", "png"); $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYRED, 0, 0); $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYGREEN, 0, 0); $color_channels->compositeimage($gmagick, Gmagick::COMPOSITE_COPYBLUE, 0, 0); $color_channels->writeimage($tempfile_plain); $imgplain = imagecreatefrompng($tempfile_plain); } // Use PECL imagick + ImageMagic to process transparent PNG images elseif (extension_loaded("imagick")) { // Native cloning was added to pecl-imagick in svn commit 263814 // the first version containing it was 3.0.1RC1 static $imagickClonable = null; if ($imagickClonable === null) { $imagickClonable = version_compare(phpversion('imagick'), '3.0.1rc1') > 0; } $imagick = new Imagick($file); $imagick->setFormat('png'); // Get opacity channel (negative of alpha channel) $alpha_channel = $imagickClonable ? clone $imagick : $imagick->clone(); $alpha_channel->separateImageChannel(Imagick::CHANNEL_ALPHA); $alpha_channel->negateImage(true); $alpha_channel->writeImage($tempfile_alpha); // Cast to 8bit+palette $imgalpha_ = imagecreatefrompng($tempfile_alpha); imagecopy($imgalpha, $imgalpha_, 0, 0, 0, 0, $wpx, $hpx); imagedestroy($imgalpha_); imagepng($imgalpha, $tempfile_alpha); // Make opaque image $color_channels = new Imagick(); $color_channels->newImage($wpx, $hpx, "#FFFFFF", "png"); $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYRED, 0, 0); $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYGREEN, 0, 0); $color_channels->compositeImage($imagick, Imagick::COMPOSITE_COPYBLUE, 0, 0); $color_channels->writeImage($tempfile_plain); $imgplain = imagecreatefrompng($tempfile_plain); } else { // allocated colors cache $allocated_colors = array(); // extract alpha channel for ($xpx = 0; $xpx < $wpx; ++$xpx) { for ($ypx = 0; $ypx < $hpx; ++$ypx) { $color = imagecolorat($img, $xpx, $ypx); $col = imagecolorsforindex($img, $color); $alpha = $col['alpha']; if ($eight_bit) { // with gamma correction $gammacorr = 2.2; $pixel = pow((((127 - $alpha) * 255 / 127) / 255), $gammacorr) * 255; } else { // without gamma correction $pixel = (127 - $alpha) * 2; $key = $col['red'] . $col['green'] . $col['blue']; if (!isset($allocated_colors[$key])) { $pixel_img = imagecolorallocate($img, $col['red'], $col['green'], $col['blue']); $allocated_colors[$key] = $pixel_img; } else { $pixel_img = $allocated_colors[$key]; } imagesetpixel($img, $xpx, $ypx, $pixel_img); } imagesetpixel($imgalpha, $xpx, $ypx, $pixel); } } // extract image without alpha channel $imgplain = imagecreatetruecolor($wpx, $hpx); imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx); imagedestroy($img); imagepng($imgalpha, $tempfile_alpha); imagepng($imgplain, $tempfile_plain); } // embed mask image $this->addImagePng($tempfile_alpha, $x, $y, $w, $h, $imgalpha, true); imagedestroy($imgalpha); // embed image, masked with previously embedded mask $this->addImagePng($tempfile_plain, $x, $y, $w, $h, $imgplain, false, true); imagedestroy($imgplain); // remove temp files unlink($tempfile_alpha); unlink($tempfile_plain); } /** * add a PNG image into the document, from a file * this should work with remote files */ function addPngFromFile($file, $x, $y, $w = 0, $h = 0) { if (!function_exists("imagecreatefrompng")) { throw new Exception("The PHP GD extension is required, but is not installed."); } //if already cached, need not to read again if (isset($this->imagelist[$file])) { $img = null; } else { $info = file_get_contents($file, false, null, 24, 5); $meta = unpack("CbitDepth/CcolorType/CcompressionMethod/CfilterMethod/CinterlaceMethod", $info); $bit_depth = $meta["bitDepth"]; $color_type = $meta["colorType"]; // http://www.w3.org/TR/PNG/#11IHDR // 3 => indexed // 4 => greyscale with alpha // 6 => fullcolor with alpha $is_alpha = in_array($color_type, array(4, 6)) || ($color_type == 3 && $bit_depth != 4); if ($is_alpha) { // exclude grayscale alpha return $this->addImagePngAlpha($file, $x, $y, $w, $h, $color_type); } //png files typically contain an alpha channel. //pdf file format or class.pdf does not support alpha blending. //on alpha blended images, more transparent areas have a color near black. //This appears in the result on not storing the alpha channel. //Correct would be the box background image or its parent when transparent. //But this would make the image dependent on the background. //Therefore create an image with white background and copy in //A more natural background than black is white. //Therefore create an empty image with white background and merge the //image in with alpha blending. $imgtmp = @imagecreatefrompng($file); if (!$imgtmp) { return; } $sx = imagesx($imgtmp); $sy = imagesy($imgtmp); $img = imagecreatetruecolor($sx, $sy); imagealphablending($img, true); // @todo is it still needed ?? $ti = imagecolortransparent($imgtmp); if ($ti >= 0) { $tc = imagecolorsforindex($imgtmp, $ti); $ti = imagecolorallocate($img, $tc['red'], $tc['green'], $tc['blue']); imagefill($img, 0, 0, $ti); imagecolortransparent($img, $ti); } else { imagefill($img, 1, 1, imagecolorallocate($img, 255, 255, 255)); } imagecopy($img, $imgtmp, 0, 0, 0, 0, $sx, $sy); imagedestroy($imgtmp); } $this->addImagePng($file, $x, $y, $w, $h, $img); if ($img) { imagedestroy($img); } } /** * add a PNG image into the document, from a memory buffer of the file */ function addPngFromBuf($file, $x, $y, $w = 0.0, $h = 0.0, &$data, $is_mask = false, $mask = null) { if (isset($this->imagelist[$file])) { $data = null; $info['width'] = $this->imagelist[$file]['w']; $info['height'] = $this->imagelist[$file]['h']; $label = $this->imagelist[$file]['label']; } else { if ($data == null) { $this->addMessage('addPngFromBuf error - data not present!'); return; } $error = 0; if (!$error) { $header = chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10); if (mb_substr($data, 0, 8, '8bit') != $header) { $error = 1; $errormsg = 'this file does not have a valid header'; } } if (!$error) { // set pointer $p = 8; $len = mb_strlen($data, '8bit'); // cycle through the file, identifying chunks $haveHeader = 0; $info = array(); $idata = ''; $pdata = ''; while ($p < $len) { $chunkLen = $this->getBytes($data, $p, 4); $chunkType = mb_substr($data, $p + 4, 4, '8bit'); switch ($chunkType) { case 'IHDR': // this is where all the file information comes from $info['width'] = $this->getBytes($data, $p + 8, 4); $info['height'] = $this->getBytes($data, $p + 12, 4); $info['bitDepth'] = ord($data[$p + 16]); $info['colorType'] = ord($data[$p + 17]); $info['compressionMethod'] = ord($data[$p + 18]); $info['filterMethod'] = ord($data[$p + 19]); $info['interlaceMethod'] = ord($data[$p + 20]); //print_r($info); $haveHeader = 1; if ($info['compressionMethod'] != 0) { $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile unsupported compression method ' . $file . ']'; } $errormsg = 'unsupported compression method'; } if ($info['filterMethod'] != 0) { $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile unsupported filter method ' . $file . ']'; } $errormsg = 'unsupported filter method'; } break; case 'PLTE': $pdata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); break; case 'IDAT': $idata .= mb_substr($data, $p + 8, $chunkLen, '8bit'); break; case 'tRNS': //this chunk can only occur once and it must occur after the PLTE chunk and before IDAT chunk //print "tRNS found, color type = ".$info['colorType']."\n"; $transparency = array(); switch ($info['colorType']) { // indexed color, rbg case 3: /* corresponding to entries in the plte chunk Alpha for palette index 0: 1 byte Alpha for palette index 1: 1 byte ...etc... */ // there will be one entry for each palette entry. up until the last non-opaque entry. // set up an array, stretching over all palette entries which will be o (opaque) or 1 (transparent) $transparency['type'] = 'indexed'; $trans = 0; for ($i = $chunkLen; $i >= 0; $i--) { if (ord($data[$p + 8 + $i]) == 0) { $trans = $i; } } $transparency['data'] = $trans; break; // grayscale case 0: /* corresponding to entries in the plte chunk Gray: 2 bytes, range 0 .. (2^bitdepth)-1 */ // $transparency['grayscale'] = $this->PRVT_getBytes($data,$p+8,2); // g = grayscale $transparency['type'] = 'indexed'; $transparency['data'] = ord($data[$p + 8 + 1]); break; // truecolor case 2: /* corresponding to entries in the plte chunk Red: 2 bytes, range 0 .. (2^bitdepth)-1 Green: 2 bytes, range 0 .. (2^bitdepth)-1 Blue: 2 bytes, range 0 .. (2^bitdepth)-1 */ $transparency['r'] = $this->getBytes($data, $p + 8, 2); // r from truecolor $transparency['g'] = $this->getBytes($data, $p + 10, 2); // g from truecolor $transparency['b'] = $this->getBytes($data, $p + 12, 2); // b from truecolor $transparency['type'] = 'color-key'; break; //unsupported transparency type default: if (DEBUGPNG) { print '[addPngFromFile unsupported transparency type ' . $file . ']'; } break; } // KS End new code break; default: break; } $p += $chunkLen + 12; } if (!$haveHeader) { $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile information header is missing ' . $file . ']'; } $errormsg = 'information header is missing'; } if (isset($info['interlaceMethod']) && $info['interlaceMethod']) { $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile no support for interlaced images in pdf ' . $file . ']'; } $errormsg = 'There appears to be no support for interlaced images in pdf.'; } } if (!$error && $info['bitDepth'] > 8) { $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile bit depth of 8 or less is supported ' . $file . ']'; } $errormsg = 'only bit depth of 8 or less is supported'; } if (!$error) { switch ($info['colorType']) { case 3: $color = 'DeviceRGB'; $ncolor = 1; break; case 2: $color = 'DeviceRGB'; $ncolor = 3; break; case 0: $color = 'DeviceGray'; $ncolor = 1; break; default: $error = 1; //debugpng if (DEBUGPNG) { print '[addPngFromFile alpha channel not supported: ' . $info['colorType'] . ' ' . $file . ']'; } $errormsg = 'transparancey alpha channel not supported, transparency only supported for palette images.'; } } if ($error) { $this->addMessage('PNG error - (' . $file . ') ' . $errormsg); return; } //print_r($info); // so this image is ok... add it in. $this->numImages++; $im = $this->numImages; $label = "I$im"; $this->numObj++; // $this->o_image($this->numObj,'new',array('label' => $label,'data' => $idata,'iw' => $w,'ih' => $h,'type' => 'png','ic' => $info['width'])); $options = array( 'label' => $label, 'data' => $idata, 'bitsPerComponent' => $info['bitDepth'], 'pdata' => $pdata, 'iw' => $info['width'], 'ih' => $info['height'], 'type' => 'png', 'color' => $color, 'ncolor' => $ncolor, 'masked' => $mask, 'isMask' => $is_mask ); if (isset($transparency)) { $options['transparency'] = $transparency; } $this->o_image($this->numObj, 'new', $options); $this->imagelist[$file] = array('label' => $label, 'w' => $info['width'], 'h' => $info['height']); } if ($is_mask) { return; } if ($w <= 0 && $h <= 0) { $w = $info['width']; $h = $info['height']; } if ($w <= 0) { $w = $h / $info['height'] * $info['width']; } if ($h <= 0) { $h = $w * $info['height'] / $info['width']; } $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ", $w, $h, $x, $y, $label)); } /** * add a JPEG image into the document, from a file */ function addJpegFromFile($img, $x, $y, $w = 0, $h = 0) { // attempt to add a jpeg image straight from a file, using no GD commands // note that this function is unable to operate on a remote file. if (!file_exists($img)) { return; } if ($this->image_iscached($img)) { $data = null; $imageWidth = $this->imagelist[$img]['w']; $imageHeight = $this->imagelist[$img]['h']; $channels = $this->imagelist[$img]['c']; } else { $tmp = getimagesize($img); $imageWidth = $tmp[0]; $imageHeight = $tmp[1]; if (isset($tmp['channels'])) { $channels = $tmp['channels']; } else { $channels = 3; } $data = file_get_contents($img); } if ($w <= 0 && $h <= 0) { $w = $imageWidth; } if ($w == 0) { $w = $h / $imageHeight * $imageWidth; } if ($h == 0) { $h = $w * $imageHeight / $imageWidth; } $this->addJpegImage_common($data, $x, $y, $w, $h, $imageWidth, $imageHeight, $channels, $img); } /** * common code used by the two JPEG adding functions */ private function addJpegImage_common( &$data, $x, $y, $w = 0, $h = 0, $imageWidth, $imageHeight, $channels = 3, $imgname ) { if ($this->image_iscached($imgname)) { $label = $this->imagelist[$imgname]['label']; //debugpng //if (DEBUGPNG) print '[addJpegImage_common Duplicate '.$imgname.']'; } else { if ($data == null) { $this->addMessage('addJpegImage_common error - (' . $imgname . ') data not present!'); return; } // note that this function is not to be called externally // it is just the common code between the GD and the file options $this->numImages++; $im = $this->numImages; $label = "I$im"; $this->numObj++; $this->o_image( $this->numObj, 'new', array( 'label' => $label, 'data' => &$data, 'iw' => $imageWidth, 'ih' => $imageHeight, 'channels' => $channels ) ); $this->imagelist[$imgname] = array( 'label' => $label, 'w' => $imageWidth, 'h' => $imageHeight, 'c' => $channels ); } $this->addContent(sprintf("\nq\n%.3F 0 0 %.3F %.3F %.3F cm /%s Do\nQ ", $w, $h, $x, $y, $label)); } /** * specify where the document should open when it first starts */ function openHere($style, $a = 0, $b = 0, $c = 0) { // this function will open the document at a specified page, in a specified style // the values for style, and the required paramters are: // 'XYZ' left, top, zoom // 'Fit' // 'FitH' top // 'FitV' left // 'FitR' left,bottom,right // 'FitB' // 'FitBH' top // 'FitBV' left $this->numObj++; $this->o_destination( $this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c) ); $id = $this->catalogId; $this->o_catalog($id, 'openHere', $this->numObj); } /** * Add JavaScript code to the PDF document * * @param string $code * * @return void */ function addJavascript($code) { $this->javascript .= $code; } /** * create a labelled destination within the document */ function addDestination($label, $style, $a = 0, $b = 0, $c = 0) { // associates the given label with the destination, it is done this way so that a destination can be specified after // it has been linked to // styles are the same as the 'openHere' function $this->numObj++; $this->o_destination( $this->numObj, 'new', array('page' => $this->currentPage, 'type' => $style, 'p1' => $a, 'p2' => $b, 'p3' => $c) ); $id = $this->numObj; // store the label->idf relationship, note that this means that labels can be used only once $this->destinations["$label"] = $id; } /** * define font families, this is used to initialize the font families for the default fonts * and for the user to add new ones for their fonts. The default bahavious can be overridden should * that be desired. */ function setFontFamily($family, $options = '') { if (!is_array($options)) { if ($family === 'init') { // set the known family groups // these font families will be used to enable bold and italic markers to be included // within text streams. html forms will be used... $this->fontFamilies['Helvetica.afm'] = array( 'b' => 'Helvetica-Bold.afm', 'i' => 'Helvetica-Oblique.afm', 'bi' => 'Helvetica-BoldOblique.afm', 'ib' => 'Helvetica-BoldOblique.afm' ); $this->fontFamilies['Courier.afm'] = array( 'b' => 'Courier-Bold.afm', 'i' => 'Courier-Oblique.afm', 'bi' => 'Courier-BoldOblique.afm', 'ib' => 'Courier-BoldOblique.afm' ); $this->fontFamilies['Times-Roman.afm'] = array( 'b' => 'Times-Bold.afm', 'i' => 'Times-Italic.afm', 'bi' => 'Times-BoldItalic.afm', 'ib' => 'Times-BoldItalic.afm' ); } } else { // the user is trying to set a font family // note that this can also be used to set the base ones to something else if (mb_strlen($family)) { $this->fontFamilies[$family] = $options; } } } /** * used to add messages for use in debugging */ function addMessage($message) { $this->messages .= $message . "\n"; } /** * a few functions which should allow the document to be treated transactionally. */ function transaction($action) { switch ($action) { case 'start': // store all the data away into the checkpoint variable $data = get_object_vars($this); $this->checkpoint = $data; unset($data); break; case 'commit': if (is_array($this->checkpoint) && isset($this->checkpoint['checkpoint'])) { $tmp = $this->checkpoint['checkpoint']; $this->checkpoint = $tmp; unset($tmp); } else { $this->checkpoint = ''; } break; case 'rewind': // do not destroy the current checkpoint, but move us back to the state then, so that we can try again if (is_array($this->checkpoint)) { // can only abort if were inside a checkpoint $tmp = $this->checkpoint; foreach ($tmp as $k => $v) { if ($k !== 'checkpoint') { $this->$k = $v; } } unset($tmp); } break; case 'abort': if (is_array($this->checkpoint)) { // can only abort if were inside a checkpoint $tmp = $this->checkpoint; foreach ($tmp as $k => $v) { $this->$k = $v; } unset($tmp); } break; } } } PKUZmrHY5Y5src/Svg/Surface/SurfaceCpdf.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg\Surface; use Svg\Document; use Svg\Style; class SurfaceCpdf implements SurfaceInterface { const DEBUG = false; /** @var \Svg\Surface\CPdf */ private $canvas; private $width; private $height; /** @var Style */ private $style; public function __construct(Document $doc, $canvas = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $dimensions = $doc->getDimensions(); $w = $dimensions["width"]; $h = $dimensions["height"]; if (!$canvas) { $canvas = new \Svg\Surface\CPdf(array(0, 0, $w, $h)); $refl = new \ReflectionClass($canvas); $canvas->fontcache = realpath(dirname($refl->getFileName()) . "/../../fonts/")."/"; } // Flip PDF coordinate system so that the origin is in // the top left rather than the bottom left $canvas->transform(array( 1, 0, 0, -1, 0, $h )); $this->width = $w; $this->height = $h; $this->canvas = $canvas; } function out() { if (self::DEBUG) echo __FUNCTION__ . "\n"; return $this->canvas->output(); } public function save() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->save(); } public function restore() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->restore(); } public function scale($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->transform($x, 0, 0, $y, 0, 0); } public function rotate($angle) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $a = deg2rad($angle); $cos_a = cos($a); $sin_a = sin($a); $this->transform( $cos_a, $sin_a, -$sin_a, $cos_a, 0, 0 ); } public function translate($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->transform( 1, 0, 0, 1, $x, $y ); } public function transform($a, $b, $c, $d, $e, $f) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->transform(array($a, $b, $c, $d, $e, $f)); } public function beginPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; // TODO: Implement beginPath() method. } public function closePath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->closePath(); } public function fillStroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fillStroke(); } public function clip() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->clip(); } public function fillText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->addText($x, $y, $this->style->fontSize, $text); } public function strokeText($text, $x, $y, $maxWidth = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->addText($x, $y, $this->style->fontSize, $text); } public function drawImage($image, $sx, $sy, $sw = null, $sh = null, $dx = null, $dy = null, $dw = null, $dh = null) { if (self::DEBUG) echo __FUNCTION__ . "\n"; if (strpos($image, "data:") === 0) { $parts = explode(',', $image, 2); $data = $parts[1]; $base64 = false; $token = strtok($parts[0], ';'); while ($token !== false) { if ($token == 'base64') { $base64 = true; } $token = strtok(';'); } if ($base64) { $data = base64_decode($data); } } else { $data = file_get_contents($image); } $image = tempnam(sys_get_temp_dir(), "svg"); file_put_contents($image, $data); $img = $this->image($image, $sx, $sy, $sw, $sh, "normal"); unlink($image); } public static function getimagesize($filename) { static $cache = array(); if (isset($cache[$filename])) { return $cache[$filename]; } list($width, $height, $type) = getimagesize($filename); if ($width == null || $height == null) { $data = file_get_contents($filename, null, null, 0, 26); if (substr($data, 0, 2) === "BM") { $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data); $width = (int)$meta['width']; $height = (int)$meta['height']; $type = IMAGETYPE_BMP; } } return $cache[$filename] = array($width, $height, $type); } function image($img, $x, $y, $w, $h, $resolution = "normal") { list($width, $height, $type) = $this->getimagesize($img); switch ($type) { case IMAGETYPE_JPEG: $this->canvas->addJpegFromFile($img, $x, $y - $h, $w, $h); break; case IMAGETYPE_GIF: case IMAGETYPE_BMP: // @todo use cache for BMP and GIF $img = $this->_convert_gif_bmp_to_png($img, $type); case IMAGETYPE_PNG: $this->canvas->addPngFromFile($img, $x, $y - $h, $w, $h); break; default: } } public function lineTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->lineTo($x, $y); } public function moveTo($x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->moveTo($x, $y); } public function quadraticCurveTo($cpx, $cpy, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; // FIXME not accurate $this->canvas->quadTo($cpx, $cpy, $x, $y); } public function bezierCurveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->curveTo($cp1x, $cp1y, $cp2x, $cp2y, $x, $y); } public function arcTo($x1, $y1, $x2, $y2, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; } public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise = false) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, $startAngle, $endAngle, false, false, false, true); } public function circle($x, $y, $radius) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->ellipse($x, $y, $radius, $radius, 0, 8, 0, 360, true, false, false, false); } public function ellipse($x, $y, $radiusX, $radiusY, $rotation, $startAngle, $endAngle, $anticlockwise) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->ellipse($x, $y, $radiusX, $radiusY, 0, 8, 0, 360, false, false, false, false); } public function fillRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->fill(); } public function rect($x, $y, $w, $h, $rx = 0, $ry = 0) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $canvas = $this->canvas; if ($rx <= 0.000001/* && $ry <= 0.000001*/) { $canvas->rect($x, $y, $w, $h); return; } $rx = min($rx, $w / 2); $rx = min($rx, $h / 2); /* Define a path for a rectangle with corners rounded by a given radius. * Start from the lower left corner and proceed counterclockwise. */ $this->moveTo($x + $rx, $y); /* Start of the arc segment in the lower right corner */ $this->lineTo($x + $w - $rx, $y); /* Arc segment in the lower right corner */ $this->arc($x + $w - $rx, $y + $rx, $rx, 270, 360); /* Start of the arc segment in the upper right corner */ $this->lineTo($x + $w, $y + $h - $rx ); /* Arc segment in the upper right corner */ $this->arc($x + $w - $rx, $y + $h - $rx, $rx, 0, 90); /* Start of the arc segment in the upper left corner */ $this->lineTo($x + $rx, $y + $h); /* Arc segment in the upper left corner */ $this->arc($x + $rx, $y + $h - $rx, $rx, 90, 180); /* Start of the arc segment in the lower left corner */ $this->lineTo($x , $y + $rx); /* Arc segment in the lower left corner */ $this->arc($x + $rx, $y + $rx, $rx, 180, 270); } public function fill() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->fill(); } public function strokeRect($x, $y, $w, $h) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->rect($x, $y, $w, $h); $this->stroke(); } public function stroke() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->stroke(); } public function endPath() { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->canvas->endPath(); } public function measureText($text) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $style = $this->getStyle(); $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight); return $this->canvas->getTextWidth($this->getStyle()->fontSize, $text); } public function getStyle() { if (self::DEBUG) echo __FUNCTION__ . "\n"; return $this->style; } public function setStyle(Style $style) { if (self::DEBUG) echo __FUNCTION__ . "\n"; $this->style = $style; $canvas = $this->canvas; if (is_array($style->stroke) && $stroke = $style->stroke) { $canvas->setStrokeColor(array((float)$stroke[0]/255, (float)$stroke[1]/255, (float)$stroke[2]/255), true); } if (is_array($style->fill) && $fill = $style->fill) { $canvas->setColor(array((float)$fill[0]/255, (float)$fill[1]/255, (float)$fill[2]/255), true); } if ($fillRule = strtolower($style->fillRule)) { $canvas->setFillRule($fillRule); } $opacity = $style->opacity; if ($opacity !== null && $opacity < 1.0) { $canvas->setLineTransparency("Normal", $opacity); $canvas->currentLineTransparency = null; $canvas->setFillTransparency("Normal", $opacity); $canvas->currentFillTransparency = null; } else { $fillOpacity = $style->fillOpacity; if ($fillOpacity !== null && $fillOpacity < 1.0) { $canvas->setFillTransparency("Normal", $fillOpacity); $canvas->currentFillTransparency = null; } $strokeOpacity = $style->strokeOpacity; if ($strokeOpacity !== null && $strokeOpacity < 1.0) { $canvas->setLineTransparency("Normal", $strokeOpacity); $canvas->currentLineTransparency = null; } } $dashArray = null; if ($style->strokeDasharray) { $dashArray = preg_split('/\s*,\s*/', $style->strokeDasharray); } $phase=0; if ($style->strokeDashoffset) { $phase = $style->strokeDashoffset; } $canvas->setLineStyle( $style->strokeWidth, $style->strokeLinecap, $style->strokeLinejoin, $dashArray, $phase ); $this->setFont($style->fontFamily, $style->fontStyle, $style->fontWeight); } public function setFont($family, $style, $weight) { $map = array( "serif" => "Times", "sans-serif" => "Helvetica", "fantasy" => "Symbol", "cursive" => "Times", "monospace" => "Courier", "arial" => "Helvetica", "verdana" => "Helvetica", ); $styleMap = array( 'Helvetica' => array( 'b' => 'Helvetica-Bold', 'i' => 'Helvetica-Oblique', 'bi' => 'Helvetica-BoldOblique', ), 'Courier' => array( 'b' => 'Courier-Bold', 'i' => 'Courier-Oblique', 'bi' => 'Courier-BoldOblique', ), 'Times' => array( '' => 'Times-Roman', 'b' => 'Times-Bold', 'i' => 'Times-Italic', 'bi' => 'Times-BoldItalic', ), ); $family = strtolower($family); $style = strtolower($style); $weight = strtolower($weight); if (isset($map[$family])) { $family = $map[$family]; } if (isset($styleMap[$family])) { $key = ""; if ($weight === "bold" || $weight === "bolder" || (is_numeric($weight) && $weight >= 600)) { $key .= "b"; } if ($style === "italic" || $style === "oblique") { $key .= "i"; } if (isset($styleMap[$family][$key])) { $family = $styleMap[$family][$key]; } } $this->canvas->selectFont("$family.afm"); } } PKUZ>4>GGsrc/Svg/Style.phpnuW+A * @license GNU LGPLv3+ http://www.gnu.org/copyleft/lesser.html */ namespace Svg; use Svg\Tag\AbstractTag; class Style { const TYPE_COLOR = 1; const TYPE_LENGTH = 2; const TYPE_NAME = 3; const TYPE_ANGLE = 4; const TYPE_NUMBER = 5; public $color; public $opacity; public $display; public $fill; public $fillOpacity; public $fillRule; public $stroke; public $strokeOpacity; public $strokeLinecap; public $strokeLinejoin; public $strokeMiterlimit; public $strokeWidth; public $strokeDasharray; public $strokeDashoffset; public $fontFamily = 'serif'; public $fontSize = 12; public $fontWeight = 'normal'; public $fontStyle = 'normal'; public $textAnchor = 'start'; protected function getStyleMap() { return array( 'color' => array('color', self::TYPE_COLOR), 'opacity' => array('opacity', self::TYPE_NUMBER), 'display' => array('display', self::TYPE_NAME), 'fill' => array('fill', self::TYPE_COLOR), 'fill-opacity' => array('fillOpacity', self::TYPE_NUMBER), 'fill-rule' => array('fillRule', self::TYPE_NAME), 'stroke' => array('stroke', self::TYPE_COLOR), 'stroke-dasharray' => array('strokeDasharray', self::TYPE_NAME), 'stroke-dashoffset' => array('strokeDashoffset', self::TYPE_NUMBER), 'stroke-linecap' => array('strokeLinecap', self::TYPE_NAME), 'stroke-linejoin' => array('strokeLinejoin', self::TYPE_NAME), 'stroke-miterlimit' => array('strokeMiterlimit', self::TYPE_NUMBER), 'stroke-opacity' => array('strokeOpacity', self::TYPE_NUMBER), 'stroke-width' => array('strokeWidth', self::TYPE_NUMBER), 'font-family' => array('fontFamily', self::TYPE_NAME), 'font-size' => array('fontSize', self::TYPE_NUMBER), 'font-weight' => array('fontWeight', self::TYPE_NAME), 'font-style' => array('fontStyle', self::TYPE_NAME), 'text-anchor' => array('textAnchor', self::TYPE_NAME), ); } /** * @param $attributes * * @return Style */ public function fromAttributes($attributes) { $this->fillStyles($attributes); if (isset($attributes["style"])) { $styles = self::parseCssStyle($attributes["style"]); $this->fillStyles($styles); } } public function inherit(AbstractTag $tag) { $group = $tag->getParentGroup(); if ($group) { $parent_style = $group->getStyle(); foreach ($parent_style as $_key => $_value) { if ($_value !== null) { $this->$_key = $_value; } } } } public function fromStyleSheets(AbstractTag $tag, $attributes) { $class = isset($attributes["class"]) ? preg_split('/\s+/', trim($attributes["class"])) : null; $stylesheets = $tag->getDocument()->getStyleSheets(); $styles = array(); foreach ($stylesheets as $_sc) { /** @var \Sabberworm\CSS\RuleSet\DeclarationBlock $_decl */ foreach ($_sc->getAllDeclarationBlocks() as $_decl) { /** @var \Sabberworm\CSS\Property\Selector $_selector */ foreach ($_decl->getSelectors() as $_selector) { $_selector = $_selector->getSelector(); // Match class name if ($class !== null) { foreach ($class as $_class) { if ($_selector === ".$_class") { /** @var \Sabberworm\CSS\Rule\Rule $_rule */ foreach ($_decl->getRules() as $_rule) { $styles[$_rule->getRule()] = $_rule->getValue() . ""; } break 2; } } } // Match tag name if ($_selector === $tag->tagName) { /** @var \Sabberworm\CSS\Rule\Rule $_rule */ foreach ($_decl->getRules() as $_rule) { $styles[$_rule->getRule()] = $_rule->getValue() . ""; } break; } } } } $this->fillStyles($styles); } protected function fillStyles($styles) { foreach ($this->getStyleMap() as $from => $spec) { if (isset($styles[$from])) { list($to, $type) = $spec; $value = null; switch ($type) { case self::TYPE_COLOR: $value = self::parseColor($styles[$from]); break; case self::TYPE_NUMBER: $value = ($styles[$from] === null) ? null : (float)$styles[$from]; break; default: $value = $styles[$from]; } if ($value !== null) { $this->$to = $value; } } } } static function parseColor($color) { $color = strtolower(trim($color)); $parts = preg_split('/[^,]\s+/', $color, 2); if (count($parts) == 2) { $color = $parts[1]; } else { $color = $parts[0]; } if ($color === "none") { return "none"; } // SVG color name if (isset(self::$colorNames[$color])) { return self::parseHexColor(self::$colorNames[$color]); } // Hex color if ($color[0] === "#") { return self::parseHexColor($color); } // RGB color if (strpos($color, "rgb") !== false) { return self::getTriplet($color); } // RGB color if (strpos($color, "hsl") !== false) { $triplet = self::getTriplet($color, true); if ($triplet == null) { return null; } list($h, $s, $l) = $triplet; $r = $l; $g = $l; $b = $l; $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s); if ($v > 0) { $m = $l + $l - $v; $sv = ($v - $m) / $v; $h *= 6.0; $sextant = floor($h); $fract = $h - $sextant; $vsf = $v * $sv * $fract; $mid1 = $m + $vsf; $mid2 = $v - $vsf; switch ($sextant) { case 0: $r = $v; $g = $mid1; $b = $m; break; case 1: $r = $mid2; $g = $v; $b = $m; break; case 2: $r = $m; $g = $v; $b = $mid1; break; case 3: $r = $m; $g = $mid2; $b = $v; break; case 4: $r = $mid1; $g = $m; $b = $v; break; case 5: $r = $v; $g = $m; $b = $mid2; break; } } return array( $r * 255.0, $g * 255.0, $b * 255.0, ); } // Gradient if (strpos($color, "url(#") !== false) { $i = strpos($color, "("); $j = strpos($color, ")"); // Bad url format if ($i === false || $j === false) { return null; } return trim(substr($color, $i + 1, $j - $i - 1)); } return null; } static function getTriplet($color, $percent = false) { $i = strpos($color, "("); $j = strpos($color, ")"); // Bad color value if ($i === false || $j === false) { return null; } $triplet = preg_split("/\\s*,\\s*/", trim(substr($color, $i + 1, $j - $i - 1))); if (count($triplet) != 3) { return null; } foreach (array_keys($triplet) as $c) { $triplet[$c] = trim($triplet[$c]); if ($percent) { if ($triplet[$c][strlen($triplet[$c]) - 1] === "%") { $triplet[$c] = floatval($triplet[$c]) / 100; } else { $triplet[$c] = $triplet[$c] / 255; } } else { if ($triplet[$c][strlen($triplet[$c]) - 1] === "%") { $triplet[$c] = round(floatval($triplet[$c]) * 2.55); } } } return $triplet; } static function parseHexColor($hex) { $c = array(0, 0, 0); // #FFFFFF if (isset($hex[6])) { $c[0] = hexdec(substr($hex, 1, 2)); $c[1] = hexdec(substr($hex, 3, 2)); $c[2] = hexdec(substr($hex, 5, 2)); } else { $c[0] = hexdec($hex[1] . $hex[1]); $c[1] = hexdec($hex[2] . $hex[2]); $c[2] = hexdec($hex[3] . $hex[3]); } return $c; } /** * Simple CSS parser * * @param $style * * @return array */ static function parseCssStyle($style) { $matches = array(); preg_match_all("/([a-z-]+)\\s*:\\s*([^;$]+)/si", $style, $matches, PREG_SET_ORDER); $styles = array(); foreach ($matches as $match) { $styles[$match[1]] = $match[2]; } return $styles; } /** * Convert a size to a float * * @param string $size SVG size * @param float $dpi DPI * @param float $referenceSize Reference size * * @return float|null */ static function convertSize($size, $referenceSize = 11.0, $dpi = 96.0) { $size = trim(strtolower($size)); if (is_numeric($size)) { return $size; } if ($pos = strpos($size, "px")) { return floatval(substr($size, 0, $pos)); } if ($pos = strpos($size, "pt")) { return floatval(substr($size, 0, $pos)); } if ($pos = strpos($size, "cm")) { return floatval(substr($size, 0, $pos)) * $dpi; } if ($pos = strpos($size, "%")) { return $referenceSize * substr($size, 0, $pos) / 100; } if ($pos = strpos($size, "em")) { return $referenceSize * substr($size, 0, $pos); } // TODO cm, mm, pc, in, etc return null; } static $colorNames = array( 'antiquewhite' => '#FAEBD7', 'aqua' => '#00FFFF', 'aquamarine' => '#7FFFD4', 'beige' => '#F5F5DC', 'black' => '#000000', 'blue' => '#0000FF', 'brown' => '#A52A2A', 'cadetblue' => '#5F9EA0', 'chocolate' => '#D2691E', 'cornflowerblue' => '#6495ED', 'crimson' => '#DC143C', 'darkblue' => '#00008B', 'darkgoldenrod' => '#B8860B', 'darkgreen' => '#006400', 'darkmagenta' => '#8B008B', 'darkorange' => '#FF8C00', 'darkred' => '#8B0000', 'darkseagreen' => '#8FBC8F', 'darkslategray' => '#2F4F4F', 'darkviolet' => '#9400D3', 'deepskyblue' => '#00BFFF', 'dodgerblue' => '#1E90FF', 'firebrick' => '#B22222', 'forestgreen' => '#228B22', 'fuchsia' => '#FF00FF', 'gainsboro' => '#DCDCDC', 'gold' => '#FFD700', 'gray' => '#808080', 'green' => '#008000', 'greenyellow' => '#ADFF2F', 'hotpink' => '#FF69B4', 'indigo' => '#4B0082', 'khaki' => '#F0E68C', 'lavenderblush' => '#FFF0F5', 'lemonchiffon' => '#FFFACD', 'lightcoral' => '#F08080', 'lightgoldenrodyellow' => '#FAFAD2', 'lightgreen' => '#90EE90', 'lightsalmon' => '#FFA07A', 'lightskyblue' => '#87CEFA', 'lightslategray' => '#778899', 'lightyellow' => '#FFFFE0', 'lime' => '#00FF00', 'limegreen' => '#32CD32', 'magenta' => '#FF00FF', 'maroon' => '#800000', 'mediumaquamarine' => '#66CDAA', 'mediumorchid' => '#BA55D3', 'mediumseagreen' => '#3CB371', 'mediumspringgreen' => '#00FA9A', 'mediumvioletred' => '#C71585', 'midnightblue' => '#191970', 'mintcream' => '#F5FFFA', 'moccasin' => '#FFE4B5', 'navy' => '#000080', 'olive' => '#808000', 'orange' => '#FFA500', 'orchid' => '#DA70D6', 'palegreen' => '#98FB98', 'palevioletred' => '#D87093', 'peachpuff' => '#FFDAB9', 'pink' => '#FFC0CB', 'powderblue' => '#B0E0E6', 'purple' => '#800080', 'red' => '#FF0000', 'royalblue' => '#4169E1', 'salmon' => '#FA8072', 'seagreen' => '#2E8B57', 'sienna' => '#A0522D', 'silver' => '#C0C0C0', 'skyblue' => '#87CEEB', 'slategray' => '#708090', 'springgreen' => '#00FF7F', 'steelblue' => '#4682B4', 'tan' => '#D2B48C', 'teal' => '#008080', 'thistle' => '#D8BFD8', 'turquoise' => '#40E0D0', 'violetred' => '#D02090', 'white' => '#FFFFFF', 'yellow' => '#FFFF00', 'aliceblue' => '#f0f8ff', 'azure' => '#f0ffff', 'bisque' => '#ffe4c4', 'blanchedalmond' => '#ffebcd', 'blueviolet' => '#8a2be2', 'burlywood' => '#deb887', 'chartreuse' => '#7fff00', 'coral' => '#ff7f50', 'cornsilk' => '#fff8dc', 'cyan' => '#00ffff', 'darkcyan' => '#008b8b', 'darkgray' => '#a9a9a9', 'darkgrey' => '#a9a9a9', 'darkkhaki' => '#bdb76b', 'darkolivegreen' => '#556b2f', 'darkorchid' => '#9932cc', 'darksalmon' => '#e9967a', 'darkslateblue' => '#483d8b', 'darkslategrey' => '#2f4f4f', 'darkturquoise' => '#00ced1', 'deeppink' => '#ff1493', 'dimgray' => '#696969', 'dimgrey' => '#696969', 'floralwhite' => '#fffaf0', 'ghostwhite' => '#f8f8ff', 'goldenrod' => '#daa520', 'grey' => '#808080', 'honeydew' => '#f0fff0', 'indianred' => '#cd5c5c', 'ivory' => '#fffff0', 'lavender' => '#e6e6fa', 'lawngreen' => '#7cfc00', 'lightblue' => '#add8e6', 'lightcyan' => '#e0ffff', 'lightgray' => '#d3d3d3', 'lightgrey' => '#d3d3d3', 'lightpink' => '#ffb6c1', 'lightseagreen' => '#20b2aa', 'lightslategrey' => '#778899', 'lightsteelblue' => '#b0c4de', 'linen' => '#faf0e6', 'mediumblue' => '#0000cd', 'mediumpurple' => '#9370db', 'mediumslateblue' => '#7b68ee', 'mediumturquoise' => '#48d1cc', 'mistyrose' => '#ffe4e1', 'navajowhite' => '#ffdead', 'oldlace' => '#fdf5e6', 'olivedrab' => '#6b8e23', 'orangered' => '#ff4500', 'palegoldenrod' => '#eee8aa', 'paleturquoise' => '#afeeee', 'papayawhip' => '#ffefd5', 'peru' => '#cd853f', 'plum' => '#dda0dd', 'rosybrown' => '#bc8f8f', 'saddlebrown' => '#8b4513', 'sandybrown' => '#f4a460', 'seashell' => '#fff5ee', 'slateblue' => '#6a5acd', 'slategrey' => '#708090', 'snow' => '#fffafa', 'tomato' => '#ff6347', 'violet' => '#ee82ee', 'wheat' => '#f5deb3', 'whitesmoke' => '#f5f5f5', 'yellowgreen' => '#9acd32', ); } PKUZ(ڕ"" README.mdnuW+A# SVG file parsing / rendering library [![Build Status](https://travis-ci.org/PhenX/php-svg-lib.svg?branch=master)](https://travis-ci.org/PhenX/php-svg-lib) [![Coverage Status](https://coveralls.io/repos/PhenX/php-svg-lib/badge.svg)](https://coveralls.io/r/PhenX/php-svg-lib) [![Latest Stable Version](https://poser.pugx.org/phenx/php-svg-lib/v/stable)](https://packagist.org/packages/phenx/php-svg-lib) [![Total Downloads](https://poser.pugx.org/phenx/php-svg-lib/downloads)](https://packagist.org/packages/phenx/php-svg-lib) [![Latest Unstable Version](https://poser.pugx.org/phenx/php-svg-lib/v/unstable)](https://packagist.org/packages/phenx/php-svg-lib) [![License](https://poser.pugx.org/phenx/php-svg-lib/license)](https://packagist.org/packages/phenx/php-svg-lib) The main purpose of this lib is to rasterize SVG to a surface which can be an image or a PDF for example, through a `\Svg\Surface` PHP interface. This project was initialized by the need to render SVG documents inside PDF files for the [DomPdf](http://dompdf.github.io) project.PKUZSCOPYINGnuW+A GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. PKUZ6V[[ composer.jsonnuW+A{ "name": "phenx/php-svg-lib", "type": "library", "description": "A library to read, parse and export to PDF SVG files.", "homepage": "https://github.com/PhenX/php-svg-lib", "license": "LGPL-3.0", "authors": [ { "name": "Fabien Ménager", "email": "fabien.menager@gmail.com" } ], "autoload": { "psr-4": { "Svg\\": "src/Svg" } }, "autoload-dev": { "psr-4": { "Svg\\Tests\\": "tests/Svg" } }, "require": { "php": "^7.4 || ^8.0", "sabberworm/php-css-parser": "^8.3" }, "require-dev": { "phpunit/phpunit": "^9.5" } } PKUZ=gMM COPYING.GPLnuW+A GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . PKUZAQsrc/autoload.phpnuW+APKUZˮ`DD#src/Svg/Tag/RadialGradient.phpnuW+APKUZ! src/Svg/Tag/UseTag.phpnuW+APKUZ<:: src/Svg/Tag/Stop.phpnuW+APKUZ a6 src/Svg/Tag/Shape.phpnuW+APKUZ9ksrc/Svg/Tag/AbstractTag.phpnuW+APKUZpS&src/Svg/Tag/Polygon.phpnuW+APKUZ3:)src/Svg/Tag/ClipPath.phpnuW+APKUZx1 tt,src/Svg/Tag/Text.phpnuW+APKUZ=`B3src/Svg/Tag/Image.phpnuW+APKUZK9src/Svg/Tag/Circle.phpnuW+APKUZW WW<src/Svg/Tag/Rect.phpnuW+APKUZ*TPCsrc/Svg/Tag/Group.phpnuW+APKUZg>  2Fsrc/Svg/Tag/LinearGradient.phpnuW+APKUZoOsrc/Svg/Tag/Ellipse.phpnuW+APKUZzhSsrc/Svg/Tag/Anchor.phpnuW+APKUZ0?ؔNNTsrc/Svg/Tag/Path.phpnuW+APKUZ)bDsrc/Svg/Tag/Line.phpnuW+APKUZiesrc/Svg/Tag/Polyline.phpnuW+APKUZ=1csrc/Svg/Tag/StyleTag.phpnuW+APKUZM(88src/Svg/Gradient/Stop.phpnuW+APKUZ\S%%|src/Svg/Document.phpnuW+APKUZ7src/Svg/DefaultStyle.phpnuW+APKUZ~RR$src/Svg/Surface/SurfaceInterface.phpnuW+APKUZ"NN"Hsrc/Svg/Surface/SurfaceGmagick.phpnuW+APKUZ==--!src/Svg/Surface/SurfacePDFLib.phpnuW+APKUZ,pp@-src/Svg/Surface/CPdf.phpnuW+APKUZmrHY5Y5src/Svg/Surface/SurfaceCpdf.phpnuW+APKUZ>4>GGsrc/Svg/Style.phpnuW+APKUZ(ڕ"" DREADME.mdnuW+APKUZSNICOPYINGnuW+APKUZ6V[[ igcomposer.jsonnuW+APKUZ=gMM jCOPYING.GPLnuW+APK!!