Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Telepen/Telepen Numeric #188

Merged
merged 3 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/BarcodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
use Picqer\Barcode\Types\TypeRms4cc;
use Picqer\Barcode\Types\TypeStandard2of5;
use Picqer\Barcode\Types\TypeStandard2of5Checksum;
use Picqer\Barcode\Types\TypeTelepen;
use Picqer\Barcode\Types\TypeUpcA;
use Picqer\Barcode\Types\TypeUpcE;
use Picqer\Barcode\Types\TypeUpcExtension2;
Expand Down Expand Up @@ -90,6 +91,8 @@ abstract class BarcodeGenerator
const TYPE_MSI_CHECKSUM = 'MSI+'; // MSI + CHECKSUM (modulo 11)
const TYPE_POSTNET = 'POSTNET';
const TYPE_PLANET = 'PLANET';
const TYPE_TELEPEN_ALPHA = 'TELEPENALPHA';
const TYPE_TELEPEN_NUMERIC = 'TELEPENNUMERIC';
const TYPE_RMS4CC = 'RMS4CC'; // RMS4CC (Royal Mail 4-state Customer Code) - CBC (Customer Bar Code)
const TYPE_KIX = 'KIX'; // KIX (Klant index - Customer index)
const TYPE_IMB = 'IMB'; // IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200
Expand Down Expand Up @@ -203,6 +206,13 @@ protected function createDataBuilderForType(string $type)

case self::TYPE_PHARMA_CODE_TWO_TRACKS:
return new TypePharmacodeTwoCode();

case self::TYPE_TELEPEN_ALPHA:
return new TypeTelepen();

case self::TYPE_TELEPEN_NUMERIC:
return new TypeTelepen('numeric');

}

throw new UnknownTypeException();
Expand Down
186 changes: 186 additions & 0 deletions src/Types/TypeTelepen.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php
/**
* Adapted by Darren Stephens <[email protected]>
* from Java implementation of Telepen by <[email protected]> Robin Stuart
* at https://github.com/woo-j/OkapiBarcode which uses the
* Apache License 2.0 http://www.apache.org/licenses/LICENSE-2.0
*
* Implements Telepen (also known as Telepen Alpha), and Telepen Numeric.
*
* Telepen can encode ASCII text input and includes a modulo-127 check digit.
* Telepen Numeric allows compression of numeric data into a Telepen symbol. Data
* can consist of pairs of numbers or pairs consisting of a numerical digit followed
* by an X character. Telepen Numeric also includes a mod-127 check digit.
*/

namespace Picqer\Barcode\Types;

use Picqer\Barcode\Barcode;
use Picqer\Barcode\BarcodeBar;
use Picqer\Barcode\Exceptions\InvalidFormatException;

define('TELEPEN_START_CHAR', '_');
define('TELEPEN_STOP_CHAR', 'z');
define('TELEPEN_ALPHA', 'alpha');
define('TELEPEN_NUMERIC', 'numeric');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like these broad define's in de codebase. I think these can be private constants in the class itself, so it is encapsuled from the rest of the application.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to const


class TypeTelepen implements TypeInterface
{
private $telepen_lookup_table;
private $mode;

public function __construct($m = 'alpha')
{
$this->mode = TELEPEN_ALPHA;
if (strtolower($m) == 'numeric') {
$this->mode = TELEPEN_NUMERIC;
}
$this->createTelepenConversionTable();
}

public function getBarcodeData(string $code): Barcode
{
/* The stream we get from the telepen output gives us the
* width of alternating black/white stripes
*/

$encoded = $this->encode($code); //binary string
$barcode = new Barcode($code);

$drawBar = true;
for ($i = 0; $i < strlen($encoded); ++$i) {
$barWidth = $encoded[$i];
$barcode->addBar(new BarcodeBar($barWidth, 250, $drawBar));
$drawBar = !$drawBar; //flip to other colour
}

return $barcode;
}

protected function encode($code) : string
{
$result = null;
if ($this->mode == TELEPEN_ALPHA) {
$result = $this->encodeAlpha($code);
} else {
$result = $this->encodeNumeric($code);
}

return $result;
}

protected function encodeAlpha($code) : string
{

if (!preg_match('/[ -~]+/', $code)) { // everything from ASCII32-ASCII127
throw new InvalidFormatException("Invalid characters in data");
}

$count = 0;

/* other implementations use the byte-chr-int type equivalence to work
* with array indices in the conversion/lookup table. It's probably
* better to be more explicit with php, hence the use of ord and chr here.
*/

// begin with start char
$dest = $this->telepen_lookup_table[ord(TELEPEN_START_CHAR)];

for ($i = 0; $i < strlen($code); $i++) {
//$ascii_code = ord(substr($code, $i, 1));
$ascii_code = ord($code[$i]);
$dest .= ($this->telepen_lookup_table[$ascii_code]);
$count += $ascii_code;
}

// Now add check and terminator
$check_digit = 127 - ($count % 127);
if ($check_digit == 127) {
$check_digit = 0;
}

$dest .= $this->telepen_lookup_table[ord($check_digit)];
$dest .= $this->telepen_lookup_table[ord(TELEPEN_STOP_CHAR)]; // Stop

return $dest;
}

private function encodeNumeric(string $code) : string
{

/* If input contains non-numeric or X, exit */
if (!preg_match('/^[0-9X]+$/', $code)) {
throw new InvalidFormatException("Invalid characters in data");
}

/* If input is an odd length, exit */
$t = '';
if (strlen($code) % 2 > 0) {
throw new InvalidFormatException("There must be an even number of digits");
}

$count = 0;
$dest = $this->telepen_lookup_table[ord(TELEPEN_START_CHAR)]; // begin with the start character _

for ($i = 0; $i < strlen($code); $i += 2) {
$c1 = $code[$i];
$c2 = $code[$i+1];
/* Input nX is allowed, but Xn is not */
if ($c1 == 'X') {
throw new InvalidFormatException("Invalid position of X in data");
}
$glyph = null;
if ($c2 == 'X') {
$glyph = (ord($c1) - ord('0')) + 17;
} else {
$glyph = ((10 * (ord($c1) - ord('0'))) + (ord($c2) - ord('0'))) + 27;
}
$count += $glyph;
$dest .= $this->telepen_lookup_table[$glyph];
}

$check_digit = 127 - ($count % 127);
if ($check_digit == 127) {
$check_digit = 0;
}

$dest .= $this->telepen_lookup_table[$check_digit];
$dest .= $this->telepen_lookup_table[ord(TELEPEN_STOP_CHAR)]; // Stop

return $dest;
}

private function createTelepenConversionTable()
{
$this->telepen_lookup_table = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be formatted better? Can you also provide a comment about how this lookup table works?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small amount of explanation added to table creation, and grouped items into consistent rows of 4 items

"1111111111111111", "1131313111", "33313111", "1111313131",
"3111313111", "11333131", "13133131", "111111313111", "31333111",
"1131113131", "33113131", "1111333111", "3111113131", "1113133111",
"1311133111", "111111113131", "3131113111", "11313331", "333331",
"111131113111", "31113331", "1133113111", "1313113111", "1111113331",
"31131331", "113111113111", "3311113111", "1111131331", "311111113111",
"1113111331", "1311111331", "11111111113111", "31313311", "1131311131",
"33311131", "1111313311", "3111311131", "11333311", "13133311",
"111111311131", "31331131", "1131113311", "33113311", "1111331131",
"3111113311", "1113131131", "1311131131", "111111113311", "3131111131",
"1131131311", "33131311", "111131111131", "3111131311", "1133111131",
"1313111131", "111111131311", "3113111311", "113111111131",
"3311111131", "111113111311", "311111111131", "111311111311",
"131111111311", "11111111111131", "3131311111", "11313133", "333133",
"111131311111", "31113133", "1133311111", "1313311111", "1111113133",
"313333", "113111311111", "3311311111", "11113333", "311111311111",
"11131333", "13111333", "11111111311111", "31311133", "1131331111",
"33331111", "1111311133", "3111331111", "11331133", "13131133",
"111111331111", "3113131111", "1131111133", "33111133", "111113131111",
"3111111133", "111311131111", "131111131111", "111111111133",
"31311313", "113131111111", "3331111111", "1111311313", "311131111111",
"11331313", "13131313", "11111131111111", "3133111111", "1131111313",
"33111313", "111133111111", "3111111313", "111313111111",
"131113111111", "111111111313", "313111111111", "1131131113",
"33131113", "11113111111111", "3111131113", "113311111111",
"131311111111", "111111131113", "3113111113", "11311111111111",
"331111111111", "111113111113", "31111111111111", "111311111113",
"131111111113"
];
}
}
16 changes: 16 additions & 0 deletions tests/TypesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,20 @@ public function test_generator_can_generate_pharma_code_2_tracks_barcode()

$this->assertGreaterThan(100, strlen($result));
}

public function test_generator_can_generate_telepen_alpha_barcode()
{
$generator = new Picqer\Barcode\BarcodeGeneratorSVG();
$result = $generator->getBarcode('1234567890ASCD', $generator::TYPE_TELEPEN_ALPHA);

$this->assertGreaterThan(100, strlen($result));
}

public function test_generator_can_generate_telepen_numeric_barcode()
{
$generator = new Picqer\Barcode\BarcodeGeneratorSVG();
$result = $generator->getBarcode('1234567890', $generator::TYPE_TELEPEN_NUMERIC);

$this->assertGreaterThan(100, strlen($result));
}
}
2 changes: 2 additions & 0 deletions tests/VerifiedBarcodeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class VerifiedBarcodeTest extends TestCase
['type' => BarcodeGenerator::TYPE_CODE_11, 'barcodes' => ['123456789']],
['type' => BarcodeGenerator::TYPE_PHARMA_CODE, 'barcodes' => ['123456789']],
['type' => BarcodeGenerator::TYPE_PHARMA_CODE_TWO_TRACKS, 'barcodes' => ['123456789']],
['type' => BarcodeGenerator::TYPE_TELEPEN_ALPHA, 'barcodes' => ['1234567890ASCD']],
['type' => BarcodeGenerator::TYPE_TELEPEN_NUMERIC, 'barcodes' => ['1234567890']]
];

public function testAllSupportedBarcodeTypes()
Expand Down
95 changes: 95 additions & 0 deletions tests/verified-files/TELEPENALPHA-1234567890ASCD.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions tests/verified-files/TELEPENNUMERIC-1234567890.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.