@@ -0,0 +1,222 @@
<?php

/*
* Copyright 2011 Piotr Śliwa <peter.pl7@gmail.com>
*
* License information is in LICENSE file
*/

namespace PHPPdf\Core\Node;

use PHPPdf\Core\ComplexAttribute\Background;
use PHPPdf\Util;
use PHPPdf\Exception\InvalidArgumentException;
use PHPPdf\Core\DrawingTask;
use PHPPdf\Core\Document;
use PHPPdf\Core\DrawingTaskHeap;

/**
* Barcode node class
*
* @author Piotr Śliwa <peter.pl7@gmail.com>
*/
class Barcode extends Node
{
const TYPE_CODE128 = 'code128';
const TYPE_CODE25 = 'code25';
const TYPE_CODE25INTERLEAYED = 'code25interlayed';
const TYPE_CODE39 = 'code39';
const TYPE_EAN13 = 'ean13';
const TYPE_EAN2 = 'ean2';
const TYPE_EAN5 = 'ean5';
const TYPE_EAN8 = 'ean8';
const TYPE_IDENTCODE = 'identcode';
const TYPE_ITF14 = 'itf14';
const TYPE_LEITCODE = 'leitcode';
const TYPE_PLANET = 'planet';
const TYPE_POSTNET = 'postnet';
const TYPE_ROYALMAIL = 'royalmail';
const TYPE_UPCA = 'upca';
const TYPE_UPCE = 'upce';

private $barcode = null;

protected static function setDefaultAttributes()
{
parent::setDefaultAttributes();

static::addAttribute('type', self::TYPE_CODE128);
static::addAttribute('code');
static::addAttribute('draw-code', true);
static::addAttribute('bar-height', 50);
static::addAttribute('with-checksum', false);
static::addAttribute('with-checksum-in-text', false);
static::addAttribute('bar-thin-width', 1);
static::addAttribute('bar-thick-width', 3);
static::addAttribute('factor', 1);
}

protected static function initializeType()
{
static::setAttributeSetters(array('type' => 'setType'));
static::setAttributeSetters(array('draw-code' => 'setDrawCode'));
static::setAttributeSetters(array('bar-height' => 'setBarHeight'));
static::setAttributeSetters(array('with-checksum' => 'setWithChecksum'));
static::setAttributeSetters(array('with-checksum-in-text' => 'setWithChecksumInText'));
static::setAttributeSetters(array('bar-thin-width' => 'setBarThinWidth'));
static::setAttributeSetters(array('bar-thick-width' => 'setBarThickWidth'));

parent::initializeType();
}

public function setType($type)
{
$const = sprintf('%s::TYPE_%s', __CLASS__, strtoupper($type));

if(!defined($const))
{
throw new InvalidArgumentException(sprintf('Barcode type "%s" dosn\'t exist.', $type));
}

$type = constant($const);

$this->setAttributeDirectly('type', $type);
}

public function setDrawCode($flag)
{
$flag = $this->filterBooleanValue($flag);
$this->setAttributeDirectly('draw-code', $flag);
}

public function setWithChecksum($flag)
{
$flag = $this->filterBooleanValue($flag);
$this->setAttributeDirectly('with-checksum', $flag);
}

public function setWithChecksumInText($flag)
{
$flag = $this->filterBooleanValue($flag);
$this->setAttributeDirectly('with-checksum-in-text', $flag);
}

public function setBarHeight($height)
{
$height = $this->convertUnit($height);
$this->setAttributeDirectly('bar-height', $height);
}

public function setBarThinWidth($value)
{
$value = $this->convertUnit($value);
$this->setAttributeDirectly('bar-thin-width', $value);
}

public function setBarThickWidth($value)
{
$value = $this->convertUnit($value);
$this->setAttributeDirectly('bar-thick-width', $value);
}

public function setAttribute($name, $value)
{
$this->barcode = null;

parent::setAttribute($name, $value);
}

protected function doDraw(Document $document, DrawingTaskHeap $tasks)
{
$callback = function(Barcode $node, Document $document){
$barcode = $node->getBarcode($document);
$gc = $node->getGraphicsContext();
$gc->drawBarcode($node->getFirstPoint()->getX(), $node->getFirstPoint()->getY(), $barcode);
};

$tasks->insert(new DrawingTask($callback, array($this, $document)));
}

protected function getDrawingTasksFromComplexAttributes(Document $document, DrawingTaskHeap $tasks)
{
$complexAttributes = $document->getComplexAttributes($this->complexAttributeBag);
foreach($complexAttributes as $complexAttribute)
{
if(!$complexAttribute instanceof Background)
{
$this->insertComplexAttributeTask($complexAttribute, $tasks, $document);
}
}
}

/**
* @internal
*/
public function getBarcode(Document $document)
{
if($this->barcode === null)
{
$this->barcode = $this->createBarcode($document);
}

return $this->barcode;
}

private function createBarcode(Document $document)
{
try
{
$foreColor = $this->convertBarcodeColor($document, $this->getRecurseAttribute('color'));
$background = $this->getComplexAttributes('background');
$backgroundColor = isset($background['color']) ? $this->convertBarcodeColor($document, $background['color']) : '#FFFFFF';

$barcodeClass = sprintf('Zend\Barcode\Object\%s', ucfirst($this->getAttribute('type')));

$barcode = new $barcodeClass(array(
'text' => $this->getAttribute('code'),
'font' => $this->getFont($document)->getCurrentResourceIdentifier(),
'fontSize' => $this->getFontSizeRecursively(),
'foreColor' => $foreColor,
'drawText' => $this->getAttribute('draw-code'),
'barHeight' => $this->getAttribute('bar-height'),
'withChecksum' => $this->getAttribute('with-checksum'),
'withChecksumInText' => $this->getAttribute('with-checksum-in-text'),
'orientation' => $this->getOrientation(),
'barThinWidth' => $this->getAttribute('bar-thin-width'),
'barThickWidth' => $this->getAttribute('bar-thick-width'),
'factor' => (float) $this->getAttribute('factor'),
'backgroundColor' => $backgroundColor,
));

return $barcode;
}
catch(\Zend\Barcode\Exception $e)
{
throw new InvalidArgumentException('Invalid arguments passed to barcode, see cause exception for more details.', $previous->getCode(), $e);
}
}

private function convertBarcodeColor(Document $document, $color)
{
return strtoupper($document->getColorFromPalette($color));;
}

private function getOrientation()
{
$radians = (float) Util::convertAngleValue($this->getAttributeDirectly('rotate'));

return rad2deg($radians);
}

protected function beforeFormat(Document $document)
{
$barcode = $this->getBarcode($document);
$this->setHeight($barcode->getHeight(true)/2);
$this->setWidth($barcode->getWidth(true)/2);
}

public function getRotate()
{
return null;
}
}
@@ -8,6 +8,8 @@

namespace PHPPdf\Core\Node;

use PHPPdf\Core\ComplexAttribute\ComplexAttribute;

use PHPPdf\Exception\OutOfBoundsException;
use PHPPdf\Exception\InvalidArgumentException;
use PHPPdf\Exception\LogicException;
@@ -64,7 +66,7 @@ abstract class Node implements Drawable, NodeAware, \ArrayAccess, \Serializable

private $boundary = null;

private $complexAttributeBag = null;
protected $complexAttributeBag = null;
private $formattersNames = array();

private $behaviours = array();
@@ -1061,7 +1063,7 @@ public function collectOrderedDrawingTasks(Document $document, DrawingTaskHeap $
{
try
{
$this->preDraw($document, $tasks);
$this->preDraw($document, $tasks);
$this->doDraw($document, $tasks);
$this->postDraw($document, $tasks);
}
@@ -1102,12 +1104,17 @@ protected function getDrawingTasksFromComplexAttributes(Document $document, Draw
$complexAttributes = $document->getComplexAttributes($this->complexAttributeBag);
foreach($complexAttributes as $complexAttribute)
{
$callback = array($complexAttribute, 'enhance');
$args = array($this, $document);
$priority = $complexAttribute->getPriority() + $this->getPriority();
$tasks->insert(new DrawingTask($callback, $args, $priority));
$this->insertComplexAttributeTask($complexAttribute, $tasks, $document);
}
}

protected function insertComplexAttributeTask(ComplexAttribute $complexAttribute, DrawingTaskHeap $tasks, Document $document)
{
$callback = array($complexAttribute, 'enhance');
$args = array($this, $document);
$priority = $complexAttribute->getPriority() + $this->getPriority();
$tasks->insert(new DrawingTask($callback, $args, $priority));
}

public function getPriority()
{
@@ -10,7 +10,7 @@
<attribute name="font-size" value="12px" />
<attribute name="font-style" value="normal" />
<attribute name="margin" value="52px 52px" />
<attribute name="color" value="black" />
<attribute name="color" value="#000000" />
<attribute name="page-size" value="595:842" />
</stylesheet>
<formatters>
@@ -23,7 +23,7 @@
<attribute name="font-type" value="helvetica" />
<attribute name="font-size" value="12px" />
<attribute name="font-style" value="normal" />
<attribute name="color" value="black" />
<attribute name="color" value="#000000" />
<attribute name="page-size" value="595:842" />
</stylesheet>
<formatters>
@@ -51,6 +51,16 @@
<post class="PHPPdf\Core\Formatter\VerticalAlignFormatter" />
</formatters>
</node>
<node name="barcode" class="PHPPdf\Core\Node\Barcode">
<formatters>
<pre class="PHPPdf\Core\Formatter\ConvertAttributesFormatter" />
<pre class="PHPPdf\Core\Formatter\FirstPointPositionFormatter" />

<post class="PHPPdf\Core\Formatter\StandardPositionFormatter" />
<post class="PHPPdf\Core\Formatter\FloatFormatter" />
<post class="PHPPdf\Core\Formatter\VerticalAlignFormatter" />
</formatters>
</node>
<node name="br" class="PHPPdf\Core\Node\Container">
<stylesheet>
<attribute name="line-break" value="1" />
@@ -259,7 +269,7 @@
</node>
<node name="td" class="PHPPdf\Core\Node\Table\Cell">
<stylesheet>
<complex-attribute name="border" color="black" size="1px" />
<complex-attribute name="border" color="#000000" size="1px" />
</stylesheet>
<formatters>
<pre class="PHPPdf\Core\Formatter\ConvertAttributesFormatter" />
@@ -285,7 +295,7 @@
<stylesheet>
<attribute name="height" value="1px" />
<attribute name="margin" value="12px 0" />
<complex-attribute name="background" color="black" />
<complex-attribute name="background" color="#000000" />
</stylesheet>
<formatters>
<pre class="PHPPdf\Core\Formatter\ConvertAttributesFormatter" />
@@ -300,7 +310,7 @@
<node name="a" class="PHPPdf\Core\Node\Text">
<stylesheet>
<attribute name="text-decoration" value="underline" />
<attribute name="color" value="blue" />
<attribute name="color" value="#0000ff" />
</stylesheet>
<formatters>
<pre class="PHPPdf\Core\Formatter\ConvertAttributesFormatter" />
@@ -29,6 +29,24 @@ public static function convertBooleanValue($value)
return isset($knownValues[$value]) ? $knownValues[$value] : (boolean) $value;
}

/**
* Converts angle value to radians.
*
* When value is "deg" suffixed, it means value is in degrees.
*
* @return float|null angle in radians or null
*/
public static function convertAngleValue($value)
{
if($value !== null && strpos($value, 'deg') !== false)
{
$value = (float) $value;
$value = deg2rad($value);
}

return $value !== null ? ((float) $value) : null;
}

public static function calculateDependantSizes($width, $height, $ratio)
{
if(!$width && $height)
@@ -10,4 +10,9 @@ public function getWidthOfText($text, $fontSize)
{
return 0;
}

public function getCurrentResourceIdentifier()
{
return 'abc';
}
}
@@ -0,0 +1,105 @@
<?php

namespace PHPPdf\Test\Core\Node;

use PHPPdf\Core\DrawingTaskHeap;
use PHPPdf\ObjectMother\NodeObjectMother;
use PHPPdf\Core\Node\Page;
use PHPPdf\Core\Node\Barcode;
use PHPPdf\PHPUnit\Framework\TestCase;

class BarcodeTest extends TestCase
{
private $barcode;
private $objectMother;
private $gc;
private $page;

public function setUp()
{
$this->barcode = new Barcode();
$this->objectMother = new NodeObjectMother($this);
$this->gc = $this->getMock('PHPPdf\Core\Engine\GraphicsContext');
$this->page = new Page();
$this->invokeMethod($this->page, 'setGraphicsContext', array($this->gc));
$this->barcode->setParent($this->page);
}

/**
* @test
* @dataProvider drawBarcodeInGraphicsContextProvider
*/
public function drawBarcodeInGraphicsContext($x, $y, $width, $height, $barHeight, $barcodeText, $drawText, $barcodeType, $fontType, $fontSize, $color, $withChecksum, $orientation, $barThinWidth, $barThickWidth, $factor)
{
$boundary = $this->objectMother->getBoundaryStub($x, $y, $width, $height);
$this->invokeMethod($this->barcode, 'setBoundary', array($boundary));
$font = $this->getMock('PHPPdf\Core\Engine\Font');
$fontPath = 'path';

$document = $this->getMockBuilder('PHPPdf\Core\Document')
->disableOriginalConstructor()
->setMethods(array('getFont', 'getColorFromPalette'))
->getMock();

$document->expects($this->once())
->method('getFont')
->with($fontType)
->will($this->returnValue($font));
$document->expects($this->once())
->method('getColorFromPalette')
->with($color)
->will($this->returnValue($color));

$this->gc->expects($this->once())
->method('drawBarcode')
->with($x, $y, $this->validateByCallback(function($barcode, TestCase $test) use($barcodeText, $drawText, $barcodeType, $fontPath, $fontSize, $color, $barHeight, $withChecksum, $orientation, $barThinWidth, $barThickWidth, $factor){
$test->assertInstanceOf('Zend\Barcode\Object', $barcode);
$test->assertTrue(stripos(get_class($barcode), $barcodeType) !== false);
$test->assertEquals($barcodeText, $barcode->getText());
$test->assertEquals($fontPath, $barcode->getFont());
$test->assertEquals($fontSize, $barcode->getFontSize());
$test->assertEquals(hexdec($color), $barcode->getForeColor());
$test->assertEquals($drawText, $barcode->getDrawText());
$test->assertEquals($barHeight, $barcode->getBarHeight());
$test->assertEquals($withChecksum, $barcode->getWithChecksum());
$test->assertEquals($withChecksum, $barcode->getWithChecksumInText());
$test->assertEquals((int) $orientation, $barcode->getOrientation());
$test->assertEquals($barThinWidth, $barcode->getBarThinWidth());
$test->assertEquals($barThickWidth, $barcode->getBarThickWidth());
$test->assertEquals($factor, $barcode->getFactor());
}, $this));

$font->expects($this->once())
->method('getCurrentResourceIdentifier')
->will($this->returnValue($fontPath));

$this->barcode->setAttribute('type', $barcodeType);
$this->barcode->setAttribute('code', $barcodeText);
$this->barcode->setAttribute('font-type', $fontType);
$this->barcode->setAttribute('font-size', $fontSize);
$this->barcode->setAttribute('color', $color);
$this->barcode->setAttribute('draw-code', $drawText);
$this->barcode->setAttribute('bar-height', $barHeight);
$this->barcode->setAttribute('with-checksum', $withChecksum);
$this->barcode->setAttribute('with-checksum-in-text', $withChecksum);
$this->barcode->setAttribute('rotate', $orientation);
$this->barcode->setAttribute('bar-thin-width', $barThinWidth);
$this->barcode->setAttribute('bar-thick-width', $barThickWidth);
$this->barcode->setAttribute('factor', $factor);

$tasks = new DrawingTaskHeap();
$this->barcode->collectOrderedDrawingTasks($document, $tasks);

foreach($tasks as $task)
{
$task->invoke();
}
}

public function drawBarcodeInGraphicsContextProvider()
{
return array(
array(100, 30, 50, 70, 55, 'abc', true, 'code128', 'some-font', 12, '#cccccc', true, '45deg', 12, 21, 2),
);
}
}