From d5c445f47254ec8865b785854bcf8c170985f2f1 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 15 Aug 2023 14:44:21 +0200 Subject: [PATCH 1/9] Added `cards` app This app will be able to render cards as synchronized by Catima --- apps/cards/Barcode.js | 9 + apps/cards/README.md | 44 ++ apps/cards/app-icon.js | 1 + apps/cards/app.js | 183 ++++++ apps/cards/app.png | Bin 0 -> 218 bytes apps/cards/codabar.js | 63 +++ apps/cards/code39.js | 105 ++++ apps/cards/metadata.json | 21 + apps/cards/qrcode.js | 675 +++++++++++++++++++++++ apps/cards/screenshot_cards_barcode.png | Bin 0 -> 2222 bytes apps/cards/screenshot_cards_card1.png | Bin 0 -> 2643 bytes apps/cards/screenshot_cards_overview.png | Bin 0 -> 2287 bytes apps/cards/screenshot_cards_qrcode.png | Bin 0 -> 2935 bytes apps/cards/settings.js | 24 + 14 files changed, 1125 insertions(+) create mode 100644 apps/cards/Barcode.js create mode 100644 apps/cards/README.md create mode 100644 apps/cards/app-icon.js create mode 100644 apps/cards/app.js create mode 100644 apps/cards/app.png create mode 100644 apps/cards/codabar.js create mode 100644 apps/cards/code39.js create mode 100644 apps/cards/metadata.json create mode 100644 apps/cards/qrcode.js create mode 100644 apps/cards/screenshot_cards_barcode.png create mode 100644 apps/cards/screenshot_cards_card1.png create mode 100644 apps/cards/screenshot_cards_overview.png create mode 100644 apps/cards/screenshot_cards_qrcode.png create mode 100644 apps/cards/settings.js diff --git a/apps/cards/Barcode.js b/apps/cards/Barcode.js new file mode 100644 index 000000000..ad27da7e6 --- /dev/null +++ b/apps/cards/Barcode.js @@ -0,0 +1,9 @@ +class Barcode{ + constructor(data, options){ + this.data = data; + this.text = options.text || data; + this.options = options; + } +} + +module.exports = Barcode; diff --git a/apps/cards/README.md b/apps/cards/README.md new file mode 100644 index 000000000..724ef9534 --- /dev/null +++ b/apps/cards/README.md @@ -0,0 +1,44 @@ +# Cards + +Basic viewer for loyalty cards synced from Catima through GadgetBridge. +The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported. + +Double tapping on the code will come back to the visualization of the card's details. + +Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes). + +### Supported codes types + +* `CODE_39` +* `CODABAR` +* `QR_CODE` + +### How to sync + +_WIP: we currently cannot synchronize cards_ + +You can test it by sending on your bangle a file like this: + +_android.cards.json_ + +```json +[ + { + "id": 1, + "name": "First card", + "value": "01234", + "note": "Some stuff", + "type": "CODE_39", + "balance": "15 EUR", + "expiration": "1691102081" + }, + { + "id": 2, + "name": "Second card", + "value": "Hello world", + "note": "This is a qr generated on the bangle!", + "type": "QR_CODE", + "balance": "2 P" + } +] +``` diff --git a/apps/cards/app-icon.js b/apps/cards/app-icon.js new file mode 100644 index 000000000..3ec6948c4 --- /dev/null +++ b/apps/cards/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4AYoIAjF/4v/F/4v/F/4v/FAdNAAsoADgv/F/4v/F/4vqu4AjF/4v/F/4v6poAjF/4AfFAYAGF/4v/F/4v/F/4v/F94A/AH4A/AH4A/ABo")) diff --git a/apps/cards/app.js b/apps/cards/app.js new file mode 100644 index 000000000..52eaa392c --- /dev/null +++ b/apps/cards/app.js @@ -0,0 +1,183 @@ +/* CARDS is a list of: + {id:int, + name, + value, + type, + expiration, + color, + balance, + note, + ... + } +*/ + +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +var FILE = "android.cards.json"; + +var Locale = require("locale"); + +var fontSmall = "6x8"; +var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2"; +var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2"; +var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4"; + +var CARDS = require("Storage").readJSON("android.cards.json",true)||[]; +var settings = require("Storage").readJSON("cards.settings.json",true)||{}; + +function getDate(timestamp) { + return new Date(timestamp*1000); +} +function formatDay(date) { + let formattedDate = Locale.dow(date,1) + " " + Locale.date(date).replace(/\d\d\d\d/,""); + if (!settings.useToday) { + return formattedDate; + } + const today = new Date(Date.now()); + if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth()) + return /*LANG*/"Today "; + else { + const tomorrow = new Date(Date.now() + 86400 * 1000); + if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) { + return /*LANG*/"Tomorrow "; + } + return formattedDate; + } +} + +function printSquareCode(binary, size) { + var ratio = g.getWidth()/size; + for (var y = 0; y < size; y++) { + for (var x = 0; x < size; x++) { + if (binary[x + y * size]) { + g.setColor(g.theme.bg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + } else { + g.setColor(g.theme.fg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + } + } + } +} +function printLinearCode(binary) { + var yFrom = 0; + var width = g.getWidth()/binary.length; + for(var b = 0; b < binary.length; b++){ + var x = b * width; + if(binary[b] === "1"){ + g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + } + else if(binary[b]){ + g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + } + } +} + +function showCode(card) { + var code; + //FIXME doesn't work.. + var listener = (data) => { + if(data.double) showCard(card); + Bangle.removeListener("tap", listener); + }; + Bangle.on("tap", listener); + switch (card.type) { + case "QR_CODE": + const getBinaryQR = require("cards.qrcode.js"); + code = getBinaryQR(card.value); + printSquareCode(code.data, code.size); + break; + case "CODE_39": + const CODE39 = require("cards.code39.js"); + code = new CODE39(card.value, {}); + printLinearCode(code.encode().data); + break; + case "CODABAR": + const codabar = require("cards.codabar.js"); + code = new codabar(card.value, {}); + printLinearCode(code.encode().data); + break; + default: + g.clear(true); + g.setFont("Vector:15"); + g.setFontAlign(0,0); + g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); + } +} + +function showCard(card) { + var lines = []; + var bodyFont = fontBig; + if(!card) return; + g.setFont(bodyFont); + //var lines = []; + if (card.name) lines = g.wrapString(card.name, g.getWidth()-10); + var titleCnt = lines.length; + var start = getDate(card.expiration); + var includeDay = true; + if (titleCnt) lines.push(""); // add blank line after name + lines = lines.concat("", /*LANG*/"Tap here to see the value"); + var valueLine = lines.length - 1; + if (card.expiration) + lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10)); + if(card.balance) + lines = lines.concat("",/*LANG*/"Balance"+": ", g.wrapString(card.balance, g.getWidth()-10)); + if(card.note && card.note.trim()) + lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10)); + lines = lines.concat("",/*LANG*/"< Back"); + E.showScroller({ + h : g.getFontHeight(), // height of each menu item in pixels + c : lines.length, // number of menu items + // a function to draw a menu item + draw : function(idx, r) { + // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 + g.setBgColor(idx=lines.length-2) + showList(); + if (idx>=valueLine) + showCode(card); + }, + back : () => showList() + }); +} + +// https://github.com/metafloor/bwip-js +// https://github.com/lindell/JsBarcode + +function showList() { + if(CARDS.length == 0) { + E.showMessage(/*LANG*/"No cards"); + return; + } + E.showScroller({ + h : 52, + c : Math.max(CARDS.length,3), // workaround for 2v10.219 firmware (min 3 not needed for 2v11) + draw : function(idx, r) {"ram" + var card = CARDS[idx]; + g.setColor(g.theme.fg); + g.clearRect(r.x,r.y,r.x+r.w, r.y+r.h); + if (!card) return; + var isPast = false; + var x = r.x+2, name = card.name; + var body = card.expiration ? formatDay(getDate(card.expiration)) : ""; + if (card.balance) body += "\n" + card.balance; + if (name) g.setFontAlign(-1,-1).setFont(fontBig) + .setColor(isPast ? "#888" : g.theme.fg).drawString(name, x+4,r.y+2); + if (body) { + g.setFontAlign(-1,-1).setFont(fontMedium).setColor(isPast ? "#888" : g.theme.fg); + g.drawString(body, x+10,r.y+20); + } + g.setColor("#888").fillRect(r.x,r.y+r.h-1,r.x+r.w-1,r.y+r.h-1); // dividing line between items + if(card.color) { + g.setColor("#"+(0x1000000+Number(card.color)).toString(16).padStart(6,"0")); + g.fillRect(r.x,r.y+4,r.x+3, r.y+r.h-4); + } + }, + select : idx => showCard(CARDS[idx]), + back : () => load() + }); +} +showList(); diff --git a/apps/cards/app.png b/apps/cards/app.png new file mode 100644 index 0000000000000000000000000000000000000000..b2bfa59f442e61c0b74d96f9779f1345f4bd6856 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWNDA?oa z;uumf=j}8{-c|=5m+ZyI^_Q?+2#Ss8R9nR;*v!N+VS?U=IHT`#&uxh`W?DdpSP*vaD3`byh- zj`jSVdaP`F%o(knzb6Yb-?RU?#Oa>>*Obm$8Oii3@>75=xxpHC@T5Rn#A2ZpKpumq LtDnm{r-UW|sxn7| literal 0 HcmV?d00001 diff --git a/apps/cards/codabar.js b/apps/cards/codabar.js new file mode 100644 index 000000000..072a8508a --- /dev/null +++ b/apps/cards/codabar.js @@ -0,0 +1,63 @@ +// Encoding specification: +// http://www.barcodeisland.com/codabar.phtml + +const Barcode = require("cards.Barcode.js"); + +class codabar extends Barcode{ + constructor(data, options){ + if (/^[0-9\-\$\:\.\+\/]+$/.test(data)) { + data = "A" + data + "A"; + } + + super(data.toUpperCase(), options); + + this.text = this.options.text || this.text.replace(/[A-D]/g, ''); + } + + valid(){ + return /^[A-D][0-9\-\$\:\.\+\/]+[A-D]$/.test(this.data) + } + + encode(){ + var result = []; + var encodings = this.getEncodings(); + for(var i = 0; i < this.data.length; i++){ + result.push(encodings[this.data.charAt(i)]); + // for all characters except the last, append a narrow-space ("0") + if (i !== this.data.length - 1) { + result.push("0"); + } + } + return { + text: this.text, + data: result.join('') + }; + } + + getEncodings(){ + return { + "0": "101010011", + "1": "101011001", + "2": "101001011", + "3": "110010101", + "4": "101101001", + "5": "110101001", + "6": "100101011", + "7": "100101101", + "8": "100110101", + "9": "110100101", + "-": "101001101", + "$": "101100101", + ":": "1101011011", + "/": "1101101011", + ".": "1101101101", + "+": "1011011011", + "A": "1011001001", + "B": "1001001011", + "C": "1010010011", + "D": "1010011001" + }; + } +} + +module.exports = codabar diff --git a/apps/cards/code39.js b/apps/cards/code39.js new file mode 100644 index 000000000..5eced539b --- /dev/null +++ b/apps/cards/code39.js @@ -0,0 +1,105 @@ +// Encoding documentation: +// https://en.wikipedia.org/wiki/Code_39#Encoding + +const Barcode = require("cards.Barcode.js"); + +class CODE39 extends Barcode { + constructor(data, options){ + data = data.toUpperCase(); + + // Calculate mod43 checksum if enabled + if(options.mod43){ + data += getCharacter(mod43checksum(data)); + } + + super(data, options); + } + + encode(){ + // First character is always a * + var result = getEncoding("*"); + + // Take every character and add the binary representation to the result + for(let i = 0; i < this.data.length; i++){ + result += getEncoding(this.data[i]) + "0"; + } + + // Last character is always a * + result += getEncoding("*"); + + return { + data: result, + text: this.text + }; + } + + valid(){ + return /^[0-9A-Z\-\.\ \$\/\+\%]+$/.test(this.data); + } +} + + + + + + +// All characters. The position in the array is the (checksum) value +var characters = [ + "0", "1", "2", "3", + "4", "5", "6", "7", + "8", "9", "A", "B", + "C", "D", "E", "F", + "G", "H", "I", "J", + "K", "L", "M", "N", + "O", "P", "Q", "R", + "S", "T", "U", "V", + "W", "X", "Y", "Z", + "-", ".", " ", "$", + "/", "+", "%", "*" +]; + +// The decimal representation of the characters, is converted to the +// corresponding binary with the getEncoding function +var encodings = [ + 20957, 29783, 23639, 30485, + 20951, 29813, 23669, 20855, + 29789, 23645, 29975, 23831, + 30533, 22295, 30149, 24005, + 21623, 29981, 23837, 22301, + 30023, 23879, 30545, 22343, + 30161, 24017, 21959, 30065, + 23921, 22385, 29015, 18263, + 29141, 17879, 29045, 18293, + 17783, 29021, 18269, 17477, + 17489, 17681, 20753, 35770 +]; + +// Get the binary representation of a character by converting the encodings +// from decimal to binary +function getEncoding(character){ + return getBinary(characterValue(character)); +} + +function getBinary(characterValue){ + return encodings[characterValue].toString(2); +} + +function getCharacter(characterValue){ + return characters[characterValue]; +} + +function characterValue(character){ + return characters.indexOf(character); +} + +function mod43checksum(data){ + var checksum = 0; + for(let i = 0; i < data.length; i++){ + checksum += characterValue(data[i]); + } + + checksum = checksum % 43; + return checksum; +} + +module.exports = CODE39; diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json new file mode 100644 index 000000000..538a8b56e --- /dev/null +++ b/apps/cards/metadata.json @@ -0,0 +1,21 @@ +{ + "id": "cards", + "name": "Cards", + "version": "0.1", + "description": "Display loyalty cards", + "icon": "app.png", + "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_event1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], + "tags": "cards", + "supports": ["BANGLEJS","BANGLEJS2"], + "readme": "README.md", + "storage": [ + {"name":"cards.app.js","url":"app.js"}, + {"name":"cards.settings.js","url":"settings.js"}, + {"name":"cards.Barcode.js","url":"Barcode.js"}, + {"name":"cards.qrcode.js","url":"qrcode.js"}, + {"name":"cards.codabar.js","url":"codabar.js"}, + {"name":"cards.code39.js","url":"code39.js"}, + {"name":"cards.img","url":"app-icon.js","evaluate":true} + ], + "data": [{"name":"cards.settings.json"}] +} diff --git a/apps/cards/qrcode.js b/apps/cards/qrcode.js new file mode 100644 index 000000000..d27c55d7b --- /dev/null +++ b/apps/cards/qrcode.js @@ -0,0 +1,675 @@ +var c = E.compiledC(` +// int get_qr(int, int) + +typedef signed char __int8_t; +typedef unsigned char __uint8_t; +typedef signed short int __int16_t; +typedef unsigned short int __uint16_t; +typedef signed int __int32_t; +typedef unsigned int __uint32_t; + +typedef __int8_t int8_t; +typedef __int16_t int16_t; +typedef __int32_t int32_t; +typedef __uint8_t uint8_t; +typedef __uint16_t uint16_t; +typedef __uint32_t uint32_t; + +typedef struct QRCode { + uint8_t version; + uint8_t size; + uint8_t ecc; + uint8_t mode; + uint8_t mask; + uint8_t *modules; +} QRCode; +uint16_t qrcode_getBufferSize(uint8_t version); + +int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data); +int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length); + +bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y); + +static const uint16_t NUM_ERROR_CORRECTION_CODEWORDS[4][40] = { + { 10, 16, 26, 36, 48, 64, 72, 88, 110, 130, 150, 176, 198, 216, 240, 280, 308, 338, 364, 416, 442, 476, 504, 560, 588, 644, 700, 728, 784, 812, 868, 924, 980, 1036, 1064, 1120, 1204, 1260, 1316, 1372}, + { 7, 10, 15, 20, 26, 36, 40, 48, 60, 72, 80, 96, 104, 120, 132, 144, 168, 180, 196, 224, 224, 252, 270, 300, 312, 336, 360, 390, 420, 450, 480, 510, 540, 570, 570, 600, 630, 660, 720, 750}, + { 17, 28, 44, 64, 88, 112, 130, 156, 192, 224, 264, 308, 352, 384, 432, 480, 532, 588, 650, 700, 750, 816, 900, 960, 1050, 1110, 1200, 1260, 1350, 1440, 1530, 1620, 1710, 1800, 1890, 1980, 2100, 2220, 2310, 2430}, + { 13, 22, 36, 52, 72, 96, 108, 132, 160, 192, 224, 260, 288, 320, 360, 408, 448, 504, 546, 600, 644, 690, 750, 810, 870, 952, 1020, 1050, 1140, 1200, 1290, 1350, 1440, 1530, 1590, 1680, 1770, 1860, 1950, 2040}, +}; + +static const uint8_t NUM_ERROR_CORRECTION_BLOCKS[4][40] = { + { 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, + { 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, + { 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, + { 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, +}; + +static const uint16_t NUM_RAW_DATA_MODULES[40] = { + 208, 359, 567, 807, 1079, 1383, 1568, 1936, 2336, 2768, 3232, 3728, 4256, 4651, 5243, 5867, 6523, + 7211, 7931, 8683, 9252, 10068, 10916, 11796, 12708, 13652, 14628, 15371, 16411, 17483, 18587, + 19723, 20891, 22091, 23008, 24272, 25568, 26896, 28256, 29648 +}; +static int max(int a, int b) { + if (a > b) { return a; } + return b; +} + +static int abs(int value) { + if (value < 0) { return -value; } + return value; +} + +static void *memset(void *s, int c, int n) { + char *arr = (char *)s; + for (int i = 0; i= '0' && c <= '9') { return (c - '0'); } + if (c >= 'A' && c <= 'Z') { return (c - 'A' + 10); } + switch (c) { + case ' ': return 36; + case '$': return 37; + case '%': return 38; + case '*': return 39; + case '+': return 40; + case '-': return 41; + case '.': return 42; + case '/': return 43; + case ':': return 44; + } + + return -1; +} + +static bool isAlphanumeric(const char *text, uint16_t length) { + while (length != 0) { + if (getAlphanumeric(text[--length]) == -1) { return false; } + } + return true; +} +static bool isNumeric(const char *text, uint16_t length) { + while (length != 0) { + char c = text[--length]; + if (c < '0' || c > '9') { return false; } + } + return true; +} +static char getModeBits(uint8_t version, uint8_t mode) { + unsigned int modeInfo = 0x7bbb80a; + if (version > 9) { modeInfo >>= 9; } + + if (version > 26) { modeInfo >>= 9; } + char result = 8 + ((modeInfo >> (3 * mode)) & 0x07); + if (result == 15) { result = 16; } + + return result; +} + +typedef struct BitBucket { + uint32_t bitOffsetOrWidth; + uint16_t capacityBytes; + uint8_t *data; +} BitBucket; +static uint16_t bb_getGridSizeBytes(uint8_t size) { + return (((size * size) + 7) / 8); +} + +static uint16_t bb_getBufferSizeBytes(uint32_t bits) { + return ((bits + 7) / 8); +} + +static void bb_initBuffer(BitBucket *bitBuffer, uint8_t *data, int32_t capacityBytes) { + bitBuffer->bitOffsetOrWidth = 0; + bitBuffer->capacityBytes = capacityBytes; + bitBuffer->data = data; + + memset(data, 0, bitBuffer->capacityBytes); +} + +static void bb_initGrid(BitBucket *bitGrid, uint8_t *data, uint8_t size) { + bitGrid->bitOffsetOrWidth = size; + bitGrid->capacityBytes = bb_getGridSizeBytes(size); + bitGrid->data = data; + + memset(data, 0, bitGrid->capacityBytes); +} + +static void bb_appendBits(BitBucket *bitBuffer, uint32_t val, uint8_t length) { + uint32_t offset = bitBuffer->bitOffsetOrWidth; + for (int8_t i = length - 1; i >= 0; i--, offset++) { + bitBuffer->data[offset >> 3] |= ((val >> i) & 1) << (7 - (offset & 7)); + } + bitBuffer->bitOffsetOrWidth = offset; +} + +static void bb_setBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool on) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + if (on) { + bitGrid->data[offset >> 3] |= mask; + } else { + bitGrid->data[offset >> 3] &= ~mask; + } +} + +static void bb_invertBit(BitBucket *bitGrid, uint8_t x, uint8_t y, bool invert) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + uint8_t mask = 1 << (7 - (offset & 0x07)); + bool on = ((bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0); + if (on ^ invert) { + bitGrid->data[offset >> 3] |= mask; + } else { + bitGrid->data[offset >> 3] &= ~mask; + } +} + +static bool bb_getBit(BitBucket *bitGrid, uint8_t x, uint8_t y) { + uint32_t offset = y * bitGrid->bitOffsetOrWidth + x; + return (bitGrid->data[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} + +static void applyMask(BitBucket *modules, BitBucket *isFunction, uint8_t mask) { + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t y = 0; y < size; y++) { + for (uint8_t x = 0; x < size; x++) { + if (bb_getBit(isFunction, x, y)) { continue; } + + bool invert = 0; + switch (mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + } + bb_invertBit(modules, x, y, invert); + } + } +} + +static void setFunctionModule(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y, bool on) { + bb_setBit(modules, x, y, on); + bb_setBit(isFunction, x, y, true); +} +static void drawFinderPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { + uint8_t size = modules->bitOffsetOrWidth; + + for (int8_t i = -4; i <= 4; i++) { + for (int8_t j = -4; j <= 4; j++) { + uint8_t dist = max(abs(i), abs(j)); + int16_t xx = x + j, yy = y + i; + if (0 <= xx && xx < size && 0 <= yy && yy < size) { + setFunctionModule(modules, isFunction, xx, yy, dist != 2 && dist != 4); + } + } + } +} +static void drawAlignmentPattern(BitBucket *modules, BitBucket *isFunction, uint8_t x, uint8_t y) { + for (int8_t i = -2; i <= 2; i++) { + for (int8_t j = -2; j <= 2; j++) { + setFunctionModule(modules, isFunction, x + j, y + i, max(abs(i), abs(j)) != 1); + } + } +} + +static void drawFormatBits(BitBucket *modules, BitBucket *isFunction, uint8_t ecc, uint8_t mask) { + + uint8_t size = modules->bitOffsetOrWidth; + uint32_t data = ecc << 3 | mask; + uint32_t rem = data; + for (int i = 0; i < 10; i++) { + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + } + + data = data << 10 | rem; + data ^= 0x5412; + for (uint8_t i = 0; i <= 5; i++) { + setFunctionModule(modules, isFunction, 8, i, ((data >> i) & 1) != 0); + } + + setFunctionModule(modules, isFunction, 8, 7, ((data >> 6) & 1) != 0); + setFunctionModule(modules, isFunction, 8, 8, ((data >> 7) & 1) != 0); + setFunctionModule(modules, isFunction, 7, 8, ((data >> 8) & 1) != 0); + + for (int8_t i = 9; i < 15; i++) { + setFunctionModule(modules, isFunction, 14 - i, 8, ((data >> i) & 1) != 0); + } + for (int8_t i = 0; i <= 7; i++) { + setFunctionModule(modules, isFunction, size - 1 - i, 8, ((data >> i) & 1) != 0); + } + + for (int8_t i = 8; i < 15; i++) { + setFunctionModule(modules, isFunction, 8, size - 15 + i, ((data >> i) & 1) != 0); + } + + setFunctionModule(modules, isFunction, 8, size - 8, true); +} +static void drawVersion(BitBucket *modules, BitBucket *isFunction, uint8_t version) { + + int8_t size = modules->bitOffsetOrWidth; + if (version < 7) { return; } + uint32_t rem = version; + for (uint8_t i = 0; i < 12; i++) { + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + } + + uint32_t data = version << 12 | rem; + for (uint8_t i = 0; i < 18; i++) { + bool bit = ((data >> i) & 1) != 0; + uint8_t a = size - 11 + i % 3, b = i / 3; + setFunctionModule(modules, isFunction, a, b, bit); + setFunctionModule(modules, isFunction, b, a, bit); + } +} + +static void drawFunctionPatterns(BitBucket *modules, BitBucket *isFunction, uint8_t version, uint8_t ecc) { + + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t i = 0; i < size; i++) { + setFunctionModule(modules, isFunction, 6, i, i % 2 == 0); + setFunctionModule(modules, isFunction, i, 6, i % 2 == 0); + } + drawFinderPattern(modules, isFunction, 3, 3); + drawFinderPattern(modules, isFunction, size - 4, 3); + drawFinderPattern(modules, isFunction, 3, size - 4); + + if (version > 1) { + + uint8_t alignCount = version / 7 + 2; + uint8_t step; + if (version != 32) { + step = (version * 4 + alignCount * 2 + 1) / (2 * alignCount - 2) * 2; + } else { + step = 26; + } + + uint8_t alignPositionIndex = alignCount - 1; + uint8_t alignPosition[alignCount]; + + alignPosition[0] = 6; + + uint8_t l_size = version * 4 + 17; + for (uint8_t i = 0, pos = l_size - 7; i < alignCount - 1; i++, pos -= step) { + alignPosition[alignPositionIndex--] = pos; + } + + for (uint8_t i = 0; i < alignCount; i++) { + for (uint8_t j = 0; j < alignCount; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == alignCount - 1) || (i == alignCount - 1 && j == 0)) { + continue; + } else { + drawAlignmentPattern(modules, isFunction, alignPosition[i], alignPosition[j]); + } + } + } + } + drawFormatBits(modules, isFunction, ecc, 0); + drawVersion(modules, isFunction, version); +} +static void drawCodewords(BitBucket *modules, BitBucket *isFunction, BitBucket *codewords) { + + uint32_t bitLength = codewords->bitOffsetOrWidth; + uint8_t *data = codewords->data; + + uint8_t size = modules->bitOffsetOrWidth; + uint32_t i = 0; + for (int16_t right = size - 1; right >= 1; right -= 2) { + if (right == 6) { right = 5; } + + for (uint8_t vert = 0; vert < size; vert++) { + for (int j = 0; j < 2; j++) { + uint8_t x = right - j; + bool upwards = ((right & 2) == 0) ^ (x < 6); + uint8_t y = upwards ? size - 1 - vert : vert; + if (!bb_getBit(isFunction, x, y) && i < bitLength) { + bb_setBit(modules, x, y, ((data[i >> 3] >> (7 - (i & 7))) & 1) != 0); + i++; + } + } + } + } +} +static uint32_t getPenaltyScore(BitBucket *modules) { + uint32_t result = 0; + + uint8_t size = modules->bitOffsetOrWidth; + for (uint8_t y = 0; y < size; y++) { + + bool colorX = bb_getBit(modules, 0, y); + for (uint8_t x = 1, runX = 1; x < size; x++) { + bool cx = bb_getBit(modules, x, y); + if (cx != colorX) { + colorX = cx; + runX = 1; + + } else { + runX++; + if (runX == 5) { + result += 3; + } else if (runX > 5) { + result++; + } + } + } + } + for (uint8_t x = 0; x < size; x++) { + bool colorY = bb_getBit(modules, x, 0); + for (uint8_t y = 1, runY = 1; y < size; y++) { + bool cy = bb_getBit(modules, x, y); + if (cy != colorY) { + colorY = cy; + runY = 1; + } else { + runY++; + if (runY == 5) { + result += 3; + } else if (runY > 5) { + result++; + } + } + } + } + + uint16_t black = 0; + for (uint8_t y = 0; y < size; y++) { + uint16_t bitsRow = 0, bitsCol = 0; + for (uint8_t x = 0; x < size; x++) { + bool color = bb_getBit(modules, x, y); + if (x > 0 && y > 0) { + bool colorUL = bb_getBit(modules, x - 1, y - 1); + bool colorUR = bb_getBit(modules, x, y - 1); + bool colorL = bb_getBit(modules, x - 1, y); + if (color == colorUL && color == colorUR && color == colorL) { + result += 3; + } + } + bitsRow = ((bitsRow << 1) & 0x7FF) | color; + bitsCol = ((bitsCol << 1) & 0x7FF) | bb_getBit(modules, y, x); + if (x >= 10) { + if (bitsRow == 0x05D || bitsRow == 0x5D0) { + result += 40; + } + if (bitsCol == 0x05D || bitsCol == 0x5D0) { + result += 40; + } + } + if (color) { black++; } + } + } + uint16_t total = size * size; + for (uint16_t k = 0; black * 20 < (9 - k) * total || black * 20 > (11 + k) * total; k++) { + result += 10; + } + + return result; +} + +static uint8_t rs_multiply(uint8_t x, uint8_t y) { + uint16_t z = 0; + for (int8_t i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; +} + +static void rs_init(uint8_t degree, uint8_t *coeff) { + memset(coeff, 0, degree); + coeff[degree - 1] = 1; + uint16_t root = 1; + for (uint8_t i = 0; i < degree; i++) { + + for (uint8_t j = 0; j < degree; j++) { + coeff[j] = rs_multiply(coeff[j], root); + if (j + 1 < degree) { + coeff[j] ^= coeff[j + 1]; + } + } + root = (root << 1) ^ ((root >> 7) * 0x11D); + } +} + +static void rs_getRemainder(uint8_t degree, uint8_t *coeff, const uint8_t *data, uint8_t length, uint8_t *result, uint8_t stride) { + for (uint8_t i = 0; i < length; i++) { + uint8_t factor = data[i] ^ result[0]; + for (uint8_t j = 1; j < degree; j++) { + result[(j - 1) * stride] = result[j * stride]; + } + result[(degree - 1) * stride] = 0; + + for (uint8_t j = 0; j < degree; j++) { + result[j * stride] ^= rs_multiply(coeff[j], factor); + } + } +} +static int8_t encodeDataCodewords(BitBucket *dataCodewords, const uint8_t *text, uint16_t length, uint8_t version) { + int8_t mode = 2; + + if (isNumeric((char*)text, length)) { + mode = 0; + bb_appendBits(dataCodewords, 1 << 0, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 0)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i < length; i++) { + accumData = accumData * 10 + ((char)(text[i]) - '0'); + accumCount++; + if (accumCount == 3) { + bb_appendBits(dataCodewords, accumData, 10); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) { + bb_appendBits(dataCodewords, accumData, accumCount * 3 + 1); + } + + } else if (isAlphanumeric((char*)text, length)) { + mode = 1; + bb_appendBits(dataCodewords, 1 << 1, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 1)); + + uint16_t accumData = 0; + uint8_t accumCount = 0; + for (uint16_t i = 0; i < length; i++) { + accumData = accumData * 45 + getAlphanumeric((char)(text[i])); + accumCount++; + if (accumCount == 2) { + bb_appendBits(dataCodewords, accumData, 11); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) { + bb_appendBits(dataCodewords, accumData, 6); + } + + } else { + bb_appendBits(dataCodewords, 1 << 2, 4); + bb_appendBits(dataCodewords, length, getModeBits(version, 2)); + for (uint16_t i = 0; i < length; i++) { + bb_appendBits(dataCodewords, (char)(text[i]), 8); + } + } + + return mode; +} + +static void performErrorCorrection(uint8_t version, uint8_t ecc, BitBucket *data) { + uint8_t numBlocks = NUM_ERROR_CORRECTION_BLOCKS[ecc][version - 1]; + uint16_t totalEcc = NUM_ERROR_CORRECTION_CODEWORDS[ecc][version - 1]; + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint8_t blockEccLen = totalEcc / numBlocks; + uint8_t numShortBlocks = numBlocks - moduleCount / 8 % numBlocks; + uint8_t shortBlockLen = moduleCount / 8 / numBlocks; + + uint8_t shortDataBlockLen = shortBlockLen - blockEccLen; + + uint8_t result[data->capacityBytes]; + memset(result, 0, sizeof(result)); + + uint8_t coeff[blockEccLen]; + rs_init(blockEccLen, coeff); + + uint16_t offset = 0; + uint8_t *dataBytes = data->data; + + for (uint8_t i = 0; i < shortDataBlockLen; i++) { + uint16_t index = i; + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { + result[offset++] = dataBytes[index]; + if (blockNum == numShortBlocks) { stride++; } + + index += stride; + } + } + { + uint16_t index = shortDataBlockLen * (numShortBlocks + 1); + uint8_t stride = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks - numShortBlocks; blockNum++) { + result[offset++] = dataBytes[index]; + + if (blockNum == 0) { stride++; } + index += stride; + } + } + + uint8_t blockSize = shortDataBlockLen; + for (uint8_t blockNum = 0; blockNum < numBlocks; blockNum++) { + if (blockNum == numShortBlocks) { blockSize++; } + + rs_getRemainder(blockEccLen, coeff, dataBytes, blockSize, &result[offset + blockNum], numBlocks); + dataBytes += blockSize; + } + + memcpy(data->data, result, data->capacityBytes); + data->bitOffsetOrWidth = moduleCount; +} + +static const uint8_t ECC_FORMAT_BITS = (0x02 << 6) | (0x03 << 4) | (0x00 << 2) | (0x01 << 0); + +uint16_t qrcode_getBufferSize(uint8_t version) { + return bb_getGridSizeBytes(4 * version + 17); +} +int8_t qrcode_initBytes(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, uint8_t *data, uint16_t length) { + uint8_t size = version * 4 + 17; + qrcode->version = version; + qrcode->size = size; + qrcode->ecc = ecc; + qrcode->modules = modules; + + uint8_t eccFormatBits = (ECC_FORMAT_BITS >> (2 * ecc)) & 0x03; + uint16_t moduleCount = NUM_RAW_DATA_MODULES[version - 1]; + uint16_t dataCapacity = moduleCount / 8 - NUM_ERROR_CORRECTION_CODEWORDS[eccFormatBits][version - 1]; + struct BitBucket codewords; + uint8_t codewordBytes[bb_getBufferSizeBytes(moduleCount)]; + bb_initBuffer(&codewords, codewordBytes, (int32_t)sizeof(codewordBytes)); + int8_t mode = encodeDataCodewords(&codewords, data, length, version); + + if (mode < 0) { return -1; } + qrcode->mode = mode; + uint32_t padding = (dataCapacity * 8) - codewords.bitOffsetOrWidth; + if (padding > 4) { padding = 4; } + bb_appendBits(&codewords, 0, padding); + bb_appendBits(&codewords, 0, (8 - codewords.bitOffsetOrWidth % 8) % 8); + for (uint8_t padByte = 0xEC; codewords.bitOffsetOrWidth < (dataCapacity * 8); padByte ^= 0xEC ^ 0x11) { + bb_appendBits(&codewords, padByte, 8); + } + + BitBucket modulesGrid; + bb_initGrid(&modulesGrid, modules, size); + + BitBucket isFunctionGrid; + uint8_t isFunctionGridBytes[bb_getGridSizeBytes(size)]; + bb_initGrid(&isFunctionGrid, isFunctionGridBytes, size); + drawFunctionPatterns(&modulesGrid, &isFunctionGrid, version, eccFormatBits); + performErrorCorrection(version, eccFormatBits, &codewords); + drawCodewords(&modulesGrid, &isFunctionGrid, &codewords); + uint8_t mask = 0; + int32_t minPenalty = (2147483647); + for (uint8_t i = 0; i < 8; i++) { + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, i); + applyMask(&modulesGrid, &isFunctionGrid, i); + int penalty = getPenaltyScore(&modulesGrid); + if (penalty < minPenalty) { + mask = i; + minPenalty = penalty; + } + applyMask(&modulesGrid, &isFunctionGrid, i); + } + + qrcode->mask = mask; + drawFormatBits(&modulesGrid, &isFunctionGrid, eccFormatBits, mask); + applyMask(&modulesGrid, &isFunctionGrid, mask); + + return 0; +} + +int8_t qrcode_initText(QRCode *qrcode, uint8_t *modules, uint8_t version, uint8_t ecc, const char *data) { + return qrcode_initBytes(qrcode, modules, version, ecc, (uint8_t*)data, strlen(data)); +} + +bool qrcode_getModule(QRCode *qrcode, uint8_t x, uint8_t y) { + if (x >= qrcode->size || y >= qrcode->size) { + return false; + } + + uint32_t offset = y * qrcode->size + x; + return (qrcode->modules[offset >> 3] & (1 << (7 - (offset & 0x07)))) != 0; +} + +int get_qr (char *string, uint8_t *qrcodeBitmap) { + // The structure to manage the QR code + QRCode qrcode; + + // Allocate a chunk of memory to store the QR code + uint8_t qrcodeBytes[qrcode_getBufferSize(3)]; + + qrcode_initText(&qrcode, qrcodeBytes, 3, 0, string); + for (uint8_t y = 0; y < qrcode.size; y++) { + for (uint8_t x = 0; x < qrcode.size; x++) { + qrcodeBitmap[x + y * qrcode.size] = qrcode_getModule(&qrcode, x, y); + } + } + return qrcode.size; +} +`); + +function getBinaryQR (value) { + var qrcodeBitmap = new Uint8Array(850); + var flatValue = Uint8Array(E.toArrayBuffer(E.toFlatString(value ,0))); + var valueAddr = E.getAddressOf(flatValue, true); + var qrAddr = E.getAddressOf(qrcodeBitmap, true); + if (valueAddr == 0 || qrAddr == 0) { + console.log ("Failed to get flat arrays.."); + //return; + } + var qrsize = c.get_qr(valueAddr, qrAddr); + return { data: qrcodeBitmap, size: qrsize }; +} + +module.exports = getBinaryQR; diff --git a/apps/cards/screenshot_cards_barcode.png b/apps/cards/screenshot_cards_barcode.png new file mode 100644 index 0000000000000000000000000000000000000000..1910c173e1e3f1f69c774f2aeeac9904346078f8 GIT binary patch literal 2222 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 literal 0 HcmV?d00001 diff --git a/apps/cards/screenshot_cards_qrcode.png b/apps/cards/screenshot_cards_qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..5bace3e6efc390a744bca7c6ff6d35ccc96ef565 GIT binary patch literal 2935 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 ai0#X-nV2|BX3xu&G>G8h?Ofv+e))gT)1V9h literal 0 HcmV?d00001 diff --git a/apps/cards/settings.js b/apps/cards/settings.js new file mode 100644 index 000000000..b11ba785a --- /dev/null +++ b/apps/cards/settings.js @@ -0,0 +1,24 @@ +(function(back) { + function gbSend(message) { + Bluetooth.println(""); + Bluetooth.println(JSON.stringify(message)); + } + var settings = require("Storage").readJSON("cards.settings.json",1)||{}; + function updateSettings() { + require("Storage").writeJSON("cards.settings.json", settings); + } + var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; + var mainmenu = { + "" : { "title" : "Cards" }, + "< Back" : back, + /*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?/*LANG*/"Yes":/*LANG*/"No" }, + /*LANG*/"Use 'Today',..." : { + value : !!settings.useToday, + onchange: v => { + settings.useToday = v; + updateSettings(); + } + }, + }; + E.showMenu(mainmenu); +}) From 56f9788034db6c61224bab32610cfd32f1317112 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 15 Aug 2023 15:31:18 +0200 Subject: [PATCH 2/9] cards: added changelog and used libraries --- apps/cards/ChangeLog | 1 + apps/cards/README.md | 8 +++++++- apps/cards/metadata.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 apps/cards/ChangeLog diff --git a/apps/cards/ChangeLog b/apps/cards/ChangeLog new file mode 100644 index 000000000..00945cd13 --- /dev/null +++ b/apps/cards/ChangeLog @@ -0,0 +1 @@ +0.01: Simple app to display loyalty cards diff --git a/apps/cards/README.md b/apps/cards/README.md index 724ef9534..4bd70a0b1 100644 --- a/apps/cards/README.md +++ b/apps/cards/README.md @@ -1,6 +1,6 @@ # Cards -Basic viewer for loyalty cards synced from Catima through GadgetBridge. +Simple app to display loyalty cards synced from Catima through GadgetBridge. The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported. Double tapping on the code will come back to the visualization of the card's details. @@ -42,3 +42,9 @@ _android.cards.json_ } ] ``` + +### Credits + +Barcode generation adapted from [lindell/JsBarcode](https://github.com/lindell/JsBarcode) + +QR code generation adapted from [ricmoo/QRCode](https://github.com/ricmoo/QRCode) diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 538a8b56e..5f72a1c5b 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -4,7 +4,7 @@ "version": "0.1", "description": "Display loyalty cards", "icon": "app.png", - "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_event1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], + "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}], "tags": "cards", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", From 2bc2d9ff3585c44a12b60548c6dae32e1c9bc602 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Wed, 16 Aug 2023 08:58:32 +0200 Subject: [PATCH 3/9] Added padding to codes and updated screenshots --- apps/cards/app.js | 29 ++++++++++++++++-------- apps/cards/metadata.json | 2 +- apps/cards/screenshot_cards_barcode.png | Bin 2222 -> 2283 bytes apps/cards/screenshot_cards_card1.png | Bin 2643 -> 2693 bytes apps/cards/screenshot_cards_card2.png | Bin 0 -> 2506 bytes 5 files changed, 20 insertions(+), 11 deletions(-) create mode 100644 apps/cards/screenshot_cards_card2.png diff --git a/apps/cards/app.js b/apps/cards/app.js index 52eaa392c..a6d802352 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -47,27 +47,30 @@ function formatDay(date) { } function printSquareCode(binary, size) { - var ratio = g.getWidth()/size; + var padding = 5; + var ratio = (g.getWidth()-(2*padding))/size; + g.setColor(g.theme.fg).fillRect(0, 0, g.getWidth(), g.getHeight()); for (var y = 0; y < size; y++) { for (var x = 0; x < size; x++) { if (binary[x + y * size]) { - g.setColor(g.theme.bg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + g.setColor(g.theme.bg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } else { - g.setColor(g.theme.fg).fillRect({x:x*ratio, y:y*ratio, w:ratio, h:ratio}); + g.setColor(g.theme.fg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } } } } function printLinearCode(binary) { - var yFrom = 0; + var yFrom = 15; + var yTo = 28; var width = g.getWidth()/binary.length; for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight()}); + g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } @@ -80,6 +83,8 @@ function showCode(card) { Bangle.removeListener("tap", listener); }; Bangle.on("tap", listener); + E.showScroller(); + g.clear(true); switch (card.type) { case "QR_CODE": const getBinaryQR = require("cards.qrcode.js"); @@ -87,18 +92,23 @@ function showCode(card) { printSquareCode(code.data, code.size); break; case "CODE_39": + g.setFont("Vector:20"); + g.setFontAlign(0,1); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); const CODE39 = require("cards.code39.js"); code = new CODE39(card.value, {}); printLinearCode(code.encode().data); break; case "CODABAR": + g.setFont("Vector:20"); + g.setFontAlign(0,1); + g.drawString(card.value, g.getWidth()/2, g.getHeight()); const codabar = require("cards.codabar.js"); code = new codabar(card.value, {}); printLinearCode(code.encode().data); break; default: - g.clear(true); - g.setFont("Vector:15"); + g.setFont("Vector:30"); g.setFontAlign(0,0); g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); } @@ -114,8 +124,7 @@ function showCard(card) { var titleCnt = lines.length; var start = getDate(card.expiration); var includeDay = true; - if (titleCnt) lines.push(""); // add blank line after name - lines = lines.concat("", /*LANG*/"Tap here to see the value"); + lines = lines.concat("", /*LANG*/"View code"); var valueLine = lines.length - 1; if (card.expiration) lines = lines.concat("",/*LANG*/"Expires"+": ", g.wrapString(formatDay(getDate(card.expiration)), g.getWidth()-10)); diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 5f72a1c5b..0aa3249fb 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -4,7 +4,7 @@ "version": "0.1", "description": "Display loyalty cards", "icon": "app.png", - "screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.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"], "readme": "README.md", diff --git a/apps/cards/screenshot_cards_barcode.png b/apps/cards/screenshot_cards_barcode.png index 1910c173e1e3f1f69c774f2aeeac9904346078f8..e57e9765a76c717f1fcd8d8b85c85def3bff566f 100644 GIT binary patch literal 2283 zcmZ{mc{r3^AIFawF*J`co{2FA4Ot?p5l!}NV<}5Yj5T9>ZINtIOxf2`#u9m=NM%iU z@{}pNv7}y3Z69fSKVPe zZMID;g?xLq_2gUS@|X3q^cdI8OOd@g^8*spPq@Mb7H;Y@#S>rrBWnFe_xRMx;it1J z?h{R}krUH8-3!dM>{WcSq4-;$G|zuzwYA$}jQ+&BWk$v^r$2bX7A`wHYf5Pe902_e z6uw*ZqnJs7c;cAGp=f zj)02#k!-amsst)w2)cDMM)*c!sLgWV`*P1#pUC+o_C3ela97!vN*{eKF8Fm>w+>Ek zC54dVC4d+@t#)3d2YPUAm!v3dV(@McVWOSNV~PfuV4dXUYz>pY9vQ^(*4q@_>5Onr z>ZnzLTWw8$=u+I%i#yy-3+CADpckHdfi#S=JlH}DcEdMv(jz zx#(bEGI216`=dlyv6Z@?TK;nHBqmsn?@8N3jnTD)GvfAqbeinnRubGiM{crXGp_}@ zENM0cRgXDEag~iezP*|=)--|jlG+y8qT;UGMNYvB1vu&zzA1}qe#uO=e(>xVVF_{^ z`KNij4f=5I1SKKUC!Fsv4ie=v!X5*Q*C&V;rdA|!ZwvQm-<(S(RwMVpnG8LweF_Gh z7)rF7TIN%KTvaC8&G@w>q%pEGSx{k2{e+*BR6Avhc2s%Qd98D2aQe%8_LmJvoDCW+ zT>Iv@&$thC##V%2ady%T?Q#d3-R8Qj>Gkx>e%i!GUyd;MMiTHUkMG|pp%(-!uXcFt z5mMw8q#0nC4qPlq+>~2krF%$WF5lKEj5N`dyFoI?%Vmg=lS^7{CQa=6>^?9nJ+^HQ z@elEPl@%Ni-cjtS)HNSu-MY;&g=AbHV+j$nNJ8Ch0FX;-#80z7yNKXKV~t_9MscV5 z(9CxaZc&)rST;Ve0_K*mGRU;A_mT=c1gS0qHI#wWBF=T*J8w&I3Y|5_1kXOa+}&fF z8}>dn;`F(|a(<2-D6sP{LNm!=Jj>r%_QpE{`5xO(Sdaswa!b z@3DYCln{#Tl-^gR(Li1z@-!t1u%A=&Jw3zYb?ju)wZT7osD0R0zxVyE2( zwPZ6J#V3>oZw|GD1D0!|qi*hi`MbCM}mgG zW`D-3LIUG1Xxd->bVlJqoBjyTR!weTfC7EV*(FiL+hU+(IC-Pb(*{Cs9>yfK&25?BJ6$>#()QM{PE$Y{(R|GEN96v6 zNTT>o%O;Chm8xQEgh^t>oojvZ5!^)9mduy_2twtxAdUP-1EyV2@`!u$ZX7e0m%E!7FEJ{-?Av*{({hkB(c@8SWL@m+}-IOtb4(}O*&1Oo+v*HMz{FcAGF z?_lLg-e)3=ikzG}nfk5h-V2oP6I!fhXXTL0^;;!Wi`$b%(i=*E_2${Mlcx}Sp62&; zqogQi_2?0_>TAOt9OHX*jz4W@?CaqT1?epuSf!7SE_4?yXY@4pzG-5)_Jt*gdZ0Ep z6%;qxZbnPS%I3cu-N5iP9!?b3&j1n3nZ;D>=En*iQs*ttnnNet0#xcuYS%xuEZH3T z_M(kvtp)VK_E`wnX^Dtl8dpKF_PYhL9AS(6Jld};nSj(X7;fAlpvuRPhAko@m@+}_ z#~&zXt*;AIH!y{HBD_H+tU)8yrj*;e)i8hnY&0V(wqY!xX1VGNK zX3?3VmsF~y@gYq@3$q|Pl`SeHzDy(Qv*2tO*2pe7{%6B1(0EEw<%7JeTu#V{mR_aC zg6wKiZ6h-Jlzwx7t+!npZn~hPsOWj2q?NKjhSwZi;q`IDO-*n%B}-4!A1WEDZK-Z~ q2oNN1t{XB8RT*c>+8IRj8mbFnU^;R!_dQg^05fAtqe=s}%l`#-lng%r literal 2222 zcmYLLdpwhEAIFBh6V2nez1HK9MV^FA%dzG#!b;>=TCoX}!-H}*C8NnR$;(1?uoZ?J zE3}@LLy6?{B**dOkdWBq_}*LZ;q(36_x-uqt+;cqOiE1Lj!S6THci^x-fnrZB*<4*<&Uj}ZvsS2mO!xL+t4u3EVxnW(}LS%)LfGj~b|7A;&E!g@G#*!2WQ(iHbAf32Nw*_#ls<8|Mxm1SAbe6BTa zmUCXRX4ASOi3M|`I)x;2!~-1-oBOQdG9|&5Qx$>$?Vtcj)wrx~LFIyt4j39v?+c3D zt640e|0fC<(lszp3O4ecjB^2M;kkrXeCX|s{`!^1Tj{O~Hd_l92C2`C8OGipkB(Iw z2&_#|%2dpR%P|_EjE4FlIF96g^SpLV_KDTk$4S~*Tr1pq3Va-^agzF2-tdWDm#S(0 zGYaU02?HoZ!74JAM}9}R)6;0t=G3Hw_g}h$osncs8J9}&{a__tBpw7#wA-!nSsp;R z$q<_6>KK`M?eo~!VNJ&g4rb|`Y#0v{j%Sj=`LPoSHC5@Be#IWjEGLp+x|Etoq^vNt zz1@xU@^2SJbuM$-Nd0CcKAxHiJ(gOGbz}Wu&@Ct1m5Q0%4^A{GO$JSJFa~nNFG|wo z;>&j{-3x#1QQvF5xTFKX~HWscLRNN0;CAq&RvVS7NbU9eoXl|S*8lV zRJJp~c|(kZsY+NAAe#x$CxxtkH52^j?QBBhEFirna_E$V@i42g@3dqb=wl-M{r23x`pPc(@tMx(_ z4722`07n_QpYiouHG6QB0GYqi55?U$PQB`^M*bz3H$cm;j`dq*V&6!hET&38zwYC< zp>)mIwF7Rp-%A28dB;aR9L1jpMNi?g=!RuQ;{CB4*(k)EabF{@H;glaJ>v3Q$8YJb&E@Gs{} z{Ezmo@^f8Wf3W0_sw)pwy_X`6LzdPzaxlmLU_cTv$FDRHruINmc@1=ZGq~ zc9doZMB^!Zf@i!k*X_xpDc#iKX!kH0lJ5pqTN{PkGG8iUc^}Z1+;ozw=d0t?4f?#& z#rKbECEuFuZBFr)t=1t3xMxjgy<^$7UPfDoEN7NE%baSIlmEP)5h7>y-AiKb;)wK- zxRL%s`+`#naRu1e#)%iAw=kmte5sZE#6OH zLWrB%y(it?aM1v43jySRuWg5@F*JCGSf4=Z2eesLy*{t|$bu*$26N4mWaqjIpxk_Q z*b6oA?eV9Gbk__3DCwpZMytKxjNwW0WB~@RwojiW;ljEbV;`HlS5#ik^e5=S|7jdLPPwI?e72_Xt>0AIsfmN7r(F6-0>~TRJzwgWJU;OkngDXkV zFc30~;p z>c^B*obHwX?(J`xC>ad^@`yCL6GS9vQ7nj*PH$6i_Rni?{44MO|0vj%Y+qguFDGf$ zMap5Ji&dTN)v=H+Q~e=ATKVEZ%AhHnC2cooi&R zQBPiMH`O6Xnm@pE)09(>YUp21g4BlE8rH}O^)i0b5@-oP>v2nKZhP4LS>iN%Ve{j9 zaHdCP$ALR_-J$8&d1QG_PsFsrV|8d_qT!_3J^yk`bI;Q3hPD7A(m0%f0b-M#Ffsaf zGR?zvkjFeUe6W`oPVj4t6!YY>x@^Wz#7LK%irM8Hl495lJiB4FA(r_c@mYCT$mXh?5KQE<)Rhk#-piN zZ>U7pqkjUH+b3jTk{b@36fZsV=vky;tf)EJD7iAj{1zI>W7jYEGiUGJ1NBnU=xM;= kur|Occax^m5%3v2p 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 B&yWBB literal 0 HcmV?d00001 From 26da9b2bb36b6842ec1583f815f27d2a4a25ce66 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Thu, 17 Aug 2023 14:19:40 +0200 Subject: [PATCH 4/9] Made code colours independent on the theme --- apps/cards/app.js | 41 +++++++++++++++++++++++----------------- apps/cards/metadata.json | 2 +- apps/cards/settings.js | 5 ----- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/apps/cards/app.js b/apps/cards/app.js index a6d802352..3a2635e69 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -14,6 +14,10 @@ Bangle.loadWidgets(); Bangle.drawWidgets(); +//may make it configurable in the future +const WHITE=-1 +const BLACK=0 + var FILE = "android.cards.json"; var Locale = require("locale"); @@ -49,13 +53,12 @@ function formatDay(date) { function printSquareCode(binary, size) { var padding = 5; var ratio = (g.getWidth()-(2*padding))/size; - g.setColor(g.theme.fg).fillRect(0, 0, g.getWidth(), g.getHeight()); for (var y = 0; y < size; y++) { for (var x = 0; x < size; x++) { if (binary[x + y * size]) { - g.setColor(g.theme.bg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); + g.setColor(BLACK).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } else { - g.setColor(g.theme.fg).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); + g.setColor(WHITE).fillRect({x:x*ratio+padding, y:y*ratio+padding, w:ratio, h:ratio}); } } } @@ -67,47 +70,51 @@ function printLinearCode(binary) { for(var b = 0; b < binary.length; b++){ var x = b * width; if(binary[b] === "1"){ - g.setColor(g.theme.fg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } else if(binary[b]){ - g.setColor(g.theme.bg).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); + g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)}); } } } function showCode(card) { - var code; - //FIXME doesn't work.. + //FIXME tap doesn't work.. var listener = (data) => { if(data.double) showCard(card); Bangle.removeListener("tap", listener); }; Bangle.on("tap", listener); E.showScroller(); - g.clear(true); + // theme independent + g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight()); switch (card.type) { - case "QR_CODE": + case "QR_CODE": { const getBinaryQR = require("cards.qrcode.js"); - code = getBinaryQR(card.value); + let code = getBinaryQR(card.value); printSquareCode(code.data, code.size); break; - case "CODE_39": + } + case "CODE_39": { g.setFont("Vector:20"); - g.setFontAlign(0,1); + g.setFontAlign(0,1).setColor(BLACK); g.drawString(card.value, g.getWidth()/2, g.getHeight()); const CODE39 = require("cards.code39.js"); - code = new CODE39(card.value, {}); + let code = new CODE39(card.value, {}); printLinearCode(code.encode().data); break; - case "CODABAR": + } + case "CODABAR": { g.setFont("Vector:20"); - g.setFontAlign(0,1); + g.setFontAlign(0,1).setColor(BLACK); g.drawString(card.value, g.getWidth()/2, g.getHeight()); const codabar = require("cards.codabar.js"); - code = new codabar(card.value, {}); + let code = new codabar(card.value, {}); printLinearCode(code.encode().data); break; + } default: + g.clear(true); g.setFont("Vector:30"); g.setFontAlign(0,0); g.drawString(card.value, g.getWidth()/2, g.getHeight()/2); @@ -146,7 +153,7 @@ function showCard(card) { }, select : function(idx) { if (idx>=lines.length-2) showList(); - if (idx>=valueLine) + else if (idx==valueLine) showCode(card); }, back : () => showList() diff --git a/apps/cards/metadata.json b/apps/cards/metadata.json index 0aa3249fb..63b7da847 100644 --- a/apps/cards/metadata.json +++ b/apps/cards/metadata.json @@ -1,7 +1,7 @@ { "id": "cards", "name": "Cards", - "version": "0.1", + "version": "0.01", "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"}], diff --git a/apps/cards/settings.js b/apps/cards/settings.js index b11ba785a..db0ab56de 100644 --- a/apps/cards/settings.js +++ b/apps/cards/settings.js @@ -1,13 +1,8 @@ (function(back) { - function gbSend(message) { - Bluetooth.println(""); - Bluetooth.println(JSON.stringify(message)); - } var settings = require("Storage").readJSON("cards.settings.json",1)||{}; function updateSettings() { require("Storage").writeJSON("cards.settings.json", settings); } - var CALENDAR = require("Storage").readJSON("android.calendar.json",true)||[]; var mainmenu = { "" : { "title" : "Cards" }, "< Back" : back, From b8a123cfe8fbfc93e27f319942977247615ab408 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Sat, 19 Aug 2023 11:56:29 +0200 Subject: [PATCH 5/9] Added support for loyalty cards from gadgetbridge --- apps/android/ChangeLog | 1 + apps/android/boot.js | 5 +++++ apps/android/metadata.json | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index f2a0c5b3f..d531e43a9 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -31,3 +31,4 @@ 0.30: Send firmware and hardware versions on connection Allow alarm enable/disable 0.31: Implement API for activity fetching +0.32: Added support for loyalty cards from gadgetbridge diff --git a/apps/android/boot.js b/apps/android/boot.js index a8027a67c..846fc40a8 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -236,6 +236,11 @@ event.t="remove"; } require("messages").pushMessage(event); + }, + "cards" : function() { + // we receive all, just override what we have + if (Array.isArray(event.d)) + require("Storage").writeJSON("android.cards.json", event.d); } }; var h = HANDLERS[event.t]; diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 8d65d32e3..68bd946c5 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.31", + "version": "0.32", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", @@ -15,6 +15,6 @@ {"name":"android.img","url":"app-icon.js","evaluate":true}, {"name":"android.boot.js","url":"boot.js"} ], - "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}], + "data": [{"name":"android.settings.json"}, {"name":"android.calendar.json"}, {"name":"android.cards.json"}], "sortorder": -8 } From 536da24a10e9d2e961fc2d2b73c372997452e752 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 22 Aug 2023 17:56:02 +0200 Subject: [PATCH 6/9] Added card's colour in the card's page --- apps/cards/app.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/cards/app.js b/apps/cards/app.js index 3a2635e69..691baf987 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -50,6 +50,17 @@ function formatDay(date) { } } +function getColor(intColor) { + return "#"+(0x1000000+Number(intColor)).toString(16).padStart(6,"0"); +} +function isLight(color) { + var r = +("0x"+color.slice(1,3)); + var g = +("0x"+color.slice(3,5)); + var b = +("0x"+color.slice(5,7)); + var threshold = 0x88 * 3; + return (r+g+b) > threshold; +} + function printSquareCode(binary, size) { var padding = 5; var ratio = (g.getWidth()-(2*padding))/size; @@ -79,7 +90,6 @@ function printLinearCode(binary) { } function showCode(card) { - //FIXME tap doesn't work.. var listener = (data) => { if(data.double) showCard(card); Bangle.removeListener("tap", listener); @@ -140,14 +150,18 @@ function showCard(card) { if(card.note && card.note.trim()) lines = lines.concat("",g.wrapString(card.note, g.getWidth()-10)); lines = lines.concat("",/*LANG*/"< Back"); + var titleBgColor = card.color ? getColor(card.color) : g.theme.bg2; + var titleColor = g.theme.fg2; + if (card.color) + titleColor = isLight(titleBgColor) ? BLACK : WHITE; E.showScroller({ h : g.getFontHeight(), // height of each menu item in pixels c : lines.length, // number of menu items // a function to draw a menu item draw : function(idx, r) { // FIXME: in 2v13 onwards, clearRect(r) will work fine. There's a bug in 2v12 - g.setBgColor(idx Date: Thu, 24 Aug 2023 18:27:22 +0200 Subject: [PATCH 7/9] Going back from the code view with button --- apps/cards/README.md | 2 +- apps/cards/app.js | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/cards/README.md b/apps/cards/README.md index 4bd70a0b1..bd9157d62 100644 --- a/apps/cards/README.md +++ b/apps/cards/README.md @@ -3,7 +3,7 @@ Simple app to display loyalty cards synced from Catima through GadgetBridge. The app can display the cards' info (balance, expiration, note, etc.) and tapping on the appropriate field will display the code, if the type is supported. -Double tapping on the code will come back to the visualization of the card's details. +To come back to the visualization of the card's details from the code view, simply press the button. Beware that the small screen of the Banglejs 2 cannot render properly complex barcodes (in fact the resolution is very limited to render most barcodes). diff --git a/apps/cards/app.js b/apps/cards/app.js index 691baf987..dcef7da76 100644 --- a/apps/cards/app.js +++ b/apps/cards/app.js @@ -90,12 +90,9 @@ function printLinearCode(binary) { } function showCode(card) { - var listener = (data) => { - if(data.double) showCard(card); - Bangle.removeListener("tap", listener); - }; - Bangle.on("tap", listener); E.showScroller(); + // keeping it on rising edge would come back twice.. + setWatch(()=>showCard(card), BTN, {edge:"falling"}); // theme independent g.setColor(WHITE).fillRect(0, 0, g.getWidth(), g.getHeight()); switch (card.type) { From 23a67398c532d8368ff84e2137a5d481e43387a6 Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Tue, 12 Sep 2023 07:20:08 +0200 Subject: [PATCH 8/9] cards: added disclaimer in README --- apps/cards/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/cards/README.md b/apps/cards/README.md index bd9157d62..8bcbb0755 100644 --- a/apps/cards/README.md +++ b/apps/cards/README.md @@ -13,9 +13,13 @@ Beware that the small screen of the Banglejs 2 cannot render properly complex ba * `CODABAR` * `QR_CODE` +### Disclaimer + +This app is a proof of concept, many codes are too complex to be rendered by the bangle's screen or hardware (at least with the current logic), keep that in mind. + ### How to sync -_WIP: we currently cannot synchronize cards_ +_WIP: we currently cannot synchronize cards, a PR is under review in GadgetBridge repo, soon we will see support on nightly builds_ You can test it by sending on your bangle a file like this: From b17c640acf5fd27ea66c18ccd5fbe76a5b6a90fc Mon Sep 17 00:00:00 2001 From: Gabriele Monaco Date: Wed, 13 Sep 2023 17:03:23 +0200 Subject: [PATCH 9/9] Added licenses and references to cards code libraries --- apps/cards/Barcode.js | 26 ++++++++++++++++++++++++++ apps/cards/codabar.js | 25 +++++++++++++++++++++++++ apps/cards/code39.js | 25 +++++++++++++++++++++++++ apps/cards/qrcode.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/apps/cards/Barcode.js b/apps/cards/Barcode.js index ad27da7e6..ea2448ca5 100644 --- a/apps/cards/Barcode.js +++ b/apps/cards/Barcode.js @@ -1,3 +1,29 @@ +/* + * 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. + */ + class Barcode{ constructor(data, options){ this.data = data; diff --git a/apps/cards/codabar.js b/apps/cards/codabar.js index 072a8508a..2d245e091 100644 --- a/apps/cards/codabar.js +++ b/apps/cards/codabar.js @@ -1,5 +1,30 @@ // Encoding specification: // http://www.barcodeisland.com/codabar.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 Barcode = require("cards.Barcode.js"); diff --git a/apps/cards/code39.js b/apps/cards/code39.js index 5eced539b..c9b81d55c 100644 --- a/apps/cards/code39.js +++ b/apps/cards/code39.js @@ -1,5 +1,30 @@ // Encoding documentation: // https://en.wikipedia.org/wiki/Code_39#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 Barcode = require("cards.Barcode.js"); diff --git a/apps/cards/qrcode.js b/apps/cards/qrcode.js index d27c55d7b..ff79d7bee 100644 --- a/apps/cards/qrcode.js +++ b/apps/cards/qrcode.js @@ -1,3 +1,33 @@ +/* + * C source adapted from https://github.com/ricmoo/QRCode + * + * The MIT License (MIT) + * + * This library is written and maintained by Richard Moore. + * Major parts were derived from Project Nayuki's library. + * + * Copyright (c) 2017 Richard Moore (https://github.com/ricmoo/QRCode) + * Copyright (c) 2017 Project Nayuki (https://www.nayuki.io/page/qr-code-generator-library) + * + * 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. + */ + var c = E.compiledC(` // int get_qr(int, int)