'use strict'; const toByteArray = (fileContents) => fileContents.split("\n").filter((line) => !line.startsWith("//")).map((line) => line.trim()).map( (line) => line.split(" ").filter(Boolean).map((cc) => parseInt(cc, 16)) ).flat(); var COMMAND = /* @__PURE__ */ ((COMMAND2) => { COMMAND2[COMMAND2["INIT"] = 1] = "INIT"; COMMAND2[COMMAND2["PRINT"] = 2] = "PRINT"; COMMAND2[COMMAND2["DATA"] = 4] = "DATA"; COMMAND2[COMMAND2["STATUS"] = 15] = "STATUS"; COMMAND2[COMMAND2["TRANSFER"] = 16] = "TRANSFER"; return COMMAND2; })(COMMAND || {}); var STATE = /* @__PURE__ */ ((STATE2) => { STATE2[STATE2["AWAIT_MAGIC_BYTES"] = 0] = "AWAIT_MAGIC_BYTES"; STATE2[STATE2["AWAIT_COMMAND"] = 1] = "AWAIT_COMMAND"; STATE2[STATE2["AWAIT_COMPRESSION_INFO"] = 2] = "AWAIT_COMPRESSION_INFO"; STATE2[STATE2["AWAIT_PACKET_DATA_LENGTH"] = 3] = "AWAIT_PACKET_DATA_LENGTH"; STATE2[STATE2["AWAIT_DATA"] = 4] = "AWAIT_DATA"; STATE2[STATE2["AWAIT_CHECKSUM"] = 5] = "AWAIT_CHECKSUM"; STATE2[STATE2["AWAIT_KEEPALIVE"] = 6] = "AWAIT_KEEPALIVE"; STATE2[STATE2["AWAIT_STATUS_QUERY"] = 7] = "AWAIT_STATUS_QUERY"; return STATE2; })(STATE || {}); const EMPTY_PACKET = { command: null, buffer: [], data: [], hasCompression: 0, dataLength: 0, checksum: 0 }; var DECOMP_MODE = /* @__PURE__ */ ((DECOMP_MODE2) => { DECOMP_MODE2[DECOMP_MODE2["DETECT_LENGTH"] = 0] = "DETECT_LENGTH"; DECOMP_MODE2[DECOMP_MODE2["COMPRESSED"] = 1] = "COMPRESSED"; DECOMP_MODE2[DECOMP_MODE2["UNCOMPRESSED"] = 2] = "UNCOMPRESSED"; return DECOMP_MODE2; })(DECOMP_MODE || {}); const parsePackets = (bytes) => { let state = STATE.AWAIT_MAGIC_BYTES; let packet = { ...EMPTY_PACKET }; const packets = []; bytes.forEach((byte) => { switch (state) { case STATE.AWAIT_MAGIC_BYTES: if (packet.buffer.length === 0 && byte === 136) { packet.buffer.push(byte); return; } else if (packet.buffer.length === 1 && byte === 51) { packet.buffer = []; state = STATE.AWAIT_COMMAND; return; } else { packet = { ...EMPTY_PACKET }; return; } case STATE.AWAIT_COMMAND: packet.command = byte; state = STATE.AWAIT_COMPRESSION_INFO; return; case STATE.AWAIT_COMPRESSION_INFO: packet.hasCompression = byte; state = STATE.AWAIT_PACKET_DATA_LENGTH; return; case STATE.AWAIT_PACKET_DATA_LENGTH: if (packet.buffer.length === 0) { packet.buffer.push(byte); return; } packet.dataLength = packet.buffer[0] + (byte << 8); packet.buffer = []; if (packet.dataLength === 0) { state = STATE.AWAIT_CHECKSUM; } else { state = STATE.AWAIT_DATA; } return; case STATE.AWAIT_DATA: if (packet.buffer.length < packet.dataLength) { packet.buffer.push(byte); return; } packet.data = packet.buffer; packet.buffer = []; state = STATE.AWAIT_CHECKSUM; return; case STATE.AWAIT_CHECKSUM: if (packet.buffer.length === 0) { packet.buffer.push(byte); return; } packet.checksum = packet.buffer[0] + (byte << 8); packet.buffer = []; state = STATE.AWAIT_KEEPALIVE; return; case STATE.AWAIT_KEEPALIVE: state = STATE.AWAIT_STATUS_QUERY; return; case STATE.AWAIT_STATUS_QUERY: state = STATE.AWAIT_MAGIC_BYTES; packets.push({ checksum: packet.checksum, command: packet.command, data: packet.data, dataLength: packet.dataLength, hasCompression: packet.hasCompression }); packet = { ...EMPTY_PACKET }; return; } }); return packets; }; const parseReducedPackets = (bytes) => { let state = STATE.AWAIT_COMMAND; let packet = { command: null, buffer: [], data: [], hasCompression: 0, dataLength: 0 }; const packets = []; const nextPacket = () => { packets.push({ command: packet.command, data: packet.data, dataLength: packet.dataLength, hasCompression: packet.hasCompression }); packet = { command: null, buffer: [], data: [], hasCompression: 0, dataLength: 0 }; state = STATE.AWAIT_COMMAND; }; bytes.forEach((byte, index) => { switch (state) { case STATE.AWAIT_COMMAND: packet.command = byte; switch (packet.command) { case COMMAND.INIT: nextPacket(); return; case COMMAND.DATA: state = STATE.AWAIT_COMPRESSION_INFO; return; case COMMAND.PRINT: state = STATE.AWAIT_PACKET_DATA_LENGTH; return; case COMMAND.TRANSFER: state = STATE.AWAIT_PACKET_DATA_LENGTH; return; default: throw new Error(`Unknown packet command: 0x${packet.command.toString(16)} at index ${index}`); } case STATE.AWAIT_COMPRESSION_INFO: packet.hasCompression = byte; state = STATE.AWAIT_PACKET_DATA_LENGTH; return; case STATE.AWAIT_PACKET_DATA_LENGTH: if (packet.buffer.length === 0) { packet.buffer.push(byte); return; } packet.dataLength = packet.buffer[0] + (byte << 8); packet.buffer = []; if (packet.dataLength === 0) { state = STATE.AWAIT_COMMAND; nextPacket(); return; } state = STATE.AWAIT_DATA; return; case STATE.AWAIT_DATA: packet.buffer.push(byte); if (packet.buffer.length === packet.dataLength) { packet.data = packet.buffer; state = STATE.AWAIT_COMMAND; if (packet.command === COMMAND.TRANSFER) { nextPacket(); packet = { buffer: [], command: COMMAND.PRINT, data: [1, 3, 228, 127], hasCompression: 0, dataLength: 4 }; } nextPacket(); } break; } }); return packets; }; const twoTiles = new Array(2 * 16).fill(0); const inflate = (arr) => { const chunks = []; let i = 0; const n = arr.length; while (i < n) { chunks.push(...twoTiles, ...arr.slice(i, i += 256), ...twoTiles); } return chunks; }; const inflateTransferPackages = (packets) => packets.map((packet) => { if (packet.command !== COMMAND.TRANSFER) { return packet; } return { ...packet, command: COMMAND.DATA, data: inflate(packet.data) }; }); const getImageDataStream = (packets) => { return packets.filter(({ command }) => command === COMMAND.DATA || command === COMMAND.PRINT); }; const unpack = (data) => { const dataOut = []; let mode = DECOMP_MODE.DETECT_LENGTH; let length = 0; data.forEach((byte) => { switch (mode) { case DECOMP_MODE.DETECT_LENGTH: if (byte & 128) { mode = DECOMP_MODE.COMPRESSED; length = (byte & 127) + 2; } else { mode = DECOMP_MODE.UNCOMPRESSED; length = byte + 1; } return; case DECOMP_MODE.UNCOMPRESSED: length -= 1; if (length === 0) { mode = DECOMP_MODE.DETECT_LENGTH; } dataOut.push(byte); return; case DECOMP_MODE.COMPRESSED: dataOut.push(...[...Array(length)].map(() => byte)); mode = DECOMP_MODE.DETECT_LENGTH; length = 0; return; } }); return dataOut; }; const decompressDataStream = (packets) => { return packets.map((packet) => { if (packet.command === COMMAND.DATA) { return { ...packet, hasCompression: 0, data: packet.hasCompression ? unpack(packet.data) : packet.data }; } return packet; }); }; const parsePaletteByte = (paletteRaw) => { return [ paletteRaw >> 6 & 3, paletteRaw >> 4 & 3, paletteRaw >> 2 & 3, paletteRaw >> 0 & 3 ]; }; const decodePrintCommands = (packets) => { return packets.map((packet) => { if (packet.command === COMMAND.PRINT) { const printData = { margins: packet.data[1], marginUpper: packet.data[1] >> 4, marginLower: packet.data[1] & 15, palette: packet.data[2], paletteData: parsePaletteByte(packet.data[2]) }; return { ...packet, data: printData }; } return packet; }); }; const harmonizePalette = (charA, charB, paletteDefinition = [3, 2, 1, 0]) => { const bits = [...Array(8)].map((_, index) => ({ a: (charB >> 7 - index) % 2, b: (charA >> 7 - index) % 2 })); const res = bits.map(({ a, b }) => (a << 1) + b).map((val) => paletteDefinition[3 - val]).map((mapped) => ({ a: (mapped >> 1) % 2, b: mapped % 2 })).reduce((acc, current, index) => ({ a: acc.a + (current.a << 7 - index), b: acc.b + (current.b << 7 - index) }), { a: 0, b: 0 }); return [ res.b & 255, res.a & 255 ]; }; const harmonizePalettes = (packets) => { let unharmonizedPackets = []; return packets.map((packet) => { switch (packet.command) { case COMMAND.DATA: unharmonizedPackets.push(packet); break; case COMMAND.PRINT: if (packet.data.palette === 0) { unharmonizedPackets = []; break; } while (unharmonizedPackets.length) { let unharmonizedPacket = unharmonizedPackets.shift(); const data = []; if (!unharmonizedPacket) { throw Error("error harmonizing"); } for (let i = 0; i < unharmonizedPacket.data.length; i += 2) { data.push( ...harmonizePalette( unharmonizedPacket.data[i], unharmonizedPacket.data[i + 1], packet.data.paletteData ) ); } Object.assign(unharmonizedPacket, { data }); } break; } return packet; }); }; const transformToClassic = (packets) => { let image = { transformed: [] }; let currentLine = []; const images = []; packets.forEach((packet) => { switch (packet.command) { case COMMAND.DATA: for (let i = 0; i < packet.data.length; i += 1) { currentLine.push(packet.data[i].toString(16).padStart(2, "0")); if (i % 16 === 15) { image.transformed.push(currentLine.join(" ")); currentLine = []; } } break; case COMMAND.PRINT: image.palette = packet.data.paletteData || image.palette; if (packet.data.marginLower !== 0) { images.push(image.transformed); image = { transformed: [] }; currentLine = []; } break; } }); if (image.transformed.length) { images.push(image.transformed); } return images; }; const fourtyLines = new Array(40).fill("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"); const completeFrame = (images) => images.map((image) => { if (image.length !== 280) { return image; } return [ ...fourtyLines, ...image, ...fourtyLines ]; }); const commandName = (command) => { switch (command) { case COMMAND.INIT: return "INIT"; case COMMAND.PRINT: return "PRINT"; case COMMAND.DATA: return "DATA"; case COMMAND.STATUS: return "STATUS"; default: return "-"; } }; const logPackets = (packets) => { console.log( packets.map(({ command, data, hasCompression, dataLength }) => ({ command: commandName(command), hasCompression: hasCompression ? "yes" : "no", dataLength, data: data.margins ? `marginUpper: ${data.marginUpper} - marginLower: ${data.marginLower}` : data.slice(0, 20).join(",") })) ); return packets; }; const parseDefaultToClassic = (bytes) => transformToClassic( harmonizePalettes( decodePrintCommands( decompressDataStream( getImageDataStream( parsePackets(bytes) ) ) ) ) ); const parsePicoToClassic = (bytes) => completeFrame( transformToClassic( harmonizePalettes( decodePrintCommands( decompressDataStream( getImageDataStream( inflateTransferPackages( parseReducedPackets(bytes) ) ) ) ) ) ) ); exports.COMMAND = COMMAND; exports.completeFrame = completeFrame; exports.decodePrintCommands = decodePrintCommands; exports.decompressDataStream = decompressDataStream; exports.getImageDataStream = getImageDataStream; exports.harmonizePalette = harmonizePalette; exports.harmonizePalettes = harmonizePalettes; exports.inflateTransferPackages = inflateTransferPackages; exports.logPackets = logPackets; exports.parseDefaultToClassic = parseDefaultToClassic; exports.parsePackets = parsePackets; exports.parsePaletteByte = parsePaletteByte; exports.parsePicoToClassic = parsePicoToClassic; exports.parseReducedPackets = parseReducedPackets; exports.toByteArray = toByteArray; exports.transformToClassic = transformToClassic; exports.unpack = unpack;