{cells.map((cell) => {
if (cell.status === 'empty' || !cell.text) return null
const cellKey = `pos-${cell.row}-${cell.col}`
const isHovered = hoveredCell === cellKey
// Estimate font size from cell height: height_pct maps to roughly pt size
// A4 at 100% = 297mm height. Cell height in % * 297mm / 100 = height_mm
// Font size ~= height_mm * 2.2 (roughly matching print)
const heightMm = cell.height_mm ?? (cell.height / 100 * A4_HEIGHT_MM)
const fontSizePt = Math.max(6, Math.min(18, heightMm * 2.2))
return (
setHoveredCell(cellKey)}
onMouseLeave={() => setHoveredCell(null)}
>
{editable ? (
{
const newText = e.currentTarget.textContent ?? ''
if (newText !== cell.text && onTextChange) {
onTextChange(cell, newText)
}
}}
>
{cell.text}
) : (
{cell.text}
)}
)
})}
)
}
export function GridOverlay({
grid,
imageUrl,
onCellClick,
onCellTextChange,
selectedCell,
showEmpty = false,
showLabels = true,
showNumbers = false,
showTextLabels = false,
showMmGrid = false,
showTextAtPosition = false,
editableText = false,
highlightedBlockNumber,
className,
}: GridOverlayProps) {
const handleCellClick = useCallback(
(cell: GridCell) => {
if (onCellClick && cell.status !== 'empty') {
onCellClick(cell)
}
},
[onCellClick]
)
const flatCells = grid.cells.flat()
return (
{/* Background image */}
{imageUrl && (

)}
{/* SVG overlay */}
{/* Positioned text HTML overlay (outside SVG for proper text rendering) */}
{showTextAtPosition && (
c.status !== 'empty' && c.text)}
editable={editableText}
onTextChange={onCellTextChange}
/>
)}
)
}
/**
* GridStats Component
*/
interface GridStatsProps {
stats: GridData['stats']
deskewAngle?: number
source?: string
className?: string
}
export function GridStats({ stats, deskewAngle, source, className }: GridStatsProps) {
const coveragePercent = Math.round(stats.coverage * 100)
return (
Erkannt: {stats.recognized}
{(stats.manual ?? 0) > 0 && (
Manuell: {stats.manual}
)}
{stats.problematic > 0 && (
Problematisch: {stats.problematic}
)}
Leer: {stats.empty}
Abdeckung: {coveragePercent}%
{deskewAngle !== undefined && deskewAngle !== 0 && (
Begradigt: {deskewAngle.toFixed(1)}
)}
{source && (
Quelle: {source === 'tesseract+grid_service' ? 'Tesseract' : 'Vision LLM'}
)}
)
}
/**
* Legend Component for GridOverlay
*/
export function GridLegend({ className }: { className?: string }) {
return (