UNPKG

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
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 `<?xml version="1.0" encoding="UTF-8"?> <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, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&apos;'); } } module.exports = SVGExporter;