app
Wire Harness Designer
Public Made by Adomby adom
Design wire harnesses in the browser and export a professional, print-ready PDF assembly drawing. Pick connectors, wire gauge, colour and stripe, sleeving, and connector-to-connector lengths; get a live fishbone diagram with a wire schedule and title block. Click any wire or connector in the drawing to edit it. Save and reload the whole harness as JSON.
master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
/*
* render-pdf.js — draw list -> jsPDF document (professional PDF export).
* Consumes exactly the ops produced by harness-layout.js, so the PDF matches
* the on-screen SVG preview 1:1.
*/
(function (global) {
'use strict';
function getCtor() {
if (global.jspdf && global.jspdf.jsPDF) return global.jspdf.jsPDF;
if (typeof require !== 'undefined') { try { return require('jspdf').jsPDF; } catch (e) {} }
throw new Error('jsPDF not loaded');
}
// parse #rgb / #rrggbb / rgb() / rgba() -> {r,g,b,a}
function parseColor(str) {
if (str == null) return null;
str = String(str).trim();
var m;
if (str[0] === '#') {
if (str.length === 4) return { r: parseInt(str[1] + str[1], 16), g: parseInt(str[2] + str[2], 16), b: parseInt(str[3] + str[3], 16), a: 1 };
return { r: parseInt(str.slice(1, 3), 16), g: parseInt(str.slice(3, 5), 16), b: parseInt(str.slice(5, 7), 16), a: 1 };
}
if ((m = str.match(/rgba?\(([^)]+)\)/))) {
var p = m[1].split(',').map(function (s) { return parseFloat(s); });
return { r: p[0], g: p[1], b: p[2], a: p.length > 3 ? p[3] : 1 };
}
return { r: 0, g: 0, b: 0, a: 1 };
}
// blend an rgba over white -> opaque rgb (jsPDF fills are opaque)
function overWhite(c) {
if (c.a >= 1) return c;
return { r: Math.round(c.r * c.a + 255 * (1 - c.a)), g: Math.round(c.g * c.a + 255 * (1 - c.a)), b: Math.round(c.b * c.a + 255 * (1 - c.a)), a: 1 };
}
function setDash(doc, dash) {
try {
if (dash && dash.length) {
if (doc.setLineDashPattern) doc.setLineDashPattern(dash, 0);
else if (doc.setLineDash) doc.setLineDash(dash, 0);
} else {
if (doc.setLineDashPattern) doc.setLineDashPattern([], 0);
else if (doc.setLineDash) doc.setLineDash([], 0);
}
} catch (e) {}
}
function setCap(doc, cap) { try { doc.setLineCap(cap || 'butt'); } catch (e) {} }
function setJoin(doc, join) { try { doc.setLineJoin(join || 'miter'); } catch (e) {} }
// convert absolute path segments to jsPDF relative lines[]
function toRelLines(d) {
var lines = [], cur = null, start = null;
for (var i = 0; i < d.length; i++) {
var c = d[i];
if (c[0] === 'M') { start = { x: c[1], y: c[2] }; cur = { x: c[1], y: c[2] }; }
else if (c[0] === 'L') { lines.push([c[1] - cur.x, c[2] - cur.y]); cur = { x: c[1], y: c[2] }; }
else if (c[0] === 'C') {
lines.push([c[1] - cur.x, c[2] - cur.y, c[3] - cur.x, c[4] - cur.y, c[5] - cur.x, c[6] - cur.y]);
cur = { x: c[5], y: c[6] };
} else if (c[0] === 'Q') {
var c1x = cur.x + 2 / 3 * (c[1] - cur.x), c1y = cur.y + 2 / 3 * (c[2] - cur.y);
var c2x = c[3] + 2 / 3 * (c[1] - c[3]), c2y = c[4] + 2 / 3 * (c[2] - c[4]);
lines.push([c1x - cur.x, c1y - cur.y, c2x - cur.x, c2y - cur.y, c[3] - cur.x, c[4] - cur.y]);
cur = { x: c[3], y: c[4] };
}
}
return { start: start, lines: lines };
}
function fontName(f) { return f === 'mono' ? 'courier' : 'helvetica'; }
function drawOp(doc, o) {
if (o.svgOnly) return; // interaction-only hit areas never go into the PDF
if (o.op === 'rect') {
var hasFill = !!o.fill, hasStroke = !!o.stroke;
var style = hasFill && hasStroke ? 'FD' : hasFill ? 'F' : 'S';
if (hasFill) { var f = overWhite(parseColor(o.fill)); doc.setFillColor(f.r, f.g, f.b); }
if (hasStroke) { var s = parseColor(o.stroke); doc.setDrawColor(s.r, s.g, s.b); doc.setLineWidth(o.lw || 1); }
setDash(doc, o.dash);
if (o.rx) doc.roundedRect(o.x, o.y, o.w, o.h, o.rx, o.rx, style);
else doc.rect(o.x, o.y, o.w, o.h, style);
setDash(doc, null);
} else if (o.op === 'line') {
var sc = parseColor(o.stroke || '#000'); doc.setDrawColor(sc.r, sc.g, sc.b);
doc.setLineWidth(o.lw || 1); setCap(doc, o.cap); setDash(doc, o.dash);
doc.line(o.x1, o.y1, o.x2, o.y2);
setDash(doc, null); setCap(doc, 'butt');
} else if (o.op === 'path') {
var rl = toRelLines(o.d);
if (!rl.start) return;
var pstyle = 'S';
if (o.fill) { var pf = overWhite(parseColor(o.fill)); doc.setFillColor(pf.r, pf.g, pf.b); pstyle = o.stroke ? 'FD' : 'F'; }
if (o.stroke) { var ps = parseColor(o.stroke); doc.setDrawColor(ps.r, ps.g, ps.b); doc.setLineWidth(o.lw || 1); }
setCap(doc, o.cap); setJoin(doc, o.join); setDash(doc, o.dash);
doc.lines(rl.lines, rl.start.x, rl.start.y, [1, 1], pstyle, false);
setDash(doc, null); setCap(doc, 'butt'); setJoin(doc, 'miter');
} else if (o.op === 'circle') {
var cstyle = o.fill && o.stroke ? 'FD' : o.fill ? 'F' : 'S';
if (o.fill) { var cf = overWhite(parseColor(o.fill)); doc.setFillColor(cf.r, cf.g, cf.b); }
if (o.stroke) { var cs = parseColor(o.stroke); doc.setDrawColor(cs.r, cs.g, cs.b); doc.setLineWidth(o.lw || 1); }
doc.circle(o.x, o.y, o.r, cstyle);
} else if (o.op === 'text') {
doc.setFont(fontName(o.font), o.weight === 'bold' ? 'bold' : 'normal');
doc.setFontSize(o.size || 8);
var tc = parseColor(o.color || '#000'); doc.setTextColor(tc.r, tc.g, tc.b);
var align = o.align === 'middle' ? 'center' : o.align === 'end' ? 'right' : 'left';
var optsT = { align: align, baseline: o.baseline || 'alphabetic' };
if (o.angle) optsT.angle = -o.angle; // jsPDF rotates CCW-positive
doc.text(String(o.text == null ? '' : o.text), o.x, o.y, optsT);
}
}
function toDoc(pages) {
var jsPDF = getCtor();
var p0 = pages[0];
var doc = new jsPDF({ unit: 'pt', format: [p0.w, p0.h], orientation: p0.w >= p0.h ? 'landscape' : 'portrait', compress: true });
for (var pi = 0; pi < pages.length; pi++) {
if (pi > 0) doc.addPage([pages[pi].w, pages[pi].h], pages[pi].w >= pages[pi].h ? 'landscape' : 'portrait');
var ops = pages[pi].ops;
for (var i = 0; i < ops.length; i++) drawOp(doc, ops[i]);
}
return doc;
}
global.RenderPDF = {
toDoc: toDoc,
buildFromModel: function (model) {
var res = global.HarnessLayout.build(model);
return { doc: toDoc(res.pages), warnings: res.warnings, pages: res.pages };
},
};
})(typeof window !== 'undefined' ? window : globalThis);