/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ // ============================================================ // core/pdf-sheet.js — gedeelde bouwstenen voor de A3-tekenbladen (jsPDF): // kleuren, nieuw document, maatlijn en titelblok. Gebruikt door WallPdf, // FloorPdf en de unit-PDF, zodat elk blad er identiek uitziet. // Plain script (geen ES-module), geladen na jspdf.umd.min.js. // ============================================================ window.PdfSheet = (() => { // Kleurlogica: rood = houtwerk-omtrekken, blauw = hoofdmaatvoering, // zwart = opening-kruis, posnum-labels en de onderste maatketens. const COLORS = { RED: [190, 25, 25], BLUE: [25, 50, 180], BLACK: [0, 0, 0], GREY: [130, 130, 130] }; // Veldnamen van de "Project eigenschappen"-modal van de wand-/vloer-editor. const PROJECT_DOM_IDS = { naam: 'inp-proj-naam', plaats: 'inp-proj-plaats', straat: 'inp-proj-straat', nummer: 'inp-proj-nummer', tekenaar: 'inp-proj-tekenaar', titel: 'inp-proj-titel', element: 'inp-proj-element', opmerking: 'inp-proj-opmerking' }; // Afbeeldingen (bijv. textuur-voorvertoningen) voor in de PDF. generate() // is synchroon, dus vooraf laden (preloadImage, bijv. zodra een materiaal // gekozen is); imageData geeft de data-URL, of null als hij (nog) niet // geladen is (dan tekent de aanroeper een kleurvlak). const imageCache = {}; function preloadImage(url) { if (!url || url in imageCache || typeof Image === 'undefined' || typeof document === 'undefined') return; imageCache[url] = null; const img = new Image(); img.onload = () => { try { const c = document.createElement('canvas'); c.width = img.naturalWidth; c.height = img.naturalHeight; c.getContext('2d').drawImage(img, 0, 0); imageCache[url] = c.toDataURL('image/png'); } catch (e) { /* laat null: kleurvlak */ } }; img.src = url; } const imageData = (url) => (url && imageCache[url]) || null; function createDoc() { const { jsPDF } = window.jspdf; return new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a3' }); } function readProjectFromDom() { const project = {}; Object.entries(PROJECT_DOM_IDS).forEach(([key, id]) => { const el = document.getElementById(id); project[key] = (el && el.value) || ''; }); return project; } // Maatlijn met kleine loodrechte eindstreepjes ("|") op beide // uiteinden, zodat duidelijk is waar de maat precies begint/eindigt // i.p.v. een lijn die zonder markering in de lucht lijkt te eindigen. const DIM_TICK = 1.5; function dimLine(doc, x1, y1, x2, y2) { doc.line(x1, y1, x2, y2); const dx = x2 - x1, dy = y2 - y1; const len = Math.hypot(dx, dy) || 1; const tx = (-dy / len) * (DIM_TICK / 2), ty = (dx / len) * (DIM_TICK / 2); doc.line(x1 - tx, y1 - ty, x1 + tx, y1 + ty); doc.line(x2 - tx, y2 - ty, x2 + tx, y2 + ty); } // Titelblok (rechtsonder), in twee kolommen zodat het weinig hoogte // inneemt, met het logo (core/logo-data.js) rechtsonder. // project: { naam, plaats, straat, nummer, tekenaar, opmerking }; // opts: { titel, element, blad } (blad bijv. "2 / 7"). function titleBlock(doc, project, opts) { const { BLACK, GREY } = COLORS; const p = project || {}; const o = opts || {}; const tbW = 118, tbH = 36; const tbX0 = 290, tbY0 = 289 - tbH; doc.setDrawColor(...BLACK); doc.setLineWidth(0.25); doc.rect(tbX0, tbY0, tbW, tbH, 'S'); const today = new Date().toLocaleDateString('nl-NL'); const columns = [ [['Projectnaam:', p.naam], ['Plaats:', p.plaats], ['Straat:', p.straat], ['Projectnummer:', p.nummer], ['Tekenaar:', p.tekenaar]], [['Titel:', o.titel], ['Element nummer:', o.element], ['Datum getekend:', today], ['Schaal:', '1:1'], ['Opmerking:', p.opmerking]] ]; const colW = tbW / 2, labelW = 19; doc.setFontSize(6.2); columns.forEach((rows, c) => { const x = tbX0 + 3 + c * colW; let rowY = tbY0 + 5; rows.forEach(([label, value]) => { doc.setTextColor(...GREY); doc.text(label, x, rowY); doc.setTextColor(...BLACK); const text = String(value || '-'); doc.text(doc.splitTextToSize(text, colW - labelW - 5)[0], x + labelW, rowY); rowY += 4.2; }); }); // Scheidingslijn + onderstrook: blad links, logo rechts. const stripY = tbY0 + 25; doc.setDrawColor(...GREY); doc.setLineWidth(0.15); doc.line(tbX0, stripY, tbX0 + tbW, stripY); if (o.blad) { doc.setTextColor(...GREY); doc.setFontSize(6); doc.text('Blad ' + o.blad, tbX0 + 3, tbY0 + tbH - 3.5); } const logoH = 7.5, logoW = logoH * (window.TECTUM_LOGO ? window.TECTUM_LOGO.aspect : 4.55); if (window.TECTUM_LOGO) { doc.addImage(window.TECTUM_LOGO.png, 'PNG', tbX0 + tbW - 3 - logoW, tbY0 + tbH - 2 - logoH, logoW, logoH, 'tectum-logo', 'FAST'); // alias: 1x in het bestand, gecomprimeerd } else { doc.setTextColor(...GREY); doc.setFontSize(6); doc.text('Van Emous Mantelzorgwoningen', tbX0 + tbW - 3, tbY0 + tbH - 3.5, { align: 'right' }); } } // Eenvoudige tabel: headers + rijen (arrays van strings) vanaf (x, y) met // vaste kolom-x'en. Geeft de y-positie onder de tabel terug. function table(doc, x, y, colX, headers, rows, opts) { const { BLACK, GREY } = COLORS; const o = opts || {}; const rowH = o.rowH || 4.3; doc.setTextColor(...BLACK); if (o.title) { doc.setFontSize(o.titleSize || 9); doc.text(o.title, x, y); y += 6; } doc.setFontSize(o.fontSize || 6.5); headers.forEach((h, i) => doc.text(h, x + colX[i], y)); y += 1.5; doc.setDrawColor(...GREY); doc.setLineWidth(0.15); doc.line(x, y, x + colX[colX.length - 1] + (o.lastColW || 14), y); y += 4; rows.forEach(r => { if (o.maxY && y > o.maxY) return; r.forEach((v, i) => doc.text(String(v), x + colX[i], y, i > 0 && o.alignRight ? { align: 'right' } : undefined)); y += rowH; }); return y; } return { COLORS, preloadImage, imageData, createDoc, readProjectFromDom, dimLine, titleBlock, table }; })();