// Renders drawing elements to SVG markup. Used for the live editor and for exports.
import { FONT_SIZES, LABEL_WEIGHT, TEXT_WEIGHT, isShape, staticPaint } from "./model.mjs";
import {
HEAD_LENGTH, HEAD_HALF_WIDTH, fmt, lineHeight, baselineOffset, approxMeasure, cylinderCap,
shapeLabelLayout, textLayout, arrowGeometry, arrowLabelBox, contentBounds,
} from "./geometry.mjs";
export function escapeXml(s) {
return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
const DASH = ' stroke-dasharray="6 6"';
export function outline(s, attrs) {
const { x, y, w, h } = s;
if (s.type === "ellipse") {
return ``;
}
if (s.type === "diamond") {
return ``;
}
if (s.type === "cylinder") {
const c = cylinderCap(s);
const rx = fmt(w / 2);
const body = `M${fmt(x)} ${fmt(y + c)} L${fmt(x)} ${fmt(y + h - c)} A${rx} ${fmt(c)} 0 0 0 ${fmt(x + w)} ${fmt(y + h - c)} L${fmt(x + w)} ${fmt(y + c)} A${rx} ${fmt(c)} 0 0 0 ${fmt(x)} ${fmt(y + c)} Z`;
return ``;
}
const r = Math.min(8, w / 4, h / 4);
return ``;
}
function textLines(lines, x, top, fs, lh, weight, fill, anchor) {
const base = baselineOffset(fs, lh);
let out = ``;
lines.forEach((line, i) => {
out += `${escapeXml(line) || " "}`;
});
return `${out}`;
}
function renderShape(s, ctx) {
const stroke = ctx.paint(s.color, "stroke");
const fill = s.fill === "solid" ? ctx.paint(s.color, "solid") : s.fill === "soft" ? ctx.paint(s.color, "soft") : "none";
const attrs = ` fill="${fill}" stroke="${stroke}" stroke-width="2" stroke-linejoin="round"${s.dash ? DASH : ""}`;
let out = outline(s, attrs);
if (s.text && ctx.hideText !== s.id) {
const l = shapeLabelLayout(s, ctx.measure);
const ink = s.fill === "solid" ? ctx.paint(s.color, "onSolid") : ctx.paint("gray", "ink");
out += textLines(l.lines, l.cx, l.top, l.fs, l.lh, LABEL_WEIGHT, ink, "middle");
}
return `${out}`;
}
function renderText(t, ctx) {
if (ctx.hideText === t.id || !t.text) return "";
const l = textLayout(t, ctx.measure);
return `${textLines(l.lines, t.x, t.y, l.fs, l.lh, TEXT_WEIGHT, ctx.paint(t.color, "ink"), "start")}`;
}
export function penPath(points) {
const p = points;
if (p.length === 1) return `M${fmt(p[0][0])} ${fmt(p[0][1])} l0.01 0`;
if (p.length === 2) return `M${fmt(p[0][0])} ${fmt(p[0][1])} L${fmt(p[1][0])} ${fmt(p[1][1])}`;
let d = `M${fmt(p[0][0])} ${fmt(p[0][1])}`;
for (let i = 1; i < p.length - 1; i++) {
const mx = (p[i][0] + p[i + 1][0]) / 2;
const my = (p[i][1] + p[i + 1][1]) / 2;
d += ` Q${fmt(p[i][0])} ${fmt(p[i][1])} ${fmt(mx)} ${fmt(my)}`;
}
const last = p[p.length - 1];
return `${d} L${fmt(last[0])} ${fmt(last[1])}`;
}
function renderPen(el, ctx) {
return ``;
}
function headPath(tip, dir) {
const bx = tip.x - dir.x * HEAD_LENGTH;
const by = tip.y - dir.y * HEAD_LENGTH;
const px = -dir.y * HEAD_HALF_WIDTH;
const py = dir.x * HEAD_HALF_WIDTH;
return `M${fmt(tip.x)} ${fmt(tip.y)} L${fmt(bx + px)} ${fmt(by + py)} L${fmt(bx - px)} ${fmt(by - py)} Z`;
}
function renderArrow(a, byId, ctx) {
const g = arrowGeometry(a, byId);
if (!g) return "";
const stroke = ctx.paint(a.color, "stroke");
let out = ``;
const head = (tip, dir) => ``;
if (a.head === "end" || a.head === "both") out += head(g.end, g.endDir);
if (a.head === "start" || a.head === "both") out += head(g.start, g.startDir);
if (a.text && ctx.hideText !== a.id) {
const box = arrowLabelBox(a, g, ctx.measure);
out += ``;
out += textLines(box.lines, g.mid.x, box.y + 2, box.fs, box.lh, TEXT_WEIGHT, ctx.paint(a.color, "ink"), "middle");
}
return `${out}`;
}
// Returns SVG markup (no