UNPKG

@hyperbrowser/agent

Version:

Hyperbrowsers Web Agent

117 lines (116 loc) 4.35 kB
"use strict"; /** * Renders visual overlay with numbered/labeled bounding boxes * Labels use encodedId format (e.g., "0-1234", "2-5678") */ Object.defineProperty(exports, "__esModule", { value: true }); exports.renderA11yOverlay = renderA11yOverlay; const jimp_1 = require("jimp"); // eslint-disable-next-line @typescript-eslint/no-require-imports const { SANS_16_WHITE } = require("jimp/fonts"); const highlight_colors_1 = require("../shared/highlight-colors"); /** * Draw a rectangle outline on the image */ function drawRect(image, x, y, width, height, color, lineWidth = 2) { // Ensure coordinates are integers and within bounds x = Math.max(0, Math.round(x)); y = Math.max(0, Math.round(y)); width = Math.round(width); height = Math.round(height); const imgWidth = image.bitmap.width; const imgHeight = image.bitmap.height; // Draw top and bottom lines for (let i = 0; i < lineWidth; i++) { for (let dx = 0; dx < width; dx++) { const px = x + dx; // Top line if (px < imgWidth && y + i < imgHeight) { image.setPixelColor(color, px, y + i); } // Bottom line if (px < imgWidth && y + height - i - 1 >= 0 && y + height - i - 1 < imgHeight) { image.setPixelColor(color, px, y + height - i - 1); } } } // Draw left and right lines for (let i = 0; i < lineWidth; i++) { for (let dy = 0; dy < height; dy++) { const py = y + dy; // Left line if (x + i < imgWidth && py < imgHeight) { image.setPixelColor(color, x + i, py); } // Right line if (x + width - i - 1 >= 0 && x + width - i - 1 < imgWidth && py < imgHeight) { image.setPixelColor(color, x + width - i - 1, py); } } } } /** * Draw a filled rectangle on the image */ function fillRect(image, x, y, width, height, color) { // Ensure coordinates are integers x = Math.max(0, Math.round(x)); y = Math.max(0, Math.round(y)); width = Math.round(width); height = Math.round(height); const imgWidth = image.bitmap.width; const imgHeight = image.bitmap.height; for (let dx = 0; dx < width; dx++) { for (let dy = 0; dy < height; dy++) { const px = x + dx; const py = y + dy; if (px < imgWidth && py < imgHeight) { image.setPixelColor(color, px, py); } } } } /** * Render a11y tree elements as visual overlay * * @param boundingBoxMap - Map of encodedId to bounding boxes * @param options - Rendering options * @returns Base64-encoded PNG image */ async function renderA11yOverlay(boundingBoxMap, options) { const { width, height, showEncodedIds = true, colorScheme = "rainbow", } = options; // Create transparent image const image = new jimp_1.Jimp({ width, height, color: 0x00000000 }); // Load font for labels const font = await (0, jimp_1.loadFont)(SANS_16_WHITE); // Draw each element let index = 0; for (const [encodedId, rect] of boundingBoxMap.entries()) { // Skip if not visible if (rect.width <= 0 || rect.height <= 0) continue; const color = (0, highlight_colors_1.getHighlightColorRgba)(index, colorScheme); // Draw bounding box outline drawRect(image, rect.x, rect.y, rect.width, rect.height, color, 2); // Draw label const label = showEncodedIds ? encodedId : `${index + 1}`; // Estimate label dimensions (approximate) const labelWidth = label.length * 9 + 8; // ~9px per char + padding const labelHeight = 20; // Label background position (above the element if possible) const labelX = Math.max(0, Math.round(rect.x)); const labelY = Math.max(0, Math.round(rect.y - labelHeight)); // Draw label background fillRect(image, labelX, labelY, labelWidth, labelHeight, color); // Draw label text image.print({ font, x: labelX + 4, y: labelY + 2, text: label }); index++; } // Convert to base64 PNG const buffer = await image.getBuffer("image/png"); return buffer.toString("base64"); }