Robustness and cleaner diagrams: PNG and video render in a child proc… · nicoloboschi/giotto@d651064 · GitHub
Skip to content

Commit d651064

Browse files
committed
Robustness and cleaner diagrams: PNG and video render in a child process (a resvg crash on a drawing used to kill the canvas and the MCP server); same-side arrows go around boxes, arrows sharing a point are spread, labels avoid other lines and labels, lines broken by hand mid-sentence reflow, and warnings for arrows through boxes or too short to see
1 parent b57466f commit d651064

5 files changed

Lines changed: 278 additions & 67 deletions

File tree

‎bin/giotto.js‎

Lines changed: 43 additions & 16 deletions

‎lib/blocks.js‎

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function blocksOf(e) {
2424
const bullet = /^\s*[-•*]\s+/.exec(line);
2525
if (bullet && out.at(-1)?.type === 'list' && out.at(-1)._fromLines) out.at(-1).items.push(line.slice(bullet[0].length));
2626
else if (bullet) out.push({ type: 'list', items: [line.slice(bullet[0].length)], _fromLines: true });
27-
else out.push({ type: 'text', text: line });
27+
else out.push({ type: 'text', text: line, _fromLines: true });
2828
}
2929
if (e.tags?.length) out.push({ type: 'chips', items: e.tags });
3030
return out;
@@ -116,6 +116,21 @@ function pills(items, room, S) {
116116
return { rows: used, h: used.length ? used.length * (h + 5) - 5 : 0, w: Math.max(0, ...used.map((r) => r.at(-1).x + r.at(-1).w)), pill: h, size };
117117
}
118118

119+
// Lines an agent broke by hand at some other width read ragged once wrapped again ("…the" / "cheapest" /
120+
// "evidence…"). When a line would wrap anyway, and the break falls mid-sentence (the line doesn't end a
121+
// sentence and the next starts lowercase), the two become one paragraph and wrap as one. Lines that fit stay
122+
// as written.
123+
function reflow(lines, room, size, S) {
124+
const fits = (t) => String(t).length * size * S.charWidth <= room;
125+
const out = [];
126+
for (const line of lines) {
127+
const prev = out.at(-1);
128+
if (prev != null && prev.trim() && line.trim() && !/[.!?:]\s*$/.test(prev) && /^\s*[a-z]/.test(line) && (!fits(prev) || !fits(line))) out[out.length - 1] = `${prev.trimEnd()} ${line.trimStart()}`;
129+
else out.push(line);
130+
}
131+
return out;
132+
}
133+
119134
function measureBlock(b, room, size, S, align) {
120135
const lh = size * LINE;
121136
const at = (x, width, w) => (align === 'center' ? x + width / 2 : x);
@@ -127,7 +142,7 @@ function measureBlock(b, room, size, S, align) {
127142
draw: (x, y, width, color) => lines.map((l, i) => richLine(l, at(x, width), y + ts * LINE * (i + 0.5), ts, color, anchor, S, { bold: true })).join('') };
128143
}
129144
case 'text': {
130-
const lines = wrapRich(b.text, room, size, S);
145+
const lines = wrapRich(reflow(String(b.text ?? '').split('\n'), room, size, S).join('\n'), room, size, S);
131146
return { w: Math.max(0, ...lines.map((l) => l.w)), h: lines.length * lh,
132147
draw: (x, y, width, color) => lines.map((l, i) => richLine(l, at(x, width), y + lh * (i + 0.5), size, color, anchor, S)).join('') };
133148
}
@@ -279,7 +294,17 @@ function measureBlock(b, room, size, S, align) {
279294

280295
// All blocks of a box, stacked: returns the size they need and a draw(x, y, width, color).
281296
export function measureBlocks(blocks, room, S, { align = 'left', size = S.fontSize } = {}) {
282-
const parts = blocks.map((b) => measureBlock(b, room, size, S, align)).filter((p) => p.h > 0);
297+
// A label's lines (each a text block) join up where they were broken by hand mid-sentence (see reflow).
298+
const joined = [];
299+
for (const b of blocks) {
300+
const prev = joined.at(-1);
301+
if (b._fromLines && prev?._fromLines) {
302+
const r = reflow([prev.text, b.text], room, size, S);
303+
if (r.length === 1) { joined[joined.length - 1] = { ...prev, text: r[0] }; continue; }
304+
}
305+
joined.push(b);
306+
}
307+
const parts = joined.map((b) => measureBlock(b, room, size, S, align)).filter((p) => p.h > 0);
283308
return {
284309
w: Math.max(0, ...parts.map((p) => p.w)),
285310
h: parts.reduce((s, p) => s + p.h, 0) + GAP * Math.max(0, parts.length - 1),

‎lib/render-job.js‎

Lines changed: 18 additions & 0 deletions

0 commit comments

Comments
 (0)