img-to-text-computational
Version:
High-performance image-to-text analyzer using pure computational methods. Convert images to structured text descriptions with 99.9% accuracy, zero AI dependencies, and complete offline processing.
548 lines (465 loc) • 16.5 kB
JavaScript
class SVGExporter {
constructor(options = {}) {
this.options = {
includeOriginalImage: options.includeOriginalImage !== false,
showBoundingBoxes: options.showBoundingBoxes !== false,
showTextElements: options.showTextElements !== false,
showColorPalette: options.showColorPalette !== false,
colorBoxSize: options.colorBoxSize || 30,
fontSize: options.fontSize || 12,
strokeWidth: options.strokeWidth || 2,
opacity: options.opacity || 0.7,
...options
};
this.colors = {
text: '#FF6B6B',
button: '#4ECDC4',
input: '#45B7D1',
navigation: '#96CEB4',
image: '#FFEAA7',
card: '#DDA0DD',
container: '#98D8C8',
default: '#95A5A6'
};
}
/**
* Export analysis results to SVG format
* @param {Object} analysisResult - Complete analysis result
* @param {Object} options - Export options
* @returns {Promise<string>} SVG content
*/
async exportToSVG(analysisResult, options = {}) {
try {
const exportOptions = { ...this.options, ...options };
const imageMetadata = analysisResult.image_metadata || {};
const width = imageMetadata.width || 1000;
const height = imageMetadata.height || 800;
// Create SVG structure
let svg = this.createSVGHeader(width, height);
// Add background image if requested
if (exportOptions.includeOriginalImage && imageMetadata.file_path) {
svg += this.addBackgroundImage(imageMetadata.file_path, width, height);
}
// Add component bounding boxes
if (exportOptions.showBoundingBoxes && analysisResult.components) {
svg += this.addComponentBoundingBoxes(analysisResult.components);
}
// Add text elements
if (exportOptions.showTextElements && analysisResult.text_extraction) {
svg += this.addTextElements(analysisResult.text_extraction);
}
// Add color palette
if (exportOptions.showColorPalette && analysisResult.color_analysis) {
svg += this.addColorPalette(analysisResult.color_analysis, width, height);
}
// Add layout guides
if (analysisResult.layout_analysis) {
svg += this.addLayoutGuides(analysisResult.layout_analysis, width, height);
}
// Add pattern annotations
if (analysisResult.advanced_patterns) {
svg += this.addPatternAnnotations(analysisResult.advanced_patterns);
}
// Add legend
svg += this.addLegend(width, height);
// Close SVG
svg += this.createSVGFooter();
return svg;
} catch (error) {
throw new Error(`SVG export failed: ${error.message}`);
}
}
/**
* Create SVG header
*/
createSVGHeader(width, height) {
return `
<svg width="${width}" height="${height + 200}" viewBox="0 0 ${width} ${height + 200}"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<style>
.component-box { fill: none; stroke-width: ${this.options.strokeWidth}; opacity: ${this.options.opacity}; }
.text-element { font-family: Arial, sans-serif; font-size: ${this.options.fontSize}px; }
.legend-text { font-family: Arial, sans-serif; font-size: 10px; fill: #333; }
.pattern-annotation { font-family: Arial, sans-serif; font-size: 11px; fill: #666; }
.layout-guide { stroke: #999; stroke-width: 1; stroke-dasharray: 5,5; opacity: 0.5; }
</style>
</defs>
<title>Image Analysis Results</title>
<desc>Generated by img-to-text-computational</desc>
`;
}
/**
* Add background image
*/
addBackgroundImage(imagePath, width, height) {
return ` <image x="0" y="0" width="${width}" height="${height}"
xlink:href="${imagePath}" opacity="0.3" />
`;
}
/**
* Add component bounding boxes
*/
addComponentBoundingBoxes(components) {
let svg = ' <!-- Component Bounding Boxes -->\n';
components.forEach((component, index) => {
const pos = component.position;
const color = this.colors[component.type] || this.colors.default;
svg += ` <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
class="component-box" stroke="${color}"
data-component-id="${component.id}" data-component-type="${component.type}">
<title>${component.type} - ${component.text_content || 'No text'}</title>
</rect>
`;
// Add component label
svg += ` <text x="${pos.x + 5}" y="${pos.y - 5}" class="text-element"
fill="${color}" font-size="10px">
${component.type}${component.confidence ? ` (${Math.round(component.confidence * 100)}%)` : ''}
</text>
`;
});
return svg;
}
/**
* Add text elements
*/
addTextElements(textExtraction) {
let svg = ' <!-- Text Elements -->\n';
if (textExtraction.structured_text) {
textExtraction.structured_text.forEach((textElement, index) => {
const pos = textElement.position;
const fontSize = textElement.font_info?.estimated_size || this.options.fontSize;
svg += ` <text x="${pos.x}" y="${pos.y + fontSize}"
class="text-element" font-size="${fontSize}px"
fill="#333" opacity="0.8"
data-text-id="${textElement.id}" data-text-type="${textElement.type}">
<title>Text: ${textElement.text.substring(0, 50)}${textElement.text.length > 50 ? '...' : ''}</title>
${this.escapeXML(textElement.text)}
</text>
`;
});
}
return svg;
}
/**
* Add color palette
*/
addColorPalette(colorAnalysis, canvasWidth, canvasHeight) {
let svg = ' <!-- Color Palette -->\n';
if (colorAnalysis.color_palette) {
const paletteY = canvasHeight + 20;
const boxSize = this.options.colorBoxSize;
svg += ` <text x="10" y="${paletteY - 5}" class="legend-text" font-weight="bold">
Color Palette
</text>
`;
colorAnalysis.color_palette.slice(0, 10).forEach((color, index) => {
const x = 10 + (index * (boxSize + 5));
svg += ` <rect x="${x}" y="${paletteY}" width="${boxSize}" height="${boxSize}"
fill="${color.hex}" stroke="#333" stroke-width="1"
data-color="${color.hex}" data-percentage="${color.percentage}">
<title>${color.hex} - ${color.percentage.toFixed(1)}%</title>
</rect>
`;
// Add color label
svg += ` <text x="${x + boxSize / 2}" y="${paletteY + boxSize + 15}"
class="legend-text" text-anchor="middle" font-size="8px">
${color.hex}
</text>
`;
});
}
return svg;
}
/**
* Add layout guides
*/
addLayoutGuides(layoutAnalysis, width, height) {
let svg = ' <!-- Layout Guides -->\n';
// Add grid lines if grid detected
if (layoutAnalysis.grid_analysis?.detected) {
const grid = layoutAnalysis.grid_analysis;
const cellWidth = width / grid.columns;
const cellHeight = height / grid.rows;
// Vertical grid lines
for (let i = 1; i < grid.columns; i++) {
const x = i * cellWidth;
svg += ` <line x1="${x}" y1="0" x2="${x}" y2="${height}" class="layout-guide" />
`;
}
// Horizontal grid lines
for (let i = 1; i < grid.rows; i++) {
const y = i * cellHeight;
svg += ` <line x1="0" y1="${y}" x2="${width}" y2="${y}" class="layout-guide" />
`;
}
}
// Add alignment guides
if (layoutAnalysis.alignment_analysis) {
const alignment = layoutAnalysis.alignment_analysis;
// Horizontal alignment lines
if (alignment.horizontal_groups) {
alignment.horizontal_groups.forEach((group, index) => {
if (group.elements.length >= 2) {
const y = group.y_position || 0;
svg += ` <line x1="0" y1="${y}" x2="${width}" y2="${y}"
class="layout-guide" stroke="#FF6B6B" opacity="0.3" />
`;
}
});
}
// Vertical alignment lines
if (alignment.vertical_groups) {
alignment.vertical_groups.forEach((group, index) => {
if (group.elements.length >= 2) {
const x = group.x_position || 0;
svg += ` <line x1="${x}" y1="0" x2="${x}" y2="${height}"
class="layout-guide" stroke="#4ECDC4" opacity="0.3" />
`;
}
});
}
}
return svg;
}
/**
* Add pattern annotations
*/
addPatternAnnotations(advancedPatterns) {
let svg = ' <!-- Pattern Annotations -->\n';
if (advancedPatterns.detected_patterns) {
advancedPatterns.detected_patterns.forEach((pattern, index) => {
// Add pattern labels at appropriate positions
const y = 20 + (index * 20);
svg += ` <text x="10" y="${y}" class="pattern-annotation" font-weight="bold">
Pattern: ${pattern.pattern} (${Math.round(pattern.confidence * 100)}%)
</text>
`;
if (pattern.evidence && pattern.evidence.length > 0) {
svg += ` <text x="20" y="${y + 12}" class="pattern-annotation" font-size="9px">
${pattern.evidence[0]}
</text>
`;
}
});
}
return svg;
}
/**
* Add legend
*/
addLegend(width, height) {
const legendY = height + 80;
let svg = ' <!-- Legend -->\n';
svg += ` <text x="10" y="${legendY}" class="legend-text" font-weight="bold">
Component Types
</text>
`;
const componentTypes = Object.keys(this.colors);
componentTypes.forEach((type, index) => {
const x = 10 + (index * 100);
const y = legendY + 20;
const color = this.colors[type];
svg += ` <rect x="${x}" y="${y - 10}" width="15" height="10"
fill="none" stroke="${color}" stroke-width="2" />
<text x="${x + 20}" y="${y}" class="legend-text">
${type}
</text>
`;
});
return svg;
}
/**
* Create SVG footer
*/
createSVGFooter() {
return '</svg>';
}
/**
* Export component hierarchy as nested SVG groups
*/
async exportComponentHierarchy(analysisResult, options = {}) {
try {
const components = analysisResult.components || [];
const relationships = analysisResult.component_relationships?.hierarchical_relationships || {};
let svg = this.createSVGHeader(1000, 800);
svg += ' <!-- Component Hierarchy -->\n';
// Build hierarchy tree
const hierarchy = this.buildHierarchyTree(components, relationships);
// Render hierarchy
svg += this.renderHierarchyNode(hierarchy, 0, 0, 0);
svg += this.createSVGFooter();
return svg;
} catch (error) {
throw new Error(`Component hierarchy export failed: ${error.message}`);
}
}
/**
* Build hierarchy tree from components and relationships
*/
buildHierarchyTree(components, relationships) {
// Simplified hierarchy building
const tree = {
id: 'root',
children: components.map(comp => ({
id: comp.id,
type: comp.type,
position: comp.position,
children: []
}))
};
return tree;
}
/**
* Render hierarchy node
*/
renderHierarchyNode(node, x, y, level) {
let svg = '';
const indent = level * 20;
const nodeHeight = 25;
if (node.id !== 'root') {
const color = this.colors[node.type] || this.colors.default;
svg += ` <g transform="translate(${x + indent}, ${y})">
<rect x="0" y="0" width="150" height="${nodeHeight}"
fill="${color}" opacity="0.2" stroke="${color}" />
<text x="5" y="15" class="legend-text">
${node.type} (${node.id})
</text>
</g>
`;
}
// Render children
let childY = y + (node.id !== 'root' ? nodeHeight + 5 : 0);
node.children.forEach(child => {
svg += this.renderHierarchyNode(child, x, childY, level + 1);
childY += nodeHeight + 5;
});
return svg;
}
/**
* Export layout wireframe
*/
async exportLayoutWireframe(analysisResult, options = {}) {
try {
const components = analysisResult.components || [];
const imageMetadata = analysisResult.image_metadata || {};
const width = imageMetadata.width || 1000;
const height = imageMetadata.height || 800;
let svg = this.createSVGHeader(width, height);
svg += ' <!-- Layout Wireframe -->\n';
// Add wireframe boxes for each component
components.forEach(component => {
const pos = component.position;
const strokeColor = this.colors[component.type] || this.colors.default;
svg += ` <rect x="${pos.x}" y="${pos.y}" width="${pos.width}" height="${pos.height}"
fill="none" stroke="${strokeColor}" stroke-width="2"
stroke-dasharray="5,5" opacity="0.8">
<title>${component.type}</title>
</rect>
`;
// Add placeholder content
if (component.type === 'button') {
svg += ` <rect x="${pos.x + 5}" y="${pos.y + 5}"
width="${pos.width - 10}" height="${pos.height - 10}"
fill="${strokeColor}" opacity="0.1" />
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
text-anchor="middle" class="legend-text">
BUTTON
</text>
`;
} else if (component.type === 'input') {
svg += ` <rect x="${pos.x + 5}" y="${pos.y + 5}"
width="${pos.width - 10}" height="${pos.height - 10}"
fill="white" stroke="#ccc" />
<text x="${pos.x + 10}" y="${pos.y + pos.height / 2}"
class="legend-text" fill="#999">
Input field
</text>
`;
} else if (component.type === 'image') {
svg += ` <rect x="${pos.x + 5}" y="${pos.y + 5}"
width="${pos.width - 10}" height="${pos.height - 10}"
fill="#f0f0f0" stroke="#ccc" />
<text x="${pos.x + pos.width / 2}" y="${pos.y + pos.height / 2}"
text-anchor="middle" class="legend-text">
IMAGE
</text>
`;
}
});
svg += this.createSVGFooter();
return svg;
} catch (error) {
throw new Error(`Layout wireframe export failed: ${error.message}`);
}
}
/**
* Export interactive SVG with click handlers
*/
async exportInteractiveSVG(analysisResult, options = {}) {
try {
const svg = await this.exportToSVG(analysisResult, options);
// Add JavaScript for interactivity
const interactiveScript = `
<script type="text/javascript">
<![CDATA[
function showComponentInfo(evt) {
const rect = evt.target;
const componentId = rect.getAttribute('data-component-id');
const componentType = rect.getAttribute('data-component-type');
// Create info popup
const popup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
popup.setAttribute('id', 'info-popup');
const bg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
bg.setAttribute('x', evt.clientX + 10);
bg.setAttribute('y', evt.clientY - 30);
bg.setAttribute('width', 200);
bg.setAttribute('height', 50);
bg.setAttribute('fill', 'white');
bg.setAttribute('stroke', 'black');
bg.setAttribute('opacity', '0.9');
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', evt.clientX + 15);
text.setAttribute('y', evt.clientY - 10);
text.textContent = componentType + ' (' + componentId + ')';
popup.appendChild(bg);
popup.appendChild(text);
// Remove existing popup
const existing = document.getElementById('info-popup');
if (existing) existing.remove();
evt.target.parentNode.appendChild(popup);
}
function hideComponentInfo(evt) {
const popup = document.getElementById('info-popup');
if (popup) popup.remove();
}
// Add event listeners to all component boxes
document.addEventListener('DOMContentLoaded', function() {
const components = document.querySelectorAll('[data-component-id]');
components.forEach(function(component) {
component.addEventListener('mouseover', showComponentInfo);
component.addEventListener('mouseout', hideComponentInfo);
});
});
]]>
</script>
`;
// Insert script before closing SVG tag
return svg.replace('</svg>', `${interactiveScript }\n</svg>`);
} catch (error) {
throw new Error(`Interactive SVG export failed: ${error.message}`);
}
}
/**
* Escape XML special characters
*/
escapeXML(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
}
module.exports = SVGExporter;