diff --git a/src/anchor/debug-textlayer.css b/src/anchor/debug-textlayer.css new file mode 100644 index 0000000..0d33326 --- /dev/null +++ b/src/anchor/debug-textlayer.css @@ -0,0 +1,24 @@ +/* + * Debug overlay for PDF text layer alignment. + * + * The text layer is normally invisible (`opacity: 0`) and selectable. + * When `.ce-debug-textlayer` is on a parent, every text span becomes a + * yellow highlight so it's obvious where text is selectable and where it + * isn't — useful for diagnosing OCR misalignment, scan-only PDFs, and + * text-layer shift caused by font fallbacks. + * + * Toggle via the "Debug text layer" entry in SessionMenu. + */ + +.ce-debug-textlayer .textLayer { + outline: 1px dashed rgba(255, 0, 0, 0.5); +} + +.ce-debug-textlayer .textLayer > span, +.ce-debug-textlayer .textLayer > br { + background: rgba(255, 220, 0, 0.35) !important; + color: rgba(0, 0, 100, 0.85) !important; + opacity: 1 !important; + /* Reveal the per-glyph span borders so misalignment is visible. */ + outline: 1px solid rgba(0, 100, 255, 0.25); +} diff --git a/src/anchor/pdf-viewer-adapter-spike.tsx b/src/anchor/pdf-viewer-adapter-spike.tsx index 50751fc..d279b90 100644 --- a/src/anchor/pdf-viewer-adapter-spike.tsx +++ b/src/anchor/pdf-viewer-adapter-spike.tsx @@ -28,6 +28,7 @@ import { } from "react-pdf-highlighter-plus"; import "react-pdf-highlighter-plus/style/style.css"; import "react-pdf-highlighter-plus/style/pdf_viewer.css"; +import "./debug-textlayer.css"; import type { NormalizedRect, Selector } from "@shared/selector"; import type { AnchorResolution, PdfSelectionCapture, ResolvedAnchorTarget } from "./types"; @@ -166,6 +167,12 @@ export interface PdfSpikeViewerProps { onSelectionCaptured(capture: PdfSelectionCapture, selectors: Selector[]): void; /** Annotation id to scroll to and highlight on mount, if any. */ readonly scrollToAnnotationId?: string; + /** + * When true, paint the PDF text-layer spans in yellow so it's + * obvious which glyphs have a selectable text overlay and which are + * image-only. Also logs every onSelection event to the console. + */ + readonly debugTextLayer?: boolean; } export interface StoredAnnotation { @@ -181,7 +188,7 @@ export interface StoredAnnotation { * - scrolls to `scrollToAnnotationId` if its highlight can be reconstructed */ export function PdfSpikeViewer(props: PdfSpikeViewerProps) { - const { pdfUrl, storedAnnotations, onSelectionCaptured, scrollToAnnotationId } = props; + const { pdfUrl, storedAnnotations, onSelectionCaptured, scrollToAnnotationId, debugTextLayer } = props; const utilsRef = useRef(null); const [didScroll, setDidScroll] = useState(null); @@ -204,24 +211,38 @@ export function PdfSpikeViewer(props: PdfSpikeViewerProps) { }, [scrollToAnnotationId, highlights, didScroll]); return ( - - {(pdfDocument) => ( - { - utilsRef.current = u; - }} - onSelection={(selection) => { - const capture = captureFromPdfSelection(selection); - const selectors = selectorsFromPdfCapture(capture); - onSelectionCaptured(capture, selectors); - }} - > - - - )} - +
+ + {(pdfDocument) => ( + { + utilsRef.current = u; + }} + onSelection={(selection) => { + const capture = captureFromPdfSelection(selection); + const selectors = selectorsFromPdfCapture(capture); + if (debugTextLayer) { + console.log("[ce] onSelection", { + text: capture.text, + page: capture.page, + rects: capture.rects, + selectorTypes: selectors.map((s) => s.type), + raw: selection, + }); + } + onSelectionCaptured(capture, selectors); + }} + > + + + )} + +
); } diff --git a/src/app/sessions/SessionMenu.tsx b/src/app/sessions/SessionMenu.tsx index d9cb8b8..0eaa461 100644 --- a/src/app/sessions/SessionMenu.tsx +++ b/src/app/sessions/SessionMenu.tsx @@ -15,7 +15,12 @@ import type { CSSProperties } from "react"; import type { SessionId } from "@shared/ids"; import type { Session } from "@shared/session"; -import { useActiveSession, useSessionListTick, useSessionService } from "@work/index"; +import { + useActiveSession, + useDebugFlag, + useSessionListTick, + useSessionService, +} from "@work/index"; import { clearAllSessionData } from "@engine/index"; import { navigateTo } from "./routing"; @@ -30,6 +35,7 @@ export function SessionMenu({ onExportZip, onImportZip, onOpenSamples }: Session const service = useSessionService(); const tick = useSessionListTick(); const active = useActiveSession(); + const [debugTextLayer, setDebugTextLayer] = useDebugFlag("textLayer"); const [open, setOpen] = useState(false); const [newName, setNewName] = useState(""); @@ -399,6 +405,25 @@ export function SessionMenu({ onExportZip, onImportZip, onOpenSamples }: Session )}
+