agent-ecosystem/packages/agent-graph/src/canvas/draw-misc.ts

128 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Utility drawing functions.
* Adapted from agent-flow's draw-misc.ts (Apache 2.0).
*/
import { measureTextCached } from './render-cache';
/**
* Truncate text to fit within maxWidth, appending "..." if needed.
* Uses binary search for efficiency.
*/
export function truncateText(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
font: string,
): string {
if (measureTextCached(ctx, font, text) <= maxWidth) return text;
let lo = 0;
let hi = text.length;
while (lo < hi) {
const mid = (lo + hi + 1) >> 1;
if (measureTextCached(ctx, font, text.slice(0, mid) + '...') <= maxWidth) {
lo = mid;
} else {
hi = mid - 1;
}
}
return lo > 0 ? text.slice(0, lo) + '...' : '...';
}
function fitTextPrefix(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
font: string,
): string {
let lo = 0;
let hi = text.length;
while (lo < hi) {
const mid = (lo + hi + 1) >> 1;
if (measureTextCached(ctx, font, text.slice(0, mid)) <= maxWidth) {
lo = mid;
} else {
hi = mid - 1;
}
}
return text.slice(0, lo);
}
/**
* Wrap text into a small fixed number of canvas lines.
* The final line is truncated with "..." when the remaining text still overflows.
*/
export function wrapTextLines(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
font: string,
maxLines: number,
): string[] {
const normalized = text.trim().replace(/\s+/g, ' ');
if (!normalized || maxLines <= 0) return [];
if (maxLines === 1) return [truncateText(ctx, normalized, maxWidth, font)];
const lines: string[] = [];
let remaining = normalized;
while (remaining && lines.length < maxLines) {
if (measureTextCached(ctx, font, remaining) <= maxWidth) {
lines.push(remaining);
break;
}
if (lines.length === maxLines - 1) {
lines.push(truncateText(ctx, remaining, maxWidth, font));
break;
}
const prefix = fitTextPrefix(ctx, remaining, maxWidth, font).trimEnd();
if (!prefix) {
lines.push(truncateText(ctx, remaining, maxWidth, font));
break;
}
const breakIndex = prefix.lastIndexOf(' ');
const line = breakIndex > 0 ? prefix.slice(0, breakIndex) : prefix;
lines.push(line);
remaining = remaining.slice(line.length).trimStart();
}
return lines;
}
// Pre-computed hex vertex unit offsets (avoids cos/sin per call)
const HEX_COS: number[] = [];
const HEX_SIN: number[] = [];
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i - Math.PI / 6;
HEX_COS.push(Math.cos(angle));
HEX_SIN.push(Math.sin(angle));
}
/**
* Draw a regular hexagon path centered at (x, y) with given radius.
*/
export function drawHexagon(
ctx: CanvasRenderingContext2D,
x: number,
y: number,
radius: number,
): void {
ctx.beginPath();
ctx.moveTo(x + radius * HEX_COS[0], y + radius * HEX_SIN[0]);
ctx.lineTo(x + radius * HEX_COS[1], y + radius * HEX_SIN[1]);
ctx.lineTo(x + radius * HEX_COS[2], y + radius * HEX_SIN[2]);
ctx.lineTo(x + radius * HEX_COS[3], y + radius * HEX_SIN[3]);
ctx.lineTo(x + radius * HEX_COS[4], y + radius * HEX_SIN[4]);
ctx.lineTo(x + radius * HEX_COS[5], y + radius * HEX_SIN[5]);
ctx.closePath();
}
/**
* SVG path data for the Claude spark logo (256×256 viewbox).
*/
export const CLAUDE_SPARK_D =
'M128,8C60.6,8,8,60.6,8,128s52.6,120,120,120s120-52.6,120-120S195.4,8,128,8z M161.6,169.6 c-4.8,8-16,10.8-24,6l-9.6-5.6l-9.6,5.6c-8,4.8-19.2,1.6-24-6c-4.8-8-1.6-19.2,6-24l9.6-5.6v-11.2l-9.6-5.6 c-8-4.8-10.8-16-6-24c4.8-8,16-10.8,24-6l9.6,5.6l9.6-5.6c8-4.8,19.2-1.6,24,6c4.8,8,1.6,19.2-6,24l-9.6,5.6v11.2l9.6,5.6 C163.2,150.4,166.4,161.6,161.6,169.6z';