'use client' import { useEffect, useRef, useState } from 'react' import type { StructureBox, StructureGraphic } from '@/app/(admin)/ai/ocr-kombi/types' import type { WordPosition } from './usePixelWordPositions' import type { EditableCell, PageRegion, RowItem, PageZone } from './StepReconstructionTypes' import { adjustCellForBoxZones } from './StepReconstructionTypes' import { StructureLayer } from './StructureLayer' interface ReconstructionOverlayProps { cells: EditableCell[] dewarpedUrl: string imageNaturalSize: { w: number; h: number } | null parentColumns: PageRegion[] parentRows: RowItem[] parentZones: PageZone[] structureBoxes: StructureBox[] structureGraphics: StructureGraphic[] showStructure: boolean fontScale: number globalBold: boolean boxZonesPct: { topPct: number; bottomPct: number }[] cellWordPositions: Map onTextChange: (cellId: string, newText: string) => void onKeyDown: (e: React.KeyboardEvent, cellId: string) => void onResetCell: (cellId: string) => void onImageNaturalSize: (size: { w: number; h: number }) => void getDisplayText: (cell: EditableCell) => string isEdited: (cell: EditableCell) => boolean } export function ReconstructionOverlay({ cells, dewarpedUrl, imageNaturalSize, parentColumns, parentRows, parentZones, structureBoxes, structureGraphics, showStructure, fontScale, globalBold, boxZonesPct, cellWordPositions, onTextChange, onKeyDown, onResetCell, onImageNaturalSize, getDisplayText, isEdited, }: ReconstructionOverlayProps) { const reconRef = useRef(null) const [reconWidth, setReconWidth] = useState(0) // Track reconstruction container width for font size calculation useEffect(() => { const el = reconRef.current if (!el) return const obs = new ResizeObserver(entries => { for (const entry of entries) setReconWidth(entry.contentRect.width) }) obs.observe(el) return () => obs.disconnect() }, []) const imgW = imageNaturalSize?.w || 1 const imgH = imageNaturalSize?.h || 1 const aspect = imgH / imgW const containerH = reconWidth * aspect return (
{/* Left: Original image */}
Originalbild
{/* eslint-disable-next-line @next/next/no-img-element */} Original { const img = e.target as HTMLImageElement onImageNaturalSize({ w: img.naturalWidth, h: img.naturalHeight }) }} />
{/* Right: Reconstructed table overlay */}
Rekonstruktion ({cells.length} Zellen)
{/* Column lines */} {parentColumns .filter(c => !['header', 'footer'].includes(c.type)) .map((col, i) => (
))} {/* Row lines */} {parentRows.map((row, i) => (
))} {/* Box zone highlight */} {parentZones .filter(z => z.zone_type === 'box' && z.box) .map((z, i) => { const box = z.box! return (
) })} {/* Structure elements (boxes, graphics) */} {/* Pixel-positioned words / editable inputs */} {cells.map((cell) => renderOverlayCell( cell, getDisplayText(cell), isEdited(cell), cellWordPositions.get(cell.cellId), adjustCellForBoxZones(cell.bboxPct, cell.cellId, boxZonesPct), containerH, fontScale, globalBold, onTextChange, onKeyDown, onResetCell, ))}
) } // --- Cell rendering for overlay mode --- function renderOverlayCell( cell: EditableCell, displayText: string, edited: boolean, wordPos: WordPosition[] | undefined, adjBbox: { x: number; y: number; w: number; h: number }, containerH: number, fontScale: number, globalBold: boolean, onTextChange: (cellId: string, text: string) => void, onKeyDown: (e: React.KeyboardEvent, cellId: string) => void, onResetCell: (cellId: string) => void, ): React.ReactNode { const cellHeightPx = containerH * (adjBbox.h / 100) // Pixel-analysed: render word-groups at detected positions as inputs if (wordPos && wordPos.length > 0) { return wordPos.map((wp, i) => { const autoFontPx = cellHeightPx * wp.fontRatio * fontScale const fs = Math.max(6, autoFontPx) // For multi-group cells, render as span (read-only positioned) if (wordPos.length > 1) { return ( {wp.text} ) } // Single group: render as editable input at pixel position return (
onTextChange(cell.cellId, e.target.value)} onKeyDown={(e) => onKeyDown(e, cell.cellId)} className={`w-full h-full bg-transparent border-0 outline-none px-0 transition-colors ${ edited ? 'bg-green-50/30' : '' }`} style={{ fontSize: `${fs}px`, fontWeight: globalBold ? 'bold' : (cell.colType === 'column_en' ? 'bold' : 'normal'), fontFamily: "'Liberation Sans', Arial, sans-serif", lineHeight: '1', color: '#1a1a1a', }} title={`${cell.cellId} (${cell.colType})`} /> {edited && ( )}
) }) } // Fallback: no pixel data — single input at cell bbox if (!cell.text) return null const fontSize = Math.max(6, cellHeightPx * fontScale) return (
onTextChange(cell.cellId, e.target.value)} onKeyDown={(e) => onKeyDown(e, cell.cellId)} className={`w-full h-full bg-transparent border-0 outline-none px-0 transition-colors ${ edited ? 'bg-green-50/30' : '' }`} style={{ fontSize: `${fontSize}px`, fontWeight: globalBold ? 'bold' : 'normal', fontFamily: "'Liberation Sans', Arial, sans-serif", lineHeight: '1', color: '#1a1a1a', }} title={`${cell.cellId} (${cell.colType})`} /> {edited && ( )}
) }