This commit is contained in:
Eden Kirin
2023-07-31 11:44:21 +02:00
commit 69f8ca2d6f
11 changed files with 7659 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
assets/qr-code-example.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

23
index.html Normal file
View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sticker Print Demo</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
<script type="text/javascript" src="lib/zpl-image/pako.js"></script>
<script type="text/javascript" src="lib/zpl-image/zpl-image.js"></script>
<script src="./sticker-print.js"></script>
</head>
<body>
<div class="container">
<h1>Sticker Print Demo</h1>
</div>
</body>
</html>

21
lib/zpl-image/LICENSE Normal file
View File

@ -0,0 +1,21 @@
zpl-image
Copyright 2019 Mark Warren
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

235
lib/zpl-image/README.md Normal file
View File

@ -0,0 +1,235 @@
# zpl-image
A pure javascript module that converts images to either Z64-encoded or ACS-encoded GRF bitmaps for use with ZPL.
The term ACS (Alternative Compression Scheme) denotes the run-length compression algorithm described in the section
of the ZPL Reference Manual titled "Alternative Data Compression Scheme". Z64 typically gives better compression
but is not available on all printers (especially older ones). The ACS encoding should work on any printer made
since the mid 90s, maybe earlier.
This module provides the following features:
- Works in both node.js and modern browsers.
- Converts the image to grayscale, then applies a user-supplied blackness
threshold to decide which pixels are black.
- Optionally removes any empty/white space around the edges of the image.
- Optionally rotates the image to one of the orthogonal angles. This step
is often necessary as ZPL does not provide the ability to rotate an image
during formatting.
- Converts the monochrome image to a GRF bitmap.
- Converts the GRF bitmap to either Z64 or ACS encoding.
- For Z64, zlib in node.js or pako.js in the browser is used for compression.
The blackness threshold is specified as an integer between 1 and 99 (think of it as a
gray percentage). Pixels darker than the gray% are converted to black. The default is 50.
Rotation is specified as one of the values:
- `'N'` : No rotation, the default.
- `'L'` : Left, 90 degrees counter-clockwise rotation.
- `'R'` : Right, 90 degrees clockwise rotation.
- `'I'` : Inverted, 180 degrees rotation.
- `'B'` : Same as `'L'` but named to match the ZPL notation.
Blackness and rotation are passed via an options object. For example, to specify
a black threshold of 56% and rotation of -90 degrees, you would pass in:
```javascript
{ black:56, rotate:'L' }
```
Trimming of empty space around the image is enabled by default. To disable, specify
the option `notrim:true`.
## Demo
Included with this module is the file `zpl-image.html`. You can run it directly
from the browser using the `file://` scheme. It lets you drag and drop an image
and then interactively adjust the blackness threshold and rotation.
When you are satisfied with the results, select either Z64 or ACS encoding and
click the clipboard icon to copy the ZPL. The ZPL will have the following format:
```
^FX filename.ext (WxHpx, X-Rotate, XX% Black)^FS
^GFA,grflen,grflen,rowlen,...ASCII-armored-encoding...
```
`^FX ... ^FS` is a ZPL comment.
`^GF` is the ZPL command for use-once image rendering (that is, the image is not
saved to the printer for later recall by other label formats).
The rendered image displayed on the page is the actual data decoded and then drawn
to a canvas. If you are interested in that bit of functionality, look for `z64ToCanvas`
and `acsToCanvas` in the `zpl-image.html` file.
## Generic Browser Usage
To use in the browser, include the following two scripts:
```html
<script type="text/javascript" src="url-path-to/pako.js"></script>
<script type="text/javascript" src="url-path-to/zpl-image.js"></script>
```
There is a version of pako.js included with this module, but it will not be updated
frequently. It is primarily intended for the demo html file but should be sufficient
for production use.
```javascript
// Works with <img> and <canvas> elements or any element that is
// compatible with CanvasRenderingContext2D.drawImage().
let img = document.getElementById('image');
let res = imageToZ64(img); // Uses all defaults
// res.length is the uncompressed GRF length.
// res.rowlen is the GRF row length.
// res.z64 is the Z64 encoded string.
let zpl = `^GFA,${res.length},${res.length},${res.rowlen},${res.z64}`;
```
An alternative for when you already have the pixel values in RGBA format
(either in a Uint8Array or Array of integers clamped to 0..255) is
`rgbaToZ64()`. This function is the lower-level converter used
by both node.js and `imageToZ64()`. See the node.js section for more details.
```javascript
// `rgba` is an array of RGBA values.
// `width` is the width of the image, in pixels.
// The return value is the same as above.
let res = rgbaToZ64(rgba, width, { black:55, rotate:'I' });
```
The same interfaces exist for ACS encoding, using the functions `imageToACS()` and
`rgbaToACS()`. The returned object from each function is identical to the above, with
the exception that the encoded text is in the `acs` property instead of `z64`.
## RequireJS Browser Usage
This is untested but the module exports are wrapped in a UMD, so in theory you
should be able to use this with RequireJS. The exports are the same as with the
generic browser usage:
```javascript
// Use the Z64 interface
const { imageToZ64, rgbaToZ64 } = require("zpl-image");
// Or the ACS interface
const { imageToACS, rgbaToACS } = require("zpl-image");
```
## Node.js Usage
The exports from `require("zpl-image")` are the functions `rgbaToZ64()` and
`rgbaToACS()`.
```javascript
// The Z64 interface
const rgbaToZ64 = require("zpl-image").rgbaToZ64;
// The ACS interface
const rgbaToACS = require("zpl-image").rgbaToACS;
```
Both methods take two or three parameters:
```
rgbaToZ64(rgba, width [, opts])
rgbaToACS(rgba, width [, opts])
```
`rgba` is an array-like object with length equal to `width * height * 4`.
An array-like object can be a Buffer, Uint8Array, or Array of integers
clamped to 0..255. `width` and `height` are the dimensions of the image, in pixels.
Each "quad" of the RGBA array is structured as:
```javascript
rgba[i] // red 0..255
rgba[i+1] // green 0..255
rgba[i+2] // blue 0..255
rgba[i+3] // alpha (0 == fully transparent, 255 == fully opaque)
```
Because of the varied nature of the node.js ecosystem, zpl-image does not include
any dependencies for image modules. You need to decide what types of images to
support and which image processing package(s) to use. Below are some simple
examples showing three different image modules:
- [pngjs](https://www.npmjs.com/package/pngjs)
- [omggif](https://www.npmjs.com/package/omggif)
- [jpeg-js](https://www.npmjs.com/package/jpeg-js)
All of the following examples show Z64 encoding but can be switched to ACS
by simply renaming `Z64` to `ACS`.
## pngjs (PNG Conversion)
[pngjs](https://www.npmjs.com/package/pngjs)
```javascript
// Synchronous pngjs usage
const fs = require('fs');
const PNG = require('pngjs').PNG;
const rgbaToZ64 = require('zpl-image').rgbaToZ64;
let buf = fs.readFileSync('tux.png');
let png = PNG.sync.read(buf);
let res = rgbaToZ64(png.data, png.width, { black:53 });
// res.length is the uncompressed GRF length.
// res.rowlen is the GRF row length.
// res.z64 is the Z64 encoded string.
let zpl = `^GFA,${res.length},${res.length},${res.rowlen},${res.z64}`;
```
```javascript
// Async pngjs usage
const fs = require('fs');
const PNG = require('pngjs').PNG;
const rgbaToZ64 = require('zpl-image').rgbaToZ64;
fs.createReadStream('tux.png')
.pipe(new PNG({ filterType: 4 }))
.on('parsed', function() {
// res is the same as above
let res = rgbaToZ64(this.data, this.width, { black:52, rotate:'R' });
});
```
## omggif (GIF Conversion)
[omggif](https://www.npmjs.com/package/omggif)
```javascript
const fs = require('fs');
const GIF = require('omggif');
const rgbaToZ64 = require('zpl-image').rgbaToZ64;
let buf = fs.readFileSync('tux.gif');
let gif = new GIF.GifReader(buf);
let rgba = Buffer.alloc(gif.width * gif.height * 4);
// Decode only the first frame
gif.decodeAndBlitFrameRGBA(0, rgba);
let res = rgbaToZ64(rgba, gif.width, { black:47 });
```
## jpeg-js (JPEG Conversion)
[jpeg-js](https://www.npmjs.com/package/jpeg-js)
```javascript
const fs = require('fs');
const JPG = require('jpeg-js');
const rgbaToZ64 = require('zpl-image').rgbaToZ64;
let buf = fs.readFileSync('tux.jpg');
let jpg = JPG.decode(buf);
let res = rgbaToZ64(jpg.data, jpg.width, { black:51, rotate:'I' });
```
## MIT License

22
lib/zpl-image/copyzpl.css Normal file
View File

@ -0,0 +1,22 @@
/* Keep the unwieldy data-url out of the main code */
#copyzpl {
position: absolute;
top: -2ex;
width: 48px;
height: 48px;
border: 1px solid #aaa;
border-radius: 4px;
background-color: #fff;
background-position:50% 50%;
background-repeat: no-repeat;
}
#copyzpl:hover {
background-color: #99beff;
}
#copyzpl:active {
background-color: #4c8dff;
}
#copyzpl {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAnCAYAAAAcsCj6AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAT5SURBVFhHtVdLbBtVFJ1fZvwZx6k99sSf1FVKPlWaBJKS8JVAlBIQqKpEJGDXCgnBogLBPguQKoRYIbFg0xWsK5AQFUWIRSVgUVEVyI8ShygORsqniZ2xx/PhvOfx1FOHJA2TI12/9+5789679557Z8wy+wfb0zOc3iqvntW2t5+s6TVLjkW/b0+nv/nj55+WMW076/xBb2+vEgqHr3Acp2Nosyxro1+TI5Frp554pru+ykdks7l3yQGCIFjRWPwrRVG+5Hm+goMtVe38zFnmD2zbZmOx+HXSTSY7L4+++Koy/tIraiAUukJ0gUBgbmpqiqOL9wDvtDticnJS3CiVnvrw0qUXKtXK2Zquy6Io5reKy8p6YXHI1KtjhmFkOZ4Xfr31W1WWo1a5vLWCRw8Wz4EHx4aDIfkm3GiSmEFF4mbBlRZxLfpUR+agM4LB4Myz586dpA//ByhLs9nsc6VS6QNd149i2OyatkqlErEsa1/uIgiHw3cQgpozJLDa2tqmoX+jUCjMMvF4/GEo/sYEve1hiShKhXgq1c9ZFvNarVZToaSAmxheEOrC7xriHUGeaTwPVztahtH1ago/H3O6Xul3dIwoSaXM0aNvh9vj40TkaOw8Nlh3pvcEiWkymZzgJGkQMtTV1fUyvPePM81omtZDfH4VfWq2qqqfonXjhblBkKOIbouLdhJczkR7BNIAm87lLjQIB1Ld9pKB437Hr1UfMEwkkdiUJOkv3JIEntlDLKTMzUQi0UwYOyjLMw3XomXZkCx/u10qnSYKVU1dLBZXPqGzwMTEhDQ9nx/ZKm9lONt2LyeJYsBkGdswdFLmKFiLNcOR0Hx+fv4Whu6le06cGL89O/sjmM6EQqFFRpYj30FPTU50pt+iqzywcT0Iqk1DSFWhlaVJR8VJs2Z0d/edauQrDvzTE8NkMvU6WeQnegeGH28c2BJDlAyDtrbtixAEQmKEdhx4DhRFXiLt0OjoKCxfAutoGbtfQTFfyh479gjZyzaYZhJ5DzQA0q4sLw8jZxQEmj2IYJvU5tqdPrIXSqNLLAJvWmA9adRM5of2aPQGgqzBUrwFZB39yr2yy9wvinLkBtnLyUEK9DmwVL6GPg2qqqbP0xkfcbz/5KMgDTGEsHSBWOje4DBgWYgiw5AKBCIxFvLZdpO0gcGRkVHcZonc7CCC6rSUyeUoaQSB5qcD2xtDpAsd+0kaQZICMMp97XhJg+uRxk/SmLpOvhZcK1k8dLVcLp8hA5DmQrFYuEz0uVxOwheA90L7BIq4tbi4WEXXPj4w8NjC9PR1WM6g0nhL22GwtLuptMHyBQ7WtrzW/STNPbDxiWG1pIWfpGkGyGMTC5vTggbXT9KILCuQ1kVzDFOZzEWiI5XeDyEYHhk7A6Po/i2vJ5bj6G0Ii/0QAq1S2W70CTwHms7bwk9U66XNBSltd49vKXL/HwL9RKkDlvJgMe9ayQn1SuMnONZsa8QTsJAWpmtXrVp9yOn6hoqmuXviYJsNhuWvtXLpeaKAuVpnKvVFpL1jVjd0ja5ywTNwgISXi2napjcusAMfzFLNMnQbiUh0sEnXSqWu9dXV91AiA0SHdJljYjHlo/r8XYGvfZPmfZVE4nM+FlOL+H/xtGkaCpSHhrAsz4Y7Ot4kDOK6+/pGNtfW3lnb2HjANs37/8u0C1DKmJiizISDwffz+fzcv7cGZ55/jBKaAAAAAElFTkSuQmCC);
}

View File

@ -0,0 +1,31 @@
{
"name": "zpl-image",
"version": "0.2.0",
"description": "A pure javascript module that converts PNG, JPEG, and GIF to Z64-encoded GRF bitmaps for use with ZPL.",
"main": "zpl-image.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/metafloor/zpl-image.git"
},
"keywords": [
"image",
"ZPL",
"Z64",
"GRF",
"PNG",
"JPEG",
"GIF"
],
"author": "Mark Warren",
"license": "MIT",
"bugs": {
"url": "https://github.com/metafloor/zpl-image/issues"
},
"homepage": "https://github.com/metafloor/zpl-image",
"directories": {
"test": "tests"
}
}

6441
lib/zpl-image/pako.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,333 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset=utf-8 />
<title>Image to ZPL</title>
<style>
* {
font-family:Arial, Helvetica, sans-serif;
font-size: 12px;
}
body, html {
border: 0;
margin: 0;
padding: 0;
}
#title {
background-color: #404040;
color: white;
padding: .75ex 2ex;
font-size: 16pt;
font-weight: bold;
}
#drop {
box-sizing: border-box;
width: 480px;
height: 90px;
text-align: center;
font-size: 16px;
line-height:61px; /* 90/2 + 16 */
color: #999;
border: 1px dashed #6495ED;
margin: 10px;
padding: 10px;
}
#drop.droppable {
background-color: #fcfeff;
color: #87cefa;
}
/* Original and rendered image text labels */
.label {
color: blue;
font-size: 125%;
font-weight: bold;
text-align: left;
padding-left: 10px;
padding-bottom: 8px;
}
/* Where the dropped and rendered images are displayed */
.content {
margin: 10px;
padding: 10px;
max-width: 600px;
max-height: 480px;
min-height: 480px;
overflow: auto;
text-align: center;
}
.content > * {
box-shadow: 0 0 0 1px blue;
}
/* Hidden but copy-able */
#zpltext {
opacity: .01;
position: absolute;
height: 0px;
}
table.fields th {
padding-left: 4ex;
text-align: right;
padding-right: 1.5ex;
}
table.fields th, table.fields td {
padding-bottom: 2ex;
}
/* Labels to the right of the input fields */
span {
font-weight: bold;
text-align: left;
padding-left: 1ex;
font-style: italic;
}
/* Black threshold */
input[type="number"] {
width: 4em;
border: 1px solid #6495ED;
border-radius: 3px;
padding: 3px .75ex;
padding-right: 1px;
text-align: right;
}
</style>
<link rel="stylesheet" type="text/css" href="copyzpl.css" />
<script type="text/javascript" src="pako.js"></script>
<script type="text/javascript" src="zpl-image.js"></script>
<script type="text/javascript">
let _filename = ''; // value set on image drop
window.addEventListener('load', function() {
document.getElementById('rotN').addEventListener('click', convertImage, false);
document.getElementById('rotL').addEventListener('click', convertImage, false);
document.getElementById('rotR').addEventListener('click', convertImage, false);
document.getElementById('rotI').addEventListener('click', convertImage, false);
document.getElementById('black').addEventListener('input', convertImage, false);
document.getElementById('notrim').addEventListener('click', convertImage, false);
document.getElementById('acscomp').addEventListener('click', convertImage, false);
document.getElementById('z64comp').addEventListener('click', convertImage, false);
document.getElementById('copyzpl').addEventListener('click', copyZPL, false);
// Setup the drop target
let drop = document.getElementById('drop');
function candrop(ev) {
ev.target.className = 'droppable';
ev.preventDefault();
return false;
}
function undrop(ev) {
ev.target.className = '';
}
drop.addEventListener('dragover', candrop, false);
drop.addEventListener('dragenter', candrop, false);
drop.addEventListener('dragleave', undrop, false);
drop.addEventListener('drop', function (ev) {
undrop(ev);
ev.preventDefault(); // stop the browser from redirecting
let xfer = ev.dataTransfer;
let files = xfer.files;
if (!files.length) {
return;
}
let file = files[0];
let reader = new FileReader();
reader.onloadend = function() {
let bin = this.result;
let img = document.getElementById('image');
_filename = file.name;
img.src = bin;
img.onload = convertImage;
// Make the img/canvas visible
let lbls = document.querySelectorAll('div.label');
for (let i = 0; i < lbls.length; i++) {
lbls[i].style.visibility = 'visible';
}
let divs = document.querySelectorAll('div.content');
for (let i = 0; i < divs.length; i++) {
divs[i].style.visibility = 'visible';
}
document.getElementById('copyzpl').style.visibility = 'visible';
}
reader.readAsDataURL(file);
return false;
}, false);
}, false);
function convertImage() {
if (!_filename) {
return;
}
let black = +document.getElementById('black').value || 50;
let rotrad = document.querySelector('input[name=rot]:checked');
let comprad = document.querySelector('input[name=compress]:checked');
let rot = rotrad && rotrad.value || 'N';
let comp = comprad && comprad.value || 'Z64';
let notrim = document.getElementById('notrim').checked;
// Get the image and convert to Z64
let img = document.getElementById('image');
let res;
let bmp; // actually a canvas object
if (comp == 'Z64') {
res = imageToZ64(img, { black:black, rotate:rot, notrim:notrim });
bmp = z64ToCanvas(res.z64, res.rowlen);
} else {
res = imageToACS(img, { black:black, rotate:rot, notrim:notrim });
bmp = acsToCanvas(res.acs, res.rowlen);
}
// Draw the image to our canvas
let cvs = document.getElementById('canvas');
cvs.width = bmp.width;
cvs.height = bmp.height;
cvs.getContext('2d').drawImage(bmp, 0, 0);
// Create the ZPL with a source comment
document.getElementById('zpltext').value =
'^FX ' + _filename + ' (' + res.width + 'x' + res.height + 'px, ' +
rot + '-Rotate, ' + black + '% Black)^FS\n' +
'^GFA,' + res.length + ',' + res.length + ',' + res.rowlen + ',' + (res.z64||res.acs) + '\n';
}
function copyZPL() {
let ta = document.getElementById('zpltext');
ta.select();
document.execCommand('copy');
}
function z64ToCanvas(z64, rowl) {
// Strip the ':Z64:' prefix and :XXXX crc16 suffix and convert to binary string.
// We do not validate the CRC.
let bin = atob(z64.substr(5, z64.length - 10));
// pako wants a Uint8Array
let buf = new Uint8Array(bin.length);
for (let i = 0, l = bin.length; i < l; i++) {
buf[i] = bin.charCodeAt(i);
}
let grf = pako.inflate(buf);
let l = grf.length;
let w = rowl * 8; // rowl is in bytes
let h = ~~(l / rowl);
// Render the GRF to a canvas
let cvs = document.createElement('canvas');
cvs.width = w;
cvs.height = h;
let ctx = cvs.getContext('2d');
let bmap = ctx.getImageData(0, 0, w, h);
let data = bmap.data;
let offs = 0;
for (let i = 0; i < l; i++) {
let byte = grf[i];
for (let bit = 0x80; bit; bit = bit >>> 1, offs += 4) {
if (bit & byte) {
data[offs] = 0;
data[offs+1] = 0;
data[offs+2] = 0;
data[offs+3] = 255; // Fully opaque
}
}
}
ctx.putImageData(bmap, 0, 0);
return cvs;
}
function acsToCanvas(acs, rowl) {
let hex = acs.replace(/[g-zG-Y]+([0-9a-fA-F])/g, ($0, $1) => {
let rep = 0;
for (let i = 0, l = $0.length-1; i < l; i++) {
let cd = $0.charCodeAt(i);
if (cd < 90) { // 'Z'
rep += cd - 70;
} else {
rep += (cd - 102) * 20;
}
}
return $1.repeat(rep);
});
let bytes = Array(hex.length/2);
for (let i = 0, l = hex.length; i < l; i += 2) {
bytes[i>>1] = parseInt(hex.substr(i,2), 16);
}
let l = bytes.length;
let w = rowl * 8; // rowl is in bytes
let h = ~~(l / rowl);
// Render the GRF to a canvas
let cvs = document.createElement('canvas');
cvs.width = w;
cvs.height = h;
let ctx = cvs.getContext('2d');
let bmap = ctx.getImageData(0, 0, w, h);
let data = bmap.data;
let offs = 0;
for (let i = 0; i < l; i++) {
let byte = bytes[i];
for (let bit = 0x80; bit; bit = bit >>> 1, offs += 4) {
if (bit & byte) {
data[offs] = 0;
data[offs+1] = 0;
data[offs+2] = 0;
data[offs+3] = 255; // Fully opaque
}
}
}
ctx.putImageData(bmap, 0, 0);
return cvs;
}
</script>
</head>
<body>
<div id="title">Image to ZPL</div>
<div>
<div style="display:inline-block">
<div id="drop">Drop image here</div>
</div>
<table style="display:inline-table" borders=0 class="fields">
<tr><th>Image Rotation<td colspan=2>
<label for="rotN"><input type="radio" name="rot" value="N"
id="rotN" checked>Normal</label>
<label for="rotR"><input type="radio" name="rot" value="R"
id="rotR">Right (CW)</label>
<label for="rotL"><input type="radio" name="rot" value="L"
id="rotL">Left (CCW)</label>
<label for="rotI"><input type="radio" name="rot" value="I"
id="rotI">Inverted</label>
<td colspan=2 style="position:relative;padding-left:10mm">
<button id="copyzpl" style="visibility:hidden"></button>
<tr><th>Black Threshold
<td><input type="number" id="black" min="1" max="99" step="1" value="50">
<span>1..99</span>
<td><label for="notrim">
<input type="checkbox" id="notrim" value="Y">&nbsp;No&nbsp;Trim</label>
<tr><th>ZPL Format<td colspan=2>
<label for="z64comp"><input type="radio" name="compress" value="Z64"
id="z64comp" checked>Z64</label>
<label for="acscomp"><input type="radio" name="compress" value="ACS"
id="acscomp">ACS</label>
</table>
</div>
<textarea rows=24 cols=112 id="zpltext" readonly></textarea>
<div style="display:inline-block">
<div class="label" style="visibility:hidden">Original Image:</div>
<div class="content" style="visibility:hidden"><img id="image"></div>
</div>
<div style="display:inline-block">
<div class="label" style="visibility:hidden">Rendered Image:</div>
<div class="content" style="visibility:hidden">
<canvas id="canvas" width=10 height=10></canvas>
</div>
</div>
</body>
</html>

444
lib/zpl-image/zpl-image.js Normal file
View File

@ -0,0 +1,444 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define([], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory();
} else {
// generic browser usage
let ex = factory();
for (let id in ex) {
root[id] = ex[id];
}
}
}(typeof self !== 'undefined' ? self : this, function () {
const zlib = typeof process == 'object' && typeof process.release == 'object' &&
process.release.name == 'node' ? require('zlib') : null;
const hexmap = (()=> {
let arr = Array(256);
for (let i = 0; i < 16; i++) {
arr[i] = '0' + i.toString(16);
}
for (let i = 16; i < 256; i++) {
arr[i] = i.toString(16);
}
return arr;
})();
// DOM-specialized version for browsers.
function imageToZ64(img, opts) {
// Draw the image to a temp canvas so we can access its RGBA data
let cvs = document.createElement('canvas');
let ctx = cvs.getContext('2d');
cvs.width = +img.width || img.offsetWidth;
cvs.height = +img.height || img.offsetHeight;
ctx.imageSmoothingQuality = 'high'; // in case canvas needs to scale image
ctx.drawImage(img, 0, 0, cvs.width, cvs.height);
let pixels = ctx.getImageData(0, 0, cvs.width, cvs.height);
return rgbaToZ64(pixels.data, pixels.width, opts);
}
// DOM-specialized version for browsers.
function imageToACS(img, opts) {
// Draw the image to a temp canvas so we can access its RGBA data
let cvs = document.createElement('canvas');
let ctx = cvs.getContext('2d');
cvs.width = +img.width || img.offsetWidth;
cvs.height = +img.height || img.offsetHeight;
ctx.imageSmoothingQuality = 'high'; // in case canvas needs to scale image
ctx.drawImage(img, 0, 0, cvs.width, cvs.height);
let pixels = ctx.getImageData(0, 0, cvs.width, cvs.height);
return rgbaToACS(pixels.data, pixels.width, opts);
}
// Uses zlib on node.js, pako.js in the browser.
//
// `rgba` can be a Uint8Array or Buffer, or an Array of integers between 0 and 255.
// `width` is the image width, in pixels
// `opts` is an options object:
// `black` is the blackness percent between 1..99, default 50.
// `rotate` is one of:
// 'N' no rotation (default)
// 'L' rotate 90 degrees counter-clockwise
// 'R' rotate 90 degrees clockwise
// 'I' rotate 180 degrees (inverted)
// 'B' same as 'L'
function rgbaToZ64(rgba, width, opts) {
opts = opts || {};
width = width|0;
if (!width || width < 0) {
throw new Error('Invalid width');
}
let height = ~~(rgba.length / width / 4);
// Create a monochome image, cropped to remove padding.
// The return is a Uint8Array with extra properties width and height.
let mono = monochrome(rgba, width, height, +opts.black || 50, opts.notrim);
let buf;
switch (opts.rotate) {
case 'R': buf = right(mono); break;
case 'B':
case 'L': buf = left(mono); break;
case 'I': buf = invert(mono); break;
default: buf = normal(mono); break;
}
// Compress and base64 encode
let imgw = buf.width;
let imgh = buf.height;
let rowl = ~~((imgw + 7) / 8);
let b64;
if (zlib) {
b64 = zlib.deflateSync(buf).toString('base64');
} else {
b64 = u8tob64(pako.deflate(buf));
}
// Example usage of the return value `rv`:
// '^GFA,' + rv.length + ',' + rv.length + ',' + rv.rowlen + ',' + rv.z64
return {
length: buf.length, // uncompressed number of bytes
rowlen: rowl, // number of packed bytes per row
width: imgw, // rotated image width in pixels
height: imgh, // rotated image height in pixels
z64: ':Z64:' + b64 + ':' + crc16(b64),
};
}
// Implements the Alternative Data Compression Scheme as described in the ref manual.
//
// `rgba` can be a Uint8Array or Buffer, or an Array of integers between 0 and 255.
// `width` is the image width, in pixels
// `opts` is an options object:
// `black` is the blackness percent between 1..99, default 50.
// `rotate` is one of:
// 'N' no rotation (default)
// 'L' rotate 90 degrees counter-clockwise
// 'R' rotate 90 degrees clockwise
// 'I' rotate 180 degrees (inverted)
// 'B' same as 'L'
function rgbaToACS(rgba, width, opts) {
opts = opts || {};
width = width|0;
if (!width || width < 0) {
throw new Error('Invalid width');
}
let height = ~~(rgba.length / width / 4);
// Create a monochome image, cropped to remove padding.
// The return is a Uint8Array with extra properties width and height.
let mono = monochrome(rgba, width, height, +opts.black || 50, opts.notrim);
let buf;
switch (opts.rotate) {
case 'R': buf = right(mono); break;
case 'B':
case 'L': buf = left(mono); break;
case 'I': buf = invert(mono); break;
default: buf = normal(mono); break;
}
// Encode in hex and apply the "Alternative Data Compression Scheme"
//
// G H I J K L M N O P Q R S T U V W X Y
// 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
//
// g h i j k l m n o p q r s t u v w x y z
// 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400
//
let imgw = buf.width;
let imgh = buf.height;
let rowl = ~~((imgw + 7) / 8);
let hex = '';
for (let i = 0, l = buf.length; i < l; i++) {
hex += hexmap[buf[i]];
}
let acs = '';
let re = /([0-9a-fA-F])\1{2,}/g;
let match = re.exec(hex);
let offset = 0;
while (match) {
acs += hex.substring(offset, match.index);
let l = match[0].length;
while (l >= 400) {
acs += 'z';
l -= 400;
}
if (l >= 20) {
acs += '_ghijklmnopqrstuvwxy'[((l / 20)|0)];
l = l % 20;
}
if (l) {
acs += '_GHIJKLMNOPQRSTUVWXY'[l];
}
acs += match[1];
offset = re.lastIndex;
match = re.exec(hex);
}
acs += hex.substr(offset);
// Example usage of the return value `rv`:
// '^GFA,' + rv.length + ',' + rv.length + ',' + rv.rowlen + ',' + rv.acs
return {
length: buf.length, // uncompressed number of bytes
rowlen: rowl, // number of packed bytes per row
width: imgw, // rotated image width in pixels
height: imgh, // rotated image height in pixels
acs: acs,
};
}
// Normal, unrotated case
function normal(mono) {
let width = mono.width;
let height = mono.height;
let buf = new Uint8Array(~~((width + 7) / 8) * height);
let idx = 0; // index into buf
let byte = 0; // current byte of image data
let bitx = 0; // bit index
for (let i = 0, n = mono.length; i < n; i++) {
byte |= mono[i] << (7 - (bitx++ & 7));
if (bitx == width || !(bitx & 7)) {
buf[idx++] = byte;
byte = 0;
if (bitx == width) {
bitx = 0;
}
}
}
buf.width = width;
buf.height = height;
return buf;
}
// Inverted 180 degrees
function invert(mono) {
let width = mono.width;
let height = mono.height;
let buf = new Uint8Array(~~((width + 7) / 8) * height);
let idx = 0; // index into buf
let byte = 0; // current byte of image data
let bitx = 0; // bit index
for (let i = mono.length-1; i >= 0; i--) {
byte |= mono[i] << (7 - (bitx++ & 7));
if (bitx == width || !(bitx & 7)) {
buf[idx++] = byte;
byte = 0;
if (bitx == width) {
bitx = 0;
}
}
}
buf.width = width;
buf.height = height;
return buf;
}
// Rotate 90 degrees counter-clockwise
function left(mono) {
let width = mono.width;
let height = mono.height;
let buf = new Uint8Array(~~((height + 7) / 8) * width);
let idx = 0; // index into buf
let byte = 0; // current byte of image data
for (let x = width - 1; x >= 0; x--) {
let bitx = 0; // bit index
for (let y = 0; y < height; y++) {
byte |= mono[y * width + x] << (7 - (bitx++ & 7));
if (y == height-1 || !(bitx & 7)) {
buf[idx++] = byte;
byte = 0;
}
}
}
buf.width = height;
buf.height = width;
return buf;
}
// Rotate 90 degrees clockwise
function right(mono) {
let width = mono.width;
let height = mono.height;
let buf = new Uint8Array(~~((height + 7) / 8) * width);
let idx = 0; // index into buf
let byte = 0; // current byte of image data
for (let x = 0; x < width; x++) {
let bitx = 0; // bit index
for (let y = height - 1; y >= 0; y--) {
byte |= mono[y * width + x] << (7 - (bitx++ & 7));
if (y == 0 || !(bitx & 7)) {
buf[idx++] = byte;
byte = 0;
}
}
}
buf.width = height;
buf.height = width;
return buf;
}
// Convert the RGBA to monochrome, 1-bit-per-byte. Crops
// empty space around the edges of the image if !notrim.
function monochrome(rgba, width, height, black, notrim) {
// Convert black from percent to 0..255 value
black = 255 * black / 100;
let minx, maxx, miny, maxy;
if (notrim) {
minx = miny = 0;
maxx = width-1;
maxy = height-1;
} else {
// Run through the image and determine bounding box
maxx = maxy = 0;
minx = width;
miny = height;
let x = 0, y = 0;
for (let i = 0, n = width * height * 4; i < n; i += 4) {
// Alpha blend with white.
let a = rgba[i+3] / 255;
let r = rgba[i] * .3 * a + 255 * (1 - a);
let g = rgba[i+1] * .59 * a + 255 * (1 - a);
let b = rgba[i+2] * .11 * a + 255 * (1 - a);
let gray = r + g + b;
if (gray <= black) {
if (minx > x) minx = x;
if (miny > y) miny = y;
if (maxx < x) maxx = x;
if (maxy < y) maxy = y;
}
if (++x == width) {
x = 0;
y++;
}
}
}
// One more time through the data, this time we create the cropped image.
let cx = maxx - minx + 1;
let cy = maxy - miny + 1;
let buf = new Uint8Array(cx * cy);
let idx = 0;
for (y = miny; y <= maxy; y++) {
let i = (y * width + minx) * 4;
for (x = minx; x <= maxx; x++) {
// Alpha blend with white.
let a = rgba[i+3] / 255;
let r = rgba[i] * .3 * a + 255 * (1 - a);
let g = rgba[i+1] * .59 * a + 255 * (1 - a);
let b = rgba[i+2] * .11 * a + 255 * (1 - a);
let gray = r + g + b;
buf[idx++] = gray <= black ? 1 : 0;
i += 4;
}
}
// Return the monochrome image
buf.width = cx;
buf.height = cy;
return buf;
}
// Cannot use btoa() with Uint8Arrays. Used only by the browser.
function u8tob64(a) {
let s = '';
let i = 0;
for (let l = a.length & 0xfffffff0; i < l; i += 16) {
s += String.fromCharCode(a[i],a[i+1],a[i+2],a[i+3],a[i+4],a[i+5],
a[i+6],a[i+7],a[i+8],a[i+9],a[i+10],
a[i+11],a[i+12],a[i+13],a[i+14],a[i+15]);
}
while (i < a.length) {
s += String.fromCharCode(a[i++]);
}
return btoa(s);
}
// CRC16 used by zebra
const crcTable = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5,
0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b,
0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210,
0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401,
0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b,
0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6,
0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5,
0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969,
0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96,
0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03,
0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd,
0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6,
0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a,
0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb,
0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1,
0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2,
0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb,
0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447,
0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2,
0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9,
0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827,
0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0,
0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d,
0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07,
0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba,
0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74,
0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
];
function crc16(s) {
// This is not an accumlating crc routine. Normally, the acc is intialized to
// 0xffff then inverted on each call. We just start with 0.
let crc = 0;
let j, i;
for (i = 0; i < s.length; i++) {
c = s.charCodeAt(i);
if (c > 255) {
throw new RangeError();
}
j = (c ^ (crc >> 8)) & 0xFF;
crc = crcTable[j] ^ (crc << 8);
}
crc = (crc & 0xffff).toString(16).toLowerCase();
return '0000'.substr(crc.length) + crc;
}
return zlib ? { rgbaToZ64, rgbaToACS } : { rgbaToZ64, rgbaToACS, imageToZ64, imageToACS };
}));

109
sticker-print.js Normal file
View File

@ -0,0 +1,109 @@
/*
^XA
^FX Template label 70x35mm
^FX --------- Agenzia Entrata logo
^FO20,85^GFA,2409,2409,33,,:::::I01LF8hL0806,I07LF8hK08812,I0MFChK0140B8,I0MFChI02384045E,001MFEhI0275802058,001MFEhI0AC6I0C44,001MFEhI0BFC004246,I0NFhH01768I0189,I0NFhI064K0C9,I07MF8hG0ACCK0E7C,J0MF8gI07V01B48K0422,L07JFCgI0FV02DDL0691,L0KFCgI0FW0EBI0800141,L0KFCgI0FV04FAI08001378,K01FI07EhG0FAL0102,K018J0Eh08F2I04I019,M03FE03J0FEF80FC03EFC03FFE03F8007F8P01E2L01414,L03IFEJ01IFC3FF03FFE03FFE07F801FFCP056O056,K01KFCI03IFC7FF83IF03FFE07F801FFEP01C5L01C42,K07LFI07C3F0F87C1F8F03FFE03F800E0FP0398M0648,K0MF800781E1E01E0F078387C0078J0FP03980041I0268,J03MFE00F00E1E01E0E07818F80078003FFP02B003C0E0017A,J07NF00F00E1IFE0E07801EI07800IFP03A08K0813C,J0OF80F00E1IFE0E07803CI07801IFP0D404J0100BC,I01OFC0F00E1IFE0E078078I07803E0FP0F4M0107C,I03OFC0701E1EJ0E0780F0E00780380FP0F8008L028,I03IF800IFE0783E0F00C1E0781F0E00780381FP0F0404001I039,I07FFCI01IF03FFE0IFE3F9FC3FFE0IFC3IFCO05060300200A91,I0IF8J0IF03FFE07FFE7F9FE3FFE0IFE3IFCN0130E0100201011,I0IFK07FF80FFE03FFC3F8FC3FFE0IFC1IFCO0B1B0200201511,001FFEK03FF8038E007CV038Q031BJ020141D,001FFCK01FFCI0EgS07B2M0D73,001FF8L0FFC001EgS03B1001CI0CCA,001QFC0FFEgS03F1003210104A,003QFC0FFCgR011F3J080124A,003QFE0FF8gS05FE01J0129,003QFE07CgT01EC0400180744,003QFEgX0FCM06D4,003QFEgW04ECM0DB,003QFEP0FU038O03ECM0BA8,003BPFEP0FU03CO01F402J01E9,003BPFEP0FU03CP0FAL017E,0033PFEP0FU03CP07AM058,0013PFEP0FU03CP03CM0E4,I03FCR01F3F003IF007E3F007F801IFC007F8I04DL034C,I03FCL07FF8003IFC07IF80FE7F83FFE03IFE01FFEI06F84I08089C,I03FCL03FF8003IFE0JF81JFC3IF03IFE03IF006241CI019804,00C3FFL03FFC003IFE07IF80JFC3IF01IFC07IF80021BCI01208,00C3FF8K03FFCI0FC1F00FJ01FE38I0F803CI07C0FC02605C004BC082,01E3FFEJ03IFCI0780F00FJ01FCK07803CI0F803C0320325IF001A,01E3IFE00JFEEI0700F00FJ01FJ07FF803CI0F003C0E881804870452,1FF1PFEFF00700F00FJ01EI01IF803CI0JFE03F400509818E4,7FF8PFE7F80700F00FJ01EI03IF803CI0JFE00780318604608,7FFCPFE7F80700F00FJ01EI07IF803CI0JFE014BDI26B433,IFE3OFC7FC0700F00FJ01EI0FC07803CI0FL049DA0D81404,JF1OFCFFC0700F00FJ01EI0F007803CI0FL021C0FEF0018,JF8OF1FFC0F00F00F01E01EI0F01F803C078FC01CI081E606FF6,JFE3MFC3FFC1FC1F80IFE0IFC0JFE03IF87IFEI078DC032,7JF0LFC1IF83FE3FC07FFE1IFE07JF03IF83IFEJ013001B8,7JFE1JF81JF83FE7FC03FFC1IFE03JF01IF01IFCJ03FI0F8,1KF8J03JFE01FC3FC01FE01IFC01FE7C007F8007FEL0E,,:::::^FS
^FX --------- QR Code
^FO300,5
^BQN,2,5,M,7
^FDQA,
https://www.zebra.com/content/dam/zebra_new_ia/en-us/manuals/printers/common/programming/zpl-zbi2-pm-en.pdf
^FS
^FX --------- Company name
^CF0,30
^FO20,15^FD
Vandelay Industries
^FS
^FX --------- External MSID
^CFA,20
^FO20,55^FD
ACS: EXT-MSID
^FS
^FX --------- Machine Model
^CFA,20
^FO20,180^FD
Model XL-123
^FS
^FX --------- MSID label
^CFA,20
^FO20,210^FD
ID ADE:
^FS
^FX --------- MSID, max 22 chars
^CFA,20
^FO20,240^FD
1234567890123456789012
^FS
^XZ
*/
const TEMPLATES = {
"sticker-70x35": {
name: "Sticker 70x35mm",
template: `
^XA
^FX Template label 70x35mm
^FX --------- Agenzia Entrata logo
^FO20,85^GFA,2409,2409,33,,:::::I01LF8hL0806,I07LF8hK08812,I0MFChK0140B8,I0MFChI02384045E,001MFEhI0275802058,001MFEhI0AC6I0C44,001MFEhI0BFC004246,I0NFhH01768I0189,I0NFhI064K0C9,I07MF8hG0ACCK0E7C,J0MF8gI07V01B48K0422,L07JFCgI0FV02DDL0691,L0KFCgI0FW0EBI0800141,L0KFCgI0FV04FAI08001378,K01FI07EhG0FAL0102,K018J0Eh08F2I04I019,M03FE03J0FEF80FC03EFC03FFE03F8007F8P01E2L01414,L03IFEJ01IFC3FF03FFE03FFE07F801FFCP056O056,K01KFCI03IFC7FF83IF03FFE07F801FFEP01C5L01C42,K07LFI07C3F0F87C1F8F03FFE03F800E0FP0398M0648,K0MF800781E1E01E0F078387C0078J0FP03980041I0268,J03MFE00F00E1E01E0E07818F80078003FFP02B003C0E0017A,J07NF00F00E1IFE0E07801EI07800IFP03A08K0813C,J0OF80F00E1IFE0E07803CI07801IFP0D404J0100BC,I01OFC0F00E1IFE0E078078I07803E0FP0F4M0107C,I03OFC0701E1EJ0E0780F0E00780380FP0F8008L028,I03IF800IFE0783E0F00C1E0781F0E00780381FP0F0404001I039,I07FFCI01IF03FFE0IFE3F9FC3FFE0IFC3IFCO05060300200A91,I0IF8J0IF03FFE07FFE7F9FE3FFE0IFE3IFCN0130E0100201011,I0IFK07FF80FFE03FFC3F8FC3FFE0IFC1IFCO0B1B0200201511,001FFEK03FF8038E007CV038Q031BJ020141D,001FFCK01FFCI0EgS07B2M0D73,001FF8L0FFC001EgS03B1001CI0CCA,001QFC0FFEgS03F1003210104A,003QFC0FFCgR011F3J080124A,003QFE0FF8gS05FE01J0129,003QFE07CgT01EC0400180744,003QFEgX0FCM06D4,003QFEgW04ECM0DB,003QFEP0FU038O03ECM0BA8,003BPFEP0FU03CO01F402J01E9,003BPFEP0FU03CP0FAL017E,0033PFEP0FU03CP07AM058,0013PFEP0FU03CP03CM0E4,I03FCR01F3F003IF007E3F007F801IFC007F8I04DL034C,I03FCL07FF8003IFC07IF80FE7F83FFE03IFE01FFEI06F84I08089C,I03FCL03FF8003IFE0JF81JFC3IF03IFE03IF006241CI019804,00C3FFL03FFC003IFE07IF80JFC3IF01IFC07IF80021BCI01208,00C3FF8K03FFCI0FC1F00FJ01FE38I0F803CI07C0FC02605C004BC082,01E3FFEJ03IFCI0780F00FJ01FCK07803CI0F803C0320325IF001A,01E3IFE00JFEEI0700F00FJ01FJ07FF803CI0F003C0E881804870452,1FF1PFEFF00700F00FJ01EI01IF803CI0JFE03F400509818E4,7FF8PFE7F80700F00FJ01EI03IF803CI0JFE00780318604608,7FFCPFE7F80700F00FJ01EI07IF803CI0JFE014BDI26B433,IFE3OFC7FC0700F00FJ01EI0FC07803CI0FL049DA0D81404,JF1OFCFFC0700F00FJ01EI0F007803CI0FL021C0FEF0018,JF8OF1FFC0F00F00F01E01EI0F01F803C078FC01CI081E606FF6,JFE3MFC3FFC1FC1F80IFE0IFC0JFE03IF87IFEI078DC032,7JF0LFC1IF83FE3FC07FFE1IFE07JF03IF83IFEJ013001B8,7JFE1JF81JF83FE7FC03FFC1IFE03JF01IF01IFCJ03FI0F8,1KF8J03JFE01FC3FC01FE01IFC01FE7C007F8007FEL0E,,:::::^FS
^FX --------- QR Code
^FO300,5
^BQN,2,5,M,7
^FDQA,{qrCodeUrl}^FS
^FX --------- Company name
^CF0,30
^FO20,15^FD{companyName}^FS
^FX --------- External MSID
^CFA,20
^FO20,55^FDACS: {externalMasterSystemId}^FS
^FX --------- Machine Model
^CFA,20
^FO20,180^FD{machineModel}^FS
^FX --------- MSID label
^CFA,20
^FO20,210^FDID ADE:^FS
^FX --------- MSID, max 22 chars
^CFA,20
^FO20,240^FD{masterSystemId}^FS
^XZ
`,
},
};
function createInternalStickerZPL(template, content) {
return template
.replace("{qrCodeUrl}", content.qrCodeUrl)
.replace("{companyName}", content.companyName)
.replace("{machineModel}", content.machineModel)
.replace("{masterSystemId}", content.masterSystemId)
.replace("{externalMasterSystemId}", content.externalMasterSystemId);
}
function imageToGRF() {}
const internalTicketZPL = createInternalStickerZPL(TEMPLATES["sticker-70x35"].template, {
qrCodeUrl: "https://en.wikipedia.org/wiki/Zebra_Programming_Language",
companyName: "Vandelay Industries",
machineModel: "Model XL-123",
masterSystemId: "master-system-id",
externalMasterSystemId: "ext-msid",
});
console.log(internalTicketZPL);