Initial commit
This commit is contained in:
commit
a410ce8b19
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/composer.lock
|
||||
/vendor
|
30
composer.json
Normal file
30
composer.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "noccylabs/gd-label",
|
||||
"description": "Generate and print labels",
|
||||
"type": "library",
|
||||
"license": "LGPL-3.0",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Christopher Vagnetoft",
|
||||
"email": "cvagnetoft@gmail.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"ext-gd": "*",
|
||||
"ext-xml": "*",
|
||||
"noccylabs/csslike": "^0.1.1",
|
||||
"picqer/php-barcode-generator": "^0.2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"NoccyLabs\\GdLabel\\": "src/"
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "composer",
|
||||
"url": "https://packages.noccylabs.info/composer/"
|
||||
}
|
||||
]
|
||||
}
|
94
src/Element.php
Normal file
94
src/Element.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel;
|
||||
|
||||
abstract class Element
|
||||
{
|
||||
/** @var Element[] $children */
|
||||
protected $children = [];
|
||||
|
||||
/** @var array $props */
|
||||
protected $props = [];
|
||||
|
||||
/**
|
||||
* Add a new child
|
||||
*
|
||||
* @param Element $element
|
||||
*/
|
||||
public function addChild(Element $element)
|
||||
{
|
||||
$this->children[] = $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all children
|
||||
*
|
||||
* @return Element[] Child elements
|
||||
*/
|
||||
public function getChildren():iterable
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the element
|
||||
*
|
||||
* @param gd $canvas
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @param int $width
|
||||
* @param int $height
|
||||
* @param array $params
|
||||
*/
|
||||
public function drawElement($canvas, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
$this->draw($canvas, $x, $y, $width, $height, $params);
|
||||
}
|
||||
|
||||
protected function draw($canvas, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function rgb($color)
|
||||
{
|
||||
if (is_integer($color)) {
|
||||
return $color;
|
||||
} elseif (preg_match('/^#[0-9A-F]{6}$/i', $color)) {
|
||||
[$r,$g,$b] = \sscanf($color, "#%02x%02x%02x");
|
||||
return $r | $g << 8 | $b << 16;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function setProp(string $key, $value)
|
||||
{
|
||||
$this->props[$key] = $value;
|
||||
}
|
||||
|
||||
public function getProp(string $key, $default=null)
|
||||
{
|
||||
return array_key_exists($key, $this->props)
|
||||
?$this->props[$key]
|
||||
:$default
|
||||
;
|
||||
}
|
||||
|
||||
public function getPropRgb(string $key, $default=null)
|
||||
{
|
||||
$color = $this->getProp($key, $default);
|
||||
return $this->rgb($color);
|
||||
}
|
||||
|
||||
public function getProps():iterable
|
||||
{
|
||||
return $this->props;
|
||||
}
|
||||
|
||||
protected function warning($msg)
|
||||
{
|
||||
defined("STDERR") && fprintf(STDERR, "%s\n", rtrim($msg));
|
||||
}
|
||||
|
||||
}
|
98
src/Elements/BarcodeElement.php
Normal file
98
src/Elements/BarcodeElement.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Elements;
|
||||
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
use Picqer\Barcode\BarcodeGeneratorPNG;
|
||||
|
||||
class BarcodeElement extends Element
|
||||
{
|
||||
|
||||
protected $image;
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
|
||||
$pw = $this->getProp("width", $width);
|
||||
$ph = $this->getProp("height", $height);
|
||||
|
||||
if (!$this->image) {
|
||||
$this->renderBarcode($params);
|
||||
}
|
||||
|
||||
$sx = imagesx($this->image);
|
||||
$sy = imagesy($this->image);
|
||||
|
||||
$aspect = $sx/$sy;
|
||||
|
||||
if ($aspect>1) {
|
||||
$dw = $width;
|
||||
$dh = $width / $aspect;
|
||||
} else {
|
||||
$dw = $height * $aspect;
|
||||
$dh = $height;
|
||||
// printf("--%d--\n", $dw);
|
||||
}
|
||||
|
||||
imagecopyresampled($gd, $this->image, $x, $y, 0, 0, $dw, $dh, $sx, $sy);
|
||||
|
||||
}
|
||||
|
||||
protected function renderBarcode(array $params)
|
||||
{
|
||||
if (($bound = $this->getProp("bind-value"))) {
|
||||
$value = array_key_exists($bound, $params)?$params[$bound]:"?{$bound}?";
|
||||
} else {
|
||||
$value = $this->getProp("value");
|
||||
}
|
||||
|
||||
$color = $this->getPropRgb("text-color");
|
||||
$type = $this->getPropType("barcode-type");
|
||||
|
||||
$barcodeGenerator = new BarcodeGeneratorPNG();
|
||||
$barcodeData = $barcodeGenerator->getBarcode($value, $type, 2, 30, [0,0,0]);
|
||||
$barcode = imagecreatefromstring($barcodeData);
|
||||
|
||||
$this->image = $barcode;
|
||||
}
|
||||
|
||||
protected function getPropType(string $name)
|
||||
{
|
||||
$typeString = $this->getProp($name);
|
||||
switch ($typeString) {
|
||||
case 'code39': $type = BarcodeGeneratorPNG::TYPE_CODE_39; break;
|
||||
case 'code39-c': $type = BarcodeGeneratorPNG::TYPE_CODE_39_CHECKSUM; break;
|
||||
case 'code39-e': $type = BarcodeGeneratorPNG::TYPE_CODE_39E; break;
|
||||
case 'code39-ec': $type = BarcodeGeneratorPNG::TYPE_CODE_39E_CHECKSUM; break;
|
||||
case 'code93': $type = BarcodeGeneratorPNG::TYPE_CODE_93; break;
|
||||
case 'standard25': $type = BarcodeGeneratorPNG::TYPE_STANDARD_2_5; break;
|
||||
case 'standard25-c': $type = BarcodeGeneratorPNG::TYPE_STANDARD_2_5_CHECKSUM; break;
|
||||
case 'interleaved25': $type = BarcodeGeneratorPNG::TYPE_INTERLEAVED_2_5; break;
|
||||
case 'interleaved25-c': $type = BarcodeGeneratorPNG::TYPE_INTERLEAVED_2_5_CHECKSUM; break;
|
||||
case 'code128': $type = BarcodeGeneratorPNG::TYPE_CODE_128; break;
|
||||
case 'code128a': $type = BarcodeGeneratorPNG::TYPE_CODE_128_A; break;
|
||||
case 'code128b': $type = BarcodeGeneratorPNG::TYPE_CODE_128_B; break;
|
||||
case 'code128c': $type = BarcodeGeneratorPNG::TYPE_CODE_128_C; break;
|
||||
case 'ean2': $type = BarcodeGeneratorPNG::TYPE_EAN_2; break;
|
||||
case 'ean5': $type = BarcodeGeneratorPNG::TYPE_EAN_5; break;
|
||||
case 'ean8': $type = BarcodeGeneratorPNG::TYPE_EAN_8; break;
|
||||
case 'ean13': $type = BarcodeGeneratorPNG::TYPE_EAN_13; break;
|
||||
case 'upca': $type = BarcodeGeneratorPNG::TYPE_UPC_A; break;
|
||||
case 'upce': $type = BarcodeGeneratorPNG::TYPE_UPC_E; break;
|
||||
case 'msi': $type = BarcodeGeneratorPNG::TYPE_MSI; break;
|
||||
case 'msi-c': $type = BarcodeGeneratorPNG::TYPE_MSI_CHECKSUM; break;
|
||||
case 'postnet': $type = BarcodeGeneratorPNG::TYPE_POSTNET; break;
|
||||
case 'planet': $type = BarcodeGeneratorPNG::TYPE_PLANET; break;
|
||||
case 'rms4cc': $type = BarcodeGeneratorPNG::TYPE_RMS4CC; break;
|
||||
case 'kix': $type = BarcodeGeneratorPNG::TYPE_KIX; break;
|
||||
case 'imb': $type = BarcodeGeneratorPNG::TYPE_IMB; break;
|
||||
case 'codabar': $type = BarcodeGeneratorPNG::TYPE_CODABAR; break;
|
||||
case 'code11': $type = BarcodeGeneratorPNG::TYPE_CODE_11; break;
|
||||
case 'pharma': $type = BarcodeGeneratorPNG::TYPE_PHARMA_CODE; break;
|
||||
case 'pharma-2': $type = BarcodeGeneratorPNG::TYPE_PHARMA_CODE_TWO_TRACKS; break;
|
||||
default: $type = BarcodeGeneratorPNG::TYPE_CODE_39; break;
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
}
|
39
src/Elements/FrameElement.php
Normal file
39
src/Elements/FrameElement.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Elements;
|
||||
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
|
||||
class FrameElement extends Element
|
||||
{
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
$color = $this->getPropRgb("line-color");
|
||||
if ($color === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$radius = (int)$this->getProp("border-radius", 0);
|
||||
|
||||
$thickness = (int)$this->getProp("line-width", 1);
|
||||
\imagesetthickness($gd, $thickness);
|
||||
|
||||
if ($radius == 0) {
|
||||
\imagerectangle($gd, $x, $y, $x + $width, $y + $height, $color);
|
||||
} else {
|
||||
\imageline($gd, $x + $radius, $y, $x + $width - $radius, $y, $color);
|
||||
\imageline($gd, $x + $radius, $y + $height, $x + $width - $radius, $y + $height, $color);
|
||||
\imageline($gd, $x, $y + $radius, $x, $y + $height - $radius, $color);
|
||||
\imageline($gd, $x + $width, $y + $radius, $x + $width, $y + $height - $radius, $color);
|
||||
\imagearc($gd, $x + $radius, $y + $radius, $radius * 2, $radius * 2, 180, 270, $color);
|
||||
\imagearc($gd, $x + $radius, $y + $height - $radius, $radius * 2, $radius * 2, 90, 180, $color);
|
||||
\imagearc($gd, $x + $width - $radius, $y + $radius, $radius * 2, $radius * 2, 270, 0, $color);
|
||||
\imagearc($gd, $x + $width - $radius, $y + $height - $radius, $radius * 2, $radius * 2, 0, 90, $color);
|
||||
}
|
||||
|
||||
\imagesetthickness($gd, 1);
|
||||
|
||||
}
|
||||
|
||||
}
|
31
src/Elements/ImageElement.php
Normal file
31
src/Elements/ImageElement.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Elements;
|
||||
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
|
||||
class ImageElement extends Element
|
||||
{
|
||||
|
||||
protected $image;
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
$src = $this->getProp("src");
|
||||
if (!$src || !\file_exists($src)) {
|
||||
return;
|
||||
}
|
||||
if (!$this->image) {
|
||||
$data = file_get_contents($src);
|
||||
$this->image = imagecreatefromstring($data);
|
||||
}
|
||||
|
||||
$sx = imagesx($this->image);
|
||||
$sy = imagesy($this->image);
|
||||
|
||||
imagecopyresampled($gd, $this->image, $x, $y, 0, 0, $width, $height, $sx, $sy);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
46
src/Elements/StringElement.php
Normal file
46
src/Elements/StringElement.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Elements;
|
||||
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
|
||||
class StringElement extends Element
|
||||
{
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
if (($bound = $this->getProp("bind-value"))) {
|
||||
$value = array_key_exists($bound, $params)?$params[$bound]:"?{$bound}?";
|
||||
} else {
|
||||
$value = $this->getProp("value");
|
||||
}
|
||||
|
||||
$value = \preg_replace_callback('/([åäöÅÄÖ]{1})/', function ($c) {
|
||||
switch ($c) {
|
||||
case 'å':
|
||||
case 'ä':
|
||||
return "a";
|
||||
case 'ö':
|
||||
return "o";
|
||||
case 'Å':
|
||||
case 'Ä':
|
||||
return "A";
|
||||
case 'Ö':
|
||||
return "O";
|
||||
}
|
||||
}, $value);
|
||||
|
||||
$color = $this->getPropRgb("text-color");
|
||||
|
||||
$size = $this->getProp("text-size", 0) * 2;
|
||||
$weight = $this->getProp("font-weight");
|
||||
if ($weight == "bold") {
|
||||
$size++;
|
||||
}
|
||||
|
||||
imagestring($gd, $size, $x, $y, $value, $color);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
72
src/Elements/TextElement.php
Normal file
72
src/Elements/TextElement.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Elements;
|
||||
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
|
||||
class TextElement extends Element
|
||||
{
|
||||
|
||||
private static $fontCache = [];
|
||||
|
||||
private static function findFont($font)
|
||||
{
|
||||
if (count(self::$fontCache)==0) {
|
||||
self::updateFonts();
|
||||
}
|
||||
|
||||
$font = strtolower($font);
|
||||
|
||||
if (array_key_exists($font, self::$fontCache)) {
|
||||
return self::$fontCache[$font];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function updateFonts()
|
||||
{
|
||||
exec("fc-list", $output, $ret);
|
||||
if ($ret != 0) {
|
||||
fprintf(STDERR, "%s\n", join("\n",$output));
|
||||
return;
|
||||
}
|
||||
foreach ($output as $line) {
|
||||
$seg = explode(":", $line);
|
||||
$file = $seg[0];
|
||||
$name = strtolower(trim(preg_replace("/[^A-Za-z0-9 ]/", '', $seg[1])));
|
||||
self::$fontCache[$name] = $file;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
if (($bound = $this->getProp("bind-value"))) {
|
||||
$value = array_key_exists($bound, $params)?$params[$bound]:"?{$bound}?";
|
||||
} else {
|
||||
$value = $this->getProp("value");
|
||||
}
|
||||
|
||||
$font = self::findFont("liberation sans");
|
||||
if (!$font) {
|
||||
return;
|
||||
}
|
||||
|
||||
$color = $this->getPropRgb("text-color");
|
||||
|
||||
$size = $this->getProp("font-size", 8);
|
||||
|
||||
$dims = imagettfbbox($size, 0, $font, $value);
|
||||
|
||||
$textAscent = abs($dims[7]);
|
||||
$textDescent = abs($dims[1]);
|
||||
$textWidth = abs($dims[0])+abs($dims[2]);
|
||||
$textHeight = $textAscent+$textDescent;
|
||||
|
||||
imagettftext($gd, $size, 0, $x, $y + $textAscent, $color, $font, $value);
|
||||
//imagestring($gd, $size, $x, $y, $value, $color);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
17
src/Label.php
Normal file
17
src/Label.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel;
|
||||
|
||||
class Label extends Element
|
||||
{
|
||||
|
||||
protected function draw($gd, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
if ($bg = $this->getPropRgb("background-color")) {
|
||||
\imagefilledrectangle($gd, $x, $y, $x + $width, $y + $height, $bg);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
98
src/Loader/XmlLoader.php
Normal file
98
src/Loader/XmlLoader.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel\Loader;
|
||||
|
||||
use NoccyLabs\CssLike\Stylesheet;
|
||||
use NoccyLabs\GdLabel\Label;
|
||||
use SimpleXMLElement;
|
||||
use NoccyLabs\GdLabel\Element;
|
||||
|
||||
class XmlLoader
|
||||
{
|
||||
|
||||
public function loadFile($filename)
|
||||
{
|
||||
$xml = simplexml_load_file($filename);
|
||||
|
||||
$label = new Label();
|
||||
$styles = new Stylesheet();
|
||||
|
||||
foreach ($xml->style as $style) {
|
||||
if (($src = (string)@$style['src'])) {
|
||||
$ss = file_get_contents($src);
|
||||
} else {
|
||||
$ss = (string)$style;
|
||||
}
|
||||
$ssb = new Stylesheet();
|
||||
$ssb->parseString($ss);
|
||||
$styles->appendStylesheet($ssb);
|
||||
}
|
||||
|
||||
$rules = $styles->select("label");
|
||||
foreach ($rules as $prop=>$value) {
|
||||
$label->setProp($prop, $value);
|
||||
}
|
||||
|
||||
$this->parseAttributes($xml, $label, $styles);
|
||||
|
||||
foreach ($xml->children() as $child) {
|
||||
if ($child->getName() == "style") {
|
||||
continue;
|
||||
}
|
||||
$this->parseElement($child, $label, $styles);
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
private function parseElement(SimpleXmlElement $xml, Element $elem, Stylesheet $style)
|
||||
{
|
||||
$name = $xml->getName();
|
||||
|
||||
$fqcn = "NoccyLabs\\GdLabel\\Elements\\".ucwords($name)."Element";
|
||||
if (!class_exists($fqcn)) {
|
||||
throw new \RuntimeException("Invalid element {$name} (expected {$fqcn})");
|
||||
}
|
||||
|
||||
$obj = new $fqcn();
|
||||
|
||||
$rules = $style->select($name);
|
||||
foreach ($rules as $prop=>$value) {
|
||||
$elem->setProp($prop, $value);
|
||||
}
|
||||
|
||||
$this->parseAttributes($xml, $obj, $style);
|
||||
|
||||
foreach ($xml->children() as $child) {
|
||||
$this->parseElement($child, $obj, $style);
|
||||
}
|
||||
|
||||
$elem->addChild($obj);
|
||||
}
|
||||
|
||||
private function parseAttributes(SimpleXmlElement $xml, Element $elem, Stylesheet $style)
|
||||
{
|
||||
$props = [];
|
||||
foreach ($xml->attributes() as $attr) {
|
||||
$name = $this->camelToDash($attr->getName());
|
||||
$value = (string)$attr;
|
||||
$props[$name] = $value;
|
||||
}
|
||||
if (array_key_exists("class", $props)) {
|
||||
$classes = explode(" ",trim($props['class']));
|
||||
foreach ($classes as $class) {
|
||||
$rule = $style->select(".".$class);
|
||||
$props = array_merge($rule, $props);
|
||||
}
|
||||
}
|
||||
foreach ($props as $prop=>$value) {
|
||||
$elem->setProp($prop, $value);
|
||||
}
|
||||
}
|
||||
|
||||
private function camelToDash($name)
|
||||
{
|
||||
return strtolower(preg_replace("/([A-Z]{1})/", "-\$1", $name));
|
||||
}
|
||||
|
||||
}
|
75
src/Renderer.php
Normal file
75
src/Renderer.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace NoccyLabs\GdLabel;
|
||||
|
||||
use NoccyLabs\GdLabel\Loader\XmlLoader;
|
||||
|
||||
class Renderer
|
||||
{
|
||||
|
||||
|
||||
public function renderFile($inputFile, array $params=[], $output=null)
|
||||
{
|
||||
|
||||
$loader = new XmlLoader();
|
||||
$label = $loader->loadFile($inputFile);
|
||||
|
||||
$rendered = $this->render($label, $params);
|
||||
|
||||
if ($output) {
|
||||
$ext = pathinfo($output, \PATHINFO_EXTENSION);
|
||||
switch (strtolower($ext)) {
|
||||
case 'png': imagepng($rendered, $output); break;
|
||||
case 'jpg': imagejpeg($rendered, $output); break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Can't write {$ext} yet");
|
||||
}
|
||||
}
|
||||
|
||||
return $rendered;
|
||||
}
|
||||
|
||||
public function render(Label $label, array $params=[])
|
||||
{
|
||||
$width = $label->getProp("width");
|
||||
$height = $label->getProp("height");
|
||||
|
||||
$gd = imagecreatetruecolor($width, $height);
|
||||
|
||||
if (is_callable("imageantialias")) {
|
||||
\imageantialias($gd, true);
|
||||
}
|
||||
|
||||
$this->renderElement($gd, $label, 0, 0, $width, $height, $params);
|
||||
|
||||
return $gd;
|
||||
}
|
||||
|
||||
protected function renderElement($gd, Element $element, int $x, int $y, int $width, int $height, array $params)
|
||||
{
|
||||
$xOffs = $x + $element->getProp("left", 0);
|
||||
$yOffs = $y + $element->getProp("top", 0);
|
||||
|
||||
if (($widthAttr = $element->getProp("width",0))) {
|
||||
$elemWidth = $widthAttr;
|
||||
} else {
|
||||
$rightAttr = $element->getProp("right",0);
|
||||
$elemWidth = $width - $rightAttr - $element->getProp("left",0);;
|
||||
}
|
||||
|
||||
if (($heightAttr = $element->getProp("height",0))) {
|
||||
$elemHeight = $heightAttr;
|
||||
} else {
|
||||
$bottomAttr = $element->getProp("bottom",0);
|
||||
$elemHeight = $height - $bottomAttr - $element->getProp("top",0);
|
||||
}
|
||||
|
||||
// printf("render[%s] at {%dx%d}→{%dx%d}\n", get_class($element), $xOffs, $yOffs, $xOffs+$elemWidth, $yOffs+$elemHeight);
|
||||
|
||||
$element->drawElement($gd, $xOffs, $yOffs, $elemWidth, $elemHeight, $params);
|
||||
|
||||
foreach ($element->getChildren() as $child) {
|
||||
$this->renderElement($gd, $child, $xOffs, $yOffs, $elemWidth, $elemHeight, $params);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user