diff --git a/barcode/BarcodeException.php b/barcode/BarcodeException.php new file mode 100644 index 0000000..16d6cb2 --- /dev/null +++ b/barcode/BarcodeException.php @@ -0,0 +1,8 @@ + EAN_pet + protected const EAN_pet = [ + 0 => ['O', 'O', 'O', 'O', 'O', 'O'], + 1 => ['O', 'O', 'E', 'O', 'E', 'E'], + 2 => ['O', 'O', 'E', 'E', 'O', 'E'], + 3 => ['O', 'O', 'E', 'E', 'E', 'O'], + 4 => ['O', 'E', 'O', 'O', 'E', 'E'], + 5 => ['O', 'E', 'E', 'O', 'O', 'E'], + 6 => ['O', 'E', 'E', 'E', 'O', 'O'], + 7 => ['O', 'E', 'E', 'E', 'O', 'E'], + 8 => ['O', 'E', 'O', 'E', 'E', 'O'], + 9 => ['O', 'E', 'E', 'O', 'E', 'O'], + ]; + + // EAN Character Set Encoding Table => EAN_cet + protected const EAN_cet = [ + 0 => ['O' => '0001101', 'E' => '0100111', 'R' => '1110010'], + 1 => ['O' => '0011001', 'E' => '0110011', 'R' => '1100110'], + 2 => ['O' => '0010011', 'E' => '0011011', 'R' => '1101100'], + 3 => ['O' => '0111101', 'E' => '0100001', 'R' => '1000010'], + 4 => ['O' => '0100011', 'E' => '0011101', 'R' => '1011100'], + 5 => ['O' => '0110001', 'E' => '0111001', 'R' => '1001110'], + 6 => ['O' => '0101111', 'E' => '0000101', 'R' => '1010000'], + 7 => ['O' => '0111011', 'E' => '0010001', 'R' => '1000100'], + 8 => ['O' => '0110111', 'E' => '0001001', 'R' => '1001000'], + 9 => ['O' => '0001011', 'E' => '0010111', 'R' => '1110100'], + ]; + + // UPC 2-Digit Parity Pattern => UPC_2dpp + protected const UPC_2dpp = [ + 0 => ['O', 'O'], + 1 => ['O', 'E'], + 2 => ['E', 'O'], + 3 => ['E', 'E'], + ]; + + // UPC 5-Digit Parity Pattern => UPC_5dpp + protected const UPC_5dpp = [ + 0 => ['E', 'E', 'O', 'O', 'O'], + 1 => ['E', 'O', 'E', 'O', 'O'], + 2 => ['E', 'O', 'O', 'E', 'O'], + 3 => ['E', 'O', 'O', 'O', 'E'], + 4 => ['O', 'E', 'E', 'O', 'O'], + 5 => ['O', 'O', 'E', 'E', 'O'], + 6 => ['O', 'O', 'O', 'E', 'E'], + 7 => ['O', 'E', 'O', 'E', 'O'], + 8 => ['O', 'E', 'O', 'O', 'E'], + 9 => ['O', 'O', 'E', 'O', 'E'], + ]; + + protected string $message; + protected string $supplemental; + + /** + * Construct EAN13 with message and supplemental. + * + * @param null|string $message Message of EAN13 code + * @param null|string $supplemental Supplemental code for EAN13 + * @return void + * @throws BarcodeException If message or supplemental has invalid length BarcodeException is thrown. + */ + private function __construct (?string $message = null, ?string $supplemental = null) { + if (is_null($this->setMessage($message))) { + throw new BarcodeException('Message invalid', 1); + } + + if (is_null($this->setSupplemental($supplemental))) { + throw new BarcodeException('Supplemental invalid', 1); + } + } + + /** + * Create EAN13 code with message and optional supplemental code. + * + * @param null|string $message Message of EAN13 + * @param null|string $supplemental Supplemental code + * @return EAN13 + * @throws BarcodeException If message or supplemental has invalid length BarcodeException is thrown. + */ + public static function create (?string $message = null, ?string $supplemental = null) : EAN13 { + return new EAN13($message, $supplemental); + } + + /** + * Get message of EAN13 with checksum digit + * + * @return null|string EAN13 message + */ + public function getMessage () : ?string { + return $this->message ?: null; + } + + /** + * Set message of EAN13 code + * + * @param null|string $message Message of EAN13 + * @return null|int Checksum of EAN13. NULL is returned if message has invalid length. If message is cleared with void string or NULL, -1 is returned. + */ + public function setMessage (?string $message = null) : ?int { + $this->message = substr(preg_replace('/\D/', '', (string) $message), 0, 12); + + $checksum = self::getBarcodeChecksum(); + + $this->message = is_int($checksum) ? $this->message . $checksum : ''; + + if ($message and is_null($checksum)) { + $this->message = ''; + return null; + } + + return $this->message ? $checksum : -1; + } + + /** + * Get supplemental code + * + * @return null|string Supplemental + */ + public function getSupplemental () : ?string { + return $this->supplemental ?: null; + } + + /** + * Set the supplemental code for EAN13 code + * + * @param null|string $supplemental Supplemental code + * @return null|int Checksum of supplemental. NULL is returned if supplemental has invalid length. If supplemental is cleared with void string or NULL, -1 is returned. + */ + public function setSupplemental (?string $supplemental = null) : ?int { + $this->supplemental = preg_replace('/\D/', '', (string) $supplemental); + + $checksum = self::getSupplementalChecksum(); + + if ($supplemental and is_null($checksum)) { + $this->supplemental = ''; + return null; + } + + return $this->supplemental ? $checksum : -1; + } + + /** + * Computes the EAN13 checksum + * + * @return null|int NULL is returned if mesage has invalid length + */ + public function getBarcodeChecksum () : ?int { + $message = substr($this->message, 0, 12); + + if (strlen($message) != 12) { + return null; + } + + $checksum = 0; + foreach (str_split(strrev($message)) as $pos => $val) { + $checksum += $val * (3 - 2 * ($pos % 2)); + } + + return ((10 - ($checksum % 10)) % 10); + } + + /** + * Computes the UPC checksum for supplemental code of EAN13 + * + * @return null|int NULL is returned if supplemental has invalid length + */ + public function getSupplementalChecksum () : ?int { + if (strlen($this->supplemental) == 2) { + return ($this->supplemental % 4); + } + + if (strlen($this->supplemental) == 5) { + $supp_checksum = 0; + foreach (str_split(strrev($this->supplemental)) as $pos => $val ) { + $supp_checksum += $val * (3 + 6 * ($pos % 2)); + } + return ($supp_checksum % 10); + } + + return null; + } + + /** + * Get EAN13 left hand bars coded + * + * @return null|string Left hand bars of EAN13 code + */ + public function getBarcodeLeftHand () : ?string { + $lh_coded = ''; + + foreach (str_split(substr($this->message, 1, 6)) as $pos => $val) { + $lh_coded .= self::EAN_cet[$val][self::EAN_pet[$this->message[0]][$pos]]; + } + + return $lh_coded ?: null; + } + + /** + * Get EAN13 right hand bars coded + * + * @return null|string Right hand bars of EAN13 code + */ + public function getBarcodeRightHand () : ?string { + $rh_coded = ''; + + foreach (str_split(substr($this->message, 7, 6)) as $pos => $val) { + $rh_coded .= self::EAN_cet[$val]['R']; + } + + return $rh_coded ?: null; + } + + /** + * Get EAN13 supplemental bars coded + * + * @return null|string EAN13 supplemental bars + */ + public function getBarcodeSupplemental () : ?string { + $supp_coded = ''; + + $supp_checksum = $this->getSupplementalChecksum(); + + $table = (strlen($this->supplemental) == 2) ? self::UPC_2dpp : self::UPC_5dpp; + foreach(str_split($this->supplemental) as $pos => $val) { + $supp_coded .= self::EAN_cet[$val][$table[$supp_checksum][$pos]] . '01'; + } + + return $supp_coded ? '1011' . substr($supp_coded, 0, -2) : null; + } + } diff --git a/composer.json b/composer.json index 1607a9e..afdaa28 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "autoload": { "psr-4": { "Fawno\\FPDF\\": "src/", + "Fawno\\Barcode\\": "barcode/", "FPDF\\Scripts\\": "scripts/" }, "classmap": [ diff --git a/src/FawnoFPDF.php b/src/FawnoFPDF.php index 188cfee..3d95a33 100644 --- a/src/FawnoFPDF.php +++ b/src/FawnoFPDF.php @@ -6,6 +6,8 @@ use Fawno\FPDF\Traits\CMYKTrait; use Fawno\FPDF\Traits\PDFMacroableTrait; use Fawno\FPDF\PDFWrapper; + use Fawno\FPDF\Traits\BasicFunctionsTrait; + use Fawno\FPDF\Traits\EAN13Trait; use Fawno\FPDF\Traits\FontsTrait; use FPDF\Scripts\Attachments\AttachmentsTrait; use FPDF\Scripts\PDFBookmark\PDFBookmarkTrait; @@ -40,6 +42,8 @@ class FawnoFPDF extends PDFWrapper { use PDFMultiCellsTableTrait; use PDFTransformTrait; use PDFCircularTextTrait; + use EAN13Trait; + use BasicFunctionsTrait; protected function _putresources () { parent::_putresources(); diff --git a/src/Traits/BasicFunctionsTrait.php b/src/Traits/BasicFunctionsTrait.php new file mode 100644 index 0000000..ba90337 --- /dev/null +++ b/src/Traits/BasicFunctionsTrait.php @@ -0,0 +1,64 @@ +k; + } + + /** + * Converts a value from points to the document unit + * + * @param float $val Value in points + * @return float Value in document units + */ + public function fromPt (float $val) : float { + return $val / $this->k; + } + + /** + * Converts a value from the document unit to points + * @param float $val Value in document units + * @return float Value in points + */ + public function toPt (float $val) : float { + return $val * $this->k; + } + + /** + * Converts a value from millimeters to points + * + * @param float $val Value in millemeters + * @return float Value in points + */ + public function mm2Pt (float $val) : float { + return $val * 720 / 254; + } + + /** + * Converts a value from centimeters to points + * + * @param float $val Value in centimeters + * @return float Value in points + */ + public function cm2Pt (float $val) : float { + return $val * 7200 / 254; + } + + /** + * Converts a value from inches to points + * + * @param float $val Value in inches + * @return float Value in points + */ + public function in2Pt (float $val) : float { + return $val * 72; + } + } diff --git a/src/Traits/EAN13Trait.php b/src/Traits/EAN13Trait.php new file mode 100644 index 0000000..cd0f7f4 --- /dev/null +++ b/src/Traits/EAN13Trait.php @@ -0,0 +1,185 @@ +mm2Pt(12.5); + + $this->ean13_margin = $box ? $this->fromPt(2) : 0; + $this->ean13_height = $this->fromPt($height); + $this->ean13_textheight = 8; + $this->ean13_barwidth = $this->fromPt($barwidth); + $this->ean13_barheight = $this->fromPt($height - 8.733071); + $this->ean13_senheight = $this->fromPt($height - 5.133071); + $this->ean13_ypos = $this->fromPt(8); + $this->ean13_xpos = 8; + + $this->SetFont('Helvetica', '', $this->ean13_textheight); + } + + /** + * Set EAN13 barcode + * + * @param float $x Abscisse of uppper left corner of the barcode + * @param float $y Ordinate of uppper left corner of the barcode + * @param string $message Message of the barcode + * @param null|string $supplemental Optional supplemental code + * @param bool $box Draw border + * @param float $width Width of barcode. Default is 40mm + * @param float $height Height of barcode. Default is 12.5mm + * @param null|float $angle Rotation of barcode + * @return void + */ + public function BarcodeEAN13 (float $x, float $y, string $message, ?string $supplemental = null, bool $box = false, float $width = 0, float $height = 0, ?float $angle = null) : void { + $ean13 = EAN13::create(); + $ean13->setMessage($message); + $message = $ean13->getMessage(); + $ean13->setSupplemental($supplemental); + $supplemental = $ean13->getSupplemental(); + + if (!$message) { + return; + } + + $this->_ean13_initialize_draw($box); + + $barcount = $supplemental ? ((strlen($supplemental) == 2) ? 135 : 162) : 105; + $ean13_width = $this->ean13_barwidth * $barcount + 2 * $this->ean13_margin; + $width = $width ?: $this->ean13_barwidth * $barcount; + $scalex = ($width > 0) ? 100 * $width / $ean13_width : 100; + + $ean13_height = $this->ean13_height + 2 * $this->ean13_margin; + $height = $height ?: $this->ean13_height; + $scaley = ($height > 0) ? 100 * $height / $ean13_height : 100; + + $this->StartTransform(); + $this->Scale($scalex, $scaley, (float) $x, (float) $y); + $this->Rotate((float) $angle, (float) $x, (float) $y); + $this->Translate((float) $x, (float) $y); + + if ($box) { + $this->SetLineWidth($this->fromPt(0.5)); + $this->Rect(0, 0, $ean13_width, $ean13_height); + } + + $this->SetLineWidth($this->ean13_barwidth); + + $this->_ean13_sentinel(0, '101'); + $this->_ean13_bars(3, $ean13->getBarcodeLeftHand()); + $this->_ean13_sentinel(45, '01010'); + $this->_ean13_bars(50, $ean13->getBarcodeRightHand()); + $this->_ean13_sentinel(92, '101'); + + $this->_ean13_put_text(-1, $this->fromPt(7), 0, $message[0]); + $this->_ean13_put_text(3, $this->fromPt(7), 42, substr($message, 1, 6)); + $this->_ean13_put_text(49, $this->fromPt(7), 42, substr($message, 7, 6)); + + if (!$supplemental) { + $this->StopTransform(); + return; + } + + $this->_ean13_bars_supp(105, $ean13->getBarcodeSupplemental()); + $this->_ean13_put_text(105, $this->fromPt(6) - $this->ean13_barheight, strlen($ean13->getBarcodeSupplemental()), $supplemental); + + $this->StopTransform(); + } + + /** + * Draw sentinel bars + * + * @param int $pos_init Initial position in bars + * @param string $data Data to draw + * @return void + */ + private function _ean13_sentinel (int $pos_init, string $data) { + $data = str_split($data); + foreach ($data as $rpos => $val) { + if ($val) { + $x = ($this->ean13_xpos + $pos_init + $rpos) * $this->ean13_barwidth + $this->ean13_barwidth / 2 + $this->ean13_margin; + $y0 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barheight + $this->ean13_barwidth / 2; + $y1 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barheight - $this->ean13_barwidth / 2 + $this->ean13_senheight; + $this->Line($x, $y0, $x, $y1); + } + } + } + + /** + * Draw code bars + * + * @param int $pos_init Initial position in bars + * @param string $data Data to draw + * @return void + */ + private function _ean13_bars (int $pos_init, string $data) { + $data = str_split($data); + foreach ($data as $rpos => $val) { + if ($val) { + $x = ($this->ean13_xpos + $pos_init + $rpos) * $this->ean13_barwidth + $this->ean13_barwidth / 2 + $this->ean13_margin; + $y0 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barheight + $this->ean13_barwidth / 2; + $y1 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barwidth / 2; + $this->Line($x, $y0, $x, $y1); + } + } + } + + /** + * Draw supplemental code bars + * + * @param int $pos_init Initial position in bars + * @param string $data Data to draw + * @return void + */ + private function _ean13_bars_supp (int $pos_init, string $data) { + $data = str_split($data); + foreach ($data as $rpos => $val) { + if ($val) { + $x = ($this->ean13_xpos + $pos_init + $rpos) * $this->ean13_barwidth + $this->ean13_barwidth / 2 + $this->ean13_margin; + $y0 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barheight + $this->ean13_barwidth / 2 + $this->fromPt(7); + $y1 = $this->ean13_height + $this->ean13_margin - $this->ean13_ypos - $this->ean13_barheight - $this->ean13_barwidth / 2 + $this->ean13_senheight; + $this->Line($x, $y0, $x, $y1); + } + } + } + + /** + * Set text in barcode + * + * @param int $pos_init Initial position in bars + * @param float $y Vertical position of text + * @param float $width Width of text box in bars + * @param string $string Text to write + * @return void + */ + private function _ean13_put_text (int $pos_init, float $y, float $width, string $string) { + $offset = ($pos_init < 0) ? $this->GetStringWidth($string[0]) : 0; + $len = strlen($string); + $width *= $this->ean13_barwidth; + $inc = ($width * ($len + 1) / $len - $this->GetStringWidth('0')) / ($this->ean13_barwidth * $len); + $y += $this->ean13_height + $this->ean13_margin - $this->ean13_ypos; + foreach (str_split($string) as $rpos => $char) { + $x = ($this->ean13_xpos + $pos_init + $rpos * $inc + 1) * $this->ean13_barwidth + $this->ean13_margin - $offset; + $this->Text($x, $y, $char); + } + } + } diff --git a/tests/Scripts/EAN13TraitTest.php b/tests/Scripts/EAN13TraitTest.php new file mode 100644 index 0000000..6c0e1fa --- /dev/null +++ b/tests/Scripts/EAN13TraitTest.php @@ -0,0 +1,27 @@ +AddPage(); + $pdf->BarcodeEAN13(80, 40, '123456789012'); + + $this->assertFileCanBeCreated($pdf); + + $this->assertPdfIsOk($pdf); + } + } diff --git a/tests/examples/example.pdf b/tests/examples/example.pdf index 89cf8d8..484988a 100644 Binary files a/tests/examples/example.pdf and b/tests/examples/example.pdf differ diff --git a/tests/examples/exampleEAN13TraitTest.pdf b/tests/examples/exampleEAN13TraitTest.pdf new file mode 100644 index 0000000..bc6ed9d Binary files /dev/null and b/tests/examples/exampleEAN13TraitTest.pdf differ diff --git a/tests/test.php b/tests/test.php index 2dea5a2..5e35241 100644 --- a/tests/test.php +++ b/tests/test.php @@ -38,6 +38,11 @@ $pdf->Bookmark('Paragraph 3', false, 1, -1); $pdf->Cell(0, 6, 'Paragraph 3'); + //EAN13Trait + $pdf->AddPage(); + $pdf->Bookmark('EAN13 Barcode', false); + $pdf->BarcodeEAN13(80, 40, '123456789012'); + //PDFTransformTrait $pdf->AddPage(); $pdf->Bookmark('PDFTransform', false);