// main.js const { app, BrowserWindow } = require('electron'); const fs = require("fs"); const path = require("path"); const chokidar = require("chokidar"); const axios = require("axios"); let OUT_DIR; // endgültiger Eingangsordner (wird in app.whenReady gesetzt) let IMG_DIR; // Ausgabeordner für latest.png let win; function createWindow() { win = new BrowserWindow({ width: 1000, height: 800, webPreferences: { nodeIntegration: true, contextIsolation: false } }); win.loadFile(path.join(__dirname, "index.html")); } app.whenReady().then(() => { // Versuche zuerst C:\ZIR_Output (praktisch für Windows Local Port) const preferedRoot = "C:\\ZIR_Output"; let dataDir = app.getPath("userData"); // fallback try { // Versuch: C:\ZIR_Output anlegen (falls möglich) if (!fs.existsSync(preferedRoot)) { fs.mkdirSync(preferedRoot, { recursive: true }); } OUT_DIR = preferedRoot; } catch (e) { // Falls es schiefgeht (Rechte), fallback in userData OUT_DIR = path.join(dataDir, "ZIR_Input"); } // IMG_DIR im userData (schützt vor Schreibrechten) IMG_DIR = path.join(dataDir, "zpl_out"); // Ordner anlegen, falls nicht existierend try { if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true }); } catch(e) {} try { if (!fs.existsSync(IMG_DIR)) fs.mkdirSync(IMG_DIR, { recursive: true }); } catch(e) {} console.log("ZIR Input (Eingangsordner):", OUT_DIR); console.log("ZIR Output (PNG-Ordner):", IMG_DIR); createWindow(); const watcher = chokidar.watch(OUT_DIR, { ignoreInitial: true, awaitWriteFinish: {stabilityThreshold: 500, pollInterval: 100} }); watcher.on("add", file => { if (!file.toLowerCase().endsWith(".zpl")) return; processFile(file); }); watcher.on("change", file => { if (!file.toLowerCase().endsWith(".zpl")) return; processFile(file); }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); /* --- Helferfunktionen --- */ function extractZPL(raw) { const start = raw.indexOf("^XA"); const end = raw.lastIndexOf("^XZ"); if (start >= 0 && end >= 0 && end > start) return raw.slice(start, end + 3); if (start >= 0) return raw.slice(start) + (raw.includes("^XZ") ? "" : "^XZ"); return "^XA\n" + raw + "\n^XZ"; } function cleanControlChars(s) { return s.replace(/[\x00-\x1F]/g, ''); } function parsePWLL(zpl) { const pwMatch = zpl.match(/\^PW\s*?(\d+)/i) || zpl.match(/\^PW(\d+)/i); const llMatch = zpl.match(/\^LL\s*?(\d+)/i) || zpl.match(/\^LL(\d+)/i); const pw = pwMatch ? parseInt(pwMatch[1], 10) : null; const ll = llMatch ? parseInt(llMatch[1], 10) : null; return { pw, ll }; } function parseMaxFO(zpl) { let maxX = 0, maxY = 0; const re = /\^FO\s*?(\d+)\s*,\s*(\d+)/g; let m; while ((m = re.exec(zpl)) !== null) { const x = parseInt(m[1], 10); const y = parseInt(m[2], 10); if (!isNaN(x) && x > maxX) maxX = x; if (!isNaN(y) && y > maxY) maxY = y; } return { maxX, maxY }; } function dotsToInches(dots, dpi) { if (!dots || !dpi) return null; return dots / dpi; } function fmtInches(v) { return (Math.round(v * 100) / 100).toString(); } async function tryLabelaryRender(zpl, printerKey, widthInches, heightInches) { const url = `http://api.labelary.com/v1/printers/${printerKey}/labels/${widthInches}x${heightInches}/0/`; try { const resp = await axios.post(url, zpl, { headers: { "Accept": "image/png" }, responseType: "arraybuffer", timeout: 15000 }); return { ok: true, buffer: resp.data }; } catch (err) { const status = err.response ? err.response.status : null; return { ok: false, status, error: err.message }; } } /* --- Größen & Druckeroptionen --- */ const STANDARD_SIZES = [ { w: 8.27, h: 11.69, name: "A4" }, { w: 8.5, h: 11, name: "Letter" }, { w: 4, h: 6, name: "4x6" }, { w: 3, h: 2, name: "3x2" }, { w: 4, h: 3, name: "4x3" }, { w: 2, h: 6, name: "2x6" }, { w: 1, h: 1, name: "1x1" } ]; const PRINTER_OPTIONS = [ { key: "8dpmm", dpi: 203 }, { key: "12dpmm", dpi: 305 } ]; /* --- Hauptverarbeitung --- */ let busy = false; async function processFile(filePath) { if (busy) { setTimeout(() => processFile(filePath), 400); return; } busy = true; try { console.log("Verarbeite:", filePath); let raw = fs.readFileSync(filePath, "utf-8"); raw = cleanControlChars(raw); let zpl = extractZPL(raw); const { pw, ll } = parsePWLL(zpl); const { maxX, maxY } = parseMaxFO(zpl); console.log("Parsed PW/LL:", pw, ll, "maxFO:", maxX, maxY); // sizesToTry aufbauen (Priorität: PW/LL -> geschätzte große Formate -> Standardgrößen) const sizesToTry = []; if (pw && ll) { for (const p of PRINTER_OPTIONS) { const wIn = dotsToInches(pw, p.dpi); const hIn = dotsToInches(ll, p.dpi); if (wIn && hIn) { sizesToTry.push({ w: wIn, h: hIn, printer: p }); sizesToTry.push({ w: hIn, h: wIn, printer: p, rotated: true }); } } } const estDefaultDPI = 203; const estWIn = maxX ? dotsToInches(maxX + 100, estDefaultDPI) : null; // margin const estHIn = maxY ? dotsToInches(maxY + 100, estDefaultDPI) : null; if (estWIn && estHIn) { if (estWIn > 6 || estHIn > 8 || estWIn > 8 || estHIn > 6) { for (const p of PRINTER_OPTIONS) { sizesToTry.push({ w: 8.27, h: 11.69, printer: p, name: "A4" }); sizesToTry.push({ w: 11.69, h: 8.27, printer: p, name: "A4_rot" }); sizesToTry.push({ w: 8.5, h: 11, printer: p, name: "Letter" }); sizesToTry.push({ w: 11, h: 8.5, printer: p, name: "Letter_rot" }); } } } for (const p of PRINTER_OPTIONS) { for (const s of STANDARD_SIZES) { const exists = sizesToTry.some(x => Math.abs(x.w - s.w) < 0.01 && Math.abs(x.h - s.h) < 0.01 && x.printer.key === p.key); if (!exists) sizesToTry.push({ w: s.w, h: s.h, printer: p, name: s.name || `${s.w}x${s.h}` }); } } // Duplikate entfernen & Reihenfolge behalten const uniqSizes = []; const seen = new Set(); for (const s of sizesToTry) { const key = `${s.printer.key}-${Math.round(s.w*100)}/${Math.round(s.h*100)}`; if (!seen.has(key)) { seen.add(key); uniqSizes.push(s); } } // Versuche rendern let renderResult = null; for (const s of uniqSizes) { const wStr = fmtInches(s.w); const hStr = fmtInches(s.h); console.log(`Versuche rendern: printer=${s.printer.key} size=${wStr}x${hStr} (${s.name || ""})`); renderResult = await tryLabelaryRender(zpl, s.printer.key, wStr, hStr); if (renderResult.ok) { console.log(`Erfolg mit ${s.printer.key} ${wStr}x${hStr}`); break; } else { console.log(`Fehler (${renderResult.status}) mit ${s.printer.key} ${wStr}x${hStr}`); } } if (!renderResult || !renderResult.ok) { console.error("Alle Render-Versuche fehlgeschlagen, Abbruch."); busy = false; return; } // PNG speichern (latest.png) — alte PNGs löschen const pngBuffer = renderResult.buffer; try { fs.readdirSync(IMG_DIR).forEach(f => { const fileToDelete = path.join(IMG_DIR, f); try { if (fs.lstatSync(fileToDelete).isFile()) fs.unlinkSync(fileToDelete); } catch(e){} }); } catch(e){} const outFile = path.join(IMG_DIR, "latest.png"); fs.writeFileSync(outFile, pngBuffer); console.log("PNG erstellt:", outFile); // verarbeitete ZPL-Datei löschen (optional) try { fs.unlinkSync(filePath); } catch(e){ console.warn("Konnte ZPL nicht löschen:", e.message); } // Fenster benachrichtigen win.webContents.send("show-png", outFile); } catch (err) { console.error("Fehler beim Rendern:", err && err.message ? err.message : err); } finally { busy = false; } }