diff --git a/apps/cards/ChangeLog b/apps/cards/ChangeLog index e7e13b8e73..b35947cdad 100644 --- a/apps/cards/ChangeLog +++ b/apps/cards/ChangeLog @@ -2,3 +2,4 @@ 0.02: Hiding widgets while showing the code 0.03: Added option to use max brightness when showing code 0.04: Minor code improvements +0.05: Add EAN & UPC codes diff --git a/apps/cards/EAN.js b/apps/cards/EAN.js new file mode 100644 index 0000000000..177874494d --- /dev/null +++ b/apps/cards/EAN.js @@ -0,0 +1,73 @@ +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); + +// Standard start end and middle bits +const SIDE_BIN = '101'; +const MIDDLE_BIN = '01010'; + +// Base class for EAN8 & EAN13 +class EAN extends Barcode { + constructor(data, options) { + super(data, options); + } + + leftText(from, to) { + return this.text.substr(from, to); + } + + leftEncode(data, structure) { + return encode(data, structure); + } + + rightText(from, to) { + return this.text.substr(from, to); + } + + rightEncode(data, structure) { + return encode(data, structure); + } + + encode() { + const data = [ + SIDE_BIN, + this.leftEncode(), + MIDDLE_BIN, + this.rightEncode(), + SIDE_BIN + ]; + + return { + data: data.join(''), + text: this.text + }; + } + +} + +module.exports = EAN; \ No newline at end of file diff --git a/apps/cards/EAN13.js b/apps/cards/EAN13.js new file mode 100644 index 0000000000..c91e385e3e --- /dev/null +++ b/apps/cards/EAN13.js @@ -0,0 +1,92 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const EAN = require("cards.EAN.js"); + +const EAN13_STRUCTURE = [ + 'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG', + 'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL' +]; + +// Calculate the checksum digit +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit +const checksum = (number) => { + const res = number + .substr(0, 12) + .split('') + .map((n) => +n) + .reduce((sum, a, idx) => ( + idx % 2 ? sum + a * 3 : sum + a + ), 0); + + return (10 - (res % 10)) % 10; +}; + +class EAN13 extends EAN { + + constructor(data, options) { + // Add checksum if it does not exist + if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) { + data += checksum(data); + } + + super(data, options); + + // Adds a last character to the end of the barcode + this.lastChar = options.lastChar; + } + + valid() { + return ( + /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && + +this.data[12] === checksum(this.data) + ); + } + + leftText() { + return super.leftText(1, 6); + } + + leftEncode() { + const data = this.data.substr(1, 6); + const structure = EAN13_STRUCTURE[this.data[0]]; + return super.leftEncode(data, structure); + } + + rightText() { + return super.rightText(7, 6); + } + + rightEncode() { + const data = this.data.substr(7, 6); + return super.rightEncode(data, 'RRRRRR'); + } + +} + +module.exports = EAN13; \ No newline at end of file diff --git a/apps/cards/EAN8.js b/apps/cards/EAN8.js new file mode 100644 index 0000000000..382ee647a9 --- /dev/null +++ b/apps/cards/EAN8.js @@ -0,0 +1,82 @@ +// Encoding documentation: +// http://www.barcodeisland.com/ean8.phtml +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const EAN = require("cards.EAN.js"); + +// Calculate the checksum digit +const checksum = (number) => { + const res = number + .substr(0, 7) + .split('') + .map((n) => +n) + .reduce((sum, a, idx) => ( + idx % 2 ? sum + a : sum + a * 3 + ), 0); + + return (10 - (res % 10)) % 10; +}; + +class EAN8 extends EAN { + + constructor(data, options) { + // Add checksum if it does not exist + if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) { + data += checksum(data); + } + + super(data, options); + } + + valid() { + return ( + /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && + +this.data[7] === checksum(this.data) + ); + } + + leftText() { + return super.leftText(0, 4); + } + + leftEncode() { + const data = this.data.substr(0, 4); + return super.leftEncode(data, 'LLLL'); + } + + rightText() { + return super.rightText(4, 4); + } + + rightEncode() { + const data = this.data.substr(4, 4); + return super.rightEncode(data, 'RRRR'); + } + +} + +module.exports = EAN8; diff --git a/apps/cards/UPC.js b/apps/cards/UPC.js new file mode 100644 index 0000000000..6f581ca18d --- /dev/null +++ b/apps/cards/UPC.js @@ -0,0 +1,79 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); + +class UPC extends Barcode{ + constructor(data, options){ + // Add checksum if it does not exist + if(/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ + data += checksum(data); + } + + super(data, options); + } + + valid(){ + return /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) && + this.data[11] == checksum(this.data); + } + + encode(){ + var result = ""; + + result += "101"; + result += encode(this.data.substr(0, 6), "LLLLLL"); + result += "01010"; + result += encode(this.data.substr(6, 6), "RRRRRR"); + result += "101"; + + return { + data: result, + text: this.text + }; + } +} + +// Calulate the checksum digit +// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit +function checksum(number){ + var result = 0; + + var i; + for(i = 1; i < 11; i += 2){ + result += parseInt(number[i]); + } + for(i = 0; i < 11; i += 2){ + result += parseInt(number[i]) * 3; + } + + return (10 - (result % 10)) % 10; +} + +module.exports = { UPC, checksum }; diff --git a/apps/cards/UPCE.js b/apps/cards/UPCE.js new file mode 100644 index 0000000000..9aaa464b73 --- /dev/null +++ b/apps/cards/UPCE.js @@ -0,0 +1,134 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding +// +// UPC-E documentation: +// https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const encode = require("cards.encode.js"); +const Barcode = require("cards.Barcode.js"); +const upc = require("cards.UPC.js"); + +const EXPANSIONS = [ + "XX00000XXX", + "XX10000XXX", + "XX20000XXX", + "XXX00000XX", + "XXXX00000X", + "XXXXX00005", + "XXXXX00006", + "XXXXX00007", + "XXXXX00008", + "XXXXX00009" +]; + +const PARITIES = [ + ["EEEOOO", "OOOEEE"], + ["EEOEOO", "OOEOEE"], + ["EEOOEO", "OOEEOE"], + ["EEOOOE", "OOEEEO"], + ["EOEEOO", "OEOOEE"], + ["EOOEEO", "OEEOOE"], + ["EOOOEE", "OEEEOO"], + ["EOEOEO", "OEOEOE"], + ["EOEOOE", "OEOEEO"], + ["EOOEOE", "OEEOEO"] +]; + +class UPCE extends Barcode{ + constructor(data, options){ + // Code may be 6 or 8 digits; + // A 7 digit code is ambiguous as to whether the extra digit + // is a UPC-A check or number system digit. + super(data, options); + this.isValid = false; + if(/^[0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ + this.middleDigits = data; + this.upcA = expandToUPCA(data, "0"); + this.text = options.text || + `${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`; + this.isValid = true; + } + else if(/^[01][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){ + this.middleDigits = data.substring(1, data.length - 1); + this.upcA = expandToUPCA(this.middleDigits, data[0]); + + if(this.upcA[this.upcA.length - 1] === data[data.length - 1]){ + this.isValid = true; + } + else{ + // checksum mismatch + return; + } + } + } + + valid(){ + return this.isValid; + } + + encode(){ + var result = ""; + + result += "101"; + result += this.encodeMiddleDigits(); + result += "010101"; + + return { + data: result, + text: this.text + }; + } + + encodeMiddleDigits() { + const numberSystem = this.upcA[0]; + const checkDigit = this.upcA[this.upcA.length - 1]; + const parity = PARITIES[parseInt(checkDigit)][parseInt(numberSystem)]; + return encode(this.middleDigits, parity); + } +} + +function expandToUPCA(middleDigits, numberSystem) { + const lastUpcE = parseInt(middleDigits[middleDigits.length - 1]); + const expansion = EXPANSIONS[lastUpcE]; + + let result = ""; + let digitIndex = 0; + for(let i = 0; i < expansion.length; i++) { + let c = expansion[i]; + if (c === 'X') { + result += middleDigits[digitIndex++]; + } else { + result += c; + } + } + + result = `${numberSystem}${result}`; + return `${result}${upc.checksum(result)}`; +} + +module.exports = UPCE; \ No newline at end of file diff --git a/apps/cards/app.js b/apps/cards/app.js index c0bacc9180..45e72831cd 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -82,16 +82,17 @@ function printSquareCode(binary, size) { } } function printLinearCode(binary) { + var padding = 5; var yFrom = 15; var yTo = 28; - var width = g.getWidth()/binary.length; + var width = (g.getWidth()-(2*padding))/binary.length; for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(BLACK).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(WHITE).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } @@ -132,6 +133,42 @@ function showCode(card) { printLinearCode(code.encode().data); break; } + case "EAN_8": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN8 = require("cards.EAN8.js"); + let code = new EAN8(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "EAN_13": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const EAN13 = require("cards.EAN13.js"); + let code = new EAN13(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "UPC_A": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const UPC = require("cards.UPC.js"); + let code = new UPC.UPC(card.value, {}); + printLinearCode(code.encode().data); + break; + } + case "UPC_E": { + g.setFont("Vector:20"); + g.setFontAlign(0,1).setColor(BLACK); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); + const UPCE = require("cards.UPCE.js"); + let code = new UPCE(card.value, {}); + printLinearCode(code.encode().data); + break; + } default: g.clear(true); g.setFont("Vector:30"); diff --git a/apps/cards/encode.js b/apps/cards/encode.js new file mode 100644 index 0000000000..7cb1fcc870 --- /dev/null +++ b/apps/cards/encode.js @@ -0,0 +1,67 @@ +/* + * JS source adapted from https://github.com/lindell/JsBarcode + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Johan Lindell (johan@lindell.me) + * + * 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. + */ + +const BINARIES = { + 'L': [ // The L (left) type of encoding + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'G': [ // The G type of encoding + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ], + 'R': [ // The R (right) type of encoding + '1110010', '1100110', '1101100', '1000010', '1011100', + '1001110', '1010000', '1000100', '1001000', '1110100' + ], + 'O': [ // The O (odd) encoding for UPC-E + '0001101', '0011001', '0010011', '0111101', '0100011', + '0110001', '0101111', '0111011', '0110111', '0001011' + ], + 'E': [ // The E (even) encoding for UPC-E + '0100111', '0110011', '0011011', '0100001', '0011101', + '0111001', '0000101', '0010001', '0001001', '0010111' + ] +}; + +// Encode data string +const encode = (data, structure, separator) => { + let encoded = data + .split('') + .map((val, idx) => BINARIES[structure[idx]]) + .map((val, idx) => val ? val[data[idx]] : ''); + + if (separator) { + const last = data.length - 1; + encoded = encoded.map((val, idx) => ( + idx < last ? val + separator : val + )); + } + + return encoded.join(''); +}; + +module.exports = encode; \ No newline at end of file diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 4bc3c9ae1e..c8d19a3753 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -1,12 +1,13 @@ { "id": "cards", "name": "Cards", - "version": "0.04", + "version": "0.05", "description": "Display loyalty cards", "icon": "app.png", "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], "tags": "cards", "supports": ["BANGLEJS","BANGLEJS2"], + "allow_emulator": true, "readme": "README.md", "storage": [ {"name":"cards.app.js","url":"app.js"}, @@ -15,6 +16,12 @@ {"name":"cards.qrcode.js","url":"qrcode.js"}, {"name":"cards.codabar.js","url":"codabar.js"}, {"name":"cards.code39.js","url":"code39.js"}, + {"name":"cards.EAN.js","url":"EAN.js"}, + {"name":"cards.EAN8.js","url":"EAN8.js"}, + {"name":"cards.EAN13.js","url":"EAN13.js"}, + {"name":"cards.UPC.js","url":"UPC.js"}, + {"name":"cards.UPCE.js","url":"UPCE.js"}, + {"name":"cards.encode.js","url":"encode.js"}, {"name":"cards.img","url":"app-icon.js","evaluate":true} ], "data": [{"name":"cards.settings.json"}]