diff --git a/README.md b/README.md
index 3f755b7..f695c6b 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
### Generate barcodes from a single PHP file. MIT license.
* Output to PNG, GIF, JPEG, or SVG.
- * Generates UPC-A, UPC-E, EAN-13, EAN-8, Code 39, Code 93, Code 128, Codabar, ITF, QR Code, and Data Matrix.
+ * Generates UPC-A, UPC-E, EAN-13, EAN-8, Code 39, Code 93, Code 128, GS1 128, Codabar, ITF, QR Code, GS1 QR Code, DataMatrix and GS1 DataMatrix.
Use from a PHP script:
@@ -50,13 +50,13 @@ barcode.php?f=svg&s=qr&d=HELLO%20WORLD&sf=8&ms=r&md=0.8
`s` - Symbology (type of barcode). One of:
```
- upc-a code-39 qr dmtx
- upc-e code-39-ascii qr-l dmtx-s
- ean-8 code-93 qr-m dmtx-r
- ean-13 code-93-ascii qr-q gs1-dmtx
- ean-13-pad code-128 qr-h gs1-dmtx-s
- ean-13-nopad codabar gs1-dmtx-r
- ean-128 itf
+ upc-a code-39 qr gs1-qr-q gs1-dmtx-r
+ upc-e code-39-ascii qr-l gs1-qr-h
+ ean-8 code-93 qr-m dmtx
+ ean-13 code-93-ascii qr-q dmtx-s
+ ean-13-pad code-128 qr-h dmtx-r
+ ean-13-nopad codabar gs1-qr-l gs1-dmtx
+ ean-128 itf gs1-qr-m gs1-dmtx-s
```
`d` - Data. For UPC or EAN, use `*` for missing digit. For Codabar, use `ABCD` or `ENT*` for start and stop characters. For QR, encode in Shift-JIS for kanji mode.
@@ -110,3 +110,17 @@ barcode.php?f=svg&s=qr&d=HELLO%20WORLD&sf=8&ms=r&md=0.8
`ww` - Width of wide modules and spaces. Applies to Code 39, Codabar, and ITF only. Default is 3.
`wn` - Width of narrow space between characters. Applies to Code 39 and Codabar only. Default is 1.
+
+#### Keywords:
+
+`\FNC1`
+- used in `d` - Data option as a part of the data string.
+- when used, it is replaced with a Group separator <GS> ASCII char 29 in encoded data string.
+- it should be used to terminate variable length GS1 Application Identifiers in GS1 128 and GS1 DataMatrix barcodes.
+- if needed, you can replace `\FNC1` keyword with `yourKeyword` in barcode.php file, the functionality will remain. Do not forget to replace all occurrences.
+- available in barcode symbologies:
+```
+ ean-128 gs1-dmtx-s gs1-qr-l gs1-qr-q
+ gs1-dmtx gs1-dmtx-r gs1-qr-m gs1-qr-h
+```
+- Do not confuse this with <FNC1> character. Initially, it was intended to use the <FNC1> character as a separator character, but since according to [this stackoverflow answer](https://stackoverflow.com/questions/31318648/what-is-the-actual-hex-binary-value-of-the-gs1-fnc1-character/31322815#31322815) by Terry Burton, FNC1 is a non-data character that requires special treatment. And I dont want to fizzle around this when it is not necessary. Instead, I decided to use the <GS> character. According to the [GS1 General Specifications](https://www.gs1.org/standards/barcodes-epcrfid-id-keys/gs1-general-specifications), the <FNC1> and <GS> characters are in the role of a separator character substitutes. The `\FNC1` remained as a keyword for its uniqueness. If you need, you can replace it with `\GS` or any other keyword you consider unique.
diff --git a/barcode-gs1-variable-length.html b/barcode-gs1-variable-length.html
new file mode 100644
index 0000000..da617d2
--- /dev/null
+++ b/barcode-gs1-variable-length.html
@@ -0,0 +1,92 @@
+
+
+
+ barcode.php gs1 variable length strings test
+
+
+
+
+
+ GS1-128 codes with variable length Application Identifiers
+ 0
+
+ 1
+
+ 2
+
+
+ GS1-DataMatrix codes with variable length Application Identifiers
+ 3
+
+
+
+
+
+ 4
+
+
+
+ - (01) 08580000000009
+ - (10) LOT123
+
+
+
+ 5
+
+
+
+ - (01) 08580000000009
+ - (10) LOT123
+ - (21) 1234567
+
+
+
+ GS1-QR codes with variable length Application Identifiers
+ 3
+
+
+
+
+
+ 4
+
+
+
+ - (01) 08580000000009
+ - (10) LOT123
+
+
+
+ 5
+
+
+
+ - (01) 08580000000009
+ - (10) LOT123
+ - (21) 1234567
+
+
+
+
+
\ No newline at end of file
diff --git a/barcode.php b/barcode.php
index b111501..7f2a7fb 100644
--- a/barcode.php
+++ b/barcode.php
@@ -239,11 +239,15 @@ private function dispatch_encode($symbology, $data, $options) {
case 'codabar' : return $this->codabar_encode($data);
case 'itf' : return $this->itf_encode($data);
case 'itf14' : return $this->itf_encode($data);
- case 'qr' : return $this->qr_encode($data, 0);
- case 'qrl' : return $this->qr_encode($data, 0);
- case 'qrm' : return $this->qr_encode($data, 1);
- case 'qrq' : return $this->qr_encode($data, 2);
- case 'qrh' : return $this->qr_encode($data, 3);
+ case 'qr' : return $this->qr_encode($data, 0, false);
+ case 'qrl' : return $this->qr_encode($data, 0, false);
+ case 'qrm' : return $this->qr_encode($data, 1, false);
+ case 'qrq' : return $this->qr_encode($data, 2, false);
+ case 'qrh' : return $this->qr_encode($data, 3, false);
+ case 'gs1qrl' : return $this->qr_encode($data, 0, true);
+ case 'gs1qrm' : return $this->qr_encode($data, 1, true);
+ case 'gs1qrq' : return $this->qr_encode($data, 2, true);
+ case 'gs1qrh' : return $this->qr_encode($data, 3, true);
case 'dmtx' : return $this->dmtx_encode($data,false,false);
case 'dmtxs' : return $this->dmtx_encode($data,false,false);
case 'dmtxr' : return $this->dmtx_encode($data, true,false);
@@ -1414,7 +1418,8 @@ private function code_93_ascii_encode($data) {
private function code_128_encode($data, $dstate, $fnc1) {
$data = preg_replace('/[\x80-\xFF]/', '', $data);
- $label = preg_replace('/[\x00-\x1F\x7F]/', ' ', $data);
+ $labelData = str_replace('\FNC1', '', $data);
+ $label = preg_replace('/[\x00-\x1F\x7F]/', ' ', $labelData);
$chars = $this->code_128_normalize($data, $dstate, $fnc1);
$checksum = $chars[0] % 103;
for ($i = 1, $n = count($chars); $i < $n; $i++) {
@@ -1437,6 +1442,7 @@ private function code_128_encode($data, $dstate, $fnc1) {
}
private function code_128_normalize($data, $dstate, $fnc1) {
+ $data = str_replace('\FNC1', chr(29), $data);
$detectcba = '/(^[0-9]{4,}|^[0-9]{2}$)|([\x60-\x7F])|([\x00-\x1F])/';
$detectc = '/(^[0-9]{6,}|^[0-9]{4,}$)/';
$detectba = '/([\x60-\x7F])|([\x00-\x1F])/';
@@ -1727,8 +1733,9 @@ private function itf_encode($data) {
/* - - - - QR ENCODER - - - - */
- private function qr_encode($data, $ecl) {
- list($mode, $vers, $ec, $data) = $this->qr_encode_data($data, $ecl);
+ private function qr_encode($data, $ecl, $fnc1) {
+ $data = str_replace('\FNC1', chr(29), $data);
+ list($mode, $vers, $ec, $data) = $this->qr_encode_data($data, $ecl, $fnc1);
$data = $this->qr_encode_ec($data, $ec, $vers);
list($size, $mtx) = $this->qr_create_matrix($vers, $data);
list($mask, $mtx) = $this->qr_apply_best_mask($mtx, $size);
@@ -1741,7 +1748,7 @@ private function qr_encode($data, $ecl) {
);
}
- private function qr_encode_data($data, $ecl) {
+ private function qr_encode_data($data, $ecl, $fnc1) {
$mode = $this->qr_detect_mode($data);
$version = $this->qr_detect_version($data, $mode, $ecl);
$version_group = (($version < 10) ? 0 : (($version < 27) ? 1 : 2));
@@ -1751,18 +1758,19 @@ private function qr_encode_data($data, $ecl) {
if ($mode == 3) $max_chars <<= 1;
$data = substr($data, 0, $max_chars);
/* Convert from character level to bit level. */
+ error_log($mode, 0);
switch ($mode) {
case 0:
- $code = $this->qr_encode_numeric($data, $version_group);
+ $code = $this->qr_encode_numeric($data, $version_group, $fnc1);
break;
case 1:
- $code = $this->qr_encode_alphanumeric($data, $version_group);
+ $code = $this->qr_encode_alphanumeric($data, $version_group, $fnc1);
break;
case 2:
- $code = $this->qr_encode_binary($data, $version_group);
+ $code = $this->qr_encode_binary($data, $version_group, $fnc1);
break;
case 3:
- $code = $this->qr_encode_kanji($data, $version_group);
+ $code = $this->qr_encode_kanji($data, $version_group, $fnc1);
break;
}
for ($i = 0; $i < 4; $i++) $code[] = 0;
@@ -1812,8 +1820,12 @@ private function qr_detect_version($data, $mode, $ecl) {
return 40;
}
- private function qr_encode_numeric($data, $version_group) {
- $code = array(0, 0, 0, 1);
+ private function qr_encode_numeric($data, $version_group, $fnc1) {
+ if ($fnc1 === true) {
+ $code = array(0, 1, 0, 1, 0, 0, 0, 1);
+ } else {
+ $code = array(0, 0, 0, 1);
+ }
$length = strlen($data);
switch ($version_group) {
case 2: /* 27 - 40 */
@@ -1855,9 +1867,13 @@ private function qr_encode_numeric($data, $version_group) {
return $code;
}
- private function qr_encode_alphanumeric($data, $version_group) {
+ private function qr_encode_alphanumeric($data, $version_group, $fnc1) {
$alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:';
- $code = array(0, 0, 1, 0);
+ if ($fnc1 === true) {
+ $code = array(0, 1, 0, 1, 0, 0, 1, 0);
+ } else {
+ $code = array(0, 0, 1, 0);
+ }
$length = strlen($data);
switch ($version_group) {
case 2: /* 27 - 40 */
@@ -1907,8 +1923,12 @@ private function qr_encode_alphanumeric($data, $version_group) {
return $code;
}
- private function qr_encode_binary($data, $version_group) {
- $code = array(0, 1, 0, 0);
+ private function qr_encode_binary($data, $version_group, $fnc1) {
+ if ($fnc1 === true) {
+ $code = array(0, 1, 0, 1, 0, 1, 0, 0);
+ } else {
+ $code = array(0, 1, 0, 0);
+ }
$length = strlen($data);
switch ($version_group) {
case 2: /* 27 - 40 */
@@ -1945,8 +1965,12 @@ private function qr_encode_binary($data, $version_group) {
return $code;
}
- private function qr_encode_kanji($data, $version_group) {
- $code = array(1, 0, 0, 0);
+ private function qr_encode_kanji($data, $version_group, $fnc1) {
+ if ($fnc1 === true) {
+ $code = array(0, 1, 0, 1, 1, 0, 0, 0);
+ } else {
+ $code = array(1, 0, 0, 0);
+ }
$length = strlen($data);
switch ($version_group) {
case 2: /* 27 - 40 */
@@ -2885,6 +2909,7 @@ private function dmtx_encode($data, $rect, $fnc1) {
private function dmtx_encode_data($data, $rect, $fnc1) {
/* Convert to data codewords. */
+ $data = str_replace('\FNC1', chr(29), $data);
$edata = ($fnc1 ? array(232) : array());
$length = strlen($data);
$offset = 0;