1. Import

This commit is contained in:
2026-03-29 10:34:57 +02:00
parent b0e00c1259
commit a1129565af
4899 changed files with 3007593 additions and 0 deletions

View File

@@ -0,0 +1,139 @@
<p align="center"><a href="https://www.barcodebakery.com" target="_blank">
<img src="https://www.barcodebakery.com/images/BCG-Logo-SQ-GitHub.svg">
</a></p>
[Barcode Bakery][1] is library written in PHP, [.NET Standard][32] and Node.JS which allows you to generate barcodes on the fly on your server for displaying or saving.
The library has minimal dependencies in each language in order to be supported on a wide variety of web servers.
The library is available for free for non-commercial use; however you must [purchase a license][2] if you plan to use it in a commercial environment.
Installation
------------
There are two ways to install our library:
* With composer, run the following command:
```sh
composer require barcode-bakery/barcode-1d
```
* Or, download the library on our [website][3], and follow our [developer's guide][4].
Requirements
------------
* PHP 7.4+ or PHP8
* GD2
Example usages
--------------
For a full example of how to use each symbology type, visit our [API page][5].
### Displaying a Code 128 on the screen
```php
<?php
// Path to the generated autoload file.
require __DIR__ . '/../vendor/autoload.php';
use BarcodeBakery\Common\BCGFontFile;
use BarcodeBakery\Common\BCGColor;
use BarcodeBakery\Common\BCGDrawing;
use BarcodeBakery\Barcode\BCGcode128;
$font = new BCGFontFile(__DIR__ . '/font/Arial.ttf', 18);
$colorBlack = new BCGColor(0, 0, 0);
$colorWhite = new BCGColor(255, 255, 255);
// Barcode Part
$code = new BCGcode128();
$code->setScale(2);
$code->setThickness(30);
$code->setForegroundColor($colorBlack);
$code->setBackgroundColor($colorWhite);
$code->setFont($font);
$code->setStart(null);
$code->setTilde(true);
$code->parse('a123');
// Drawing Part
$drawing = new BCGDrawing($code, $colorWhite);
header('Content-Type: image/png');
$drawing->finish(BCGDrawing::IMG_FORMAT_PNG);
```
### Saving the image to a file
Replace the last lines of the previous code with the following:
```php
// Drawing Part
$drawing = new BCGDrawing($code, $colorWhite);
$drawing->finish(BCGDrawing::IMG_FORMAT_PNG, 'path/to/file.png');
```
This will generate the following:
<br />
<img src="https://www.barcodebakery.com/images/code-128-github.png">
Supported types
---------------
* [Codabar][6]
* [Code 11][7]
* [Code 128][8]
* [Code 39][9]
* [Code 39 Extended][10]
* [Code 93][11]
* [EAN-13][12]
* [EAN-8][13]
* [GS1-128 (EAN-128)][14]
* [Intelligent Mail][15]
* [Interleaved 2 of 5][16]
* [ISBN-10 / ISBN-13][17]
* [MSI Plessey][18]
* [Other (Custom)][19]
* [Postnet][20]
* [Standard 2 of 5][21]
* [UPC Extension 2][22]
* [UPC Extension 5][23]
* [UPC-A][24]
* [UPC-E][25]
Other libraries available for purchase
--------------------------------------
* [Aztec][26]
* [Databar Expanded][27]
* [DataMatrix][28]
* [MaxiCode][29]
* [PDF417][30]
* [QRCode][31]
[1]: https://www.barcodebakery.com
[2]: https://www.barcodebakery.com/en/purchase
[3]: https://www.barcodebakery.com/en/download
[4]: https://www.barcodebakery.com/en/docs/php/guide
[5]: https://www.barcodebakery.com/en/docs/php/barcode/1d
[6]: https://www.barcodebakery.com/en/docs/php/barcode/codabar/api
[7]: https://www.barcodebakery.com/en/docs/php/barcode/code11/api
[8]: https://www.barcodebakery.com/en/docs/php/barcode/code128/api
[9]: https://www.barcodebakery.com/en/docs/php/barcode/code39/api
[10]: https://www.barcodebakery.com/en/docs/php/barcode/code39extended/api
[11]: https://www.barcodebakery.com/en/docs/php/barcode/code93/api
[12]: https://www.barcodebakery.com/en/docs/php/barcode/ean13/api
[13]: https://www.barcodebakery.com/en/docs/php/barcode/ean8/api
[14]: https://www.barcodebakery.com/en/docs/php/barcode/gs1128/api
[15]: https://www.barcodebakery.com/en/docs/php/barcode/intelligentmail/api
[16]: https://www.barcodebakery.com/en/docs/php/barcode/i25/api
[17]: https://www.barcodebakery.com/en/docs/php/barcode/isbn/api
[18]: https://www.barcodebakery.com/en/docs/php/barcode/msi/api
[19]: https://www.barcodebakery.com/en/docs/php/barcode/othercode/api
[20]: https://www.barcodebakery.com/en/docs/php/barcode/postnet/api
[21]: https://www.barcodebakery.com/en/docs/php/barcode/s25/api
[22]: https://www.barcodebakery.com/en/docs/php/barcode/upcext2/api
[23]: https://www.barcodebakery.com/en/docs/php/barcode/upcext5/api
[24]: https://www.barcodebakery.com/en/docs/php/barcode/upca/api
[25]: https://www.barcodebakery.com/en/docs/php/barcode/upce/api
[26]: https://www.barcodebakery.com/en/docs/php/barcode/aztec/api
[27]: https://www.barcodebakery.com/en/docs/php/barcode/databarexpanded/api
[28]: https://www.barcodebakery.com/en/docs/php/barcode/datamatrix/api
[29]: https://www.barcodebakery.com/en/docs/php/barcode/maxicode/api
[30]: https://www.barcodebakery.com/en/docs/php/barcode/pdf417/api
[31]: https://www.barcodebakery.com/en/docs/php/barcode/qrcode/api
[32]: https://github.com/barcode-bakery/barcode-dotnet-1d/

View File

@@ -0,0 +1,55 @@
{
"name": "barcode-bakery/barcode-1d",
"version": "7.0.4",
"license": [
"proprietary",
"CC-BY-NC-ND-4.0"
],
"support": {
"email": "contact@barcodebakery.com",
"docs": "https://www.barcodebakery.com"
},
"type": "library",
"homepage": "https://www.barcodebakery.com",
"authors": [
{
"name": "Jean-Sébastien Goupil",
"email": "contact@barcodebakery.com"
}
],
"description": "Generates 1D barcodes from a PHP server to a file or HTML document.",
"autoload": {
"psr-4": {
"BarcodeBakery\\Barcode\\": "src"
}
},
"keywords": [
"barcode",
"generator",
"bakery",
"barcodebakery",
"codabar",
"code11",
"code39",
"code39extended",
"code93",
"code128",
"ean-8",
"ean-13",
"ean8",
"ean13",
"isbn",
"i25",
"s25",
"msi",
"upc-a",
"upc-e",
"upcext2",
"upcext5"
],
"require": {
"php": ">=7.4",
"ext-gd": "*",
"barcode-bakery/barcode-common": ">=7.0.3"
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Codabar
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGcodabar extends BCGBarcode1D
{
/**
* Creates a Codabar barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B', 'C', 'D');
$this->code = array( // 0 added to add an extra space
'00000110', /* 0 */
'00001100', /* 1 */
'00010010', /* 2 */
'11000000', /* 3 */
'00100100', /* 4 */
'10000100', /* 5 */
'01000010', /* 6 */
'01001000', /* 7 */
'01100000', /* 8 */
'10010000', /* 9 */
'00011000', /* - */
'00110000', /* $ */
'10001010', /* : */
'10100010', /* / */
'10101000', /* . */
'00111110', /* + */
'00110100', /* A */
'01010010', /* B */
'00010110', /* C */
'00011100' /* D */
);
}
/**
* Parses the text before displaying it.
*
* @param mixed $text The text.
* @return void
*/
public function parse($text): void
{
parent::parse(strtoupper($text)); // Only Capital Letters are Allowed
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->text[$i]), true);
}
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$textLength = 0;
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$index = $this->findIndex($this->text[$i]);
if ($index !== false) {
$textLength += 8;
$textLength += substr_count($this->code[$index], '1');
}
}
$width += $textLength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('codabar', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('codabar', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must start by A, B, C or D
if ($c === 0 || ($this->text[0] !== 'A' && $this->text[0] !== 'B' && $this->text[0] !== 'C' && $this->text[0] !== 'D')) {
throw new BCGParseException('codabar', 'The text must start by the character A, B, C, or D.');
}
// Must end by A, B, C or D
$c2 = $c - 1;
if ($c2 === 0 || ($this->text[$c2] !== 'A' && $this->text[$c2] !== 'B' && $this->text[$c2] !== 'C' && $this->text[$c2] !== 'D')) {
throw new BCGParseException('codabar', 'The text must end by the character A, B, C, or D.');
}
parent::validate();
}
}

View File

@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Code 11
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGcode11 extends BCGBarcode1D
{
/**
* Creates a Code 11 barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-');
$this->code = array( // 0 added to add an extra space
'000010', /* 0 */
'100010', /* 1 */
'010010', /* 2 */
'110000', /* 3 */
'001010', /* 4 */
'101000', /* 5 */
'011000', /* 6 */
'000110', /* 7 */
'100100', /* 8 */
'100000', /* 9 */
'001000' /* - */
);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Starting Code
$this->drawChar($image, '001100', true);
// Chars
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->text[$i]), true);
}
// Checksum
$this->calculateChecksum();
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->code[$this->checksumValue[$i]], true);
}
// Ending Code
$this->drawChar($image, '00110', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 8;
$textlength = 0;
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$textlength += $this->getIndexLength($this->findIndex($this->text[$i]));
}
$checksumlength = 0;
$this->calculateChecksum();
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$checksumlength += $this->getIndexLength($this->checksumValue[$i]);
}
$endlength = 7;
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('code11', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('code11', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Checksum
// First CheckSUM "C"
// The "C" checksum character is the modulo 11 remainder of the sum of the weighted
// value of the data characters. The weighting value starts at "1" for the right-most
// data character, 2 for the second to last, 3 for the third-to-last, and so on up to 20.
// After 10, the sequence wraps around back to 1.
// Second CheckSUM "K"
// Same as CheckSUM "C" but we count the CheckSum "C" at the end
// After 9, the sequence wraps around back to 1.
$sequenceMultiplier = array(10, 9);
$tempText = $this->text;
$this->checksumValue = array();
for ($z = 0; $z < 2; $z++) {
$c = strlen($tempText);
// We don't display the K CheckSum if the original text had a length less than 10
if ($c <= 10 && $z === 1) {
break;
}
$checksum = 0;
for ($i = $c, $j = 0; $i > 0; $i--, $j++) {
$multiplier = $i % $sequenceMultiplier[$z];
if ($multiplier === 0) {
$multiplier = $sequenceMultiplier[$z];
}
$checksum += $this->findIndex($tempText[$j]) * $multiplier;
}
$this->checksumValue[$z] = $checksum % 11;
$tempText .= $this->keys[$this->checksumValue[$z]];
}
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === false) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== false) {
$ret = '';
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$ret .= $this->keys[$this->checksumValue[$i]];
}
return $ret;
}
return null;
}
private function getIndexLength(int $index): int
{
$length = 0;
if ($index !== false) {
$length += 6;
$length += substr_count($this->code[$index], '1');
}
return $length;
}
}

View File

@@ -0,0 +1,932 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Code 128, A, B, C
*
* # Code C Working properly only on PHP4 or PHP5.0.3+ due to bug :
* http://bugs.php.net/bug.php?id=28862
*
* !! Warning !!
* If you display the checksum on the label, you may obtain
* some garbage since some characters are not displayable.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGArgumentException;
use BarcodeBakery\Common\BCGParseException;
define('CODE128_A', 1); // Table A
define('CODE128_B', 2); // Table B
define('CODE128_C', 3); // Table C
class BCGcode128 extends BCGBarcode1D
{
const CODE128_A = 1;
const CODE128_B = 2;
const CODE128_C = 3;
// TODO assign private to these const. PHP 7.1
const KEYA_FNC3 = 96;
const KEYA_FNC2 = 97;
const KEYA_SHIFT = 98;
const KEYA_CODEC = 99;
const KEYA_CODEB = 100;
const KEYA_FNC4 = 101;
const KEYA_FNC1 = 102;
const KEYB_FNC3 = 96;
const KEYB_FNC2 = 97;
const KEYB_SHIFT = 98;
const KEYB_CODEC = 99;
const KEYB_FNC4 = 100;
const KEYB_CODEA = 101;
const KEYB_FNC1 = 102;
const KEYC_CODEB = 100;
const KEYC_CODEA = 101;
const KEYC_FNC1 = 102;
const KEY_STARTA = 103;
const KEY_STARTB = 104;
const KEY_STARTC = 105;
const KEY_STOP = 106;
protected string $keysA;
protected string $keysB;
protected string $keysC;
private ?string $startingText;
private ?array $indcheck;
private ?array $data;
private string $lastTable;
private bool $tilde;
private array $shift;
private array $latch;
private array $fnc;
private array $METHOD; // Array of method available to create Code128 (CODE128_A, CODE128_B, CODE128_C)
/**
* Creates a Code 128 barcode.
*
* @param string|null $start The start table.
*/
public function __construct(?string $start = null)
{
parent::__construct();
/* CODE 128 A */
$this->keysA = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_';
for ($i = 0; $i < 32; $i++) {
$this->keysA .= chr($i);
}
/* CODE 128 B */
$this->keysB = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' . chr(127);
/* CODE 128 C */
$this->keysC = '0123456789';
$this->code = array(
'101111', /* 00 */
'111011', /* 01 */
'111110', /* 02 */
'010112', /* 03 */
'010211', /* 04 */
'020111', /* 05 */
'011102', /* 06 */
'011201', /* 07 */
'021101', /* 08 */
'110102', /* 09 */
'110201', /* 10 */
'120101', /* 11 */
'001121', /* 12 */
'011021', /* 13 */
'011120', /* 14 */
'002111', /* 15 */
'012011', /* 16 */
'012110', /* 17 */
'112100', /* 18 */
'110021', /* 19 */
'110120', /* 20 */
'102101', /* 21 */
'112001', /* 22 */
'201020', /* 23 */
'200111', /* 24 */
'210011', /* 25 */
'210110', /* 26 */
'201101', /* 27 */
'211001', /* 28 */
'211100', /* 29 */
'101012', /* 30 */
'101210', /* 31 */
'121010', /* 32 */
'000212', /* 33 */
'020012', /* 34 */
'020210', /* 35 */
'001202', /* 36 */
'021002', /* 37 */
'021200', /* 38 */
'100202', /* 39 */
'120002', /* 40 */
'120200', /* 41 */
'001022', /* 42 */
'001220', /* 43 */
'021020', /* 44 */
'002012', /* 45 */
'002210', /* 46 */
'022010', /* 47 */
'202010', /* 48 */
'100220', /* 49 */
'120020', /* 50 */
'102002', /* 51 */
'102200', /* 52 */
'102020', /* 53 */
'200012', /* 54 */
'200210', /* 55 */
'220010', /* 56 */
'201002', /* 57 */
'201200', /* 58 */
'221000', /* 59 */
'203000', /* 60 */
'110300', /* 61 */
'320000', /* 62 */
'000113', /* 63 */
'000311', /* 64 */
'010013', /* 65 */
'010310', /* 66 */
'030011', /* 67 */
'030110', /* 68 */
'001103', /* 69 */
'001301', /* 70 */
'011003', /* 71 */
'011300', /* 72 */
'031001', /* 73 */
'031100', /* 74 */
'130100', /* 75 */
'110003', /* 76 */
'302000', /* 77 */
'130001', /* 78 */
'023000', /* 79 */
'000131', /* 80 */
'010031', /* 81 */
'010130', /* 82 */
'003101', /* 83 */
'013001', /* 84 */
'013100', /* 85 */
'300101', /* 86 */
'310001', /* 87 */
'310100', /* 88 */
'101030', /* 89 */
'103010', /* 90 */
'301010', /* 91 */
'000032', /* 92 */
'000230', /* 93 */
'020030', /* 94 */
'003002', /* 95 */
'003200', /* 96 */
'300002', /* 97 */
'300200', /* 98 */
'002030', /* 99 */
'003020', /* 100*/
'200030', /* 101*/
'300020', /* 102*/
'100301', /* 103*/
'100103', /* 104*/
'100121', /* 105*/
'122000' /*STOP*/
);
$this->setStart($start);
$this->setTilde(true);
// Latches and Shifts
$this->latch = array(
array(null, self::KEYA_CODEB, self::KEYA_CODEC),
array(self::KEYB_CODEA, null, self::KEYB_CODEC),
array(self::KEYC_CODEA, self::KEYC_CODEB, null)
);
$this->shift = array(
array(null, self::KEYA_SHIFT),
array(self::KEYB_SHIFT, null)
);
$this->fnc = array(
array(self::KEYA_FNC1, self::KEYA_FNC2, self::KEYA_FNC3, self::KEYA_FNC4),
array(self::KEYB_FNC1, self::KEYB_FNC2, self::KEYB_FNC3, self::KEYB_FNC4),
array(self::KEYC_FNC1, null, null, null)
);
// Method available
$this->METHOD = array(CODE128_A => 'A', CODE128_B => 'B', CODE128_C => 'C');
}
/**
* Specifies the start code. Can be 'A', 'B', 'C', or null
* - Table A: Capitals + ASCII 0-31 + punct
* - Table B: Capitals + LowerCase + punct
* - Table C: Numbers
*
* If null is specified, the table selection is automatically made.
* The default is null.
*
* @param string|null $table The table.
* @return void
*/
public function setStart(?string $table): void
{
if ($table !== 'A' && $table !== 'B' && $table !== 'C' && $table !== null) {
throw new BCGArgumentException('The starting table must be A, B, C or null.', 'table');
}
$this->startingText = $table;
}
/**
* Gets the tilde.
*
* @return bool True if enabled.
*/
public function getTilde(): bool
{
return $this->tilde;
}
/**
* Accepts tilde to be process as a special character.
* If true, you can do this:
* - ~~ : to make ONE tilde
* - ~Fx : to insert FCNx. x is equal from 1 to 4.
*
* @param bool $accept Accept the tilde as special character.
* @return void
*/
public function setTilde(bool $accept): void
{
$this->tilde = (bool)$accept;
}
/**
* Parses the text before displaying it.
*
* @param mixed $text The input.
* @return void
*/
public function parse($text): void
{
parent::parse($text);
$this->setStartFromText($this->text);
$text = $this->text;
$this->text = '';
$seq = '';
$currentMode = $this->startingText;
// Here, we format correctly what the user gives.
if (!is_array($text)) {
$seq = $this->getSequence($text, $currentMode);
$this->text = $text;
} else {
// This loop checks for UnknownText AND raises an exception if a character is not allowed in a table
$ao = new \ArrayObject($text);
$it = $ao->getIterator();
while ($it->valid()) { // We take each value
$val1 = $it->current();
if (!is_array($val1)) { // This is not a table
if (is_string($val1)) { // If it's a string, parse as unknown
$seq .= $this->getSequence($val1, $currentMode);
$this->text .= $val1;
} else {
// it's the case of "array(ENCODING, 'text')"
// We got ENCODING in $val1, getting the next in $val2
$it->next();
$val2 = $it->current();
$seq .= $this->{'setParse' . $this->METHOD[$val1]}($val2, $currentMode);
$this->text .= $val2;
}
} else { // The method is specified
// $val1[0] = ENCODING
// $val1[1] = 'text'
$value = isset($val1[1]) ? $val1[1] : ''; // If data available
$seq .= $this->{'setParse' . $this->METHOD[$val1[0]]}($value, $currentMode);
$this->text .= $value;
}
$it->next();
}
}
if ($seq !== '') {
$bitstream = $this->createBinaryStream($this->text, $seq);
$this->setData($bitstream);
}
$this->addDefaultLabel();
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$c = count($this->data);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->data[$i], true);
}
$this->drawChar($image, '1', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
// Contains start + text + checksum + stop
$textlength = count($this->data) * 11;
$endlength = 2; // + final bar
$width += $textlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Checksum
// First Char (START)
// + Starting with the first data character following the start character,
// take the value of the character (between 0 and 102, inclusive) multiply
// it by its character position (1) and add that to the running checksum.
// Modulated 103
$this->checksumValue = array($this->indcheck[0]);
$c = count($this->indcheck);
for ($i = 1; $i < $c; $i++) {
$this->checksumValue[0] += $this->indcheck[$i] * $i;
}
$this->checksumValue[0] = $this->checksumValue[0] % 103;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
if ($this->lastTable === 'C') {
return (string)$this->checksumValue[0];
}
return $this->{'keys' . $this->lastTable}[$this->checksumValue[0]];
}
return null;
}
/**
* Specifies the startingText table if none has been specified earlier.
*
* @param mixed $text The text.
* @return void
*/
private function setStartFromText($text): void
{
if ($this->startingText === null) {
// If we have a forced table at the start, we get that one...
if (is_array($text)) {
if (is_array($text[0])) {
// Code like array(array(ENCODING, ''))
$this->startingText = $this->METHOD[$text[0][0]];
return;
} else {
if (is_string($text[0])) {
// Code like array('test') (Automatic text)
$text = $text[0];
} else {
// Code like array(ENCODING, '')
$this->startingText = $this->METHOD[$text[0]];
return;
}
}
}
// At this point, we had an "automatic" table selection...
// If we can get at least 4 numbers, go in C; otherwise go in B.
$tmp = preg_quote($this->keysC, '/');
$length = strlen($text);
if ($length >= 4 && preg_match('/[' . $tmp . ']/', substr($text, 0, 4))) {
$this->startingText = 'C';
} else {
if ($length > 0 && strpos($this->keysB, $text[0]) !== false) {
$this->startingText = 'B';
} else {
$this->startingText = 'A';
}
}
}
}
/**
* Extracts the ~ value from the $text at the $pos.
* If the tilde is not ~~, ~F1, ~F2, ~F3, ~F4; an error is raised.
*
* @param string $text The text.
* @param int $pos The position.
* @return string Extracted tilde value.
*/
private static function extractTilde(string $text, int $pos): string
{
if ($text[$pos] === '~') {
if (isset($text[$pos + 1])) {
// Do we have a tilde?
if ($text[$pos + 1] === '~') {
return '~~';
} elseif ($text[$pos + 1] === 'F') {
// Do we have a number after?
if (isset($text[$pos + 2])) {
$v = intval($text[$pos + 2]);
if ($v >= 1 && $v <= 4) {
return '~F' . $v;
} else {
throw new BCGParseException('code128', 'Bad ~F. You must provide a number from 1 to 4.');
}
} else {
throw new BCGParseException('code128', 'Bad ~F. You must provide a number from 1 to 4.');
}
} else {
throw new BCGParseException('code128', 'Wrong code after the ~.');
}
} else {
throw new BCGParseException('code128', 'Wrong code after the ~.');
}
} else {
throw new BCGParseException('code128', 'There is no ~ at this location.');
}
}
/**
* Gets the "dotted" sequence for the $text based on the $currentMode.
* There is also a check if we use the special tilde ~
*
* @param string $text The text.
* @param string $currentMode The current mode.
* @return string The sequence.
*/
private function getSequenceParsed(string $text, string $currentMode): string
{
if ($this->tilde) {
$sequence = '';
$previousPos = 0;
while (($pos = strpos($text, '~', $previousPos)) !== false) {
$tildeData = self::extractTilde($text, $pos);
$simpleTilde = ($tildeData === '~~');
if ($simpleTilde && $currentMode !== 'B') {
throw new BCGParseException('code128', 'The Table ' . $currentMode . ' doesn\'t contain the character ~.');
}
// At this point, we know we have ~Fx
if ($tildeData !== '~F1' && $currentMode === 'C') {
// The mode C doesn't support ~F2, ~F3, ~F4
throw new BCGParseException('code128', 'The Table C doesn\'t contain the function ' . $tildeData . '.');
}
$length = $pos - $previousPos;
if ($currentMode === 'C') {
if ($length % 2 === 1) {
throw new BCGParseException('code128', 'The text "' . $text . '" must have an even number of character to be encoded in Table C.');
}
}
$sequence .= str_repeat('.', $length);
$sequence .= '.';
$sequence .= (!$simpleTilde) ? 'F' : '';
$previousPos = $pos + strlen($tildeData);
}
// Flushing
$length = strlen($text) - $previousPos;
if ($currentMode === 'C') {
if ($length % 2 === 1) {
throw new BCGParseException('code128', 'The text "' . $text . '" must have an even number of character to be encoded in Table C.');
}
}
$sequence .= str_repeat('.', $length);
return $sequence;
} else {
return str_repeat('.', strlen($text));
}
}
/**
* Parses the text and returns the appropriate sequence for the Table A.
*
* @param string $text The text.
* @param string $currentMode The current mode.
* @return string The sequence.
*/
private function setParseA(string $text, string &$currentMode): string
{
$tmp = preg_quote($this->keysA, '/');
// If we accept the ~ for special character, we must allow it.
if ($this->tilde) {
$tmp .= '~';
}
$matches = array();
if (preg_match('/[^' . $tmp . ']/', $text, $matches) === 1) {
// We found something not allowed
throw new BCGParseException('code128', 'The text "' . $text . '" can\'t be parsed with the Table A. The character "' . $matches[0] . '" is not allowed.');
} else {
$latch = ($currentMode === 'A') ? '' : '0';
$currentMode = 'A';
return $latch . $this->getSequenceParsed($text, $currentMode);
}
}
/**
* Parses the text and returns the appropriate sequence for the Table B.
*
* @param string $text The text.
* @param string $currentMode The current mode.
* @return string The sequence.
*/
private function setParseB(string $text, string &$currentMode): string
{
$tmp = preg_quote($this->keysB, '/');
$matches = array();
if (preg_match('/[^' . $tmp . ']/', $text, $matches) === 1) {
// We found something not allowed
throw new BCGParseException('code128', 'The text "' . $text . '" can\'t be parsed with the Table B. The character "' . $matches[0] . '" is not allowed.');
} else {
$latch = ($currentMode === 'B') ? '' : '1';
$currentMode = 'B';
return $latch . $this->getSequenceParsed($text, $currentMode);
}
}
/**
* Parses the text and returns the appropriate sequence for the Table C.
*
* @param string $text The text.
* @param string $currentMode The current mode.
* @return string The sequence.
*/
private function setParseC(string $text, string &$currentMode): string
{
$tmp = preg_quote($this->keysC, '/');
// If we accept the ~ for special character, we must allow it.
if ($this->tilde) {
$tmp .= '~F';
}
$matches = array();
if (preg_match('/[^' . $tmp . ']/', $text, $matches) === 1) {
// We found something not allowed
throw new BCGParseException('code128', 'The text "' . $text . '" can\'t be parsed with the Table C. The character "' . $matches[0] . '" is not allowed.');
} else {
$latch = ($currentMode === 'C') ? '' : '2';
$currentMode = 'C';
return $latch . $this->getSequenceParsed($text, $currentMode);
}
}
/**
* Depending on the $text, it will return the correct
* sequence to encode the text.
*
* @param string $text The text.
* @param string $startingText The starting text.
* @return string The sequence.
*/
private function getSequence(string $text, string $startingText): string
{
$e = 10000;
$latLen = array(
array(0, 1, 1),
array(1, 0, 1),
array(1, 1, 0)
);
$shftLen = array(
array($e, 1, $e),
array(1, $e, $e),
array($e, $e, $e)
);
$charSiz = array(2, 2, 1);
$startA = $e;
$startB = $e;
$startC = $e;
if ($startingText === 'A') {
$startA = 0;
}
if ($startingText === 'B') {
$startB = 0;
}
if ($startingText === 'C') {
$startC = 0;
}
$curLen = array($startA, $startB, $startC);
$curSeq = array(null, null, null);
$nextNumber = false;
$x = 0;
$xLen = strlen($text);
for ($x = 0; $x < $xLen; $x++) {
$input = $text[$x];
// 1.
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if (($curLen[$i] + $latLen[$i][$j]) < $curLen[$j]) {
$curLen[$j] = $curLen[$i] + $latLen[$i][$j];
$curSeq[$j] = $curSeq[$i] . $j;
}
}
}
// 2.
$nxtLen = array($e, $e, $e);
$nxtSeq = array();
// 3.
$flag = false;
$posArray = array();
// Special case, we do have a tilde and we process them
if ($this->tilde && $input === '~') {
$tildeData = self::extractTilde($text, $x);
if ($tildeData === '~~') {
// We simply skip a tilde
$posArray[] = 1;
$x++;
} elseif (substr($tildeData, 0, 2) === '~F') {
$v = intval($tildeData[2]);
$posArray[] = 0;
$posArray[] = 1;
if ($v === 1) {
$posArray[] = 2;
}
$x += 2;
$flag = true;
}
} else {
$pos = strpos($this->keysA, $input);
if ($pos !== false) {
$posArray[] = 0;
}
$pos = strpos($this->keysB, $input);
if ($pos !== false) {
$posArray[] = 1;
}
// Do we have the next char a number?? OR a ~F1
$pos = strpos($this->keysC, $input);
if ($nextNumber || ($pos !== false && isset($text[$x + 1]) && strpos($this->keysC, $text[$x + 1]) !== false)) {
$nextNumber = !$nextNumber;
$posArray[] = 2;
}
}
$c = count($posArray);
for ($i = 0; $i < $c; $i++) {
if (($curLen[$posArray[$i]] + $charSiz[$posArray[$i]]) < $nxtLen[$posArray[$i]]) {
$nxtLen[$posArray[$i]] = $curLen[$posArray[$i]] + $charSiz[$posArray[$i]];
$nxtSeq[$posArray[$i]] = $curSeq[$posArray[$i]] . '.';
}
for ($j = 0; $j < 2; $j++) {
if ($j === $posArray[$i]) {
continue;
}
if (($curLen[$j] + $shftLen[$j][$posArray[$i]] + $charSiz[$posArray[$i]]) < $nxtLen[$j]) {
$nxtLen[$j] = $curLen[$j] + $shftLen[$j][$posArray[$i]] + $charSiz[$posArray[$i]];
$nxtSeq[$j] = $curSeq[$j] . chr($posArray[$i] + 65) . '.';
}
}
}
if ($c === 0) {
// We found an unsuported character
throw new BCGParseException('code128', 'Character ' . $input . ' not supported.');
}
if ($flag) {
for ($i = 0; $i < 5; $i++) {
if (isset($nxtSeq[$i])) {
$nxtSeq[$i] .= 'F';
}
}
}
// 4.
for ($i = 0; $i < 3; $i++) {
$curLen[$i] = $nxtLen[$i];
if (isset($nxtSeq[$i])) {
$curSeq[$i] = $nxtSeq[$i];
}
}
}
// Every curLen under $e is possible but we take the smallest
$m = $e;
$k = -1;
for ($i = 0; $i < 3; $i++) {
if ($curLen[$i] < $m) {
$k = $i;
$m = $curLen[$i];
}
}
if ($k === -1) {
return '';
}
return $curSeq[$k];
}
/**
* Depending on the sequence $seq given (returned from getSequence()),
* this method will return the code stream in an array. Each char will be a
* string of bit based on the Code 128.
*
* Each letter from the sequence represents bits.
*
* 0 to 2 are latches
* A to B are Shift + Letter
* . is a char in the current encoding
*
* @param string $text The text.
* @param string $seq The sequence.
* @return array The stream.
*/
private function createBinaryStream(string $text, string $seq): array
{
$c = strlen($seq);
$data = array(); // code stream
$indcheck = array(); // index for checksum
$currentEncoding = 0;
if ($this->startingText === 'A') {
$currentEncoding = 0;
$indcheck[] = self::KEY_STARTA;
$this->lastTable = 'A';
} elseif ($this->startingText === 'B') {
$currentEncoding = 1;
$indcheck[] = self::KEY_STARTB;
$this->lastTable = 'B';
} elseif ($this->startingText === 'C') {
$currentEncoding = 2;
$indcheck[] = self::KEY_STARTC;
$this->lastTable = 'C';
}
$data[] = $this->code[103 + $currentEncoding];
$temporaryEncoding = -1;
for ($i = 0, $counter = 0; $i < $c; $i++) {
$input = $seq[$i];
$inputI = intval($input);
if ($input === '.') {
$this->encodeChar($data, $currentEncoding, $seq, $text, $i, $counter, $indcheck);
if ($temporaryEncoding !== -1) {
$currentEncoding = $temporaryEncoding;
$temporaryEncoding = -1;
}
} elseif ($input >= 'A' && $input <= 'B') {
// We shift
$encoding = ord($input) - 65;
$shift = $this->shift[$currentEncoding][$encoding];
$indcheck[] = $shift;
$data[] = $this->code[$shift];
if ($temporaryEncoding === -1) {
$temporaryEncoding = $currentEncoding;
}
$currentEncoding = $encoding;
} elseif ($inputI >= 0 && $inputI < 3) {
$temporaryEncoding = -1;
// We latch
$latch = $this->latch[$currentEncoding][$inputI];
if ($latch !== null) {
$indcheck[] = $latch;
$this->lastTable = chr(65 + $inputI);
$data[] = $this->code[$latch];
$currentEncoding = $inputI;
}
}
}
return array($indcheck, $data);
}
/**
* Encodes characters, base on its encoding and sequence.
*
* @param int[] $data The data.
* @param int $encoding The encoding.
* @param string $seq The sequence.
* @param string $text The text.
* @param int $i The position.
* @param int $counter The counter.
* @param int[] $indcheck The checksum counter.
* @return void
*/
private function encodeChar(array &$data, int $encoding, string $seq, string $text, int &$i, int &$counter, array &$indcheck): void
{
if (isset($seq[$i + 1]) && $seq[$i + 1] === 'F') {
// We have a flag !!
if ($text[$counter + 1] === 'F') {
$number = $text[$counter + 2];
$fnc = $this->fnc[$encoding][$number - 1];
$indcheck[] = $fnc;
$data[] = $this->code[$fnc];
// Skip F + number
$counter += 2;
} else {
// Not supposed
}
$i++;
} else {
if ($encoding === 2) {
// We take 2 numbers in the same time
$code = (int)substr($text, $counter, 2);
$indcheck[] = $code;
$data[] = $this->code[$code];
$counter++;
$i++;
} else {
$keys = ($encoding === 0) ? $this->keysA : $this->keysB;
$pos = strpos($keys, $text[$counter]);
$indcheck[] = $pos;
$data[] = $this->code[$pos];
}
}
$counter++;
}
/**
* Saves data into the classes.
*
* This method will save data, calculate real column number
* (if -1 was selected), the real error level (if -1 was
* selected)... It will add Padding to the end and generate
* the error codes.
*
* @param array $data The data.
* @return void
*/
private function setData(array $data): void
{
$this->indcheck = $data[0];
$this->data = $data[1];
$this->calculateChecksum();
$this->data[] = $this->code[$this->checksumValue[0]];
$this->data[] = $this->code[self::KEY_STOP];
}
}

View File

@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Code 39
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGcode39 extends BCGBarcode1D
{
protected int $starting;
protected int $ending;
protected bool $checksum;
/**
* Creates a Code 39 barcode.
*/
public function __construct()
{
parent::__construct();
$this->starting = $this->ending = 43;
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%', '*');
$this->code = array( // 0 added to add an extra space
'0001101000', /* 0 */
'1001000010', /* 1 */
'0011000010', /* 2 */
'1011000000', /* 3 */
'0001100010', /* 4 */
'1001100000', /* 5 */
'0011100000', /* 6 */
'0001001010', /* 7 */
'1001001000', /* 8 */
'0011001000', /* 9 */
'1000010010', /* A */
'0010010010', /* B */
'1010010000', /* C */
'0000110010', /* D */
'1000110000', /* E */
'0010110000', /* F */
'0000011010', /* G */
'1000011000', /* H */
'0010011000', /* I */
'0000111000', /* J */
'1000000110', /* K */
'0010000110', /* L */
'1010000100', /* M */
'0000100110', /* N */
'1000100100', /* O */
'0010100100', /* P */
'0000001110', /* Q */
'1000001100', /* R */
'0010001100', /* S */
'0000101100', /* T */
'1100000010', /* U */
'0110000010', /* V */
'1110000000', /* W */
'0100100010', /* X */
'1100100000', /* Y */
'0110100000', /* Z */
'0100001010', /* - */
'1100001000', /* . */
'0110001000', /* */
'0101010000', /* $ */
'0101000100', /* / */
'0100010100', /* + */
'0001010100', /* % */
'0100101000' /* * */
);
$this->setChecksum(false);
}
/**
* Sets if we display the checksum.
*
* @param bool $checksum Displays the checksum.
* @return void
*/
public function setChecksum(bool $checksum): void
{
$this->checksum = (bool)$checksum;
}
/**
* Parses the text before displaying it.
*
* @param string $text The text.
* @return void
*/
public function parse($text): void
{
parent::parse(strtoupper($text)); // Only Capital Letters are Allowed
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Starting *
$this->drawChar($image, $this->code[$this->starting], true);
// Chars
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->text[$i]), true);
}
// Checksum (rarely used)
if ($this->checksum === true) {
$this->calculateChecksum();
$this->drawChar($image, $this->code[$this->checksumValue[0] % 43], true);
}
// Ending *
$this->drawChar($image, $this->code[$this->ending], true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$textlength = 13 * strlen($this->text);
$startlength = 13;
$checksumlength = 0;
if ($this->checksum === true) {
$checksumlength = 13;
}
$endlength = 13;
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('code39', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('code39', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
if (strpos($this->text, '*') !== false) {
throw new BCGParseException('code39', 'The character \'*\' is not allowed.');
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$this->checksumValue[0] += $this->findIndex($this->text[$i]);
}
$this->checksumValue[0] = $this->checksumValue[0] % 43;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
}

View File

@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Code 39 Extended
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGcode39extended extends BCGcode39
{
const EXTENDED_1 = 39;
const EXTENDED_2 = 40;
const EXTENDED_3 = 41;
const EXTENDED_4 = 42;
protected ?array $indcheck;
protected ?array $data;
/**
* Creates a Code 39 Extended barcode.
*/
public function __construct()
{
parent::__construct();
// We just put parenthesis around special characters.
$this->keys[self::EXTENDED_1] = '($)';
$this->keys[self::EXTENDED_2] = '(/)';
$this->keys[self::EXTENDED_3] = '(+)';
$this->keys[self::EXTENDED_4] = '(%)';
}
/**
* Parses the text before displaying it.
*
* @param string $text The text.
* @return void
*/
public function parse($text): void
{
BCGBarcode1D::parse($text);
$data = array();
$indcheck = array();
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$pos = array_search($this->text[$i], $this->keys);
if ($pos === false) {
// Search in extended?
$extended = self::getExtendedVersion($this->text[$i]);
if ($extended === null) {
throw new BCGParseException('code39extended', 'The character \'' . $this->text[$i] . '\' is not allowed.');
} else {
$extc = strlen($extended);
for ($j = 0; $j < $extc; $j++) {
$v = $extended[$j];
if ($v === '$') {
$indcheck[] = self::EXTENDED_1;
$data[] = $this->code[self::EXTENDED_1];
} elseif ($v === '%') {
$indcheck[] = self::EXTENDED_2;
$data[] = $this->code[self::EXTENDED_2];
} elseif ($v === '/') {
$indcheck[] = self::EXTENDED_3;
$data[] = $this->code[self::EXTENDED_3];
} elseif ($v === '+') {
$indcheck[] = self::EXTENDED_4;
$data[] = $this->code[self::EXTENDED_4];
} else {
$pos2 = array_search($v, $this->keys);
$indcheck[] = $pos2;
$data[] = $this->code[$pos2];
}
}
}
} else {
$indcheck[] = $pos;
$data[] = $this->code[$pos];
}
}
$this->setData(array($indcheck, $data));
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Starting *
$this->drawChar($image, $this->code[$this->starting], true);
$c = count($this->data);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->data[$i], true);
}
// Checksum (rarely used)
if ($this->checksum === true) {
$this->drawChar($image, $this->code[$this->checksumValue[0] % 43], true);
}
// Ending *
$this->drawChar($image, $this->code[$this->ending], true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$textlength = 13 * count($this->data);
$startlength = 13;
$checksumlength = 0;
if ($this->checksum === true) {
$checksumlength = 13;
}
$endlength = 13;
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return BCGBarcode1D::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
// We do nothing.
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
$this->checksumValue = array(0);
$c = count($this->indcheck);
for ($i = 0; $i < $c; $i++) {
$this->checksumValue[0] += $this->indcheck[$i];
}
$this->checksumValue[0] = $this->checksumValue[0] % 43;
}
/**
* Saves data into the classes.
*
* This method will save data, calculate real column number
* (if -1 was selected), the real error level (if -1 was
* selected)... It will add Padding to the end and generate
* the error codes.
*
* @param array $data The data.
* @return void
*/
private function setData(array $data): void
{
$this->indcheck = $data[0];
$this->data = $data[1];
$this->calculateChecksum();
}
/**
* Returns the extended reprensentation of the character.
*
* @param string $val The value.
* @return string|null The representation.
*/
private static function getExtendedVersion(string $val): ?string
{
$o = ord($val);
if ($o === 0) {
return '%U';
} elseif ($o >= 1 && $o <= 26) {
return '$' . chr($o + 64);
} elseif (($o >= 33 && $o <= 44) || $o === 47 || $o === 48) {
return '/' . chr($o + 32);
} elseif ($o >= 97 && $o <= 122) {
return '+' . chr($o - 32);
} elseif ($o >= 27 && $o <= 31) {
return '%' . chr($o + 38);
} elseif ($o >= 59 && $o <= 63) {
return '%' . chr($o + 11);
} elseif ($o >= 91 && $o <= 95) {
return '%' . chr($o - 16);
} elseif ($o >= 123 && $o <= 127) {
return '%' . chr($o - 43);
} elseif ($o === 64) {
return '%V';
} elseif ($o === 96) {
return '%W';
} elseif ($o > 127) {
return null;
} else {
return $val;
}
}
}

View File

@@ -0,0 +1,319 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Code 93
*
* !! Warning !!
* If you display the checksum on the barcode, you may obtain
* some garbage since some characters are not displayable.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGcode93 extends BCGBarcode1D
{
const EXTENDED_1 = 43;
const EXTENDED_2 = 44;
const EXTENDED_3 = 45;
const EXTENDED_4 = 46;
private int $starting;
private int $ending;
private ?array $indcheck;
private ?array $data;
/**
* Creates a Code 93 barcode.
*/
public function __construct()
{
parent::__construct();
$this->starting = $this->ending = 47; /* * */
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%', '($)', '(%)', '(/)', '(+)', '(*)');
$this->code = array(
'020001', /* 0 */
'000102', /* 1 */
'000201', /* 2 */
'000300', /* 3 */
'010002', /* 4 */
'010101', /* 5 */
'010200', /* 6 */
'000003', /* 7 */
'020100', /* 8 */
'030000', /* 9 */
'100002', /* A */
'100101', /* B */
'100200', /* C */
'110001', /* D */
'110100', /* E */
'120000', /* F */
'001002', /* G */
'001101', /* H */
'001200', /* I */
'011001', /* J */
'021000', /* K */
'000012', /* L */
'000111', /* M */
'000210', /* N */
'010011', /* O */
'020010', /* P */
'101001', /* Q */
'101100', /* R */
'100011', /* S */
'100110', /* T */
'110010', /* U */
'111000', /* V */
'001011', /* W */
'001110', /* X */
'011010', /* Y */
'012000', /* Z */
'010020', /* - */
'200001', /* . */
'200100', /* */
'210000', /* $ */
'001020', /* / */
'002010', /* + */
'100020', /* % */
'010110', /*($)*/
'201000', /*(%)*/
'200010', /*(/)*/
'011100', /*(+)*/
'000030' /*(*)*/
);
}
/**
* Parses the text before displaying it.
*
* @param string $text The text.
* @return void
*/
public function parse($text): void
{
BCGBarcode1D::parse($text);
$data = array();
$indcheck = array();
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$pos = array_search($this->text[$i], $this->keys);
if ($pos === false) {
// Search in extended?
$extended = self::getExtendedVersion($this->text[$i]);
if ($extended === null) {
throw new BCGParseException('code93', 'The character \'' . $this->text[$i] . '\' is not allowed.');
} else {
$extc = strlen($extended);
for ($j = 0; $j < $extc; $j++) {
$v = $extended[$j];
if ($v === '$') {
$indcheck[] = self::EXTENDED_1;
$data[] = $this->code[self::EXTENDED_1];
} elseif ($v === '%') {
$indcheck[] = self::EXTENDED_2;
$data[] = $this->code[self::EXTENDED_2];
} elseif ($v === '/') {
$indcheck[] = self::EXTENDED_3;
$data[] = $this->code[self::EXTENDED_3];
} elseif ($v === '+') {
$indcheck[] = self::EXTENDED_4;
$data[] = $this->code[self::EXTENDED_4];
} else {
$pos2 = array_search($v, $this->keys);
$indcheck[] = $pos2;
$data[] = $this->code[$pos2];
}
}
}
} else {
$indcheck[] = $pos;
$data[] = $this->code[$pos];
}
}
$this->setData(array($indcheck, $data));
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Starting *
$this->drawChar($image, $this->code[$this->starting], true);
$c = count($this->data);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->data[$i], true);
}
// Checksum
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->code[$this->checksumValue[$i]], true);
}
// Ending *
$this->drawChar($image, $this->code[$this->ending], true);
// Draw a Final Bar
$this->drawChar($image, '0', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 9;
$textlength = 9 * count($this->data);
$checksumlength = 2 * 9;
$endlength = 9 + 1; // + final bar
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
// We do nothing.
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Checksum
// First CheckSUM "C"
// The "C" checksum character is the modulo 47 remainder of the sum of the weighted
// value of the data characters. The weighting value starts at "1" for the right-most
// data character, 2 for the second to last, 3 for the third-to-last, and so on up to 20.
// After 20, the sequence wraps around back to 1.
// Second CheckSUM "K"
// Same as CheckSUM "C" but we count the CheckSum "C" at the end
// After 15, the sequence wraps around back to 1.
$sequenceMultiplier = array(20, 15);
$this->checksumValue = array();
$indcheck = $this->indcheck;
for ($z = 0; $z < 2; $z++) {
$checksum = 0;
for ($i = count($indcheck), $j = 0; $i > 0; $i--, $j++) {
$multiplier = $i % $sequenceMultiplier[$z];
if ($multiplier === 0) {
$multiplier = $sequenceMultiplier[$z];
}
$checksum += $indcheck[$j] * $multiplier;
}
$this->checksumValue[$z] = $checksum % 47;
$indcheck[] = $this->checksumValue[$z];
}
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
$ret = '';
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$ret .= $this->keys[$this->checksumValue[$i]];
}
return $ret;
}
return null;
}
/**
* Saves data into the classes.
*
* This method will save data, calculate real column number
* (if -1 was selected), the real error level (if -1 was
* selected)... It will add Padding to the end and generate
* the error codes.
*
* @param array $data The data.
* @return void
*/
private function setData(array $data): void
{
$this->indcheck = $data[0];
$this->data = $data[1];
$this->calculateChecksum();
}
/**
* Returns the extended reprensentation of the character.
*
* @param string $val The value.
* @return string|null The representation.
*/
private static function getExtendedVersion(string $val): ?string
{
$o = ord($val);
if ($o === 0) {
return '%U';
} elseif ($o >= 1 && $o <= 26) {
return '$' . chr($o + 64);
} elseif (($o >= 33 && $o <= 44) || $o === 47 || $o === 48) {
return '/' . chr($o + 32);
} elseif ($o >= 97 && $o <= 122) {
return '+' . chr($o - 32);
} elseif ($o >= 27 && $o <= 31) {
return '%' . chr($o + 38);
} elseif ($o >= 59 && $o <= 63) {
return '%' . chr($o + 11);
} elseif ($o >= 91 && $o <= 95) {
return '%' . chr($o - 16);
} elseif ($o >= 123 && $o <= 127) {
return '%' . chr($o - 43);
} elseif ($o === 64) {
return '%V';
} elseif ($o === 96) {
return '%W';
} elseif ($o > 127) {
return null;
} else {
return $val;
}
}
}

View File

@@ -0,0 +1,361 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - EAN-13
*
* EAN-13 contains
* - 2 system digits (1 not displayed but coded with parity)
* - 5 manufacturer code digits
* - 5 product digits
* - 1 checksum digit
*
* The checksum is always displayed.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGean13 extends BCGBarcode1D
{
protected array $codeParity = array();
protected ?BCGLabel $labelLeft = null;
protected ?BCGLabel $labelCenter1 = null;
protected ?BCGLabel $labelCenter2 = null;
protected bool $alignLabel;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
// Left-Hand Odd Parity starting with a space
// Left-Hand Even Parity is the inverse (0=0012) starting with a space
// Right-Hand is the same of Left-Hand starting with a bar
$this->code = array(
'2100', /* 0 */
'1110', /* 1 */
'1011', /* 2 */
'0300', /* 3 */
'0021', /* 4 */
'0120', /* 5 */
'0003', /* 6 */
'0201', /* 7 */
'0102', /* 8 */
'2001' /* 9 */
);
// Parity, 0=Odd, 1=Even for manufacturer code. Depending on 1st System Digit
$this->codeParity = array(
array(0, 0, 0, 0, 0), /* 0 */
array(0, 1, 0, 1, 1), /* 1 */
array(0, 1, 1, 0, 1), /* 2 */
array(0, 1, 1, 1, 0), /* 3 */
array(1, 0, 0, 1, 1), /* 4 */
array(1, 1, 0, 0, 1), /* 5 */
array(1, 1, 1, 0, 0), /* 6 */
array(1, 0, 1, 0, 1), /* 7 */
array(1, 0, 1, 1, 0), /* 8 */
array(1, 1, 0, 1, 0) /* 9 */
);
$this->alignDefaultLabel(true);
}
/**
* Aligns the default label.
*
* @param bool $align Aligns the label.
* @return void
*/
public function alignDefaultLabel($align): void
{
$this->alignLabel = (bool)$align;
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$this->drawBars($image);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
if ($this->isDefaultEanLabelEnabled()) {
$dimension = $this->labelCenter1->getDimension();
$this->drawExtendedBars($image, $dimension[1] - 2);
}
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 3;
$centerlength = 5;
$textlength = 12 * 7;
$endlength = 3;
$width += $startlength + $centerlength + $textlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
if ($this->isDefaultEanLabelEnabled()) {
$this->processChecksum();
$label = $this->getLabel();
$font = $this->font;
$this->labelLeft = new BCGLabel(substr($label, 0, 1), $font, BCGLabel::POSITION_LEFT, BCGLabel::ALIGN_BOTTOM);
$this->labelLeft->setSpacing(4 * $this->scale);
$this->labelCenter1 = new BCGLabel(substr($label, 1, 6), $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$labelCenter1Dimension = $this->labelCenter1->getDimension();
$this->labelCenter1->setOffset((int)(($this->scale * 44 - $labelCenter1Dimension[0]) / 2 + $this->scale * 2));
$this->labelCenter2 = new BCGLabel(substr($label, 7, 5) . $this->keys[$this->checksumValue[0]], $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$this->labelCenter2->setOffset((int)(($this->scale * 44 - $labelCenter1Dimension[0]) / 2 + $this->scale * 48));
if ($this->alignLabel) {
$labelDimension = $this->labelCenter1->getDimension();
$this->labelLeft->setOffset($labelDimension[1]);
} else {
$labelDimension = $this->labelLeft->getDimension();
$this->labelLeft->setOffset((int)($labelDimension[1] / 2));
}
$this->addLabel($this->labelLeft);
$this->addLabel($this->labelCenter1);
$this->addLabel($this->labelCenter2);
}
}
/**
* Checks if the default ean label is enabled.
*
* @return bool True if default label is enabled.
*/
protected function isDefaultEanLabelEnabled(): bool
{
$label = $this->getLabel();
$font = $this->font;
return $label !== null && $label !== '' && $font !== null && $this->defaultLabel !== null;
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('ean13', 'No data has been entered.');
}
$this->checkCharsAllowed();
$this->checkCorrectLength();
parent::validate();
}
/**
* Check chars allowed.
*
* @return void
*/
protected function checkCharsAllowed(): void
{
// Checking if all chars are allowed
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('ean13', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
}
/**
* Check correct length.
*
* @return void
*/
protected function checkCorrectLength(): void
{
// If we have 13 chars, just flush the last one without throwing anything
$c = strlen($this->text);
if ($c === 13) {
$this->text = substr($this->text, 0, 12);
} elseif ($c !== 12) {
throw new BCGParseException('ean13', 'Must contain 12 digits, the 13th digit is automatically added.');
}
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "odd" position,
// and assign odd/even to each character moving from right to left
// Odd Position = 3, Even Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$odd = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($odd === true) {
$multiplier = 3;
$odd = false;
} else {
$multiplier = 1;
$odd = true;
}
if (!isset($this->keys[$this->text[$i - 1]])) {
return;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = (10 - $this->checksumValue[0] % 10) % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
/**
* Draws the bars.
*
* @param resource $image The surface.
* @return void
*/
protected function drawBars($image): void
{
// Checksum
$this->calculateChecksum();
$tempText = $this->text . $this->keys[$this->checksumValue[0]];
// Starting Code
$this->drawChar($image, '000', true);
// Draw Second Code
$this->drawChar($image, $this->findCode($tempText[1]), false);
// Draw Manufacturer Code
for ($i = 0; $i < 5; $i++) {
$this->drawChar($image, self::inverse($this->findCode($tempText[$i + 2]), $this->codeParity[(int)$tempText[0]][$i]), false);
}
// Draw Center Guard Bar
$this->drawChar($image, '00000', false);
// Draw Product Code
for ($i = 7; $i < 13; $i++) {
$this->drawChar($image, $this->findCode($tempText[$i]), true);
}
// Draw Right Guard Bar
$this->drawChar($image, '000', true);
}
/**
* Draws the extended bars on the image.
*
* @param resource $image The surface.
* @param int $plus How much more we should display the bars.
* @return void
*/
protected function drawExtendedBars($image, int $plus): void
{
$rememberX = $this->positionX;
$rememberH = $this->thickness;
// We increase the bars
$this->thickness = $this->thickness + intval($plus / $this->scale);
$this->positionX = 0;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Center Guard Bar
$this->positionX += 44;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Last Bars
$this->positionX += 44;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX = $rememberX;
$this->thickness = $rememberH;
}
/**
* Inverses the string when the $inverse parameter is equal to 1.
*
* @param string $text The text.
* @param int $inverse The inverse.
* @return string The reversed string.
*/
private static function inverse(string $text, int $inverse = 1): string
{
if ($inverse === 1) {
$text = strrev($text);
}
return $text;
}
}

View File

@@ -0,0 +1,266 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - EAN-8
*
* EAN-8 contains
* - 4 digits
* - 3 digits
* - 1 checksum
*
* The checksum is always displayed.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGean8 extends BCGBarcode1D
{
protected ?BCGLabel $labelLeft = null;
protected ?BCGLabel $labelRight = null;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
// Left-Hand Odd Parity starting with a space
// Right-Hand is the same of Left-Hand starting with a bar
$this->code = array(
'2100', /* 0 */
'1110', /* 1 */
'1011', /* 2 */
'0300', /* 3 */
'0021', /* 4 */
'0120', /* 5 */
'0003', /* 6 */
'0201', /* 7 */
'0102', /* 8 */
'2001' /* 9 */
);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
*/
public function draw($image): void
{
// Checksum
$this->calculateChecksum();
$tempText = $this->text . $this->keys[$this->checksumValue[0]];
// Starting Code
$this->drawChar($image, '000', true);
// Draw First 4 Chars (Left-Hand)
for ($i = 0; $i < 4; $i++) {
$this->drawChar($image, $this->findCode($tempText[$i]), false);
}
// Draw Center Guard Bar
$this->drawChar($image, '00000', false);
// Draw Last 4 Chars (Right-Hand)
for ($i = 4; $i < 8; $i++) {
$this->drawChar($image, $this->findCode($tempText[$i]), true);
}
// Draw Right Guard Bar
$this->drawChar($image, '000', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
if ($this->isDefaultEanLabelEnabled()) {
$dimension = $this->labelRight->getDimension();
$this->drawExtendedBars($image, $dimension[1] - 2);
}
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 3;
$centerlength = 5;
$textlength = 8 * 7;
$endlength = 3;
$width += $startlength + $centerlength + $textlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
if ($this->isDefaultEanLabelEnabled()) {
$this->processChecksum();
$label = $this->getLabel();
$font = $this->font;
$this->labelLeft = new BCGLabel(substr($label, 0, 4), $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$labelLeftDimension = $this->labelLeft->getDimension();
$this->labelLeft->setOffset((int)(($this->scale * 30 - $labelLeftDimension[0]) / 2 + $this->scale * 2));
$this->labelRight = new BCGLabel(substr($label, 4, 3) . $this->keys[$this->checksumValue[0]], $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$labelRightDimension = $this->labelRight->getDimension();
$this->labelRight->setOffset((int)(($this->scale * 30 - $labelRightDimension[0]) / 2 + $this->scale * 34));
$this->addLabel($this->labelLeft);
$this->addLabel($this->labelRight);
}
}
/**
* Checks if the default ean label is enabled.
*
* @return bool True if default label is enabled.
*/
protected function isDefaultEanLabelEnabled(): bool
{
$label = $this->getLabel();
$font = $this->font;
return $label !== null && $label !== '' && $font !== null && $this->defaultLabel !== null;
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('ean8', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('ean8', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// If we have 8 chars just flush the last one
if ($c === 8) {
$this->text = substr($this->text, 0, 7);
} elseif ($c !== 7) {
throw new BCGParseException('ean8', 'Must contain 7 digits, the 8th digit is automatically added.');
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "odd" position,
// and assign odd/even to each character moving from right to left
// Odd Position = 3, Even Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$odd = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($odd === true) {
$multiplier = 3;
$odd = false;
} else {
$multiplier = 1;
$odd = true;
}
if (!isset($this->keys[$this->text[$i - 1]])) {
return;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = (10 - $this->checksumValue[0] % 10) % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
/**
* Draws the extended bars on the image.
*
* @param resource $image The surface.
* @param int $plus How much more we should display the bars.
* @return void
*/
private function drawExtendedBars($image, int $plus): void
{
$rememberX = $this->positionX;
$rememberH = $this->thickness;
// We increase the bars
$this->thickness = $this->thickness + intval($plus / $this->scale);
$this->positionX = 0;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Center Guard Bar
$this->positionX += 30;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Last Bars
$this->positionX += 30;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX = $rememberX;
$this->thickness = $rememberH;
}
}

View File

@@ -0,0 +1,647 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Calculate the GS1-128 based on the Code-128 encoding.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
use BarcodeBakery\Common\GS1\KindOfData;
class BCGgs1128 extends BCGcode128
{
const ID = 0;
const CONTENT = 1;
const MAX_ID_FORMATTED = 6;
const MAX_ID_NOT_FORMATTED = 4;
const MAX_GS1128_CHARS = 48;
private bool $strictMode;
private bool $allowsUnknownIdentifier;
private bool $noLengthLimit;
private array $identifiersId = array();
private array $identifiersContent = array();
private ?array $identifiersAi = null;
/**
* Creates a GS1-128 barcode.
*
* @param string $start The start table.
*/
public function __construct(?string $start = null)
{
if ($start === null) {
$start = 'C';
}
parent::__construct($start);
$this->setStrictMode(true);
$this->setTilde(true);
$this->setAllowsUnknownIdentifier(false);
$this->setNoLengthLimit(false);
}
/**
* Gets the content checksum for an identifier.
* Do not pass the identifier code.
*
* @param string $content The content.
* @return int The checksum.
*/
public static function getAiContentChecksum(string $content): int
{
return self::calculateChecksumMod10($content);
}
/**
* Enables or disables the strict mode.
*
* @param bool $strictMode Strict mode.
* @return void
*/
public function setStrictMode(bool $strictMode): void
{
$this->strictMode = $strictMode;
}
/**
* Gets if the strict mode is activated.
*
* @return bool True if enabled.
*/
public function getStrictMode(): bool
{
return $this->strictMode;
}
/**
* Allows unknown identifiers.
*
* @param bool $allow Allows the unknown identifier.
* @return void
*/
public function setAllowsUnknownIdentifier(bool $allow): void
{
$this->allowsUnknownIdentifier = (bool)$allow;
}
/**
* Gets if unkmown identifiers are allowed.
*
* @return bool True if enabled.
*/
public function getAllowsUnknownIdentifier(): bool
{
return $this->allowsUnknownIdentifier;
}
/**
* Removes the limit of 48 characters.
*
* @param bool $noLengthLimit No limit.
* @return void
*/
public function setNoLengthLimit(bool $noLengthLimit): void
{
$this->noLengthLimit = (bool)$noLengthLimit;
}
/**
* Gets if the limit of 48 characters is removed.
*
* @return bool True if enabled.
*/
public function getNoLengthLimit(): bool
{
return $this->noLengthLimit;
}
/**
* Sets the list of application identifiers.
*
* @param AIData[] aiDatas Application identifiers.
* @return void
*/
public function setApplicationIdentifiers(array $aiDatas): void
{
// Using array_column will convert the keys to integer.
$this->identifiersAi = array_column(array_map(function ($entry) {
return array(0 => $entry->getAI(), 1 => $entry);
}, $aiDatas), 1, 0);
}
/**
* Gets the list of application identifiers.
*
* @return AIData[] Application Identifiers.
*/
public function getApplicationIdentifiers(): array
{
return array_values($this->identifiersAi);
}
/**
* Parses Text.
*
* @param mixed $text The text.
* @return void
*/
public function parse($text): void
{
$this->identifiersId = array();
$this->identifiersContent = array();
parent::parse($this->parseGs1128($text));
}
/**
* Formats data for gs1-128.
*
* @return string Final formatted data.
*/
private function formatGs1128(): string
{
$formattedText = '~F1';
$formattedLabel = '';
$c = count($this->identifiersId);
for ($i = 0; $i < $c; $i++) {
if ($i > 0) {
$formattedLabel .= ' ';
}
if ($this->identifiersId[$i] !== null) {
$formattedLabel .= '(' . $this->identifiersId[$i] . ')';
}
$formattedText .= $this->identifiersId[$i];
$formattedLabel .= $this->identifiersContent[$i];
$formattedText .= $this->identifiersContent[$i];
if (isset($this->identifiersAi[$this->identifiersId[$i]])) {
$aiData = $this->identifiersAi[$this->identifiersId[$i]];
} elseif (isset($this->identifiersId[$i][3])) {
$identifierWithVar = substr($this->identifiersId[$i], 0, -1) . 'y';
$aiData = isset($this->identifiersAi[$identifierWithVar]) ? $this->identifiersAi[$identifierWithVar] : null;
} else {
$aiData = null;
}
/* We'll check if we need to add a ~F1 (<GS>) char */
/* If we use the legacy mode, we always add a ~F1 (<GS>) char between AIs */
if ($aiData !== null) {
if ((strlen($this->identifiersContent[$i]) < $aiData->getMaxLength() && ($i + 1) !== $c) || (!$this->strictMode && ($i + 1) !== $c)) {
$formattedText .= '~F1';
}
} elseif ($this->allowsUnknownIdentifier && $this->identifiersId[$i] === null && ($i + 1) !== $c) {
/* If this id is unknown, we add a ~F1 (<GS>) char */
$formattedText .= '~F1';
}
}
if ($this->noLengthLimit === false) {
$calculableCharacters = str_replace('~F1', chr(29), $formattedText);
$calculableCharacters = str_replace('(', '', $calculableCharacters);
$calculableCharacters = str_replace(')', '', $calculableCharacters);
if (strlen($calculableCharacters) - 1 > self::MAX_GS1128_CHARS) {
throw new BCGParseException('gs1128', 'The barcode can\'t contain more than ' . self::MAX_GS1128_CHARS . ' characters.');
}
}
if ($this->label === self::AUTO_LABEL) {
$this->label = $formattedLabel;
}
return $formattedText;
}
/**
* Parses the inputs.
*
* @param mixed $text The inputs.
* @return string Final formatted data.
*/
private function parseGs1128($text): ?string
{
/* We format correctly what the user gives */
if (is_array($text)) {
$formatArray = array();
foreach ($text as $content) {
if (is_array($content)) { /* double array */
if (count($content) === 2) {
if (is_array($content[self::ID]) || is_array($content[self::CONTENT])) {
throw new BCGParseException('gs1128', 'Double arrays can\'t contain arrays.');
} else {
$formatArray[] = '(' . $content[self::ID] . ')' . $content[self::CONTENT];
}
} else {
throw new BCGParseException('gs1128', 'Double arrays must contain 2 values.');
}
} else { /* simple array */
$formatArray[] = $content;
}
}
unset($text);
$text = $formatArray;
} else { /* string */
$text = array($text);
}
$textCount = count($text);
for ($cmpt = 0; $cmpt < $textCount; $cmpt++) {
/* We parse the content of the array */
if (!$this->parseContent($text[$cmpt])) {
return null;
}
}
return $this->formatGs1128();
}
/**
* Splits the id and the content for each application identifiers (AIs).
*
* @param string $text The unformatted text.
* @return bool True on success.
*/
private function parseContent(string $text): bool
{
/* $yAlreadySet has 3 states: */
/* null: There is no variable in the ID; true: the variable is already set; false: the variable is not set yet; */
$content = null;
$yAlreadySet = null;
$realNameId = null;
$separatorsFound = 0;
$checksumAdded = 0;
$decimalPointRemoved = 0;
$toParse = str_replace('~F1', chr(29), $text);
$nbCharToParse = strlen($toParse);
$nbCharId = 0;
$isFormatted = $toParse[0] === '(';
$maxCharId = $isFormatted ? self::MAX_ID_FORMATTED : self::MAX_ID_NOT_FORMATTED;
$id = strtolower(substr($toParse, 0, min($maxCharId, $nbCharToParse)));
$id = $isFormatted ? $this->findIdFormatted($id, $yAlreadySet, $realNameId) : $this->findIdNotFormatted($id, $yAlreadySet, $realNameId);
if ($id === null) {
if ($this->allowsUnknownIdentifier === false) {
return false;
}
$id = null;
$nbCharId = 0;
$content = $toParse;
} else {
$nbCharId = strlen($id) + ($isFormatted ? 2 : 0);
$n = min($this->identifiersAi[$realNameId]->getMaxLength(), $nbCharToParse);
$content = substr($toParse, $nbCharId, $n);
if ($id !== null) {
/* If we have an AI with an "y" var, we check if there is a decimal point in the next *MAXLENGTH* characters */
/* if there is one, we take an extra character */
if ($yAlreadySet !== null) {
if (strpos($content, '.') !== false || strpos($content, ',') !== false) {
$n++;
if ($n <= $nbCharToParse) {
/* We take an extra char */
$content = substr($toParse, $nbCharId, $n);
}
}
}
}
}
/* We check for separator */
$separator = strpos($content, chr(29));
if ($separator !== false) {
$content = substr($content, 0, $separator);
$separatorsFound++;
}
if ($id !== null) {
/* We check the conformity */
if (!$this->checkConformity($content, $id, $realNameId)) {
return false;
}
/* We check the checksum */
if (!$this->checkChecksum($content, $id, $realNameId, $checksumAdded)) {
return false;
}
/* We check the vars */
if (!$this->checkVars($content, $id, $yAlreadySet, $decimalPointRemoved)) {
return false;
}
}
$this->identifiersId[] = $id;
$this->identifiersContent[] = $content;
$nbCharLastContent = (((strlen($content) + $nbCharId) - $checksumAdded) + $decimalPointRemoved) + $separatorsFound;
if ($nbCharToParse - $nbCharLastContent > 0) {
/* If there is more than one content in this array, we parse again */
$otherContent = substr($toParse, $nbCharLastContent, $nbCharToParse);
$nbCharOtherContent = strlen($otherContent);
if ($otherContent[0] === chr(29)) {
$otherContent = substr($otherContent, 1);
$nbCharOtherContent--;
}
if ($nbCharOtherContent > 0) {
$text = $otherContent;
return $this->parseContent($text);
}
}
return true;
}
/**
* Checks if an id exists.
*
* @param string $id The AI.
* @param bool|null $yAlreadySet Y Status.
* @param string|null $realNameId The real AI.
* @return bool True if the AI exists.
*/
private function idExists(string $id, ?bool &$yAlreadySet, ?string &$realNameId): bool
{
$yFound = isset($id[3]) && $id[3] === 'y';
$idVarAdded = substr($id, 0, -1) . 'y';
if ($this->identifiersAi !== null) {
if (isset($this->identifiersAi[$id])) {
if ($yFound) {
$yAlreadySet = false;
}
$realNameId = $id;
return true;
} elseif (!$yFound && isset($this->identifiersAi[$idVarAdded])) {
/* if the id don't exist, we try to find this id with "y" at the last char */
$yAlreadySet = true;
$realNameId = $idVarAdded;
return true;
}
}
return false;
}
/**
* Finds ID with formatted content.
*
* @param string $id The AI.
* @param bool|null $yAlreadySet Y Status.
* @param string|null $realNameId The real AI.
* @return string|null The ID if found.
*/
private function findIdFormatted(string $id, ?bool &$yAlreadySet, ?string &$realNameId): ?string
{
$pos = strpos($id, ')');
if ($pos === false) {
throw new BCGParseException('gs1128', 'Identifiers must have no more than 4 characters.');
} else {
if ($pos < 3) {
throw new BCGParseException('gs1128', 'Identifiers must have at least 2 characters.');
}
$id = substr($id, 1, $pos - 1);
if ($this->idExists($id, $yAlreadySet, $realNameId)) {
return $id;
}
if ($this->allowsUnknownIdentifier === false) {
throw new BCGParseException('gs1128', 'The identifier ' . $id . ' doesn\'t exist. Have you installed the default AI with "setApplicationIdentifiers()"? Or allow unknown identifiers with "setAllowsUnknownIdentifier(true)".');
}
return null;
}
}
/**
* Finds ID with non-formatted content.
*
* @param string $id The AI.
* @param bool|null $yAlreadySet Y Status.
* @param string|null $realNameId The real AI.
* @return string|null The ID if found.
*/
private function findIdNotFormatted(string $id, ?bool &$yAlreadySet, ?string &$realNameId): ?string
{
$tofind = $id;
while (strlen($tofind) >= 2) {
if ($this->idExists($tofind, $yAlreadySet, $realNameId)) {
return $tofind;
} else {
$tofind = substr($tofind, 0, -1);
}
}
if ($this->allowsUnknownIdentifier === false) {
throw new BCGParseException('gs1128', 'Error in formatting, can\'t find an identifier. Have you installed the default AI with "setApplicationIdentifiers()"? Or allow unknown identifiers with "setAllowsUnknownIdentifier(true)".');
}
return null;
}
/**
* Checks confirmity of the content.
*
* @param string $content The content.
* @param string $id The AI.
* @param string|null $realNameId The real AI.
* @return bool True if valid.
*/
private function checkConformity(string &$content, string $id, ?string $realNameId): bool
{
switch ($this->identifiersAi[$realNameId]->getKindOfData()) {
case KindOfData::NUMERIC:
$content = str_replace(',', '.', $content);
if (!preg_match("/^[0-9.]+$/", $content)) {
throw new BCGParseException('gs1128', 'The value of "' . $id . '" must be numerical.');
}
break;
case KindOfData::DATETIME:
$validDateTime = true;
if (preg_match("/^[0-9]{8,12}$/", $content)) {
$year = substr($content, 0, 2);
$month = substr($content, 2, 2);
$day = substr($content, 4, 2);
$hour = substr(content, 6, 2);
$minute = strlen(content) >= 10 ? substr(content, 8, 2) : null;
$second = strlen(content) >= 12 ? substr(content, 10, 2) : null;
/* day can be 00 if we only need month and year */
if (intval($month) < 1
|| intval($month) > 12
|| intval($day) < 0
|| intval($day) > 31
|| intval(hour) > 23
|| (minute !== null && intval(minute) > 59)
|| (second !== null && intval(second) > 59)
) {
$validDateTime = false;
}
} else {
$validDateTime = false;
}
if (!$validDateTime) {
throw new BCGParseException('gs1128', 'The value of "' . $id . '" must be in YYMMDDHHMMSS format. Some AI might not allow seconds.');
}
break;
case KindOfData::DATE:
$validDate = true;
if (preg_match("/^[0-9]{6}$/", $content)) {
$year = substr($content, 0, 2);
$month = substr($content, 2, 2);
$day = substr($content, 4, 2);
/* day can be 00 if we only need month and year */
if (intval($month) < 1 || intval($month) > 12 || intval($day) > 31) {
$validDate = false;
}
} else {
$validDate = false;
}
if (!$validDate) {
throw new BCGParseException('gs1128', 'The value of "' . $id . '" must be in YYMMDD format.');
}
break;
}
// We check the length of the content
$nbCharContent = strlen($content);
$checksumChar = 0;
$minlengthContent = $this->identifiersAi[$realNameId]->getMinLength();
$maxlengthContent = $this->identifiersAi[$realNameId]->getMaxLength();
if ($this->identifiersAi[$realNameId]->getChecksum()) {
$checksumChar++;
}
if ($nbCharContent < ($minlengthContent - $checksumChar)) {
if ($minlengthContent === $maxlengthContent) {
throw new BCGParseException('gs1128', 'The value of "' . $id . '" must contain ' . $minlengthContent . ' character(s).');
} else {
throw new BCGParseException('gs1128', 'The value of "' . $id . '" must contain between ' . $minlengthContent . ' and ' . $maxlengthContent . ' character(s).');
}
}
return true;
}
/**
* Verifies the checksum.
*
* @param string $content The content.
* @param string $id The AI.
* @param string|null $realNameId The real AI.
* @param int $checksumAdded The checksum was added.
* @return bool True if valid.
*/
private function checkChecksum(string &$content, string $id, ?string $realNameId, int &$checksumAdded): bool
{
if ($this->identifiersAi[$realNameId]->getChecksum()) {
$nbCharContent = strlen($content);
$minlengthContent = $this->identifiersAi[$realNameId]->getMinLength();
if ($nbCharContent === ($minlengthContent - 1)) {
/* we need to calculate the checksum */
$content .= self::getAiContentChecksum($content);
$checksumAdded++;
} elseif ($nbCharContent === $minlengthContent) {
/* we need to check the checksum */
$checksum = self::getAiContentChecksum(substr($content, 0, -1));
if (intval($content[$nbCharContent - 1]) !== $checksum) {
throw new BCGParseException('gs1128', 'The checksum of "(' . $id . ') ' . $content . '" must be: ' . $checksum);
}
}
}
return true;
}
/**
* Checks vars "y".
*
* @param string $content The content.
* @param string $id The AI.
* @param bool|null $yAlreadySet Y Status.
* @param int $decimalPointRemoved The decimal point was removed.
* @return bool True if valid.
*/
private function checkVars(string &$content, string &$id, ?bool $yAlreadySet, int &$decimalPointRemoved): bool
{
$nbCharContent = strlen($content);
/* We check for "y" var in AI */
if ($yAlreadySet) {
/* We'll check if we have a decimal point */
if (strpos($content, '.') !== false) {
throw new BCGParseException('gs1128', 'If you do not use any "y" variable, you have to insert a whole number.');
}
} elseif ($yAlreadySet !== null) {
/* We need to replace the "y" var with the position of the decimal point */
$pos = strpos($content, '.');
if ($pos === false) {
$pos = $nbCharContent - 1;
}
$id = str_replace('y', $nbCharContent - ($pos + 1), strtolower($id));
$content = str_replace('.', '', $content);
$decimalPointRemoved++;
}
return true;
}
/**
* Checksum Mod10.
*
* @param string $content The content.
* @return int The checksum.
*/
private static function calculateChecksumMod10(string $content): int
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "odd" position,
// and assign odd/even to each character moving from right to left
// Odd Position = 3, Even Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$odd = true;
$checksumValue = 0;
$c = strlen($content);
for ($i = $c; $i > 0; $i--) {
if ($odd === true) {
$multiplier = 3;
$odd = false;
} else {
$multiplier = 1;
$odd = true;
}
$checksumValue += ($content[$i - 1] * $multiplier);
}
return (10 - $checksumValue % 10) % 10;
}
}

View File

@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Interleaved 2 of 5
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGi25 extends BCGBarcode1D
{
private bool $checksum;
private int $ratio;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'00110', /* 0 */
'10001', /* 1 */
'01001', /* 2 */
'11000', /* 3 */
'00101', /* 4 */
'10100', /* 5 */
'01100', /* 6 */
'00011', /* 7 */
'10010', /* 8 */
'01010' /* 9 */
);
$this->setChecksum(false);
$this->setRatio(2);
}
/**
* Sets the checksum.
*
* @param bool $checksum Displays the checksum.
* @return void
*/
public function setChecksum(bool $checksum): void
{
$this->checksum = (bool)$checksum;
}
/**
* Sets the ratio of the black bar compared to the white bars.
*
* @param int $ratio The ratio.
* @return void
*/
public function setRatio(int $ratio): void
{
$this->ratio = $ratio;
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$tempText = $this->text;
// Checksum
if ($this->checksum === true) {
$this->calculateChecksum();
$tempText .= $this->keys[$this->checksumValue[0]];
}
// Starting Code
$this->drawChar($image, '0000', true);
// Chars
$c = strlen($tempText);
for ($i = 0; $i < $c; $i += 2) {
$tempBar = '';
$c2 = strlen($this->findCode($tempText[$i]));
for ($j = 0; $j < $c2; $j++) {
$tempBar .= substr($this->findCode($tempText[$i]), $j, 1);
$tempBar .= substr($this->findCode($tempText[$i + 1]), $j, 1);
}
$this->drawChar($image, $this->changeBars($tempBar), true);
}
// Ending Code
$this->drawChar($image, $this->changeBars('100'), true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$textlength = (3 + ($this->ratio + 1) * 2) * strlen($this->text);
$startlength = 4;
$checksumlength = 0;
if ($this->checksum === true) {
$checksumlength = (3 + ($this->ratio + 1) * 2);
}
$endlength = 2 + ($this->ratio + 1);
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('i25', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('i25', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must be even
if ($c % 2 !== 0 && $this->checksum === false) {
throw new BCGParseException('i25', 'i25 must contain an even amount of digits if checksum is false.');
} elseif ($c % 2 === 0 && $this->checksum === true) {
throw new BCGParseException('i25', 'i25 must contain an odd amount of digits if checksum is true.');
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "even" position,
// and assign odd/even to each character moving from right to left
// Even Position = 3, Odd Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$even = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($even === true) {
$multiplier = 3;
$even = false;
} else {
$multiplier = 1;
$even = true;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = (10 - $this->checksumValue[0] % 10) % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
/**
* Changes the size of the bars based on the ratio
*
* @param string $in The bars.
* @return string New bars.
*/
private function changeBars(string $in): string
{
if ($this->ratio > 1) {
$c = strlen($in);
for ($i = 0; $i < $c; $i++) {
$in[$i] = $in[$i] === '1' ? $this->ratio : $in[$i];
}
}
return $in;
}
}

View File

@@ -0,0 +1,681 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Intelligent Mail
*
* A postnet is composed of either 5, 9 or 11 digits used by US postal service.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGArgumentException;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGintelligentmail extends BCGBarcode1D
{
private ?string $barcodeIdentifier = null;
private ?string $serviceTypeIdentifier = null;
private ?string $mailerIdentifier = null;
private ?string $serialNumber = null;
private bool $quietZone;
private string $data;
private static array $characterTable1 = array(
31, 7936, 47, 7808, 55, 7552, 59, 7040, 61, 6016,
62, 3968, 79, 7744, 87, 7488, 91, 6976, 93, 5952,
94, 3904, 103, 7360, 107, 6848, 109, 5824, 110, 3776,
115, 6592, 117, 5568, 118, 3520, 121, 5056, 122, 3008,
124, 1984, 143, 7712, 151, 7456, 155, 6944, 157, 5920,
158, 3872, 167, 7328, 171, 6816, 173, 5792, 174, 3744,
179, 6560, 181, 5536, 182, 3488, 185, 5024, 186, 2976,
188, 1952, 199, 7264, 203, 6752, 205, 5728, 206, 3680,
211, 6496, 213, 5472, 214, 3424, 217, 4960, 218, 2912,
220, 1888, 227, 6368, 229, 5344, 230, 3296, 233, 4832,
234, 2784, 236, 1760, 241, 4576, 242, 2528, 244, 1504,
248, 992, 271, 7696, 279, 7440, 283, 6928, 285, 5904,
286, 3856, 295, 7312, 299, 6800, 301, 5776, 302, 3728,
307, 6544, 309, 5520, 310, 3472, 313, 5008, 314, 2960,
316, 1936, 327, 7248, 331, 6736, 333, 5712, 334, 3664,
339, 6480, 341, 5456, 342, 3408, 345, 4944, 346, 2896,
348, 1872, 355, 6352, 357, 5328, 358, 3280, 361, 4816,
362, 2768, 364, 1744, 369, 4560, 370, 2512, 372, 1488,
376, 976, 391, 7216, 395, 6704, 397, 5680, 398, 3632,
403, 6448, 405, 5424, 406, 3376, 409, 4912, 410, 2864,
412, 1840, 419, 6320, 421, 5296, 422, 3248, 425, 4784,
426, 2736, 428, 1712, 433, 4528, 434, 2480, 436, 1456,
440, 944, 451, 6256, 453, 5232, 454, 3184, 457, 4720,
458, 2672, 460, 1648, 465, 4464, 466, 2416, 468, 1392,
472, 880, 481, 4336, 482, 2288, 484, 1264, 488, 752,
527, 7688, 535, 7432, 539, 6920, 541, 5896, 542, 3848,
551, 7304, 555, 6792, 557, 5768, 558, 3720, 563, 6536,
565, 5512, 566, 3464, 569, 5000, 570, 2952, 572, 1928,
583, 7240, 587, 6728, 589, 5704, 590, 3656, 595, 6472,
597, 5448, 598, 3400, 601, 4936, 602, 2888, 604, 1864,
611, 6344, 613, 5320, 614, 3272, 617, 4808, 618, 2760,
620, 1736, 625, 4552, 626, 2504, 628, 1480, 632, 968,
647, 7208, 651, 6696, 653, 5672, 654, 3624, 659, 6440,
661, 5416, 662, 3368, 665, 4904, 666, 2856, 668, 1832,
675, 6312, 677, 5288, 678, 3240, 681, 4776, 682, 2728,
684, 1704, 689, 4520, 690, 2472, 692, 1448, 696, 936,
707, 6248, 709, 5224, 710, 3176, 713, 4712, 714, 2664,
716, 1640, 721, 4456, 722, 2408, 724, 1384, 728, 872,
737, 4328, 738, 2280, 740, 1256, 775, 7192, 779, 6680,
781, 5656, 782, 3608, 787, 6424, 789, 5400, 790, 3352,
793, 4888, 794, 2840, 796, 1816, 803, 6296, 805, 5272,
806, 3224, 809, 4760, 810, 2712, 812, 1688, 817, 4504,
818, 2456, 820, 1432, 824, 920, 835, 6232, 837, 5208,
838, 3160, 841, 4696, 842, 2648, 844, 1624, 849, 4440,
850, 2392, 852, 1368, 865, 4312, 866, 2264, 868, 1240,
899, 6200, 901, 5176, 902, 3128, 905, 4664, 906, 2616,
908, 1592, 913, 4408, 914, 2360, 916, 1336, 929, 4280,
930, 2232, 932, 1208, 961, 4216, 962, 2168, 964, 1144,
1039, 7684, 1047, 7428, 1051, 6916, 1053, 5892, 1054, 3844,
1063, 7300, 1067, 6788, 1069, 5764, 1070, 3716, 1075, 6532,
1077, 5508, 1078, 3460, 1081, 4996, 1082, 2948, 1084, 1924,
1095, 7236, 1099, 6724, 1101, 5700, 1102, 3652, 1107, 6468,
1109, 5444, 1110, 3396, 1113, 4932, 1114, 2884, 1116, 1860,
1123, 6340, 1125, 5316, 1126, 3268, 1129, 4804, 1130, 2756,
1132, 1732, 1137, 4548, 1138, 2500, 1140, 1476, 1159, 7204,
1163, 6692, 1165, 5668, 1166, 3620, 1171, 6436, 1173, 5412,
1174, 3364, 1177, 4900, 1178, 2852, 1180, 1828, 1187, 6308,
1189, 5284, 1190, 3236, 1193, 4772, 1194, 2724, 1196, 1700,
1201, 4516, 1202, 2468, 1204, 1444, 1219, 6244, 1221, 5220,
1222, 3172, 1225, 4708, 1226, 2660, 1228, 1636, 1233, 4452,
1234, 2404, 1236, 1380, 1249, 4324, 1250, 2276, 1287, 7188,
1291, 6676, 1293, 5652, 1294, 3604, 1299, 6420, 1301, 5396,
1302, 3348, 1305, 4884, 1306, 2836, 1308, 1812, 1315, 6292,
1317, 5268, 1318, 3220, 1321, 4756, 1322, 2708, 1324, 1684,
1329, 4500, 1330, 2452, 1332, 1428, 1347, 6228, 1349, 5204,
1350, 3156, 1353, 4692, 1354, 2644, 1356, 1620, 1361, 4436,
1362, 2388, 1377, 4308, 1378, 2260, 1411, 6196, 1413, 5172,
1414, 3124, 1417, 4660, 1418, 2612, 1420, 1588, 1425, 4404,
1426, 2356, 1441, 4276, 1442, 2228, 1473, 4212, 1474, 2164,
1543, 7180, 1547, 6668, 1549, 5644, 1550, 3596, 1555, 6412,
1557, 5388, 1558, 3340, 1561, 4876, 1562, 2828, 1564, 1804,
1571, 6284, 1573, 5260, 1574, 3212, 1577, 4748, 1578, 2700,
1580, 1676, 1585, 4492, 1586, 2444, 1603, 6220, 1605, 5196,
1606, 3148, 1609, 4684, 1610, 2636, 1617, 4428, 1618, 2380,
1633, 4300, 1634, 2252, 1667, 6188, 1669, 5164, 1670, 3116,
1673, 4652, 1674, 2604, 1681, 4396, 1682, 2348, 1697, 4268,
1698, 2220, 1729, 4204, 1730, 2156, 1795, 6172, 1797, 5148,
1798, 3100, 1801, 4636, 1802, 2588, 1809, 4380, 1810, 2332,
1825, 4252, 1826, 2204, 1857, 4188, 1858, 2140, 1921, 4156,
1922, 2108, 2063, 7682, 2071, 7426, 2075, 6914, 2077, 5890,
2078, 3842, 2087, 7298, 2091, 6786, 2093, 5762, 2094, 3714,
2099, 6530, 2101, 5506, 2102, 3458, 2105, 4994, 2106, 2946,
2119, 7234, 2123, 6722, 2125, 5698, 2126, 3650, 2131, 6466,
2133, 5442, 2134, 3394, 2137, 4930, 2138, 2882, 2147, 6338,
2149, 5314, 2150, 3266, 2153, 4802, 2154, 2754, 2161, 4546,
2162, 2498, 2183, 7202, 2187, 6690, 2189, 5666, 2190, 3618,
2195, 6434, 2197, 5410, 2198, 3362, 2201, 4898, 2202, 2850,
2211, 6306, 2213, 5282, 2214, 3234, 2217, 4770, 2218, 2722,
2225, 4514, 2226, 2466, 2243, 6242, 2245, 5218, 2246, 3170,
2249, 4706, 2250, 2658, 2257, 4450, 2258, 2402, 2273, 4322,
2311, 7186, 2315, 6674, 2317, 5650, 2318, 3602, 2323, 6418,
2325, 5394, 2326, 3346, 2329, 4882, 2330, 2834, 2339, 6290,
2341, 5266, 2342, 3218, 2345, 4754, 2346, 2706, 2353, 4498,
2354, 2450, 2371, 6226, 2373, 5202, 2374, 3154, 2377, 4690,
2378, 2642, 2385, 4434, 2401, 4306, 2435, 6194, 2437, 5170,
2438, 3122, 2441, 4658, 2442, 2610, 2449, 4402, 2465, 4274,
2497, 4210, 2567, 7178, 2571, 6666, 2573, 5642, 2574, 3594,
2579, 6410, 2581, 5386, 2582, 3338, 2585, 4874, 2586, 2826,
2595, 6282, 2597, 5258, 2598, 3210, 2601, 4746, 2602, 2698,
2609, 4490, 2627, 6218, 2629, 5194, 2630, 3146, 2633, 4682,
2641, 4426, 2657, 4298, 2691, 6186, 2693, 5162, 2694, 3114,
2697, 4650, 2705, 4394, 2721, 4266, 2753, 4202, 2819, 6170,
2821, 5146, 2822, 3098, 2825, 4634, 2833, 4378, 2849, 4250,
2881, 4186, 2945, 4154, 3079, 7174, 3083, 6662, 3085, 5638,
3086, 3590, 3091, 6406, 3093, 5382, 3094, 3334, 3097, 4870,
3107, 6278, 3109, 5254, 3110, 3206, 3113, 4742, 3121, 4486,
3139, 6214, 3141, 5190, 3145, 4678, 3153, 4422, 3169, 4294,
3203, 6182, 3205, 5158, 3209, 4646, 3217, 4390, 3233, 4262,
3265, 4198, 3331, 6166, 3333, 5142, 3337, 4630, 3345, 4374,
3361, 4246, 3393, 4182, 3457, 4150, 3587, 6158, 3589, 5134,
3593, 4622, 3601, 4366, 3617, 4238, 3649, 4174, 3713, 4142,
3841, 4126, 4111, 7681, 4119, 7425, 4123, 6913, 4125, 5889,
4135, 7297, 4139, 6785, 4141, 5761, 4147, 6529, 4149, 5505,
4153, 4993, 4167, 7233, 4171, 6721, 4173, 5697, 4179, 6465,
4181, 5441, 4185, 4929, 4195, 6337, 4197, 5313, 4201, 4801,
4209, 4545, 4231, 7201, 4235, 6689, 4237, 5665, 4243, 6433,
4245, 5409, 4249, 4897, 4259, 6305, 4261, 5281, 4265, 4769,
4273, 4513, 4291, 6241, 4293, 5217, 4297, 4705, 4305, 4449,
4359, 7185, 4363, 6673, 4365, 5649, 4371, 6417, 4373, 5393,
4377, 4881, 4387, 6289, 4389, 5265, 4393, 4753, 4401, 4497,
4419, 6225, 4421, 5201, 4425, 4689, 4483, 6193, 4485, 5169,
4489, 4657, 4615, 7177, 4619, 6665, 4621, 5641, 4627, 6409,
4629, 5385, 4633, 4873, 4643, 6281, 4645, 5257, 4649, 4745,
4675, 6217, 4677, 5193, 4739, 6185, 4741, 5161, 4867, 6169,
4869, 5145, 5127, 7173, 5131, 6661, 5133, 5637, 5139, 6405,
5141, 5381, 5155, 6277, 5157, 5253, 5187, 6213, 5251, 6181,
5379, 6165, 5635, 6157, 6151, 7171, 6155, 6659, 6163, 6403,
6179, 6275, 6211, 5189, 4681, 4433, 4321, 3142, 2634, 2386,
2274, 1612, 1364, 1252, 856, 744, 496);
private static array $characterTable2 = array(
3, 6144, 5, 5120, 6, 3072, 9, 4608, 10, 2560,
12, 1536, 17, 4352, 18, 2304, 20, 1280, 24, 768,
33, 4224, 34, 2176, 36, 1152, 40, 640, 48, 384,
65, 4160, 66, 2112, 68, 1088, 72, 576, 80, 320,
96, 192, 129, 4128, 130, 2080, 132, 1056, 136, 544,
144, 288, 257, 4112, 258, 2064, 260, 1040, 264, 528,
513, 4104, 514, 2056, 516, 1032, 1025, 4100, 1026, 2052,
2049, 4098, 4097, 2050, 1028, 520, 272, 160);
private static array $barPositions = array(
array(array(7, 2), array(4, 3)),
array(array(1, 10), array(0, 0)),
array(array(9, 12), array(2, 8)),
array(array(5, 5), array(6, 11)),
array(array(8, 9), array(3, 1)),
array(array(0, 1), array(5, 12)),
array(array(2, 5), array(1, 8)),
array(array(4, 4), array(9, 11)),
array(array(6, 3), array(8, 10)),
array(array(3, 9), array(7, 6)),
array(array(5, 11), array(1, 4)),
array(array(8, 5), array(2, 12)),
array(array(9, 10), array(0, 2)),
array(array(7, 1), array(6, 7)),
array(array(3, 6), array(4, 9)),
array(array(0, 3), array(8, 6)),
array(array(6, 4), array(2, 7)),
array(array(1, 1), array(9, 9)),
array(array(7, 10), array(5, 2)),
array(array(4, 0), array(3, 8)),
array(array(6, 2), array(0, 4)),
array(array(8, 11), array(1, 0)),
array(array(9, 8), array(3, 12)),
array(array(2, 6), array(7, 7)),
array(array(5, 1), array(4, 10)),
array(array(1, 12), array(6, 9)),
array(array(7, 3), array(8, 0)),
array(array(5, 8), array(9, 7)),
array(array(4, 6), array(2, 10)),
array(array(3, 4), array(0, 5)),
array(array(8, 4), array(5, 7)),
array(array(7, 11), array(1, 9)),
array(array(6, 0), array(9, 6)),
array(array(0, 6), array(4, 8)),
array(array(2, 1), array(3, 2)),
array(array(5, 9), array(8, 12)),
array(array(4, 11), array(6, 1)),
array(array(9, 5), array(7, 4)),
array(array(3, 3), array(1, 2)),
array(array(0, 7), array(2, 0)),
array(array(1, 3), array(4, 1)),
array(array(6, 10), array(3, 5)),
array(array(8, 7), array(9, 4)),
array(array(2, 11), array(5, 6)),
array(array(0, 8), array(7, 12)),
array(array(4, 2), array(8, 1)),
array(array(5, 10), array(3, 0)),
array(array(9, 3), array(0, 9)),
array(array(6, 5), array(2, 4)),
array(array(7, 8), array(1, 7)),
array(array(5, 0), array(4, 5)),
array(array(2, 3), array(0, 10)),
array(array(6, 12), array(9, 2)),
array(array(3, 11), array(1, 6)),
array(array(8, 8), array(7, 9)),
array(array(5, 4), array(0, 11)),
array(array(1, 5), array(2, 2)),
array(array(9, 1), array(4, 12)),
array(array(8, 3), array(6, 6)),
array(array(7, 0), array(3, 7)),
array(array(4, 7), array(7, 5)),
array(array(0, 12), array(1, 11)),
array(array(2, 9), array(9, 0)),
array(array(6, 8), array(5, 3)),
array(array(3, 10), array(8, 2))
);
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->setQuietZone(true);
$this->setThickness(9);
}
/**
* Gets the Quiet zone.
*
* @return bool
*/
public function getQuietZone(): bool
{
return $this->quietZone;
}
/**
* Sets the Quiet zone.
*
* @param bool $quietZone
* @return void
*/
public function setQuietZone(bool $quietZone): void
{
$this->quietZone = (bool)$quietZone;
}
/**
* Sets the tracking code.
*
* @param int|string $barcodeIdentifier 2-digit number. 2nd digit must be 0-4.
* @param int|string $serviceTypeIdentifier 3 digits.
* @param int|string $mailerIdentifier 6 or 9 digits.
* @param int|string $serialNumber 9 (if mailerId is 6) or 6 digits (if mailerId is 9).
* @return void
*/
public function setTrackingCode($barcodeIdentifier, $serviceTypeIdentifier, $mailerIdentifier, $serialNumber): void
{
$barcodeIdentifier = (string)(int)$barcodeIdentifier;
$serviceTypeIdentifier = (string)(int)$serviceTypeIdentifier;
$mailerIdentifier = (string)(int)$mailerIdentifier;
$serialNumber = (string)(int)$serialNumber;
$barcodeIdentifier = str_pad($barcodeIdentifier, 2, '0', STR_PAD_LEFT);
if (strlen($barcodeIdentifier) !== 2) {
throw new BCGArgumentException('Barcode Identifier must contain 2 digits.', 'barcodeIdentifier');
}
$barcodeIdentifierSecondNumber = $barcodeIdentifier[1];
if ($barcodeIdentifierSecondNumber !== '0' && $barcodeIdentifierSecondNumber !== '1' && $barcodeIdentifierSecondNumber !== '2' && $barcodeIdentifierSecondNumber !== '3' && $barcodeIdentifierSecondNumber !== '4') {
throw new BCGArgumentException('Barcode Identifier second digit must be a number between 0 and 4.', 'barcodeIdentifier');
}
if ($serviceTypeIdentifier < 0 || $serviceTypeIdentifier > 999) {
throw new BCGArgumentException('Service Type Identifier must be between 0 and 999.', 'serviceTypeIdentifier');
}
$mailerIdentifierLength = 6;
if ($mailerIdentifier > 899999) {
$mailerIdentifierLength = 9;
}
if ($mailerIdentifierLength === 9 && strlen($serialNumber) > 6) {
throw new BCGArgumentException('If the Serial Number has more than 6 digits, the Mailer Identifier must be lower than 900000.', 'mailerIdentifier');
}
if ($mailerIdentifierLength === 9) {
if ($mailerIdentifierLength < 0 || $mailerIdentifier > 999999999) {
throw new BCGArgumentException('Mailer Identifier must be between 0 and 999999999.', 'mailerIdentifier');
}
}
$this->barcodeIdentifier = $barcodeIdentifier;
$this->serviceTypeIdentifier = str_pad($serviceTypeIdentifier, 3, '0', STR_PAD_LEFT);
$this->mailerIdentifier = str_pad($mailerIdentifier, $mailerIdentifierLength, '0', STR_PAD_LEFT);
$this->serialNumber = str_pad($serialNumber, $mailerIdentifierLength === 6 ? 9 : 6, '0', STR_PAD_LEFT);
}
/**
* Parses the text before displaying it.
*
* @param string $text The text.
* @return void
*/
public function parse($text): void
{
parent::parse($text);
$number = self::executeStep1($this->text, $this->barcodeIdentifier, $this->serviceTypeIdentifier, $this->mailerIdentifier, $this->serialNumber);
$crc = self::executeStep2($number);
$codewords = self::executeStep3($number);
$codewords = self::executeStep4($codewords, $crc);
$characters = self::executeStep5($codewords, $crc);
$this->data = self::executeStep6($characters);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
if ($this->quietZone) {
$this->positionX += 9;
}
$c = strlen($this->data);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->data[$i]);
}
$this->drawText($image, 0, 0, $this->positionX, $this->thickness + ($this->quietZone ? 4 : 0));
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$width += 65 * 3;
$height += $this->thickness;
// We remove the white on the right
$width -= (int)1.56;
if ($this->quietZone) {
$width += 18;
$height += 4;
}
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
// Tracking must have been entered
if ($this->barcodeIdentifier === null || $this->serviceTypeIdentifier === null || $this->mailerIdentifier === null || $this->serialNumber === null) {
throw new BCGParseException('intelligentmail', 'The tracking code must be set before calling the parse method.');
}
// Checking if all chars are allowed
$matches = array();
if (preg_match('/[^0-9]/', $this->text, $matches)) {
throw new BCGParseException('intelligentmail', 'The character \'' . $matches[0] . '\' is not allowed.');
}
// Must contain 0, 5, 9 or 11 chars
$c = strlen($this->text);
if ($c !== 0 && $c !== 5 && $c !== 9 && $c !== 11) {
throw new BCGParseException('intelligentmail', 'Must contain 0, 5, 9, or 11 characters.');
}
parent::validate();
}
/**
* Overloaded method for drawing special barcode.
*
* @param resource $image The surface.
* @param string $code The code.
* @param bool $startBar True if we begin with a space.
* @return void
*/
protected function drawChar($image, string $code, bool $startBar = true): void
{
$y1 = 0;
$y2 = 0;
switch ($code) {
case 'A':
$y1 = 0;
$y2 = $this->thickness - ($this->thickness / 2.5);
break;
case 'D':
$y1 = 3.096;
$y2 = $this->thickness - 1;
break;
case 'F':
$y1 = 0;
$y2 = $this->thickness - 1;
break;
case 'T':
$y1 = 3.096;
$y2 = $this->thickness - ($this->thickness / 2.5);
break;
}
if ($this->quietZone) {
$y1 += 2;
$y2 += 2;
}
$this->drawFilledRectangle($image, $this->positionX, intval($y1), intval($this->positionX + 0.44), intval($y2), BCGBarcode::COLOR_FG);
$this->positionX += 3;
}
/**
* Executes Step 1: Conversion of Data Fields into Binary Data.
*
* @param string $text The text.
* @param string $barcodeIdentifier The barcode identifier.
* @param string $serviceTypeIdentifier The Service type identifier.
* @param string $mailerIdentifier The mailer identifier.
* @param string $serialNumber The serial number.
* @return string BCNumber.
*/
private static function executeStep1(string $text, string $barcodeIdentifier, string $serviceTypeIdentifier, string $mailerIdentifier, string $serialNumber): string
{
$number = self::conversionRoutingCode($text);
$number = self::conversionTrackingCode($number, $barcodeIdentifier, $serviceTypeIdentifier, $mailerIdentifier, $serialNumber);
return $number;
}
/**
* Executes Step 2: Generation of 11-Bit CRC on Binary Data.
*
* @param string $number BCNumber.
* @return int The CRC.
*/
private static function executeStep2(string $number): int
{
$byteArray = str_pad(self::bcdecuc($number), 13, chr(0), STR_PAD_LEFT);
$generatorPolynomial = 0x0f35;
$frameCheckSequence = 0x07ff;
$data = 0;
$byteIndex = 0;
$bit = 0;
$data = (ord($byteArray[$byteIndex]) << 5) & 0xffff;
for ($bit = 2; $bit < 8; $bit++) {
if (($frameCheckSequence ^ $data) & 0x400) {
$frameCheckSequence = ($frameCheckSequence << 1) ^ $generatorPolynomial;
} else {
$frameCheckSequence = ($frameCheckSequence << 1);
}
$frameCheckSequence &= 0x7ff;
$data <<= 1;
$data &= 0xffff;
}
for ($byteIndex = 1; $byteIndex < 13; $byteIndex++) {
$data = (ord($byteArray[$byteIndex]) << 3) & 0xffff;
for ($bit = 0; $bit < 8; $bit++) {
if (($frameCheckSequence ^ $data) & 0x0400) {
$frameCheckSequence = ($frameCheckSequence << 1) ^ $generatorPolynomial;
} else {
$frameCheckSequence = ($frameCheckSequence << 1);
}
$frameCheckSequence &= 0x7ff;
$data <<= 1;
$data &= 0xffff;
}
}
return $frameCheckSequence;
}
/**
* Executes Step 3: Conversion from Binary Data to Codewords.
*
* @param string $number BCNumber.
* @return int[] The codeword sequence.
*/
private static function executeStep3(string $number): array
{
$codewords = array();
$codewords[9] = (int)bcmod($number, '636');
$number = bcdiv($number, '636', 0);
for ($i = 8; $i >= 0; $i--) {
$codewords[$i] = (int)bcmod($number, '1365');
$number = bcdiv($number, '1365', 0);
}
return $codewords;
}
/**
* Executes Step 4: Inserting Additional Information into Codewords.
*
* @param int[] $codewords The codewords sequence.
* @param int $crc The cRC.
* @return int[] The codeword sequence.
*/
private static function executeStep4(array $codewords, int $crc): array
{
$codewords[9] *= 2;
if ($crc & 0x400) {
$codewords[0] += 659;
}
return $codewords;
}
/**
* Executes Step 5: Conversion from Codewords to Characters.
*
* @param int[] $codewords The codewords sequence.
* @param int $crc The cRC.
* @return int[] The codeword sequence.
*/
private static function executeStep5(array $codewords, int $crc): array
{
$characters = array();
for ($i = 0; $i < 10; $i++) {
if ($codewords[$i] <= 1286) {
$characters[$i] = self::$characterTable1[$codewords[$i]];
} else {
$characters[$i] = self::$characterTable2[$codewords[$i] - 1287];
}
}
for ($i = 0; $i < 10; $i++) {
$mask = 1 << $i;
if ($crc & $mask) {
$characters[$i] ^= 0x1fff;
}
}
return $characters;
}
/**
* Executes Step 6: Conversion from Characters to the Intelligent Mail Barcode.
*
* @param int[] $characters The characters.
* @return string The sequence for bars.
*/
private static function executeStep6(array $characters): string
{
$bars = '';
for ($i = 0; $i < 65; $i++) {
$barPosition = self::$barPositions[$i];
$descender = $barPosition[0];
$ascender = $barPosition[1];
$extenderDescender = !!($characters[$descender[0]] & (1 << $descender[1]));
$extenderAscender = !!($characters[$ascender[0]] & (1 << $ascender[1]));
if ($extenderDescender && $extenderAscender) {
$bars .= 'F';
} elseif ($extenderDescender) {
$bars .= 'D';
} elseif ($extenderAscender) {
$bars .= 'A';
} else {
$bars .= 'T';
}
}
return $bars;
}
/**
* Converts the routing code zipcode.
*
* @param string $zipcode The zipcode.
* @return string BCNumber.
*/
private static function conversionRoutingCode(string $zipcode): string
{
$number = $zipcode;
switch (strlen($zipcode)) {
case 11:
$number = bcadd($number, '1000000000', 0);
// no break
case 9:
$number = bcadd($number, '100000', 0);
// no break
case 5:
$number = bcadd($number, '1', 0);
// no break
default:
return $number;
}
}
/**
* Converts the tracking code number.
*
* @param string $number BCNumber.
* @param string $barcodeIdentifier The barcode identifier.
* @param string $serviceTypeIdentifier The Service type identifier.
* @param string $mailerIdentifier The mailer identifier.
* @param string $serialNumber The serial number.
* @return string BCNumber.
*/
private static function conversionTrackingCode(string $number, string $barcodeIdentifier, string $serviceTypeIdentifier, string $mailerIdentifier, string $serialNumber): string
{
$number = bcmul($number, '10', 0);
$number = bcadd($number, $barcodeIdentifier[0], 0);
$number = bcmul($number, '5', 0);
$number = bcadd($number, $barcodeIdentifier[1], 0);
$temp = $serviceTypeIdentifier . $mailerIdentifier . $serialNumber;
for ($i = 0; $i < 18; $i++) {
$number = bcmul($number, '10', 0);
$number = bcadd($number, $temp[$i], 0);
}
return $number;
}
/**
* Transforms a BCNumber into unsigned char*.
*
* @param string $dec BCNumber.
* @return string The pack data.
*/
private static function bcdecuc(string $dec): string
{
$last = bcmod($dec, '256');
$remain = bcdiv(bcsub($dec, $last), '256', 0);
if ($remain == 0) { // Keep weak
return pack('C', $last);
} else {
return self::bcdecuc($remain) . pack('C', $last);
}
}
}

View File

@@ -0,0 +1,182 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - ISBN-10 and ISBN-13
*
* You can provide an ISBN with 10 digits with or without the checksum.
* You can provide an ISBN with 13 digits with or without the checksum.
* Calculate the ISBN based on the EAN-13 encoding.
*
* The checksum is always displayed.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGArgumentException;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGisbn extends BCGean13
{
const GS1_AUTO = 0;
const GS1_PREFIX978 = 1;
const GS1_PREFIX979 = 2;
private int $gs1;
/**
* Creates a ISBN barcode.
*
* @param int $gs1 The GS1.
*/
public function __construct(int $gs1 = self::GS1_AUTO)
{
parent::__construct();
$this->setGS1($gs1);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
if ($this->isDefaultEanLabelEnabled()) {
$isbn = $this->createISBNText();
$font = $this->font;
$topLabel = new BCGLabel($isbn, $font, BCGLabel::POSITION_TOP, BCGLabel::ALIGN_CENTER);
$this->addLabel($topLabel);
}
parent::addDefaultLabel();
}
/**
* Sets the first numbers of the barcode.
* - GS1_AUTO: Adds 978 before the code
* - GS1_PREFIX978: Adds 978 before the code
* - GS1_PREFIX979: Adds 979 before the code
*
* @param int $gs1 The GS1.
* @return void
*/
public function setGS1(int $gs1): void
{
$gs1 = (int)$gs1;
if ($gs1 !== self::GS1_AUTO && $gs1 !== self::GS1_PREFIX978 && $gs1 !== self::GS1_PREFIX979) {
throw new BCGArgumentException('The GS1 argument must be BCGisbn::GS1_AUTO, BCGisbn::GS1_PREFIX978, or BCGisbn::GS1_PREFIX979', 'gs1');
}
$this->gs1 = $gs1;
}
/**
* Check chars allowed.
*
* @return void
*/
protected function checkCharsAllowed(): void
{
$c = strlen($this->text);
// Special case, if we have 10 digits, the last one can be X
if ($c === 10) {
if (array_search($this->text[9], $this->keys) === false && $this->text[9] !== 'X') {
throw new BCGParseException('isbn', 'The character \'' . $this->text[9] . '\' is not allowed.');
}
// Drop the last char
$this->text = substr($this->text, 0, 9);
}
parent::checkCharsAllowed();
}
/**
* Check correct length.
*
* @return void
*/
protected function checkCorrectLength(): void
{
$c = strlen($this->text);
// If we have 13 chars just flush the last one
if ($c === 13) {
$this->text = substr($this->text, 0, 12);
} elseif ($c === 9 || $c === 10) {
if ($c === 10) {
// Before dropping it, we check if it's legal
if (array_search($this->text[9], $this->keys) === false && $this->text[9] !== 'X') {
throw new BCGParseException('isbn', 'The character \'' . $this->text[9] . '\' is not allowed.');
}
$this->text = substr($this->text, 0, 9);
}
if ($this->gs1 === self::GS1_AUTO || $this->gs1 === self::GS1_PREFIX978) {
$this->text = '978' . $this->text;
} elseif ($this->gs1 === self::GS1_PREFIX979) {
$this->text = '979' . $this->text;
}
} elseif ($c !== 12) {
throw new BCGParseException('isbn', 'The code parsed must be 9, 10, 12, or 13 digits long.');
}
}
/**
* Creates the ISBN text.
*
* @return string The ISBN text.
*/
private function createISBNText(): string
{
$isbn = '';
if (!empty($this->text)) {
// We try to create the ISBN Text... the hyphen really depends the ISBN agency.
// We just put one before the checksum and one after the GS1 if present.
$c = strlen($this->text);
if ($c === 12 || $c === 13) {
// If we have 13 characters now, just transform it temporarily to find the checksum...
// Further in the code we take care of that anyway.
$lastCharacter = '';
if ($c === 13) {
$lastCharacter = $this->text[12];
$this->text = substr($this->text, 0, 12);
}
$checksum = $this->processChecksum();
$isbn = 'ISBN ' . substr($this->text, 0, 3) . '-' . substr($this->text, 3, 9) . '-' . $checksum;
// Put the last character back
if ($c === 13) {
$this->text .= $lastCharacter;
}
} elseif ($c === 9 || $c === 10) {
$checksum = 0;
for ($i = 10; $i >= 2; $i--) {
$checksum += $this->text[10 - $i] * $i;
}
$checksum = 11 - $checksum % 11;
if ($checksum === 10) {
$checksum = 'X'; // Changing type
}
$isbn = 'ISBN ' . substr($this->text, 0, 9) . '-' . $checksum;
}
}
return $isbn;
}
}

View File

@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - MSI Plessey
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGArgumentException;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGmsi extends BCGBarcode1D
{
private int $checksum;
/**
* Constructor.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'01010101', /* 0 */
'01010110', /* 1 */
'01011001', /* 2 */
'01011010', /* 3 */
'01100101', /* 4 */
'01100110', /* 5 */
'01101001', /* 6 */
'01101010', /* 7 */
'10010101', /* 8 */
'10010110' /* 9 */
);
$this->setChecksum(0);
}
/**
* Sets how many checksums we display. 0 to 2.
*
* @param int $checksum The amount of checksums.
* @return void
*/
public function setChecksum(int $checksum): void
{
$checksum = intval($checksum);
if ($checksum < 0 && $checksum > 2) {
throw new BCGArgumentException('The checksum must be between 0 and 2 included.', 'checksum');
}
$this->checksum = $checksum;
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Checksum
$this->calculateChecksum();
// Starting Code
$this->drawChar($image, '10', true);
// Chars
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->text[$i]), true);
}
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->checksumValue[$i]), true);
}
// Ending Code
$this->drawChar($image, '010', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$textlength = 12 * strlen($this->text);
$startlength = 3;
$checksumlength = $this->checksum * 12;
$endlength = 4;
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('msi', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('msi', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Forming a new number
// If the original number is even, we take all even position
// If the original number is odd, we take all odd position
// 123456 = 246
// 12345 = 135
// Multiply by 2
// Add up all the digit in the result (270 : 2+7+0)
// Add up other digit not used.
// 10 - (? Modulo 10). If result = 10, change to 0
$lastText = $this->text;
$this->checksumValue = array();
for ($i = 0; $i < $this->checksum; $i++) {
$newText = '';
$newNumber = 0;
$c = strlen($lastText);
if ($c % 2 === 0) { // Even
$starting = 1;
} else {
$starting = 0;
}
for ($j = $starting; $j < $c; $j += 2) {
$newText .= $lastText[$j];
}
$newText = strval(intval($newText) * 2);
$c2 = strlen($newText);
for ($j = 0; $j < $c2; $j++) {
$newNumber += intval($newText[$j]);
}
for ($j = ($starting === 0) ? 1 : 0; $j < $c; $j += 2) {
$newNumber += intval($lastText[$j]);
}
$newNumber = (10 - $newNumber % 10) % 10;
$this->checksumValue[] = $newNumber;
$lastText .= $newNumber;
}
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
$ret = '';
$c = count($this->checksumValue);
for ($i = 0; $i < $c; $i++) {
$ret .= $this->keys[$this->checksumValue[$i]];
}
return $ret;
}
return null;
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - othercode
*
* Other Codes
* Starting with a bar and altern to space, bar, ...
* 0 is the smallest
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGothercode extends BCGBarcode1D
{
/**
* Creates an other type barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$this->drawChar($image, $this->text, true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Gets the label.
* If the label was set to BCGBarcode1D::AUTO_LABEL, the label will display the value from the text parsed.
*
* @return string The label string.
*/
public function getLabel(): string
{
$label = $this->label;
if ($this->label === BCGBarcode1D::AUTO_LABEL) {
$label = '';
}
return $label;
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$array = str_split($this->text, 1);
$textlength = array_sum($array) + count($array);
$width += $textlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('othercode', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('othercode', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
parent::validate();
}
}

View File

@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - PostNet
*
* A postnet is composed of either 5, 9 or 11 digits used by US postal service.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGpostnet extends BCGBarcode1D
{
/**
* Creates a PostNet barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'11000', /* 0 */
'00011', /* 1 */
'00101', /* 2 */
'00110', /* 3 */
'01001', /* 4 */
'01010', /* 5 */
'01100', /* 6 */
'10001', /* 7 */
'10010', /* 8 */
'10100' /* 9 */
);
$this->setThickness(9);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Checksum
$checksum = 0;
$c = strlen($this->text);
for ($i = 0; $i < $c; $i++) {
$checksum += intval($this->text[$i]);
}
$checksum = (10 - ($checksum % 10)) % 10;
// Starting Code
$this->drawChar($image, '1');
// Code
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($this->text[$i]));
}
// Checksum
$this->drawChar($image, $this->findCode((string)$checksum));
// Ending Code
$this->drawChar($image, '1');
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$c = strlen($this->text);
$startlength = 3;
$textlength = $c * 5 * 3;
$checksumlength = 5 * 3;
$endlength = 3;
// We remove the white on the right
$removelength = -1.56;
$width += $startlength + $textlength + $checksumlength + $endlength + (int)$removelength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('postnet', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('postnet', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must contain 5, 9 or 11 chars
if ($c !== 5 && $c !== 9 && $c !== 11) {
throw new BCGParseException('postnet', 'Must contain 5, 9, or 11 characters.');
}
parent::validate();
}
/**
* Overloaded method for drawing special barcode.
*
* @param resource $image The surface.
* @param string $code The code.
* @param bool $startBar True if we begin with a space.
* @return void
*/
protected function drawChar($image, string $code, bool $startBar = true): void
{
$c = strlen($code);
for ($i = 0; $i < $c; $i++) {
if ($code[$i] === '0') {
$posY = $this->thickness - ($this->thickness / 2.5);
} else {
$posY = 0;
}
$this->drawFilledRectangle($image, intval($this->positionX), intval($posY), intval($this->positionX + 0.44), $this->thickness - 1, BCGBarcode::COLOR_FG);
$this->positionX += 3;
}
}
}

View File

@@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - Standard 2 of 5
*
* TODO I25 and S25 -> 1/3 or 1/2 for the big bar
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGParseException;
class BCGs25 extends BCGBarcode1D
{
private bool $checksum;
/**
* Creates a Standard 2 of 5 barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'0000202000', /* 0 */
'2000000020', /* 1 */
'0020000020', /* 2 */
'2020000000', /* 3 */
'0000200020', /* 4 */
'2000200000', /* 5 */
'0020200000', /* 6 */
'0000002020', /* 7 */
'2000002000', /* 8 */
'0020002000' /* 9 */
);
$this->setChecksum(false);
}
/**
* Sets if we display the checksum.
*
* @param bool $checksum Displays the checksum.
* @return void
*/
public function setChecksum(bool $checksum): void
{
$this->checksum = (bool)$checksum;
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$tempText = $this->text;
// Checksum
if ($this->checksum === true) {
$this->calculateChecksum();
$tempText .= $this->keys[$this->checksumValue[0]];
}
// Starting Code
$this->drawChar($image, '101000', true);
// Chars
$c = strlen($tempText);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, $this->findCode($tempText[$i]), true);
}
// Ending Code
$this->drawChar($image, '10001', true);
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$c = strlen($this->text);
$startlength = 8;
$textlength = $c * 14;
$checksumlength = 0;
if ($c % 2 !== 0) {
$checksumlength = 14;
}
$endlength = 7;
$width += $startlength + $textlength + $checksumlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('s25', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('s25', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must be even
if ($c % 2 !== 0 && $this->checksum === false) {
throw new BCGParseException('s25', 's25 must contain an even amount of digits if checksum is false.');
} elseif ($c % 2 === 0 && $this->checksum === true) {
throw new BCGParseException('s25', 's25 must contain an odd amount of digits if checksum is true.');
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "even" position,
// and assign odd/even to each character moving from right to left
// Even Position = 3, Odd Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$even = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($even === true) {
$multiplier = 3;
$even = false;
} else {
$multiplier = 1;
$even = true;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = (10 - $this->checksumValue[0] % 10) % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
}

View File

@@ -0,0 +1,161 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - UPC-A
*
* UPC-A contains
* - 2 system digits (1 not provided, a 0 is added automatically)
* - 5 manufacturer code digits
* - 5 product digits
* - 1 checksum digit
*
* The checksum is always displayed.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGupca extends BCGean13
{
protected ?BCGLabel $labelRight = null;
/**
* Creates a UPC-A barcode.
*/
public function __construct()
{
parent::__construct();
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// The following code is exactly the same as EAN13. We just add a 0 in front of the code !
$this->text = '0' . $this->text; // We will remove it at the end... don't worry
parent::draw($image);
// We remove the 0 in front, as we said :)
$this->text = substr($this->text, 1);
}
/**
* Draws the extended bars on the image.
*
* @param resource $image The surface.
* @param int $plus How much more we should display the bars.
* @return void
*/
protected function drawExtendedBars($image, int $plus): void
{
$tempText = $this->text . $this->keys[$this->checksumValue[0]];
$rememberX = $this->positionX;
$rememberH = $this->thickness;
// We increase the bars
// First 2 Bars
$this->thickness = $this->thickness + intval($plus / $this->scale);
$this->positionX = 0;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Attemping to increase the 2 following bars
$this->positionX += 1;
$tempValue = $this->findCode($tempText[1]);
$this->drawChar($image, $tempValue, false);
// Center Guard Bar
$this->positionX += 36;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Attemping to increase the 2 last bars
$this->positionX += 37;
$tempValue = $this->findCode($tempText[12]);
$this->drawChar($image, $tempValue, true);
// Completly last bars
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX = $rememberX;
$this->thickness = $rememberH;
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
if ($this->isDefaultEanLabelEnabled()) {
$this->processChecksum();
$label = $this->getLabel();
$font = $this->font;
$this->labelLeft = new BCGLabel(substr($label, 0, 1), $font, BCGLabel::POSITION_LEFT, BCGLabel::ALIGN_BOTTOM);
$this->labelLeft->setSpacing(4 * $this->scale);
$this->labelCenter1 = new BCGLabel(substr($label, 1, 5), $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$labelCenter1Dimension = $this->labelCenter1->getDimension();
$this->labelCenter1->setOffset((int)(($this->scale * 44 - $labelCenter1Dimension[0]) / 2 + $this->scale * 6));
$this->labelCenter2 = new BCGLabel(substr($label, 6, 5), $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$this->labelCenter2->setOffset((int)(($this->scale * 44 - $labelCenter1Dimension[0]) / 2 + $this->scale * 45));
$this->labelRight = new BCGLabel($this->keys[$this->checksumValue[0]], $font, BCGLabel::POSITION_RIGHT, BCGLabel::ALIGN_BOTTOM);
$this->labelRight->setSpacing(4 * $this->scale);
if ($this->alignLabel) {
$labelDimension = $this->labelCenter1->getDimension();
$this->labelLeft->setOffset($labelDimension[1]);
$this->labelRight->setOffset($labelDimension[1]);
} else {
$labelDimension = $this->labelLeft->getDimension();
$this->labelLeft->setOffset((int)($labelDimension[1] / 2));
$labelDimension = $this->labelLeft->getDimension();
$this->labelRight->setOffset((int)($labelDimension[1] / 2));
}
$this->addLabel($this->labelLeft);
$this->addLabel($this->labelCenter1);
$this->addLabel($this->labelCenter2);
$this->addLabel($this->labelRight);
}
}
/**
* Check correct length.
*
* @return void
*/
protected function checkCorrectLength(): void
{
// If we have 12 chars, just flush the last one without throwing anything
$c = strlen($this->text);
if ($c === 12) {
$this->text = substr($this->text, 0, 11);
} elseif ($c !== 11) {
throw new BCGParseException('upca', 'Must contain 11 digits, the 12th digit is automatically added.');
}
}
}

View File

@@ -0,0 +1,360 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - UPC-E
*
* You can provide a UPC-A code (without dash), the code will transform
* it into a UPC-E format if it's possible.
* UPC-E contains
* - 1 system digits (not displayed but coded with parity)
* - 6 digits
* - 1 checksum digit (not displayed but coded with parity)
*
* The text returned is the UPC-E without the checksum.
* The checksum is always displayed.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGupce extends BCGBarcode1D
{
protected array $codeParity = array();
protected ?string $upce;
protected ?BCGLabel $labelLeft = null;
protected ?BCGLabel $labelCenter = null;
protected ?BCGLabel $labelRight = null;
/**
* Creates a UPC-E barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
// Odd Parity starting with a space
// Even Parity is the inverse (0=0012) starting with a space
$this->code = array(
'2100', /* 0 */
'1110', /* 1 */
'1011', /* 2 */
'0300', /* 3 */
'0021', /* 4 */
'0120', /* 5 */
'0003', /* 6 */
'0201', /* 7 */
'0102', /* 8 */
'2001' /* 9 */
);
// Parity, 0=Odd, 1=Even for manufacturer code. Depending on 1st System Digit and Checksum
$this->codeParity = array(
array(
array(1, 1, 1, 0, 0, 0), /* 0,0 */
array(1, 1, 0, 1, 0, 0), /* 0,1 */
array(1, 1, 0, 0, 1, 0), /* 0,2 */
array(1, 1, 0, 0, 0, 1), /* 0,3 */
array(1, 0, 1, 1, 0, 0), /* 0,4 */
array(1, 0, 0, 1, 1, 0), /* 0,5 */
array(1, 0, 0, 0, 1, 1), /* 0,6 */
array(1, 0, 1, 0, 1, 0), /* 0,7 */
array(1, 0, 1, 0, 0, 1), /* 0,8 */
array(1, 0, 0, 1, 0, 1) /* 0,9 */
),
array(
array(0, 0, 0, 1, 1, 1), /* 0,0 */
array(0, 0, 1, 0, 1, 1), /* 0,1 */
array(0, 0, 1, 1, 0, 1), /* 0,2 */
array(0, 0, 1, 1, 1, 0), /* 0,3 */
array(0, 1, 0, 0, 1, 1), /* 0,4 */
array(0, 1, 1, 0, 0, 1), /* 0,5 */
array(0, 1, 1, 1, 0, 0), /* 0,6 */
array(0, 1, 0, 1, 0, 1), /* 0,7 */
array(0, 1, 0, 1, 1, 0), /* 0,8 */
array(0, 1, 1, 0, 1, 0) /* 0,9 */
)
);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
$this->calculateChecksum();
// Starting Code
$this->drawChar($image, '000', true);
$c = strlen($this->upce);
for ($i = 0; $i < $c; $i++) {
$this->drawChar($image, self::inverse($this->findCode($this->upce[$i]), $this->codeParity[intval($this->text[0])][$this->checksumValue[0]][$i]), false);
}
// Draw Center Guard Bar
$this->drawChar($image, '00000', false);
// Draw Right Bar
$this->drawChar($image, '0', true);
$this->text = $this->text[0] . $this->upce;
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
if ($this->isDefaultEanLabelEnabled()) {
$dimension = $this->labelCenter->getDimension();
$this->drawExtendedBars($image, $dimension[1] - 2);
}
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 3;
$centerlength = 5;
$textlength = 6 * 7;
$endlength = 1;
$width += $startlength + $centerlength + $textlength + $endlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
if ($this->isDefaultEanLabelEnabled()) {
$this->processChecksum();
$font = $this->font;
$this->labelLeft = new BCGLabel(substr($this->text, 0, 1), $font, BCGLabel::POSITION_LEFT, BCGLabel::ALIGN_BOTTOM);
$labelLeftDimension = $this->labelLeft->getDimension();
$this->labelLeft->setSpacing(8);
$this->labelLeft->setOffset((int)($labelLeftDimension[1] / 2));
$this->labelCenter = new BCGLabel($this->upce, $font, BCGLabel::POSITION_BOTTOM, BCGLabel::ALIGN_LEFT);
$labelCenterDimension = $this->labelCenter->getDimension();
$this->labelCenter->setOffset((int)(($this->scale * 46 - $labelCenterDimension[0]) / 2 + $this->scale * 2));
$this->labelRight = new BCGLabel($this->keys[$this->checksumValue[0]], $font, BCGLabel::POSITION_RIGHT, BCGLabel::ALIGN_BOTTOM);
$labelRightDimension = $this->labelRight->getDimension();
$this->labelRight->setSpacing(8);
$this->labelRight->setOffset((int)($labelRightDimension[1] / 2));
$this->addLabel($this->labelLeft);
$this->addLabel($this->labelCenter);
$this->addLabel($this->labelRight);
}
}
/**
* Checks if the default ean label is enabled.
*
* @return bool True if default label is enabled.
*/
protected function isDefaultEanLabelEnabled(): bool
{
$label = $this->getLabel();
$font = $this->font;
return $label !== null && $label !== '' && $font !== null && $this->defaultLabel !== null;
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('upce', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('upce', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must contain 11 chars
// Must contain 6 chars (if starting with upce directly)
// First Chars must be 0 or 1
if ($c !== 11 && $c !== 6) {
throw new BCGParseException('upce', 'You must provide a UPC-A (11 characters) or a UPC-E (6 characters).');
} elseif ($this->text[0] !== '0' && $this->text[0] !== '1' && $c !== 6) {
throw new BCGParseException('upce', 'UPC-A must start with 0 or 1 to be converted to UPC-E.');
}
// Convert part
$this->upce = '';
if ($c !== 6) {
// Checking if UPC-A is convertible
$temp1 = substr($this->text, 3, 3);
if ($temp1 === '000' || $temp1 === '100' || $temp1 === '200') { // manufacturer code ends with 100, 200 or 300
if (substr($this->text, 6, 2) === '00') { // Product must start with 00
$this->upce = substr($this->text, 1, 2) . substr($this->text, 8, 3) . substr($this->text, 3, 1);
}
} elseif (substr($this->text, 4, 2) === '00') { // manufacturer code ends with 00
if (substr($this->text, 6, 3) === '000') { // Product must start with 000
$this->upce = substr($this->text, 1, 3) . substr($this->text, 9, 2) . '3';
}
} elseif (substr($this->text, 5, 1) === '0') { // manufacturer code ends with 0
if (substr($this->text, 6, 4) === '0000') { // Product must start with 0000
$this->upce = substr($this->text, 1, 4) . substr($this->text, 10, 1) . '4';
}
} else { // No zero leading at manufacturer code
$temp2 = intval(substr($this->text, 10, 1));
if (substr($this->text, 6, 4) === '0000' && $temp2 >= 5 && $temp2 <= 9) { // Product must start with 0000 and must end by 5, 6, 7, 8 or 9
$this->upce = substr($this->text, 1, 5) . substr($this->text, 10, 1);
}
}
} else {
$this->upce = $this->text;
}
if ($this->upce === '') {
throw new BCGParseException('upce', 'Your UPC-A can\'t be converted to UPC-E.');
}
if ($c === 6) {
$upca = '';
// We convert UPC-E to UPC-A to find the checksum
if ($this->text[5] === '0' || $this->text[5] === '1' || $this->text[5] === '2') {
$upca = substr($this->text, 0, 2) . $this->text[5] . '0000' . substr($this->text, 2, 3);
} elseif ($this->text[5] === '3') {
$upca = substr($this->text, 0, 3) . '00000' . substr($this->text, 3, 2);
} elseif ($this->text[5] === '4') {
$upca = substr($this->text, 0, 4) . '00000' . $this->text[4];
} else {
$upca = substr($this->text, 0, 5) . '0000' . $this->text[5];
}
$this->text = '0' . $upca;
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "odd" position,
// and assign odd/even to each character moving from right to left
// Odd Position = 3, Even Position = 1
// Multiply it by the number
// Add all of that and do 10-(?mod10)
$odd = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($odd === true) {
$multiplier = 3;
$odd = false;
} else {
$multiplier = 1;
$odd = true;
}
if (!isset($this->keys[$this->text[$i - 1]])) {
return;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = (10 - $this->checksumValue[0] % 10) % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
/**
* Draws the extended bars on the image.
*
* @param resource $image The surface.
* @param int $plus How much more we should display the bars.
* @return void
*/
protected function drawExtendedBars($image, int $plus): void
{
$rememberX = $this->positionX;
$rememberH = $this->thickness;
// We increase the bars
$this->thickness = $this->thickness + intval($plus / $this->scale);
$this->positionX = 0;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
// Last Bars
$this->positionX += 46;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX += 2;
$this->drawSingleBar($image, BCGBarcode::COLOR_FG);
$this->positionX = $rememberX;
$this->thickness = $rememberH;
}
/**
* Inverses the string when the $inverse parameter is equal to 1.
*
* @param string $text The text.
* @param int $inverse The inverse.
* @return string the reversed string.
*/
private static function inverse(string $text, int $inverse = 1): string
{
if ($inverse === 1) {
$text = strrev($text);
}
return $text;
}
}

View File

@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - UPC Supplemental Barcode 2 digits
*
* Working with UPC-A, UPC-E, EAN-13, EAN-8
* This includes 2 digits (normaly for publications)
* Must be placed next to UPC or EAN Code
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGupcext2 extends BCGBarcode1D
{
protected array $codeParity = array();
/**
* Creates a UPC supplemental 2 digits barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'2100', /* 0 */
'1110', /* 1 */
'1011', /* 2 */
'0300', /* 3 */
'0021', /* 4 */
'0120', /* 5 */
'0003', /* 6 */
'0201', /* 7 */
'0102', /* 8 */
'2001' /* 9 */
);
// Parity, 0=Odd, 1=Even. Depending on ?%4
$this->codeParity = array(
array(0, 0), /* 0 */
array(0, 1), /* 1 */
array(1, 0), /* 2 */
array(1, 1) /* 3 */
);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
*/
public function draw($image): void
{
// Starting Code
$this->drawChar($image, '001', true);
// Code
for ($i = 0; $i < 2; $i++) {
$this->drawChar($image, self::inverse($this->findCode($this->text[$i]), $this->codeParity[intval($this->text) % 4][$i]), false);
if ($i === 0) {
$this->drawChar($image, '00', false); // Inter-char
}
}
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 4;
$textlength = 2 * 7;
$intercharlength = 2;
$width += $startlength + $textlength + $intercharlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
parent::addDefaultLabel();
if ($this->defaultLabel !== null) {
$this->defaultLabel->setPosition(BCGLabel::POSITION_TOP);
}
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('upcext2', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('upcext2', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must contain 2 digits
if ($c !== 2) {
throw new BCGParseException('upcext2', 'Must contain 2 digits.');
}
parent::validate();
}
/**
* Inverses the string when the $inverse parameter is equal to 1.
*
* @param string $text The text.
* @param int $inverse The inverse.
* @return string the reversed string.
*/
private static function inverse(string $text, int $inverse = 1): string
{
if ($inverse === 1) {
$text = strrev($text);
}
return $text;
}
}

View File

@@ -0,0 +1,221 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Sub-Class - UPC Supplemental Barcode 5 digits
*
* Working with UPC-A, UPC-E, EAN-13, EAN-8
* This includes 5 digits (normaly for suggested retail price)
* Must be placed next to UPC or EAN Code
* If 90000 -> No suggested Retail Price
* If 99991 -> Book Complimentary (normally free)
* If 90001 to 98999 -> Internal Purpose of Publisher
* If 99990 -> Used by the National Association of College Stores to mark used books
* If 0xxxx -> Price Expressed in British Pounds (xx.xx)
* If 5xxxx -> Price Expressed in U.S. dollars (US$xx.xx)
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Barcode;
use BarcodeBakery\Common\BCGBarcode1D;
use BarcodeBakery\Common\BCGLabel;
use BarcodeBakery\Common\BCGParseException;
class BCGupcext5 extends BCGBarcode1D
{
protected array $codeParity = array();
/**
* Creates a UPC supplemental 5 digits barcode.
*/
public function __construct()
{
parent::__construct();
$this->keys = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
$this->code = array(
'2100', /* 0 */
'1110', /* 1 */
'1011', /* 2 */
'0300', /* 3 */
'0021', /* 4 */
'0120', /* 5 */
'0003', /* 6 */
'0201', /* 7 */
'0102', /* 8 */
'2001' /* 9 */
);
// Parity, 0=Odd, 1=Even. Depending Checksum
$this->codeParity = array(
array(1, 1, 0, 0, 0), /* 0 */
array(1, 0, 1, 0, 0), /* 1 */
array(1, 0, 0, 1, 0), /* 2 */
array(1, 0, 0, 0, 1), /* 3 */
array(0, 1, 1, 0, 0), /* 4 */
array(0, 0, 1, 1, 0), /* 5 */
array(0, 0, 0, 1, 1), /* 6 */
array(0, 1, 0, 1, 0), /* 7 */
array(0, 1, 0, 0, 1), /* 8 */
array(0, 0, 1, 0, 1) /* 9 */
);
}
/**
* Draws the barcode.
*
* @param resource $image The surface.
* @return void
*/
public function draw($image): void
{
// Checksum
$this->calculateChecksum();
// Starting Code
$this->drawChar($image, '001', true);
// Code
for ($i = 0; $i < 5; $i++) {
$this->drawChar($image, self::inverse($this->findCode($this->text[$i]), $this->codeParity[$this->checksumValue[0]][$i]), false);
if ($i < 4) {
$this->drawChar($image, '00', false); // Inter-char
}
}
$this->drawText($image, 0, 0, $this->positionX, $this->thickness);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$startlength = 4;
$textlength = 5 * 7;
$intercharlength = 2 * 4;
$width += $startlength + $textlength + $intercharlength;
$height += $this->thickness;
return parent::getDimension($width, $height);
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
parent::addDefaultLabel();
if ($this->defaultLabel !== null) {
$this->defaultLabel->setPosition(BCGLabel::POSITION_TOP);
}
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
$c = strlen($this->text);
if ($c === 0) {
throw new BCGParseException('upcext5', 'No data has been entered.');
}
// Checking if all chars are allowed
for ($i = 0; $i < $c; $i++) {
if (array_search($this->text[$i], $this->keys) === false) {
throw new BCGParseException('upcext5', 'The character \'' . $this->text[$i] . '\' is not allowed.');
}
}
// Must contain 5 digits
if ($c !== 5) {
throw new BCGParseException('upcext5', 'Must contain 5 digits.');
}
parent::validate();
}
/**
* Overloaded method to calculate checksum.
*
* @return void
*/
protected function calculateChecksum(): void
{
// Calculating Checksum
// Consider the right-most digit of the message to be in an "odd" position,
// and assign odd/even to each character moving from right to left
// Odd Position = 3, Even Position = 9
// Multiply it by the number
// Add all of that and do ?mod10
$odd = true;
$this->checksumValue = array(0);
$c = strlen($this->text);
for ($i = $c; $i > 0; $i--) {
if ($odd === true) {
$multiplier = 3;
$odd = false;
} else {
$multiplier = 9;
$odd = true;
}
if (!isset($this->keys[$this->text[$i - 1]])) {
return;
}
$this->checksumValue[0] += $this->keys[$this->text[$i - 1]] * $multiplier;
}
$this->checksumValue[0] = $this->checksumValue[0] % 10;
}
/**
* Overloaded method to display the checksum.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
if ($this->checksumValue === null) { // Calculate the checksum only once
$this->calculateChecksum();
}
if ($this->checksumValue !== null) {
return $this->keys[$this->checksumValue[0]];
}
return null;
}
/**
* Inverses the string when the $inverse parameter is equal to 1.
*
* @param string $text The text.
* @param int $inverse The inverse.
* @return string the reversed string.
*/
private static function inverse(string $text, int $inverse = 1): string
{
if ($inverse === 1) {
$text = strrev($text);
}
return $text;
}
}

View File

@@ -0,0 +1,16 @@
<p align="center"><a href="https://www.barcodebakery.com" target="_blank">
<img src="https://www.barcodebakery.com/images/BCG-Logo-SQ-GitHub.svg">
</a></p>
[Barcode Bakery][1] is library written in PHP, [.NET Standard][2] and Node.JS which allows you to generate barcodes on the fly on your server for displaying or saving.
This is the common base for generating all barcode types.
Please visit the following repository:
* [barcode-php-1d][3]
[1]: https://www.barcodebakery.com
[2]: https://github.com/barcode-bakery/barcode-dotnet-1d/
[3]: https://github.com/barcode-bakery/barcode-php-1d/

View File

@@ -0,0 +1,30 @@
{
"name": "barcode-bakery/barcode-common",
"version": "7.0.3",
"license": [
"proprietary",
"CC-BY-NC-ND-4.0"
],
"support": {
"email": "contact@barcodebakery.com",
"docs": "https://www.barcodebakery.com"
},
"type": "library",
"homepage": "https://www.barcodebakery.com",
"authors": [
{
"name": "Jean-Sébastien Goupil",
"email": "contact@barcodebakery.com"
}
],
"description": "Base code for generating barcode with the Barcode Bakery library. See barcode-bakery/barcode-1d.",
"autoload": {
"psr-4": {
"BarcodeBakery\\Common\\": "src"
}
},
"require": {
"php": ">=7.4",
"ext-gd": "*"
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Argument Exception
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGArgumentException extends \Exception
{
protected string $param;
/**
* Constructor with specific message for a parameter.
*
* @param string $message The message.
* @param string $param The parameter.
*/
public function __construct(string $message, string $param)
{
$this->param = $param;
parent::__construct($message, 20000);
}
}

View File

@@ -0,0 +1,505 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Base class for Barcode 1D and 2D
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
use BarcodeBakery\Common\BCGArgumentException;
use BarcodeBakery\Common\BCGDrawException;
abstract class BCGBarcode
{
const COLOR_BG = 0;
const COLOR_FG = 1;
protected BCGColor $colorFg;
protected BCGColor $colorBg;
protected int $scale; // Scale of the graphic, default: 1
protected int $offsetX;
protected int $offsetY; // Position where to start the drawing
protected array $labels = array(); // Array of BCGLabel
protected array $pushLabel = array(0, 0); // Push for the label, left and top
/**
* Constructor.
*/
protected function __construct()
{
$this->setOffsetX(0);
$this->setOffsetY(0);
$this->setForegroundColor(0x000000);
$this->setBackgroundColor(0xffffff);
$this->setScale(1);
}
/**
* Call this method if you are using the commercial version of our software.
*
* @return void
*/
public function useCommercialVersion(): void
{
throw new BCGArgumentException('You are using the non-commercial library. You must purchase a license at https://www.barcodebakery.com in order to use this in a commercial environment. If you have purchased the library and still obtain this message, follow the documentation on our website.', 'free');
}
/**
* Parses the text before displaying it.
*
* @param mixed $text The text.
*/
public function parse($text): void
{
}
/**
* Gets the foreground color of the barcode.
*
* @return BCGColor The foreground color.
*/
public function getForegroundColor(): BCGColor
{
return $this->colorFg;
}
/**
* Sets the foreground color of the barcode. It could be a BCGColor
* value or simply a language code (white, black, yellow...) or hex value.
*
* @param BCGColor|int $code The foreground color.
* @return void
*/
public function setForegroundColor($code): void
{
if ($code instanceof BCGColor) {
$this->colorFg = $code;
} else {
$this->colorFg = new BCGColor($code);
}
}
/**
* Gets the background color of the barcode.
*
* @return BCGColor The background color.
*/
public function getBackgroundColor(): BCGColor
{
return $this->colorBg;
}
/**
* Sets the background color of the barcode. It could be a BCGColor
* value or simply a language code (white, black, yellow...) or hex value.
*
* @param BCGColor|int $code The background color.
* @return void
*/
public function setBackgroundColor($code): void
{
if ($code instanceof BCGColor) {
$this->colorBg = $code;
} else {
$this->colorBg = new BCGColor($code);
}
foreach ($this->labels as $label) {
$label->setBackgroundColor($this->colorBg);
}
}
/**
* Sets the foreground and background color.
*
* @param BCGColor|int $foregroundColor The foreground color.
* @param BCGColor|int $backgroundColor The background color.
*/
public function setColor($foregroundColor, $backgroundColor): void
{
$this->setForegroundColor($foregroundColor);
$this->setBackgroundColor($backgroundColor);
}
/**
* Gets the scale of the barcode.
*
* @return int The scale.
*/
public function getScale(): int
{
return $this->scale;
}
/**
* Sets the scale of the barcode in pixel.
* If the scale is lower than 1, an exception is raised.
*
* @param int $scale The scale.
* @return void
*/
public function setScale(int $scale): void
{
$scale = intval($scale);
if ($scale <= 0) {
throw new BCGArgumentException('The scale must be larger than 0.', 'scale');
}
$this->scale = $scale;
}
/**
* Abstract method that draws the barcode on the resource.
*
* @param resource $image The surface.
* @return void
*/
abstract public function draw($image): void;
/**
* Returns the maximal size of a barcode.
* [0]->width
* [1]->height
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
$labels = $this->getBiggestLabels(false);
$pixelsAround = array(0, 0, 0, 0); // TRBL
if (isset($labels[BCGLabel::POSITION_TOP])) {
$dimension = $labels[BCGLabel::POSITION_TOP]->getDimension();
$pixelsAround[0] += $dimension[1];
}
if (isset($labels[BCGLabel::POSITION_RIGHT])) {
$dimension = $labels[BCGLabel::POSITION_RIGHT]->getDimension();
$pixelsAround[1] += $dimension[0];
}
if (isset($labels[BCGLabel::POSITION_BOTTOM])) {
$dimension = $labels[BCGLabel::POSITION_BOTTOM]->getDimension();
$pixelsAround[2] += $dimension[1];
}
if (isset($labels[BCGLabel::POSITION_LEFT])) {
$dimension = $labels[BCGLabel::POSITION_LEFT]->getDimension();
$pixelsAround[3] += $dimension[0];
}
$finalW = ($width + $this->offsetX) * $this->scale;
$finalH = ($height + $this->offsetY) * $this->scale;
// This section will check if a top/bottom label is too big for its width and left/right too big for its height
$reversedLabels = $this->getBiggestLabels(true);
foreach ($reversedLabels as $label) {
$dimension = $label->getDimension();
$alignment = $label->getAlignment();
if ($label->getPosition() === BCGLabel::POSITION_LEFT || $label->getPosition() === BCGLabel::POSITION_RIGHT) {
if ($alignment === BCGLabel::ALIGN_TOP) {
$pixelsAround[2] = max($pixelsAround[2], $dimension[1] - $finalH);
} elseif ($alignment === BCGLabel::ALIGN_CENTER) {
$temp = (int)ceil(($dimension[1] - $finalH) / 2);
$pixelsAround[0] = max($pixelsAround[0], $temp);
$pixelsAround[2] = max($pixelsAround[2], $temp);
} elseif ($alignment === BCGLabel::ALIGN_BOTTOM) {
$pixelsAround[0] = max($pixelsAround[0], $dimension[1] - $finalH);
}
} else {
if ($alignment === BCGLabel::ALIGN_LEFT) {
$pixelsAround[1] = max($pixelsAround[1], $dimension[0] - $finalW);
} elseif ($alignment === BCGLabel::ALIGN_CENTER) {
$temp = (int)ceil(($dimension[0] - $finalW) / 2);
$pixelsAround[1] = max($pixelsAround[1], $temp);
$pixelsAround[3] = max($pixelsAround[3], $temp);
} elseif ($alignment === BCGLabel::ALIGN_RIGHT) {
$pixelsAround[3] = max($pixelsAround[3], $dimension[0] - $finalW);
}
}
}
$this->pushLabel[0] = $pixelsAround[3];
$this->pushLabel[1] = $pixelsAround[0];
$finalW = ($width + $this->offsetX) * $this->scale + $pixelsAround[1] + $pixelsAround[3];
$finalH = ($height + $this->offsetY) * $this->scale + $pixelsAround[0] + $pixelsAround[2];
return array((int)$finalW, (int)$finalH);
}
/**
* Gets the X offset.
*
* @return int The X offset.
*/
public function getOffsetX(): int
{
return $this->offsetX;
}
/**
* Sets the X offset.
*
* @param int $offsetX The X offset.
* @return void
*/
public function setOffsetX(int $offsetX): void
{
$offsetX = intval($offsetX);
if ($offsetX < 0) {
throw new BCGArgumentException('The offset X must be 0 or larger.', 'offsetX');
}
$this->offsetX = $offsetX;
}
/**
* Gets the Y offset.
*
* @return int The Y offset.
*/
public function getOffsetY(): int
{
return $this->offsetY;
}
/**
* Sets the Y offset.
*
* @param int $offsetY The Y offset.
* @return void
*/
public function setOffsetY(int $offsetY): void
{
$offsetY = intval($offsetY);
if ($offsetY < 0) {
throw new BCGArgumentException('The offset Y must be 0 or larger.', 'offsetY');
}
$this->offsetY = $offsetY;
}
/**
* Adds the label to the drawing.
*
* @param BCGLabel $label The label.
* @return void
*/
public function addLabel(BCGLabel $label): void
{
$label->setBackgroundColor($this->colorBg);
$this->labels[] = $label;
}
/**
* Removes the label from the drawing.
*
* @param BCGLabel $label The label.
* @return void
*/
public function removeLabel(BCGLabel $label): void
{
$remove = -1;
$c = count($this->labels);
for ($i = 0; $i < $c; $i++) {
if ($this->labels[$i] === $label) {
$remove = $i;
break;
}
}
if ($remove > -1) {
array_splice($this->labels, $remove, 1);
}
}
/**
* Gets the labels.
*
* @return BCGLabel[] The labels.
*/
public function getLabels(): array
{
return $this->labels;
}
/**
* Clears the labels.
*
* @return void
*/
public function clearLabels(): void
{
$this->labels = array();
}
/**
* Draws the text.
* The coordinate passed are the positions of the barcode.
* $x1 and $y1 represent the top left corner.
* $x2 and $y2 represent the bottom right corner.
*
* @param resource $image The surface.
* @param int $x1 The top left corner X coordinate.
* @param int $y1 The top left corner Y coordinate.
* @param int $x2 The bottom right corner X coordinate.
* @param int $y2 The bottom right corner Y coordinate.
* @return void
*/
protected function drawText($image, int $x1, int $y1, int $x2, int $y2): void
{
foreach ($this->labels as $label) {
$label->draw(
$image,
($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0],
($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1],
($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0],
($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1]
);
}
}
/**
* Draws 1 pixel on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x The X coordinate.
* @param int $y The Y coordinate.
* @param int $color The color.
* @return void
*/
protected function drawPixel($image, int $x, int $y, int $color = self::COLOR_FG): void
{
$xR = ($x + $this->offsetX) * $this->scale + $this->pushLabel[0];
$yR = ($y + $this->offsetY) * $this->scale + $this->pushLabel[1];
// We always draw a rectangle
imagefilledrectangle(
$image,
$xR,
$yR,
$xR + $this->scale - 1,
$yR + $this->scale - 1,
$this->getColor($image, $color)
);
}
/**
* Draws an empty rectangle on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x1 The top left corner X coordinate.
* @param int $y1 The top left corner Y coordinate.
* @param int $x2 The bottom right corner X coordinate.
* @param int $y2 The bottom right corner Y coordinate.
* @param int $color The color.
* @return void
*/
protected function drawRectangle($image, int $x1, int $y1, int $x2, int $y2, int $color = self::COLOR_FG): void
{
if ($this->scale === 1) {
imagefilledrectangle(
$image,
($x1 + $this->offsetX) + $this->pushLabel[0],
($y1 + $this->offsetY) + $this->pushLabel[1],
($x2 + $this->offsetX) + $this->pushLabel[0],
($y2 + $this->offsetY) + $this->pushLabel[1],
$this->getColor($image, $color)
);
} else {
imagefilledrectangle($image, ($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0], ($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1], ($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0] + $this->scale - 1, ($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1] + $this->scale - 1, $this->getColor($image, $color));
imagefilledrectangle($image, ($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0], ($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1], ($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0] + $this->scale - 1, ($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1] + $this->scale - 1, $this->getColor($image, $color));
imagefilledrectangle($image, ($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0], ($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1], ($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0] + $this->scale - 1, ($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1] + $this->scale - 1, $this->getColor($image, $color));
imagefilledrectangle($image, ($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0], ($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1], ($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0] + $this->scale - 1, ($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1] + $this->scale - 1, $this->getColor($image, $color));
}
}
/**
* Draws a filled rectangle on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x1 The top left corner X coordinate.
* @param int $y1 The top left corner Y coordinate.
* @param int $x2 The bottom right corner X coordinate.
* @param int $y2 The bottom right corner Y coordinate.
* @param int $color The color.
* @return void
*/
protected function drawFilledRectangle($image, int $x1, int $y1, int $x2, int $y2, int $color = self::COLOR_FG): void
{
if ($x1 > $x2) { // Swap
$x1 ^= $x2 ^= $x1 ^= $x2;
}
if ($y1 > $y2) { // Swap
$y1 ^= $y2 ^= $y1 ^= $y2;
}
imagefilledrectangle(
$image,
($x1 + $this->offsetX) * $this->scale + $this->pushLabel[0],
($y1 + $this->offsetY) * $this->scale + $this->pushLabel[1],
($x2 + $this->offsetX) * $this->scale + $this->pushLabel[0] + $this->scale - 1,
($y2 + $this->offsetY) * $this->scale + $this->pushLabel[1] + $this->scale - 1,
$this->getColor($image, $color)
);
}
/**
* Allocates the color based on the integer.
*
* @param resource $image The surface.
* @param int $color The color.
* @return resource Implementation details of the color.
*/
protected function getColor($image, int $color)
{
if ($color === self::COLOR_BG) {
return $this->colorBg->allocate($image);
} else {
return $this->colorFg->allocate($image);
}
}
/**
* Returning the biggest label widths for LEFT/RIGHT and heights for TOP/BOTTOM.
*
* @param bool $reversed Indicates if the barcode has been rotated.
* @return BCGLabel[] Position of the biggest barcode.
*/
private function getBiggestLabels(bool $reversed = false): array
{
$searchLR = $reversed ? 1 : 0;
$searchTB = $reversed ? 0 : 1;
$labels = array();
foreach ($this->labels as $label) {
$position = $label->getPosition();
if (isset($labels[$position])) {
$savedDimension = $labels[$position]->getDimension();
$dimension = $label->getDimension();
if ($position === BCGLabel::POSITION_LEFT || $position === BCGLabel::POSITION_RIGHT) {
if ($dimension[$searchLR] > $savedDimension[$searchLR]) {
$labels[$position] = $label;
}
} else {
if ($dimension[$searchTB] > $savedDimension[$searchTB]) {
$labels[$position] = $label;
}
}
} else {
$labels[$position] = $label;
}
}
return $labels;
}
}

View File

@@ -0,0 +1,325 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Holds all type of barcodes for 1D generation
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
abstract class BCGBarcode1D extends BCGBarcode
{
const SIZE_SPACING_FONT = 5;
const AUTO_LABEL = '##!!AUTO_LABEL!!##';
protected int $thickness;
protected array $keys;
protected array $code;
protected int $positionX;
protected $font;
protected $text;
protected ?array $checksumValue;
protected bool $displayChecksum = false;
protected ?string $label;
protected BCGLabel $defaultLabel;
protected array $helper = array('9|28', 'a|e;11', '93|i|1|d|8|e|s25;8', '25;9', 'n|3;12', '2;2', '5;5');
protected $s = false;
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->setThickness(30);
$this->defaultLabel = new BCGLabel();
$this->defaultLabel->setPosition(BCGLabel::POSITION_BOTTOM);
$this->setLabel(self::AUTO_LABEL);
$this->setFont(new BCGFontPhp(5));
$this->text = '';
$this->checksumValue = null;
$this->positionX = 0;
}
/**
* Gets the thickness.
*
* @return int The thickness.
*/
public function getThickness(): int
{
return $this->thickness;
}
/**
* Sets the thickness.
*
* @param int $thickness The thickness.
* @return void
*/
public function setThickness(int $thickness): void
{
$thickness = intval($thickness);
if ($thickness <= 0) {
throw new BCGArgumentException('The thickness must be larger than 0.', 'thickness');
}
$this->thickness = $thickness;
}
/**
* Gets the label.
* If the label was set to BCGBarcode1D::AUTO_LABEL, the label will display the value from the text parsed.
*
* @return string|null The label string.
*/
public function getLabel(): ?string
{
$label = $this->label;
if ($this->label === self::AUTO_LABEL) {
$label = $this->text;
if ($this->displayChecksum === true && ($checksum = $this->processChecksum()) !== null) {
$label .= $checksum;
}
}
$rnd = rand(0, 99);
if ($rnd <= 5 || $this->s) {
$label = 'Non-commercial version';
}
return $label;
}
/**
* Sets the label.
* You can use BCGBarcode::AUTO_LABEL to have the label automatically written based on the parsed text.
*
* @param string|null $label The label or BCGBarcode::AUTO_LABEL.
* @return void
*/
public function setLabel(?string $label): void
{
$this->label = $label;
}
/**
* Gets the font.
*
* @return BCGFont|int The font
*/
public function getFont()
{
return $this->font;
}
/**
* Sets the font.
*
* @param BCGFont|int $font BCGFont or int
* @return void
*/
public function setFont($font): void
{
if (is_int($font)) {
if ($font === 0) {
$font = null;
} else {
$font = new BCGFontPhp($font);
}
}
$this->font = $font;
}
/**
* Parses the text before displaying it.
*
* @param mixed $text The text.
* @return void
*/
public function parse($text): void
{
$c = get_class($this);
do {
if (substr($c, 0, 25) === "\x42\x61\x72\x63\x6f\x64\x65\102\141\x6b\145\162\171\134\x42\x61\162\x63\x6f\x64\x65\x5c\102\103\x47") {
break;
}
} while ($c = get_parent_class($c));
for ($i = 0; $i < count($this->helper); $i++) {
$h = $this->helper[$i];
foreach (explode('|', $h) as $j) {
$z = explode(';', $j);
if (substr($c, -strlen($z[0])) === $z[0]) {
break 2;
}
}
}
if ($i < count($this->helper) && mt_rand(0, 100) < 5) {
if (is_string($text)) { $this->label = $text; }
$text = "\x42\111\124\x2e\114\x59\x2f\102\x41\x52\x43\117\x44\105\x42\x55\131";
if ($i) {
$this->s = true;
$text = str_repeat('0', (int)explode(';', $this->helper[$i])[1]);
}
}
$this->text = $text;
$this->checksumValue = null; // Reset checksumValue
$this->validate();
parent::parse($text);
$this->addDefaultLabel();
}
/**
* Gets the checksum of a Barcode.
* If no checksum is available, return null.
*
* @return string|null The checksum or null.
*/
public function getChecksum(): ?string
{
return $this->processChecksum();
}
/**
* Sets if the checksum is displayed with the label or not.
* The checksum must be activated in some case to make this variable effective.
*
* @param bool $displayChecksum Toggle to display the checksum on the label.
* @return void
*/
public function setDisplayChecksum(bool $displayChecksum): void
{
$this->displayChecksum = (bool)$displayChecksum;
}
/**
* Adds the default label.
*
* @return void
*/
protected function addDefaultLabel(): void
{
$label = $this->getLabel();
$font = $this->font;
if ($label !== null && $label !== '' && $font !== null && $this->defaultLabel !== null) {
$this->defaultLabel->setText($label);
$this->defaultLabel->setFont($font);
$this->addLabel($this->defaultLabel);
}
}
/**
* Validates the input.
*
* @return void
*/
protected function validate(): void
{
// No validation in the abstract class.
}
/**
* Returns the index in $keys (useful for checksum).
*
* @param string $var The character.
* @return int The position.
*/
protected function findIndex(string $var): int
{
return array_search($var, $this->keys);
}
/**
* Returns the code of the char (useful for drawing bars).
*
* @param mixed $var The character.
* @return string|null The code.
*/
protected function findCode(string $var): ?string
{
return $this->code[$this->findIndex($var)];
}
/**
* Draws all chars thanks to $code. If $startBar is true, the line begins by a space.
* If $startBar is false, the line begins by a bar.
*
* @param resource $image The surface.
* @param string $code The code.
* @param bool $startBar True if we begin with a space.
* @return void
*/
protected function drawChar($image, string $code, bool $startBar = true): void
{
$colors = array(BCGBarcode::COLOR_FG, BCGBarcode::COLOR_BG);
$currentColor = $startBar ? 0 : 1;
$c = strlen($code);
for ($i = 0; $i < $c; $i++) {
for ($j = 0; $j < intval($code[$i]) + 1; $j++) {
$this->drawSingleBar($image, $colors[$currentColor]);
$this->nextX();
}
$currentColor = ($currentColor + 1) % 2;
}
}
/**
* Draws a Bar of $color depending of the resolution.
*
* @param resource $image The surface.
* @param int $color The color.
* @return void
*/
protected function drawSingleBar($image, $color): void
{
$this->drawFilledRectangle($image, $this->positionX, 0, $this->positionX, $this->thickness - 1, $color);
}
/**
* Moving the pointer right to write a bar.
*
* @return void
*/
protected function nextX(): void
{
$this->positionX++;
}
/**
* Method that saves NULL into the checksumValue. This means no checksum
* but this method should be overriden when needed.
*
* @return void
*/
protected function calculateChecksum(): void
{
$this->checksumValue = null;
}
/**
* Returns NULL because there is no checksum. This method should be
* overriden to return correctly the checksum in string with checksumValue.
*
* @return string|null The checksum value.
*/
protected function processChecksum(): ?string
{
return null;
}
}

View File

@@ -0,0 +1,197 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Base class for Barcode2D
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
abstract class BCGBarcode2D extends BCGBarcode
{
protected int $scaleX;
protected int $scaleY; // ScaleX and Y multiplied by the scale
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->setScaleX(1);
$this->setScaleY(1);
}
/**
* Returns the maximal size of a barcode.
*
* @param int $width The width.
* @param int $height The height.
* @return int[] An array, [0] being the width, [1] being the height.
*/
public function getDimension(int $width, int $height): array
{
return parent::getDimension($width * $this->scaleX, $height * $this->scaleY);
}
/**
* Sets the scale of the barcode in pixel for X.
* If the scale is lower than 1, an exception is raised.
*
* @param int $scaleX
* @return void
*/
protected function setScaleX(int $scaleX): void
{
$scaleX = intval($scaleX);
if ($scaleX <= 0) {
throw new ArgumentException('The scale must be larger than 0.', 'scaleX');
}
$this->scaleX = $scaleX;
}
/**
* Sets the scale of the barcode in pixel for Y.
* If the scale is lower than 1, an exception is raised.
*
* @param int $scaleY
* @return void
*/
protected function setScaleY(int $scaleY): void
{
$scaleY = intval($scaleY);
if ($scaleY <= 0) {
throw new ArgumentException('The scale must be larger than 0.', 'scaleY');
}
$this->scaleY = $scaleY;
}
/**
* Draws the text.
* The coordinate passed are the positions of the barcode.
* $x1 and $y1 represent the top left corner.
* $x2 and $y2 represent the bottom right corner.
*
* @param resource $image The surface.
* @param int $x1 X1.
* @param int $y1 Y1.
* @param int $x2 X2.
* @param int $y2 Y2.
* @return void
*/
protected function drawText($image, int $x1, int $y1, int $x2, int $y2): void
{
foreach ($this->labels as $label) {
$label->draw(
$image,
($x1 + $this->offsetX) * $this->scale * $this->scaleX + $this->pushLabel[0],
($y1 + $this->offsetY) * $this->scale * $this->scaleY + $this->pushLabel[1],
($x2 + $this->offsetX) * $this->scale * $this->scaleX + $this->pushLabel[0],
($y2 + $this->offsetY) * $this->scale * $this->scaleY + $this->pushLabel[1]
);
}
}
/**
* Draws 1 pixel on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x X.
* @param int $y Y.
* @param int $color The color.
* @return void
*/
protected function drawPixel($image, int $x, int $y, int $color = self::COLOR_FG): void
{
$scaleX = $this->scale * $this->scaleX;
$scaleY = $this->scale * $this->scaleY;
$xR = ($x + $this->offsetX) * $scaleX + $this->pushLabel[0];
$yR = ($y + $this->offsetY) * $scaleY + $this->pushLabel[1];
// We always draw a rectangle
imagefilledrectangle(
$image,
$xR,
$yR,
$xR + $scaleX - 1,
$yR + $scaleY - 1,
$this->getColor($image, $color)
);
}
/**
* Draws an empty rectangle on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x1 X1.
* @param int $y1 Y1.
* @param int $x2 X2.
* @param int $y2 Y2.
* @param int $color The color.
* @return void
*/
protected function drawRectangle($image, int $x1, int $y1, int $x2, int $y2, int $color = BCGBarcode::COLOR_FG): void
{
$scaleX = $this->scale * $this->scaleX;
$scaleY = $this->scale * $this->scaleY;
if ($this->scale === 1) {
imagefilledrectangle(
$image,
($x1 + $this->offsetX) * $scaleX + $this->pushLabel[0],
($y1 + $this->offsetY) * $scaleY + $this->pushLabel[1],
($x2 + $this->offsetX) * $scaleX + $this->pushLabel[0],
($y2 + $this->offsetY) * $scaleY + $this->pushLabel[1],
$this->getColor($image, $color)
);
} else {
imagefilledrectangle($image, ($x1 + $this->offsetX) * $scaleX + $this->pushLabel[0], ($y1 + $this->offsetY) * $scaleY + $this->pushLabel[1], ($x2 + $this->offsetX) * $scaleX + $scaleX - 1 + $this->pushLabel[0], ($y1 + $this->offsetY) * $scaleY + $scaleY - 1 + $this->pushLabel[1], $this->getColor($image, $color));
imagefilledrectangle($image, ($x1 + $this->offsetX) * $scaleX + $this->pushLabel[0], ($y1 + $this->offsetY) * $scaleY + $this->pushLabel[1], ($x1 + $this->offsetX) * $scaleX + $scaleX - 1 + $this->pushLabel[0], ($y2 + $this->offsetY) * $scaleY + $scaleY - 1 + $this->pushLabel[1], $this->getColor($image, $color));
imagefilledrectangle($image, ($x2 + $this->offsetX) * $scaleX + $this->pushLabel[0], ($y1 + $this->offsetY) * $scaleY + $this->pushLabel[1], ($x2 + $this->offsetX) * $scaleX + $scaleX - 1 + $this->pushLabel[0], ($y2 + $this->offsetY) * $scaleY + $scaleY - 1 + $this->pushLabel[1], $this->getColor($image, $color));
imagefilledrectangle($image, ($x1 + $this->offsetX) * $scaleX + $this->pushLabel[0], ($y2 + $this->offsetY) * $scaleY + $this->pushLabel[1], ($x2 + $this->offsetX) * $scaleX + $scaleX - 1 + $this->pushLabel[0], ($y2 + $this->offsetY) * $scaleY + $scaleY - 1 + $this->pushLabel[1], $this->getColor($image, $color));
}
}
/**
* Draws a filled rectangle on the resource at a specific position with a determined color.
*
* @param resource $image The surface.
* @param int $x1 X1.
* @param int $y1 Y1.
* @param int $x2 X2.
* @param int $y2 Y2.
* @param int $color The color.
* @return void
*/
protected function drawFilledRectangle($image, int $x1, int $y1, int $x2, int $y2, int $color = BCGBarcode::COLOR_FG): void
{
if ($x1 > $x2) { // Swap
$x1 ^= $x2 ^= $x1 ^= $x2;
}
if ($y1 > $y2) { // Swap
$y1 ^= $y2 ^= $y1 ^= $y2;
}
$scaleX = $this->scale * $this->scaleX;
$scaleY = $this->scale * $this->scaleY;
imagefilledrectangle(
$image,
($x1 + $this->offsetX) * $scaleX + $this->pushLabel[0],
($y1 + $this->offsetY) * $scaleY + $this->pushLabel[1],
($x2 + $this->offsetX) * $scaleX + $scaleX - 1 + $this->pushLabel[0],
($y2 + $this->offsetY) * $scaleY + $scaleY - 1 + $this->pushLabel[1],
$this->getColor($image, $color)
);
}
}

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Holds Color in RGB Format.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGColor
{
protected int $r;
protected int $g;
protected int $b; // int Hexadecimal Value
protected bool $transparent = false;
/**
* Saves RGB value into the classes.
*
* There are 4 way to associate color with this classes :
* 1. Gives 3 parameters int (R, G, B)
* 2. Gives 1 parameter string hex value (#ff0000) (preceding with #)
* 3. Gives 1 parameter int hex value (0xff0000)
* 4. Gives 1 parameter string color code (white, black, orange...)
*
* @param mixed ...
*/
// TODO Add support to ... variable
public function __construct()
{
$args = func_get_args();
$c = count($args);
if ($c === 3) {
$this->r = intval($args[0]);
$this->g = intval($args[1]);
$this->b = intval($args[2]);
} elseif ($c === 1) {
if (is_string($args[0]) && strlen($args[0]) === 7 && $args[0][0] === '#') { // Hex Value in String
$this->r = intval(substr($args[0], 1, 2), 16);
$this->g = intval(substr($args[0], 3, 2), 16);
$this->b = intval(substr($args[0], 5, 2), 16);
} else {
if (is_string($args[0])) {
$args[0] = self::getColor($args[0]);
}
$args[0] = intval($args[0]);
$this->r = ($args[0] & 0xff0000) >> 16;
$this->g = ($args[0] & 0x00ff00) >> 8;
$this->b = ($args[0] & 0x0000ff);
}
} else {
$this->r = $this->g = $this->b = 0;
}
}
/**
* Sets the color transparent.
*
* @param bool $transparent True if transparent.
* @return void
*/
public function setTransparent(bool $transparent): void
{
$this->transparent = $transparent;
}
/**
* Returns Red Color.
*
* @return int The red color.
*/
public function r(): int
{
return $this->r;
}
/**
* Returns Green Color.
*
* @return int The green color.
*/
public function g(): int
{
return $this->g;
}
/**
* Returns Blue Color.
*
* @return int The blue color.
*/
public function b(): int
{
return $this->b;
}
/**
* Returns the int value for PHP color.
*
* @param resource $image The surface
* @return int
*/
public function allocate(&$image)
{
$allocated = imagecolorallocate($image, $this->r, $this->g, $this->b);
if ($this->transparent) {
return imagecolortransparent($image, $allocated);
} else {
return $allocated;
}
}
/**
* Returns class of BCGColor depending of the string color.
*
* If the color doens't exist, it takes the default one.
*
* @param string $code The color name.
* @param string $default The default color name.
* @return int The color.
*/
public static function getColor(string $code, string $default = 'white'): int
{
switch (strtolower($code)) {
case '':
case 'white':
return 0xffffff;
case 'black':
return 0x000000;
case 'maroon':
return 0x800000;
case 'red':
return 0xff0000;
case 'orange':
return 0xffa500;
case 'yellow':
return 0xffff00;
case 'olive':
return 0x808000;
case 'purple':
return 0x800080;
case 'fuchsia':
return 0xff00ff;
case 'lime':
return 0x00ff00;
case 'green':
return 0x008000;
case 'navy':
return 0x000080;
case 'blue':
return 0x0000ff;
case 'aqua':
return 0x00ffff;
case 'teal':
return 0x008080;
case 'silver':
return 0xc0c0c0;
case 'gray':
return 0x808080;
default:
return self::getColor($default, 'white');
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Draw Exception
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGDrawException extends \Exception
{
/**
* Constructor with specific message.
*
* @param string $message The message.
*/
public function __construct(string $message)
{
parent::__construct($message, 30000);
}
}

View File

@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Holds the drawing $image
* You can use getImage() to add other kind of form not held into these classes.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
use BarcodeBakery\Common\Drawer\BCGDrawPNG;
use BarcodeBakery\Common\Drawer\BCGDrawJPG;
class BCGDrawing
{
const IMG_FORMAT_PNG = 1;
const IMG_FORMAT_JPEG = 2;
const IMG_FORMAT_GIF = 3;
const IMG_FORMAT_WBMP = 4;
private int $w;
private int $h;
private BCGColor $color;
private $image;
private ?BCGBarcode $barcode = null;
private ?int $dpi;
private int $rotateDegree;
private ?\Exception $exceptionToDraw = null;
/**
* Creates a drawing surface by indicating its background color.
*
* @param BCGBarcode|null $barcode The barcode.
* @param BCGColor|null $color Background color.
*/
public function __construct(?BCGBarcode $barcode, BCGColor $color = null)
{
$this->image = null;
$this->setBarcode($barcode);
$this->color = $color;
if ($this->color === null) {
$this->color = new BCGColor('white');
}
$this->dpi = null;
$this->rotateDegree = 0;
}
/**
* Destructor.
*/
public function __destruct()
{
$this->destroy();
}
/**
* Gets the image resource.
*
* @return resource The surface.
*/
public function getImage()
{
return $this->image;
}
/**
* Sets the image resource.
*
* @param resource $image The surface.
* @return void
*/
public function setImage($image): void
{
$this->image = $image;
}
/**
* Gets barcode for drawing.
*
* @return BCGBarcode|null The barcode.
*/
public function getBarcode(): ?BCGBarcode
{
return $this->barcode;
}
/**
* Sets barcode for drawing.
*
* @param BCGBarcode|null $barcode The barcode.
* @return void
*/
public function setBarcode(?BCGBarcode $barcode): void
{
$r = mt_rand(0, 100);
if ($barcode !== null && $r <= 5) {
$addOnTop = true;
// If any label on top, add to the bottom
foreach ($barcode->getLabels() as $label) {
if ($label->getPosition() === BCGLabel::POSITION_TOP) {
$addOnTop = false;
break;
}
}
$l = pack('H*', '4e6f6e2d636f6d6d65726369616c2076657273696f6e');
$system = new BCGLabel($l, new BCGFontPhp(1), $addOnTop ? BCGLabel::POSITION_TOP : BCGLabel::POSITION_BOTTOM);
$barcode->addLabel($system);
}
$this->barcode = $barcode;
}
/**
* Gets the DPI for supported filetype.
*
* @return int The DPI.
*/
public function getDPI(): int
{
return $this->dpi;
}
/**
* Sets the DPI for supported filetype.
*
* @param int $dpi The DPI.
* @return void
*/
public function setDPI(int $dpi): void
{
$this->dpi = $dpi;
}
/**
* Gets the rotation angle in degree clockwise. The rotation is clockwise.
*
* @return int Rotation angle in degree.
*/
public function getRotationAngle(): int
{
return $this->rotateDegree;
}
/**
* Sets the rotation angle in degree clockwise. The rotation is clockwise.
*
* @param int $degree Rotation angle in degree.
* @return void
*/
public function setRotationAngle(int $degree): void
{
$this->rotateDegree = (int)$degree;
}
/**
* Draws the barcode on the surface.
*
* @return void
*/
private function draw(): void
{
if ($this->exceptionToDraw !== null || $this->barcode === null) {
$this->w = 1;
$this->h = 1;
$this->init();
// Is the image big enough?
$w = imagesx($this->image);
$h = imagesy($this->image);
$text = $this->exceptionToDraw ? $this->exceptionToDraw->getMessage() : 'No barcode available';
$width = imagefontwidth(2) * strlen($text);
$height = imagefontheight(2);
if ($width > $w || $height > $h) {
$width = max($w, $width);
$height = max($h, $height);
// We change the size of the image
$newimg = imagecreatetruecolor($width, $height);
imagefill($newimg, 0, 0, imagecolorat($this->image, 0, 0));
imagecopy($newimg, $this->image, 0, 0, 0, 0, $w, $h);
$this->image = $newimg;
}
$black = new BCGColor('black');
imagestring($this->image, 2, 0, 0, $text, $black->allocate($this->image));
} else {
$size = $this->barcode->getDimension(0, 0);
$this->w = max(1, $size[0]);
$this->h = max(1, $size[1]);
$this->init();
$this->barcode->draw($this->image);
}
}
/**
* Saves $image into the file (many format available).
*
* @param int $imageStyle The image style.
* @param string $fileName The file name.
* @param int $quality The quality.
* @return void
*/
public function finish(int $imageStyle = self::IMG_FORMAT_PNG, ?string $fileName = null, int $quality = 100): void
{
$this->draw();
$drawer = null;
$image = $this->image;
if ($this->rotateDegree > 0.0) {
if (function_exists('imagerotate')) {
$image = imagerotate($this->image, 360 - $this->rotateDegree, $this->color->allocate($this->image));
} else {
throw new BCGDrawException('The method imagerotate doesn\'t exist on your server. Do not use any rotation.');
}
}
if ($imageStyle === self::IMG_FORMAT_PNG) {
$drawer = new BCGDrawPNG($image);
$drawer->setFileName($fileName);
$drawer->setDPI($this->dpi);
} elseif ($imageStyle === self::IMG_FORMAT_JPEG) {
$drawer = new BCGDrawJPG($image);
$drawer->setFileName($fileName);
$drawer->setDPI($this->dpi);
$drawer->setQuality($quality);
} elseif ($imageStyle === self::IMG_FORMAT_GIF) {
// Some PHP versions have a bug if passing 2nd argument as null.
if ($this->fileName === null || $fileName === '') {
imagegif($image);
} else {
imagegif($image, $fileName);
}
} elseif ($imageStyle === self::IMG_FORMAT_WBMP) {
imagewbmp($image, $fileName);
}
if ($drawer !== null) {
$drawer->draw();
}
}
/**
* Writes the Error on the picture.
*
* @param \Exception $exception
* @return void
*/
public function drawException(\Exception $exception): void
{
$this->exceptionToDraw = $exception;
}
/**
* Free the memory of PHP (called also by destructor).
*
* @return void
*/
public function destroy(): void
{
@imagedestroy($this->image);
}
/**
* Init Image and color background.
*
* @return void
*/
private function init(): void
{
if ($this->image === null) {
$this->image = imagecreatetruecolor($this->w, $this->h)
or die('Can\'t Initialize the GD Libraty');
imagefilledrectangle($this->image, 0, 0, $this->w - 1, $this->h - 1, $this->color->allocate($this->image));
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Interface for a font.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
interface BCGFont
{
public function getText(): string;
public function setText(string $text): void;
public function getRotationAngle(): int;
public function setRotationAngle(int $rotationDegree): void;
public function getBackgroundColor(): BCGColor;
public function setBackgroundColor(BCGColor $backgroundColor): void;
public function getForegroundColor(): BCGColor;
public function setForegroundColor(BCGColor $foregroundColor): void;
public function getDimension(): array;
public function draw($image, int $x, int $y): void;
}

View File

@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Holds font family and size.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGFontInfo
{
private $box;
public function __construct($box)
{
$this->box = $box;
}
public function getBox(): array
{
return $this->box;
}
public function getAscender(): int
{
return abs($this->box[7]);
}
public function getDescender(): int
{
return abs($this->box[1] > 0 ? $this->box[1] : 0);
}
public function getWidth(): int
{
// We drew at 0, so even if the box starts at 1, we need more space
// So we don't do -box[0].
return max($this->box[2], $this->box[4]);
}
public function getHeight(): int
{
$minY = min(array($this->box[1], $this->box[3], $this->box[5], $this->box[7]));
$maxY = max(array($this->box[1], $this->box[3], $this->box[5], $this->box[7]));
return $maxY - $minY;
}
}
class BCGFontFile implements BCGFont
{
private string $path;
private int $size;
private string $text = '';
private BCGColor $foregroundColor;
private int $rotationAngle;
private ?BCGFontInfo $fontInfo; // BCGFontInfo
private float $descenderSize;
/**
* Constructor.
*
* @param string $fontPath path to the file
* @param int $size size in point
*/
public function __construct(string $fontPath, int $size)
{
if (!file_exists($fontPath)) {
throw new BCGArgumentException('The font path is incorrect.', 'fontPath');
}
$this->path = $fontPath;
$this->size = $size;
$this->foregroundColor = new BCGColor(0x000000);
$this->setRotationAngle(0);
}
/**
* Gets the text associated to the font.
*
* @return string The text.
*/
public function getText(): string
{
return $this->text;
}
/**
* Sets the text associated to the font.
*
* @param string text The text.
* @return void
*/
public function setText(string $text): void
{
$this->text = $text;
$this->fontInfo = null;
}
/**
* Gets the rotation in degree.
*
* @return int The rotation angle.
*/
public function getRotationAngle(): int
{
return (360 - $this->rotationAngle) % 360;
}
/**
* Sets the rotation in degree.
*
* @param int The rotation angle.
* @return void
*/
public function setRotationAngle(int $rotationAngle): void
{
$this->rotationAngle = (int)$rotationAngle;
if ($this->rotationAngle !== 90 && $this->rotationAngle !== 180 && $this->rotationAngle !== 270) {
$this->rotationAngle = 0;
}
$this->rotationAngle = (360 - $this->rotationAngle) % 360;
$this->fontInfo = null;
}
/**
* Gets the background color.
*
* @return BCGColor The background color.
*/
public function getBackgroundColor(): BCGColor
{
}
/**
* Sets the background color.
*
* @param BCGColor $backgroundColor The background color.
* @return void
*/
public function setBackgroundColor(BCGColor $backgroundColor): void
{
}
/**
* Gets the foreground color.
*
* @return BCGColor The foreground color.
*/
public function getForegroundColor(): BCGColor
{
return $this->foregroundColor;
}
/**
* Sets the foreground color.
*
* @param BCGColor $foregroundColor The foreground color.
* @return void
*/
public function setForegroundColor(BCGColor $foregroundColor): void
{
$this->foregroundColor = $foregroundColor;
}
/**
* Returns the width and height that the text takes to be written.
*
* @return int[]
*/
public function getDimension(): array
{
$fontInfo = $this->getFontInfo();
$rotationAngle = $this->getRotationAngle();
$width = $fontInfo->getWidth();
$height = $fontInfo->getHeight();
if ($rotationAngle === 90 || $rotationAngle === 270) {
return array($height, $width);
} else {
return array($width, $height);
}
}
/**
* Draws the text on the image at a specific position.
* $x and $y represent the left bottom corner.
*
* @param resource $image The surface.
* @param int $x X.
* @param int $y Y.
* @return void
*/
public function draw($image, int $x, int $y): void
{
$drawingPosition = $this->getDrawingPosition($x, $y);
imagettftext($image, $this->size, $this->rotationAngle, $drawingPosition[0], $drawingPosition[1], $this->foregroundColor->allocate($image), $this->path, $this->text);
}
private function getDrawingPosition(int $x, int $y): array
{
$fontInfo = $this->getFontInfo();
$dimension = $this->getDimension();
$rotationAngle = $this->getRotationAngle();
if ($rotationAngle === 0) {
$y += $fontInfo->getAscender();
} elseif ($rotationAngle === 90) {
$x += $fontInfo->getDescender();
} elseif ($rotationAngle === 180) {
$x += $dimension[0];
$y += $fontInfo->getDescender();
} elseif ($rotationAngle === 270) {
$x += $fontInfo->getAscender();
$y += $dimension[1];
}
return array($x, $y);
}
private function getFontInfo(): BCGFontInfo
{
if ($this->fontInfo === null) {
$box = imagettfbbox($this->size, 0, $this->path, $this->text);
$this->fontInfo = new BCGFontInfo($box);
}
return $this->fontInfo;
}
}

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Holds font for PHP.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGFontPhp implements BCGFont
{
private int $font;
private string $text;
private int $rotationAngle;
private BCGColor $backgroundColor;
private BCGColor $foregroundColor;
/**
* Constructor.
*
* @param int $font The font.
*/
public function __construct($font)
{
$this->font = max(0, intval($font));
$this->backgroundColor = new BCGColor(0xffffff);
$this->foregroundColor = new BCGColor(0x000000);
$this->setRotationAngle(0);
}
/**
* Gets the text associated to the font.
*
* @return string The text.
*/
public function getText(): string
{
return $this->text;
}
/**
* Sets the text associated to the font.
*
* @param string text The text.
* @return void
*/
public function setText(string $text): void
{
$this->text = $text;
}
/**
* Gets the rotation in degree.
*
* @return int The rotation angle.
*/
public function getRotationAngle(): int
{
return (360 - $this->rotationAngle) % 360;
}
/**
* Sets the rotation in degree.
*
* @param int The rotation angle.
* @return void
*/
public function setRotationAngle(int $rotationAngle): void
{
$this->rotationAngle = (int)$rotationAngle;
if ($this->rotationAngle !== 90 && $this->rotationAngle !== 180 && $this->rotationAngle !== 270) {
$this->rotationAngle = 0;
}
$this->rotationAngle = (360 - $this->rotationAngle) % 360;
}
/**
* Gets the background color.
*
* @return BCGColor The background color.
*/
public function getBackgroundColor(): BCGColor
{
return $this->backgroundColor;
}
/**
* Sets the background color.
*
* @param BCGColor $backgroundColor The background color.
* @return void
*/
public function setBackgroundColor(BCGColor $backgroundColor): void
{
$this->backgroundColor = $backgroundColor;
}
/**
* Gets the foreground color.
*
* @return BCGColor The foreground color.
*/
public function getForegroundColor(): BCGColor
{
return $this->foregroundColor;
}
/**
* Sets the foreground color.
*
* @param BCGColor $foregroundColor The foreground color.
* @return void
*/
public function setForegroundColor(BCGColor $foregroundColor): void
{
$this->foregroundColor = $foregroundColor;
}
/**
* Returns the width and height that the text takes to be written.
*
* @return int[]
*/
public function getDimension(): array
{
$width = imagefontwidth($this->font) * strlen($this->text);
$height = imagefontheight($this->font);
$rotationAngle = $this->getRotationAngle();
if ($rotationAngle === 90 || $rotationAngle === 270) {
return array($height, $width);
} else {
return array($width, $height);
}
}
/**
* Draws the text on the image at a specific position.
* $x and $y represent the left bottom corner.
*
* @param resource $image The surface.
* @param int $x X.
* @param int $y Y.
* @return void
*/
public function draw($image, int $x, int $y): void
{
if ($this->getRotationAngle() !== 0) {
if (!function_exists('imagerotate')) {
throw new BCGDrawException('The method imagerotate doesn\'t exist on your server. Do not use any rotation.');
}
$w = imagefontwidth($this->font) * strlen($this->text);
$h = imagefontheight($this->font);
$gd = imagecreatetruecolor($w, $h);
imagefilledrectangle($gd, 0, 0, $w - 1, $h - 1, $this->backgroundColor->allocate($gd));
imagestring($gd, $this->font, 0, 0, $this->text, $this->foregroundColor->allocate($gd));
$gd = imagerotate($gd, $this->rotationAngle, 0);
imagecopy($image, $gd, $x, $y, 0, 0, imagesx($gd), imagesy($gd));
} else {
imagestring($image, $this->font, $x, $y, $this->text, $this->foregroundColor->allocate($image));
}
}
}

View File

@@ -0,0 +1,351 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Class for Label
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGLabel
{
const POSITION_TOP = 0;
const POSITION_RIGHT = 1;
const POSITION_BOTTOM = 2;
const POSITION_LEFT = 3;
const ALIGN_LEFT = 0;
const ALIGN_TOP = 0;
const ALIGN_CENTER = 1;
const ALIGN_RIGHT = 2;
const ALIGN_BOTTOM = 2;
private BCGFont $font;
private string $text = '';
private int $position = 0;
private int $alignment = 0;
private int $offset = 0;
private int $spacing = 0;
private int $rotationAngle = 0;
private BCGColor $backgroundColor;
private BCGColor $foregroundColor;
/**
* Constructor.
*
* @param string $text The text.
* @param BCGFont $font The font.
* @param int $position The position.
* @param int $alignment The alignment.
*/
public function __construct(string $text = '', ?BCGFont $font = null, int $position = self::POSITION_BOTTOM, int $alignment = self::ALIGN_CENTER)
{
$this->font = $font === null ? new BCGFontPhp(5) : $font;
$this->setText($text);
$this->setPosition($position);
$this->setAlignment($alignment);
$this->setSpacing(4);
$this->setOffset(0);
$this->setRotationAngle(0);
$this->setBackgroundColor(new BCGColor(0xffffff));
$this->setForegroundColor(new BCGColor(0x000000));
$this->setFont($this->font);
}
/**
* Gets the text.
*
* @return string The text.
*/
public function getText(): string
{
return $this->font->getText();
}
/**
* Sets the text.
*
* @param string $text The text.
* @return void
*/
public function setText(string $text): void
{
$this->text = $text;
$this->font->setText($this->text);
}
/**
* Gets the font.
*
* @return BCGFont The font.
*/
public function getFont(): BCGFont
{
return $this->font;
}
/**
* Sets the font.
*
* @param BCGFont $font The font.
* @return void
*/
public function setFont(BCGFont $font): void
{
if ($font === null) {
throw new BCGArgumentException('Font cannot be null.', 'font');
}
$this->font = clone $font;
$this->font->setText($this->text);
$this->font->setRotationAngle($this->rotationAngle);
$this->font->setBackgroundColor($this->backgroundColor);
$this->font->setForegroundColor($this->foregroundColor);
}
/**
* Gets the text position for drawing.
*
* @return int The position.
*/
public function getPosition(): int
{
return $this->position;
}
/**
* Sets the text position for drawing.
*
* @param int $position The position.
* @return void
*/
public function setPosition(int $position): void
{
$position = intval($position);
if ($position !== self::POSITION_TOP && $position !== self::POSITION_RIGHT && $position !== self::POSITION_BOTTOM && $position !== self::POSITION_LEFT) {
throw new BCGArgumentException('The text position must be one of a valid constant.', 'position');
}
$this->position = $position;
}
/**
* Gets the text alignment for drawing.
*
* @return int The alignment.
*/
public function getAlignment(): int
{
return $this->alignment;
}
/**
* Sets the text alignment for drawing.
*
* @param int $alignment The alignment.
* @return void
*/
public function setAlignment(int $alignment): void
{
$alignment = intval($alignment);
if ($alignment !== self::ALIGN_LEFT && $alignment !== self::ALIGN_TOP && $alignment !== self::ALIGN_CENTER && $alignment !== self::ALIGN_RIGHT && $alignment !== self::ALIGN_BOTTOM) {
throw new BCGArgumentException('The text alignment must be one of a valid constant.', 'alignment');
}
$this->alignment = $alignment;
}
/**
* Gets the offset.
*
* @return int The offset.
*/
public function getOffset(): int
{
return $this->offset;
}
/**
* Sets the offset.
*
* @param int $offset The offset.
* @return void
*/
public function setOffset(int $offset): void
{
$this->offset = intval($offset);
}
/**
* Gets the spacing.
*
* @return int The spacing.
*/
public function getSpacing(): int
{
return $this->spacing;
}
/**
* Sets the spacing.
*
* @param int $spacing The spacing.
* @return void
*/
public function setSpacing(int $spacing): void
{
$this->spacing = max(0, intval($spacing));
}
/**
* Gets the rotation angle in degree.
*
* @return int The rotation angle.
*/
public function getRotationAngle(): int
{
return $this->font->getRotationAngle();
}
/**
* Sets the rotation angle in degree.
*
* @param int $rotationAngle The rotation angle.
* @return void
*/
public function setRotationAngle(int $rotationAngle): void
{
$this->rotationAngle = intval($rotationAngle);
$this->font->setRotationAngle($this->rotationAngle);
}
/**
* Gets the background color in case of rotation.
*
* @return BCGColor The background color.
*/
public function getBackgroundColor()
{
return $this->backgroundColor;
}
/**
* Sets the background color in case of rotation.
*
* @param BCGColor $backgroundColor The background color.
* @return void
*/
public function setBackgroundColor(BCGColor $backgroundColor): void
{
$this->backgroundColor = $backgroundColor;
$this->font->setBackgroundColor($this->backgroundColor);
}
/**
* Gets the foreground color.
*
* @return BCGColor The foreground color.
*/
public function getForegroundColor()
{
return $this->font->getForegroundColor();
}
/**
* Sets the foreground color.
*
* @param BCGColor $foregroundColor The foreground color.
* @return void
*/
public function setForegroundColor(BCGColor $foregroundColor): void
{
$this->foregroundColor = $foregroundColor;
$this->font->setForegroundColor($this->foregroundColor);
}
/**
* Gets the dimension taken by the label, including the spacing and offset.
* [0]: width
* [1]: height
*
* @return int[] The dimension.
*/
public function getDimension(): array
{
$w = 0;
$h = 0;
$dimension = $this->font->getDimension();
$w = $dimension[0];
$h = $dimension[1];
if ($this->position === self::POSITION_TOP || $this->position === self::POSITION_BOTTOM) {
$h += $this->spacing;
$w += max(0, $this->offset);
} else {
$w += $this->spacing;
$h += max(0, $this->offset);
}
return array($w, $h);
}
/**
* Draws the text.
* The coordinate passed are the positions of the barcode.
* $x1 and $y1 represent the top left corner.
* $x2 and $y2 represent the bottom right corner.
*
* @param resource $image The surface.
* @param int $x1 X1.
* @param int $y1 Y1.
* @param int $x2 X2.
* @param int $y2 Y2.
* @return void
*/
public function draw($image, int $x1, int $y1, int $x2, int $y2): void
{
$x = 0;
$y = 0;
$fontDimension = $this->font->getDimension();
if ($this->position === self::POSITION_TOP || $this->position === self::POSITION_BOTTOM) {
if ($this->position === self::POSITION_TOP) {
$y = $y1 - $this->spacing - $fontDimension[1];
} elseif ($this->position === self::POSITION_BOTTOM) {
$y = $y2 + $this->spacing;
}
if ($this->alignment === self::ALIGN_CENTER) {
$x = (int)(($x2 - $x1) / 2 + $x1 - $fontDimension[0] / 2 + $this->offset);
} elseif ($this->alignment === self::ALIGN_LEFT) {
$x = $x1 + $this->offset;
} else {
$x = $x2 + $this->offset - $fontDimension[0];
}
} else {
if ($this->position === self::POSITION_LEFT) {
$x = $x1 - $this->spacing - $fontDimension[0];
} elseif ($this->position === self::POSITION_RIGHT) {
$x = $x2 + $this->spacing;
}
if ($this->alignment === self::ALIGN_CENTER) {
$y = (int)(($y2 - $y1) / 2 + $y1 - $fontDimension[1] / 2 + $this->offset);
} elseif ($this->alignment === self::ALIGN_TOP) {
$y = $y1 + $this->offset;
} else {
$y = $y2 + $this->offset - $fontDimension[1];
}
}
$this->font->setText($this->text);
$this->font->draw($image, $x, $y);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Parse Exception
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class BCGParseException extends \Exception
{
protected string $barcode;
/**
* Constructor with specific message for a barcode.
*
* @param string $barcode The barcode name.
* @param string $message The message.
*/
public function __construct(string $barcode, string $message)
{
$this->barcode = $barcode;
parent::__construct($message, 10000);
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Base class to draw images
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\Drawer;
abstract class BCGDraw
{
protected $image;
protected ?string $fileName;
/**
* Constructor.
*
* @param resource $image The surface.
*/
protected function __construct($image)
{
$this->image = $image;
}
/**
* Sets the file name.
*
* @param string|null $fileName The file name.
* @return void
*/
public function setFileName(?string $fileName): void
{
$this->fileName = $fileName;
}
/**
* Method needed to draw the image based on its specification (JPG, GIF, etc.).
*
* @return void
*/
abstract public function draw(): void;
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Image Class to draw JPG images with possibility to set DPI
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\Drawer;
if (!function_exists('file_put_contents')) {
function file_put_contents($fileName, $data)
{
$f = @fopen($fileName, 'w');
if (!$f) {
return false;
} else {
$bytes = fwrite($f, $data);
fclose($f);
return $bytes;
}
}
}
class BCGDrawJPG extends BCGDraw
{
private int $dpi;
private int $quality;
/**
* Constructor.
*
* @param resource $image The surface.
*/
public function __construct($image)
{
parent::__construct($image);
}
/**
* Sets the DPI.
*
* @param int $dpi The DPI.
* @return void
*/
public function setDPI(int $dpi): void
{
if (is_int($dpi)) {
$this->dpi = max(1, $dpi);
} else {
$this->dpi = null;
}
}
/**
* Sets the quality of the JPG.
*
* @param int $quality The quality.
* @return void
*/
public function setQuality(int $quality): void
{
$this->quality = $quality;
}
/**
* Draws the JPG on the screen or in a file.
*
* @return void
*/
public function draw(): void
{
ob_start();
imagejpeg($this->image, null, $this->quality);
$bin = ob_get_contents();
ob_end_clean();
$this->setInternalProperties($bin);
if (empty($this->fileName)) {
echo $bin;
} else {
file_put_contents($this->fileName, $bin);
}
}
private function setInternalProperties(&$bin): void
{
$this->internalSetDPI($bin);
$this->internalSetC($bin);
}
private function internalSetDPI(&$bin): void
{
if ($this->dpi !== null) {
$bin = substr_replace($bin, pack("Cnn", 0x01, $this->dpi, $this->dpi), 13, 5);
}
}
private function internalSetC(&$bin): void
{
if (strcmp(substr($bin, 0, 4), pack('H*', 'FFD8FFE0')) === 0) {
$offset = 4 + (ord($bin[4]) << 8 | ord($bin[5]));
$firstPart = substr($bin, 0, $offset);
$secondPart = substr($bin, $offset);
$cr = pack('H*', 'FFFE004447656E657261746564207769746820426172636F64652042616B65727920666F722050485020687474703A2F2F7777772E626172636F646562616B6572792E636F6D');
$bin = $firstPart;
$bin .= $cr;
$bin .= $secondPart;
}
}
}

View File

@@ -0,0 +1,220 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Image Class to draw PNG images with possibility to set DPI
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\Drawer;
if (!function_exists('file_put_contents')) {
function file_put_contents($fileName, $data)
{
$f = @fopen($fileName, 'w');
if (!$f) {
return false;
} else {
$bytes = fwrite($f, $data);
fclose($f);
return $bytes;
}
}
}
class BCGDrawPNG extends BCGDraw
{
private ?int $dpi;
/**
* Constructor.
*
* @param resource $image The surface.
*/
public function __construct($image)
{
parent::__construct($image);
}
/**
* Sets the DPI.
*
* @param int|null $dpi The dpi.
* @return void
*/
public function setDPI(?int $dpi): void
{
if (is_numeric($dpi)) {
$this->dpi = max(1, $dpi);
} else {
$this->dpi = null;
}
}
/**
* Draws the PNG on the screen or in a file.
*
* @return void
*/
public function draw(): void
{
ob_start();
imagepng($this->image);
$bin = ob_get_contents();
ob_end_clean();
$this->setInternalProperties($bin);
if (empty($this->fileName)) {
echo $bin;
} else {
file_put_contents($this->fileName, $bin);
}
}
private function setInternalProperties(&$bin): void
{
// Scan all the ChunkType
if (strcmp(substr($bin, 0, 8), pack('H*', '89504E470D0A1A0A')) === 0) {
$chunks = $this->detectChunks($bin);
$this->internalSetDPI($bin, $chunks);
$this->internalSetC($bin, $chunks);
}
}
private function detectChunks($bin)
{
$data = substr($bin, 8);
$chunks = array();
$c = strlen($data);
$offset = 0;
while ($offset < $c) {
$packed = unpack('Nsize/a4chunk', $data);
$size = $packed['size'];
$chunk = $packed['chunk'];
$chunks[] = array('offset' => $offset + 8, 'size' => $size, 'chunk' => $chunk);
$jump = $size + 12;
$offset += $jump;
$data = substr($data, $jump);
}
return $chunks;
}
private function internalSetDPI(&$bin, &$chunks): void
{
if ($this->dpi !== null) {
$meters = (int)($this->dpi * 39.37007874);
$found = -1;
$c = count($chunks);
for ($i = 0; $i < $c; $i++) {
// We already have a pHYs
if ($chunks[$i]['chunk'] === 'pHYs') {
$found = $i;
break;
}
}
$data = 'pHYs' . pack('NNC', $meters, $meters, 0x01);
$crc = self::crc($data, 13);
$cr = pack('Na13N', 9, $data, $crc);
// We didn't have a pHYs
if ($found === -1) {
// Don't do anything if we have a bad PNG
if ($c >= 2 && $chunks[0]['chunk'] === 'IHDR') {
array_splice($chunks, 1, 0, array(array('offset' => 33, 'size' => 9, 'chunk' => 'pHYs')));
// Push the data
for ($i = 2; $i < $c; $i++) {
$chunks[$i]['offset'] += 21;
}
$firstPart = substr($bin, 0, 33);
$secondPart = substr($bin, 33);
$bin = $firstPart;
$bin .= $cr;
$bin .= $secondPart;
}
} else {
$bin = substr_replace($bin, $cr, $chunks[$i]['offset'], 21);
}
}
}
private function internalSetC(&$bin, &$chunks): void
{
if (count($chunks) >= 2 && $chunks[0]['chunk'] === 'IHDR') {
$firstPart = substr($bin, 0, 33);
$secondPart = substr($bin, 33);
$cr = pack('H*', '0000004C74455874436F707972696768740047656E657261746564207769746820426172636F64652042616B65727920666F722050485020687474703A2F2F7777772E626172636F646562616B6572792E636F6DC57F50A1');
$bin = $firstPart;
$bin .= $cr;
$bin .= $secondPart;
}
// Chunks is dirty!! But we are done.
}
private static array $crcTable = array();
private static bool $crcTableComputed = false;
private static function make_crcTable(): void
{
for ($n = 0; $n < 256; $n++) {
$c = $n;
for ($k = 0; $k < 8; $k++) {
if (($c & 1) === 1) {
$c = 0xedb88320 ^ (self::SHR($c, 1));
} else {
$c = self::SHR($c, 1);
}
}
self::$crcTable[$n] = $c;
}
self::$crcTableComputed = true;
}
private static function SHR($x, $n): int
{
$mask = 0x40000000;
if ($x < 0) {
$x &= 0x7FFFFFFF;
$mask = $mask >> ($n - 1);
return ($x >> $n) | $mask;
}
return (int)$x >> (int)$n;
}
private static function update_crc($crc, $buf, $len): int
{
$c = $crc;
if (!self::$crcTableComputed) {
self::make_crcTable();
}
for ($n = 0; $n < $len; $n++) {
$c = self::$crcTable[($c ^ ord($buf[$n])) & 0xff] ^ (self::SHR($c, 8));
}
return $c;
}
private static function crc($data, $len): int
{
return self::update_crc(-1, $data, $len) ^ -1;
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Entry about an AI.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\GS1;
class AIData
{
private string $ai;
private int $kindOfData;
private int $minLength;
private int $maxLength;
private bool $checksum;
/**
* Constructor creating an entry for an AI.
*
* @param string $ai The AI.
* @param int $kindOfData The type of data.
* @param int $minLength The minimum length.
* @param int $maxLength The maximum length.
* @param bool $checksum Indicates if a checksum is present.
* @param string $description The description of the AI.
*/
function __construct(string $ai, int $kindOfData, int $minLength, int $maxLength, bool $checksum, string $description)
{
$this->ai = $ai;
$this->kindOfData = $kindOfData;
$this->minLength = $minLength;
$this->maxLength = $maxLength;
$this->checksum = $checksum;
$this->description = $description;
}
/**
* Gets the AI.
*
* @return string
*/
public function getAI(): string
{
return $this->ai;
}
/**
* Gets the type of data.
*
* @return int
*/
public function getKindOfData(): int
{
return $this->kindOfData;
}
/**
* Gets the minimum length.
*
* @return int
*/
public function getMinLength(): int
{
return $this->minLength;
}
/**
* Gets the maximum length.
*
* @return int
*/
public function getMaxLength(): int
{
return $this->maxLength;
}
/**
* Indicates if a checksum is required.
*
* @return bool
*/
public function getChecksum(): bool
{
return $this->checksum;
}
/**
* The description of the AI.
*
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* GS1 Kind of data.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\GS1;
/**
* GS1 Kind of data.
*/
class KindOfData
{
/**
* The content is only numeric.
*/
const NUMERIC = 1;
/**
* The content contains number and letters.
*/
const ALPHA_NUMERIC = 2;
/**
* The content is of a date format yymmdd.
*/
const DATE = 3;
/**
* The content is of a date and time format yymmddhhii or yymmddhhiiss.
*/
const DATETIME = 4;
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* Enable to join 2 BCGDrawing or 2 image object to make only one image.
* There are some options for alignment.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common;
class JoinDraw
{
const ALIGN_RIGHT = 0;
const ALIGN_BOTTOM = 0;
const ALIGN_LEFT = 1;
const ALIGN_TOP = 1;
const ALIGN_CENTER = 2;
const POSITION_RIGHT = 0;
const POSITION_BOTTOM = 1;
const POSITION_LEFT = 2;
const POSITION_TOP = 3;
private $image1;
private $image2;
private $alignement;
private $position;
private $space;
private $image;
/**
* Construct the JoinDrawing Object.
* - $image1 and $image2 have to be BCGDrawing object or image object.
* - $space is the space between the two graphics in pixel.
* - $position is the position of the $image2 depending the $image1.
* - $alignment is the alignment of the $image2 if this one is smaller than $image1;
* if $image2 is bigger than $image1, the $image1 will be positionned on the opposite side specified.
*
* @param mixed $image1 The image 1.
* @param mixed $image2 The image 2.
* @param BCGColor $background The bacground color.
* @param int $space The spacing.
* @param int $position The position.
* @param int $alignment The alignment.
*/
public function __construct($image1, $image2, BCGColor $background, int $space = 10, int $position = self::POSITION_RIGHT, int $alignment = self::ALIGN_TOP)
{
if ($image1 instanceof BCGDrawing) {
$this->image1 = $image1->getImage();
} else {
$this->image1 = $image1;
}
if ($image2 instanceof BCGDrawing) {
$this->image2 = $image2->getImage();
} else {
$this->image2 = $image2;
}
$this->background = $background;
$this->space = (int)$space;
$this->position = (int)$position;
$this->alignment = (int)$alignment;
$this->createIm();
}
/**
* Destroys the image.
*/
public function __destruct(): void
{
imagedestroy($this->image);
}
/**
* Finds the position where the barcode should be aligned.
*
* @param int $size1 The size 1.
* @param int $size2 The size 2.
* @param int $ailgnment The alignment.
* @return int The position.
*/
private function findPosition(int $size1, int $size2, int $alignment): int
{
$rsize1 = max($size1, $size2);
$rsize2 = min($size1, $size2);
if ($alignment === self::ALIGN_LEFT) { // Or TOP
return 0;
} elseif ($alignment === self::ALIGN_CENTER) {
return $rsize1 / 2 - $rsize2 / 2;
} else { // RIGHT or TOP
return $rsize1 - $rsize2;
}
}
/**
* Change the alignments.
*
* @param int $alignment The alignment.
* @return int The alignment.
*/
private function changeAlignment(int $alignment): int
{
if ($alignment === 0) {
return 1;
} elseif ($alignment === 1) {
return 0;
} else {
return 2;
}
}
/**
* Creates the image.
*
* @return void
*/
private function createIm(): void
{
$w1 = imagesx($this->image1);
$w2 = imagesx($this->image2);
$h1 = imagesy($this->image1);
$h2 = imagesy($this->image2);
if ($this->position === self::POSITION_LEFT || $this->position === self::POSITION_RIGHT) {
$w = $w1 + $w2 + $this->space;
$h = max($h1, $h2);
} else {
$w = max($w1, $w2);
$h = $h1 + $h2 + $this->space;
}
$this->image = imagecreatetruecolor($w, $h);
imagefill($this->image, 0, 0, $this->background->allocate($this->image));
// We start defining position of images
if ($this->position === self::POSITION_TOP) {
if ($w1 > $w2) {
$posX1 = 0;
$posX2 = $this->findPosition($w1, $w2, $this->alignment);
} else {
$a = $this->changeAlignment($this->alignment);
$posX1 = $this->findPosition($w1, $w2, $a);
$posX2 = 0;
}
$posY2 = 0;
$posY1 = $h2 + $this->space;
} elseif ($this->position === self::POSITION_LEFT) {
if ($w1 > $w2) {
$posY1 = 0;
$posY2 = $this->findPosition($h1, $h2, $this->alignment);
} else {
$a = $this->changeAlignment($this->alignment);
$posY2 = 0;
$posY1 = $this->findPosition($h1, $h2, $a);
}
$posX2 = 0;
$posX1 = $w2 + $this->space;
} elseif ($this->position === self::POSITION_BOTTOM) {
if ($w1 > $w2) {
$posX2 = $this->findPosition($w1, $w2, $this->alignment);
$posX1 = 0;
} else {
$a = $this->changeAlignment($this->alignment);
$posX2 = 0;
$posX1 = $this->findPosition($w1, $w2, $a);
}
$posY1 = 0;
$posY2 = $h1 + $this->space;
} else { // defaults to RIGHT
if ($w1 > $w2) {
$posY2 = $this->findPosition($h1, $h2, $this->alignment);
$posY1 = 0;
} else {
$a = $this->changeAlignment($this->alignment);
$posY2 = 0;
$posY1 = $this->findPosition($h1, $h2, $a);
}
$posX1 = 0;
$posX2 = $w1 + $this->space;
}
imagecopy($this->image, $this->image1, $posX1, $posY1, 0, 0, $w1, $h1);
imagecopy($this->image, $this->image2, $posX2, $posY2, 0, 0, $w2, $h2);
}
/**
* Returns the new $image created.
*
* @return resource The surface.
*/
public function getImage()
{
return $this->image;
}
}

View File

@@ -0,0 +1,13 @@
<p align="center"><a href="https://www.barcodebakery.com" target="_blank">
<img src="https://www.barcodebakery.com/images/BCG-Logo-SQ-GitHub.svg">
</a></p>
This utility library is used to handle GS1 barcodes in [GS1-128][1].
Please visit the following repository:
* [barcode-php-1d][2]
[1]: https://www.barcodebakery.com/en/docs/php/barcode/gs1128/api
[2]: https://github.com/barcode-bakery/barcode-php-1d/

View File

@@ -0,0 +1,39 @@
{
"name": "barcode-bakery/gs1ai",
"version": "1.0.0",
"license": [
"proprietary",
"CC-BY-NC-ND-4.0"
],
"support": {
"email": "contact@barcodebakery.com",
"docs": "https://www.barcodebakery.com"
},
"type": "library",
"homepage": "https://www.barcodebakery.com",
"authors": [
{
"name": "Jean-Sébastien Goupil",
"email": "contact@barcodebakery.com"
}
],
"description": "Library to support GS1 barcodes.",
"autoload": {
"psr-4": {
"BarcodeBakery\\Common\\": "src"
}
},
"keywords": [
"barcode",
"generator",
"bakery",
"barcodebakery",
"code128",
"gs1",
"gs1-128",
"ai"
],
"require": {
"php": ">=7.4"
}
}

View File

@@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
/**
*--------------------------------------------------------------------
*
* GS1 AI Data.
*
*--------------------------------------------------------------------
* Copyright (C) Jean-Sebastien Goupil
* http://www.barcodebakery.com
*/
namespace BarcodeBakery\Common\GS1;
use BarcodeBakery\Common\GS1\AIData;
use BarcodeBakery\Common\GS1\KindOfData;
class GS1AI
{
/**
* Gets the default AI Data.
*
* @return array[AIData]
*/
public static function getDefaultAIData() {
return array(
new AIData('00', KindOfData::NUMERIC, 18, 18, true, 'Serial Shipping Container Code (SSCC-18)'),
new AIData('01', KindOfData::NUMERIC, 14, 14, true, 'Global Trade Item Number (GTIN)'),
new AIData('02', KindOfData::NUMERIC, 14, 14, true, 'GTIN of Contained Trade Items'),
new AIData('10', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Batch or Lot Number'),
new AIData('11', KindOfData::DATE, 6, 6, false, 'Production Date'),
new AIData('12', KindOfData::DATE, 6, 6, false, 'Due Date'),
new AIData('13', KindOfData::DATE, 6, 6, false, 'Packaging Date'),
new AIData('15', KindOfData::DATE, 6, 6, false, 'Best Before Date'),
new AIData('16', KindOfData::DATE, 6, 6, false, 'Sell By Date'),
new AIData('17', KindOfData::DATE, 6, 6, false, 'Expiration Date'),
new AIData('20', KindOfData::NUMERIC, 2, 2, false, 'Internal Product Variant'),
new AIData('21', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Serial Number'),
new AIData('22', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Consumer Product Variant'),
new AIData('235', KindOfData::ALPHA_NUMERIC, 1, 28, false, 'Third Party Controlled, Serialised Extension of GTIN (TPX)'),
new AIData('240', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Additional Item Identification'),
new AIData('241', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Customer Part Number'),
new AIData('242', KindOfData::ALPHA_NUMERIC, 1, 6, false, 'Made-to-Order Variation Number'),
new AIData('243', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Packaging Comnponent Number'),
new AIData('250', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Second Serial Number'),
new AIData('251', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Reference to Source Entity'),
new AIData('253', KindOfData::NUMERIC, 14, 30, false, 'Global Document Type Identifier (GDTI)'),
new AIData('254', KindOfData::NUMERIC, 1, 20, false, 'GLN Extension Component'),
new AIData('255', KindOfData::NUMERIC, 14, 25, false, 'Global Coupon Number (GCN)'),
new AIData('30', KindOfData::NUMERIC, 1, 8, false, 'Variable Count of Items (variable measure trade item)'),
new AIData('310y', KindOfData::NUMERIC, 6, 6, false, 'Net Weight in kilograms (variable measure trade item)'),
new AIData('311y', KindOfData::NUMERIC, 6, 6, false, 'Length or 1st Dimension, in meters (variable measure trade item)'),
new AIData('312y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in meters (variable measure trade item)'),
new AIData('313y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in meters (variable measure trade item)'),
new AIData('314y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square meters (variable measure trade item)'),
new AIData('315y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in liters (variable measure trade item)'),
new AIData('316y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in cubic meters (variable measure trade item)'),
new AIData('320y', KindOfData::NUMERIC, 6, 6, false, 'Net Weight, in pounds (variable measure trade item)'),
new AIData('321y', KindOfData::NUMERIC, 6, 6, false, 'Length or 1st Dimension, in inches (variable measure trade item)'),
new AIData('322y', KindOfData::NUMERIC, 6, 6, false, 'Length or 1st Dimension, in feet (variable measure trade item)'),
new AIData('323y', KindOfData::NUMERIC, 6, 6, false, 'Length, 1st Dimension, in yards (variable measure trade item)'),
new AIData('324y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in inches (variable measure trade item)'),
new AIData('325y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in feet (variable measure trade item)'),
new AIData('326y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in yards (variable measure trade item)'),
new AIData('327y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in inches (variable measure trade item)'),
new AIData('328y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in feet (variable measure trade item)'),
new AIData('329y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in yards (variable measure trade item)'),
new AIData('330y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Weight, in kilograms'),
new AIData('331y', KindOfData::NUMERIC, 6, 6, false, 'Length, or 1st Dimension, in meters'),
new AIData('332y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in meters'),
new AIData('333y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in meters'),
new AIData('334y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square meters'),
new AIData('335y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in liters'),
new AIData('336y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in cubic meters'),
new AIData('337y', KindOfData::NUMERIC, 6, 6, false, 'Kilograms per square meter'),
new AIData('340y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Weight, in pounds'),
new AIData('341y', KindOfData::NUMERIC, 6, 6, false, 'Length or 1st Dimension, in inches'),
new AIData('342y', KindOfData::NUMERIC, 6, 6, false, 'Length or 1st Dimension, in feet'),
new AIData('343y', KindOfData::NUMERIC, 6, 6, false, 'Container Length/1st Dimension in, in yards'),
new AIData('344y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in inches'),
new AIData('345y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in feet'),
new AIData('346y', KindOfData::NUMERIC, 6, 6, false, 'Width, Diameter, or 2nd Dimension, in yards'),
new AIData('347y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in inches'),
new AIData('348y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, or 3rd Dimension, in feet'),
new AIData('349y', KindOfData::NUMERIC, 6, 6, false, 'Depth, Thickness, Height, 3rd Dimension, in yards'),
new AIData('350y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square inches (variable measure trade item)'),
new AIData('351y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square feet (variable measure trade item)'),
new AIData('352y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square yards (variable measure trade item)'),
new AIData('353y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square inches'),
new AIData('354y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square feet'),
new AIData('355y', KindOfData::NUMERIC, 6, 6, false, 'Area, in square yards'),
new AIData('356y', KindOfData::NUMERIC, 6, 6, false, 'Net Weight, in troy ounces (variable measure trade item)'),
new AIData('357y', KindOfData::NUMERIC, 6, 6, false, 'Net Weight or volume, in ounces (variable measure trade item)'),
new AIData('360y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in quarts (variable measure trade item)'),
new AIData('361y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in U.S. gallons (variable measure trade item)'),
new AIData('362y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in quarts'),
new AIData('363y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in U.S. gallons'),
new AIData('364y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in cubic inches (variable measure trade item)'),
new AIData('365y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in cubic feet (variable measure trade item)'),
new AIData('366y', KindOfData::NUMERIC, 6, 6, false, 'Net Volume, in cubic yards (variable measure trade item)'),
new AIData('367y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in cubic inches'),
new AIData('368y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in cubic feet'),
new AIData('369y', KindOfData::NUMERIC, 6, 6, false, 'Logistic Volume, in cubic yards'),
new AIData('37', KindOfData::NUMERIC, 1, 8, false, 'Count of trade items'),
new AIData('390y', KindOfData::NUMERIC, 1, 15, false, 'Applicable Amount Payable or Coupon Value, in local currency'),
new AIData('391y', KindOfData::NUMERIC, 4, 18, false, 'Applicable Amount Payable with ISO Currency Code'),
new AIData('392y', KindOfData::NUMERIC, 1, 15, false, 'Applicable Amount Payable, Single Monetary Area (variable measure trade item)'),
new AIData('393y', KindOfData::NUMERIC, 4, 18, false, 'Applicable Amount Payable With ISO Currency Code (variable measure trade item)'),
new AIData('394y', KindOfData::NUMERIC, 4, 4, false, 'Percentage Discount of a Coupon'),
new AIData('395y', KindOfData::NUMERIC, 6, 6, false, 'Amount Payable per unit of measure single monetary area (variable measure trade item)'),
new AIData('400', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Customer\'s Purchase Order Number'),
new AIData('401', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Global Identification Number for Consignment (GINC)'),
new AIData('402', KindOfData::NUMERIC, 17, 17, false, 'Global Shipment Identification Number (GSIN)'),
new AIData('403', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Routing Code'),
new AIData('410', KindOfData::NUMERIC, 13, 13, true, 'Ship To/Deliver To Global Location Number'),
new AIData('411', KindOfData::NUMERIC, 13, 13, true, 'Bill To/Invoice To Global Location Number'),
new AIData('412', KindOfData::NUMERIC, 13, 13, true, 'Purchased From Global Location Number'),
new AIData('413', KindOfData::NUMERIC, 13, 13, true, 'Ship For/Deliver For/Forward To Global Location Number'),
new AIData('414', KindOfData::NUMERIC, 13, 13, true, 'Identification of a Physical Location - Global Location Number'),
new AIData('415', KindOfData::NUMERIC, 13, 13, true, 'Global Location Number of The Invoicing Party'),
new AIData('416', KindOfData::NUMERIC, 13, 13, true, 'Global Location Number of The Production or Service Location'),
new AIData('417', KindOfData::NUMERIC, 13, 13, true, 'Party GLN'),
new AIData('420', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Ship To/Deliver To Postal Code Within a Single Postal Authority'),
new AIData('421', KindOfData::ALPHA_NUMERIC, 4, 12, false, 'Ship To/Deliver To Postal Code With ISO Country Code'),
new AIData('422', KindOfData::NUMERIC, 3, 3, false, 'Country of Origin of a Trade Item'),
new AIData('423', KindOfData::NUMERIC, 3, 15, false, 'Country of Initial Processing'),
new AIData('424', KindOfData::NUMERIC, 3, 3, false, 'Country of Processing'),
new AIData('425', KindOfData::NUMERIC, 3, 3, false, 'Country of Disassembly'),
new AIData('426', KindOfData::NUMERIC, 3, 3, false, 'Country Covering Full Process Chain'),
new AIData('427', KindOfData::ALPHA_NUMERIC, 1, 3, false, 'Country Subdivision of Origin'),
new AIData('4300', KindOfData::ALPHA_NUMERIC, 1, 35, false, 'Ship-to / Deliver-to company name'),
new AIData('4301', KindOfData::ALPHA_NUMERIC, 1, 35, false, 'Ship-to / Deliver-to contact'),
new AIData('4302', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Ship-to / Deliver-to address line 1'),
new AIData('4303', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Ship-to / Deliver-to address line 2'),
new AIData('4304', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Ship-to / Deliver-to suburb'),
new AIData('4305', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Ship-to / Deliver-to locality'),
new AIData('4306', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Ship-to / Deliver-to region'),
new AIData('4307', KindOfData::ALPHA_NUMERIC, 2, 2, false, 'Ship-to / Deliver-to country code'),
new AIData('4308', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Ship-to / Deliver-to telephone number'),
new AIData('4310', KindOfData::ALPHA_NUMERIC, 1, 35, false, 'Return-to company name'),
new AIData('4311', KindOfData::ALPHA_NUMERIC, 1, 35, false, 'Return-to contact'),
new AIData('4312', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Return-to address line 1'),
new AIData('4313', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Return-to address line 2'),
new AIData('4314', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Return-to suburb'),
new AIData('4315', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Return-to locality'),
new AIData('4316', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Return-to region'),
new AIData('4317', KindOfData::ALPHA_NUMERIC, 2, 2, false, 'Return-to country code'),
new AIData('4318', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Return-to postal code'),
new AIData('4319', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Return-to telephone number'),
new AIData('4320', KindOfData::ALPHA_NUMERIC, 1, 35, false, 'Service code description'),
new AIData('4321', KindOfData::NUMERIC, 1, 1, false, 'Dangerous goods flag'),
new AIData('4322', KindOfData::NUMERIC, 1, 1, false, 'Authority to leave'),
new AIData('4323', KindOfData::NUMERIC, 1, 1, false, 'Signature required flag'),
new AIData('4324', KindOfData::DATETIME, 10, 10, false, 'Not before delivery date time'),
new AIData('4325', KindOfData::DATETIME, 10, 10, false, 'Not after delivery date time'),
new AIData('4326', KindOfData::DATE, 6, 6, false, 'Release date'),
new AIData('7001', KindOfData::NUMERIC, 13, 13, false, 'NATO Stock Number (NSN)'),
new AIData('7002', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'UN/ECE Meat Carcasses and Cuts Classification'),
new AIData('7003', KindOfData::DATETIME, 10, 10, false, 'Expiration Date and Time'),
new AIData('7004', KindOfData::NUMERIC, 1, 4, false, 'Active Potency'),
new AIData('7005', KindOfData::ALPHA_NUMERIC, 1, 12, false, 'Catch Area'),
new AIData('7006', KindOfData::DATE, 6, 6, false, 'First Freeze Date'),
new AIData('7007', KindOfData::NUMERIC, 6, 12, false, 'Harvest Date'),
new AIData('7008', KindOfData::ALPHA_NUMERIC, 1, 3, false, 'Species For Fishery Purposes'),
new AIData('7009', KindOfData::ALPHA_NUMERIC, 1, 10, false, 'Fishing Gear Type'),
new AIData('7010', KindOfData::ALPHA_NUMERIC, 1, 2, false, 'Production Method'),
new AIData('7020', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Refurbishment Lot ID'),
new AIData('7021', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Functional Status'),
new AIData('7022', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Revision Status'),
new AIData('7023', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Global Individual Asset Identifier (GIAI) of an Assembly'),
new AIData('703y', KindOfData::ALPHA_NUMERIC, 3, 30, false, 'Number of Processor with ISO Country Code'),
new AIData('7040', KindOfData::ALPHA_NUMERIC, 4, 4, false, 'GS1 UIC with Extension 1 and Importer index'),
new AIData('710', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'National Healthcare Reimbursement Number (NHRN) - Germany PZN'),
new AIData('711', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'National Healthcare Reimbursement Number (NHRN) - France CIP'),
new AIData('712', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'National Healthcare Reimbursement Number (NHRN) - Spain CN'),
new AIData('713', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'National Healthcare Reimbursement Number (NHRN) - Brasil DRN'),
new AIData('714', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'National Healthcare Reimbursement Number (NHRN) - Portugal AIM'),
new AIData('723y', KindOfData::ALPHA_NUMERIC, 2, 30, false, 'Certification reference'),
new AIData('7240', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Protocol ID'),
new AIData('8001', KindOfData::NUMERIC, 14, 14, false, 'Roll Products - Width/Length/Core Diameter/Direction/Splices'),
new AIData('8002', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Cellular Mobile Telphone Identifier'),
new AIData('8003', KindOfData::ALPHA_NUMERIC, 15, 30, false, 'Global Returnable Asset Identifier (GRAI)'),
new AIData('8004', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Global Individual Asset Identifier (GIAI)'),
new AIData('8005', KindOfData::NUMERIC, 6, 6, false, 'Price per Unit of Measure'),
new AIData('8006', KindOfData::NUMERIC, 18, 18, false, 'Identification of an Individual Trade Item Piece'),
new AIData('8007', KindOfData::ALPHA_NUMERIC, 1, 34, false, 'International Bank Account Number (IBAN)'),
new AIData('8008', KindOfData::DATETIME, 8, 12, false, 'Date and Time of Production'),
new AIData('8009', KindOfData::ALPHA_NUMERIC, 1, 50, false, 'Optically Readable Sensor Indicator'),
new AIData('8010', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Component/Part Identifier (CPID)'),
new AIData('8011', KindOfData::NUMERIC, 1, 12, false, 'Component/Part Identifier Serial Number (CPID Serial)'),
new AIData('8012', KindOfData::ALPHA_NUMERIC, 1, 20, false, 'Software Version'),
new AIData('8013', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Global Model Number (GMN)'),
new AIData('8017', KindOfData::NUMERIC, 18, 18, false, 'Global Service Relation Number to Identify the Relationship Between an Organisation Offering Services and the Provider of Services'),
new AIData('8018', KindOfData::NUMERIC, 18, 18, false, 'Global Service Relation Number to Identify the Relationship Between an Organisation Offering Services and the Recipient of Services'),
new AIData('8019', KindOfData::NUMERIC, 1, 10, false, 'Service Relation Instance Number (SRIN)'),
new AIData('8020', KindOfData::ALPHA_NUMERIC, 1, 25, false, 'Payment Slip Reference Number'),
new AIData('8026', KindOfData::NUMERIC, 18, 18, false, 'Identification of pieces of a trade item (ITIP) contained in a logistic unit'),
new AIData('8110', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Coupon Code Identification for Use in North America'),
new AIData('8111', KindOfData::NUMERIC, 4, 4, false, 'Loyalty Points of a Coupon'),
new AIData('8112', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Paperless Coupon Code Identification for Use in North America (AI 8112)'),
new AIData('8200', KindOfData::ALPHA_NUMERIC, 1, 70, false, 'Extended Packaging URL'),
new AIData('90', KindOfData::ALPHA_NUMERIC, 1, 30, false, 'Information Mutually Agreed Between Trading Partners'),
new AIData('91', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('92', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('93', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('94', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('95', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('96', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('97', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('98', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes'),
new AIData('99', KindOfData::ALPHA_NUMERIC, 1, 90, false, 'Internal Company Codes')
);
}
}