diff --git a/app/logic/document_outputs.py b/app/logic/document_outputs.py index 0b1f6ae..33428e4 100644 --- a/app/logic/document_outputs.py +++ b/app/logic/document_outputs.py @@ -384,6 +384,15 @@ def _build_word_entries_for_page(page_layout: dict, page_h: float) -> list[dict] box_width = max(1.0, right - left) box_height = max(1.0, bottom - top) + source_font_size = word.get("font_size_guess") + try: + font_size = float(source_font_size) + except (TypeError, ValueError): + font_size = _fit_font_size_for_bbox_text(word_text, box_width, box_height) + + if font_size <= 0: + font_size = _fit_font_size_for_bbox_text(word_text, box_width, box_height) + entries.append( { "text": word_text, @@ -391,11 +400,11 @@ def _build_word_entries_for_page(page_layout: dict, page_h: float) -> list[dict] "pdf_y": page_h - bottom, "box_width": box_width, "box_height": box_height, - "font_family_guess": "Helvetica", - "font_size_guess": _fit_font_size_for_bbox_text(word_text, box_width, box_height), - "text_color_guess": "#000000", - "text_render_mode_clean": 0, - "text_render_mode_scan_backed": 3, + "font_family_guess": word.get("font_family_guess") or "Helvetica", + "font_size_guess": font_size, + "text_color_guess": word.get("text_color_guess") or "#000000", + "text_render_mode_clean": word.get("text_render_mode_clean", 0), + "text_render_mode_scan_backed": word.get("text_render_mode_scan_backed", 3), "bbox_source": [left, top, right, bottom], } ) diff --git a/app/routes/documents.py b/app/routes/documents.py index cd1a738..f160c9a 100644 --- a/app/routes/documents.py +++ b/app/routes/documents.py @@ -1938,6 +1938,9 @@ def _layout_review_group_words_into_lines(words, y_tol: float = 12.0): async def save_layout_review(document_id: str, request: Request, db: Session = Depends(get_db)): form = await request.form() payload_raw = form.get("layout_review_json") + print("[save_layout_review] payload present:", bool(payload_raw)) + print("[save_layout_review] payload length:", len(payload_raw) if payload_raw else 0) + print(f"[save_layout_review] document_id={document_id} payload_present={bool(payload_raw)} payload_len={len(payload_raw) if payload_raw else 0}", flush=True) if not payload_raw: return RedirectResponse( diff --git a/app/templates/documents/detail.html b/app/templates/documents/detail.html index cced2c0..54ea5c7 100644 --- a/app/templates/documents/detail.html +++ b/app/templates/documents/detail.html @@ -557,7 +557,7 @@ document.addEventListener("DOMContentLoaded", () => { 100% - + Ready @@ -876,17 +876,21 @@ document.addEventListener("DOMContentLoaded", () => { return words.find(w => String(w.id) === String(selectedId)) || null; } - function syncFontInputs(value) { + function syncFontInputs(value, word = null) { const v = Number.isFinite(Number(value)) ? Number(value) : ""; if (fontSizeInput) fontSizeInput.value = v; + if (popoverFontSizeInput) popoverFontSizeInput.value = v; + + if (!word) return; + ensureStyle(word); resolveStyle(word); + if (fontFamilyInput) fontFamilyInput.value = word.resolved_style?.font_family || "Helvetica"; if (fontWeightInput) fontWeightInput.value = String(word.resolved_style?.font_weight || 400); if (fontStyleInput) fontStyleInput.value = word.resolved_style?.font_style || "normal"; if (letterSpacingInput) letterSpacingInput.value = String(word.resolved_style?.letter_spacing || 0); if (textColorInput) textColorInput.value = word.resolved_style?.text_color || "#000000"; - if (popoverFontSizeInput) popoverFontSizeInput.value = v; } function pageToCanvasPoint(px, py, displayWidth, displayHeight) { @@ -988,7 +992,7 @@ document.addEventListener("DOMContentLoaded", () => { for (const word of sortedWords) { const bbox = normalizeBBox(word.bbox || [0,0,0,0]); const cy = (bbox[1] + bbox[3]) / 2; - let placed = False; + let placed = false; for (const group of groups) { if (Math.abs(cy - group.centerY) <= LINE_GROUP_TOLERANCE) { group.words.push(word); @@ -1312,7 +1316,7 @@ document.addEventListener("DOMContentLoaded", () => { if (textInput) textInput.value = nextText; if (popoverTextInput) popoverTextInput.value = nextText; - syncFontInputs(w.font_size_guess); + syncFontInputs(w.font_size_guess, w); w.bbox = normalizeBBox([ Number(x1Input ? x1Input.value : 0), @@ -1337,6 +1341,19 @@ document.addEventListener("DOMContentLoaded", () => { applyEditorValues(true); } + function applyFontSizeInputToSelected(push = true) { + const w = getSelectedWord(); + if (!w || !fontSizeInput) return; + const next = Number(fontSizeInput.value); + if (!Number.isFinite(next) || next <= 0) return; + if (push) pushHistory(); + w.font_size_guess = next; + syncFontInputs(next, w); + syncReviewedTextarea(); + renderCanvas(); + setStatus("Font size applied: " + next); + } + function applyFontSizeToAllWords() { const w = getSelectedWord(); if (!w) return; @@ -1350,7 +1367,7 @@ document.addEventListener("DOMContentLoaded", () => { item.font_size_guess = chosen; if (!item.font_family_guess) item.font_family_guess = getWordFontFamily(w); } - syncFontInputs(chosen); + syncFontInputs(chosen, w); syncReviewedTextarea(); renderCanvas(); setStatus("Applied font size to all"); @@ -1393,22 +1410,72 @@ document.addEventListener("DOMContentLoaded", () => { page: page.page || 1, page_width: page.page_width || 1, page_height: page.page_height || 1, - words: words.map((w, idx) => ({ - id: Number(w.id || (idx + 1)), - text: w.text || "", - font_size_guess: getWordFontSize(w), - font_family_guess: getWordFontFamily(w), - bbox: normalizeBBox([ - Number((w.bbox || [0,0,0,0])[0] || 0), - Number((w.bbox || [0,0,0,0])[1] || 0), - Number((w.bbox || [0,0,0,0])[2] || 0), - Number((w.bbox || [0,0,0,0])[3] || 0), - ]), - })), + words: words.map((w, idx) => { + ensureStyle(w); + resolveStyle(w); + return { + id: Number(w.id || (idx + 1)), + text: w.text || "", + font_size_guess: getWordFontSize(w), + font_family_guess: getWordFontFamily(w), + font_weight_guess: Number((w.resolved_style && w.resolved_style.font_weight) || 400), + font_style_guess: String((w.resolved_style && w.resolved_style.font_style) || "normal"), + letter_spacing_guess: Number((w.resolved_style && w.resolved_style.letter_spacing) || 0), + text_color_guess: String((w.resolved_style && w.resolved_style.text_color) || "#000000"), + bbox: normalizeBBox([ + Number((w.bbox || [0,0,0,0])[0] || 0), + Number((w.bbox || [0,0,0,0])[1] || 0), + Number((w.bbox || [0,0,0,0])[2] || 0), + Number((w.bbox || [0,0,0,0])[3] || 0), + ]), + }; + }), }], }); } + window.prepareLayoutReviewSubmit = function () { + applyEditorValues(false); + syncReviewedTextarea(); + const payload = buildLayoutReviewPayload(); + if (saveJsonInput) saveJsonInput.value = payload; + if (statusEl) statusEl.textContent = "[layout-review] manual payload bytes: " + (payload ? payload.length : -1); + return payload; + }; + + + function prepareLayoutReviewSubmit() { + try { applyEditorValues(false); } catch (e) { console.error("[layout-review] applyEditorValues failed", e); } + try { syncReviewedTextarea(); } catch (e) { console.error("[layout-review] syncReviewedTextarea failed", e); } + try { + if (saveJsonInput) { + saveJsonInput.value = buildLayoutReviewPayload(); + console.log("[layout-review] prepared payload length", saveJsonInput.value.length); + } + } catch (e) { + console.error("[layout-review] build payload failed", e); + } + } + + window.buildLayoutReviewPayload = buildLayoutReviewPayload; + window.prepareLayoutReviewSubmit = prepareLayoutReviewSubmit; + + function prepareLayoutReviewSubmit() { + try { applyEditorValues(false); } catch (e) { console.error("[layout-review] applyEditorValues failed", e); } + try { syncReviewedTextarea(); } catch (e) { console.error("[layout-review] syncReviewedTextarea failed", e); } + try { + if (saveJsonInput) { + saveJsonInput.value = buildLayoutReviewPayload(); + console.log("[layout-review] prepared payload length", saveJsonInput.value.length); + } + } catch (e) { + console.error("[layout-review] build payload failed", e); + } + } + + window.buildLayoutReviewPayload = buildLayoutReviewPayload; + window.prepareLayoutReviewSubmit = prepareLayoutReviewSubmit; + function beginPan(ev) { dragState = { mode: "pan", @@ -1665,6 +1732,8 @@ document.addEventListener("DOMContentLoaded", () => { document.getElementById("layout-font-down")?.addEventListener("click", () => changeSelectedFontSize(-0.5)); document.getElementById("layout-font-up")?.addEventListener("click", () => changeSelectedFontSize(0.5)); document.getElementById("layout-font-all")?.addEventListener("click", applyFontSizeToAllWords); + fontSizeInput?.addEventListener("change", () => applyFontSizeInputToSelected(true)); + fontSizeInput?.addEventListener("blur", () => applyFontSizeInputToSelected(true)); document.getElementById("layout-popover-font-down")?.addEventListener("click", () => changeSelectedFontSize(-0.5)); document.getElementById("layout-popover-font-up")?.addEventListener("click", () => changeSelectedFontSize(0.5)); document.getElementById("layout-popover-font-all")?.addEventListener("click", applyFontSizeToAllWords); @@ -1708,12 +1777,21 @@ document.addEventListener("DOMContentLoaded", () => { window.addEventListener("keydown", handleKeyDown); if (saveForm) { - saveForm.addEventListener("submit", function () { - applyEditorValues(false); - syncReviewedTextarea(); - if (saveJsonInput) saveJsonInput.value = buildLayoutReviewPayload(); - }); - } + saveForm.addEventListener("submit", function (ev) { + try { + applyEditorValues(false); + syncReviewedTextarea(); + const payload = buildLayoutReviewPayload(); + if (saveJsonInput) saveJsonInput.value = payload; + console.log("[layout-review] payload bytes", payload ? payload.length : -1); + if (statusEl) statusEl.textContent = "[layout-review] payload bytes: " + (payload ? payload.length : -1); + } catch (err) { + ev.preventDefault(); + console.error("[layout-review] submit error", err); + if (statusEl) statusEl.textContent = "[layout-review] submit error: " + (err && err.message ? err.message : err); + } + }); + } pushHistory(); setTool("select"); @@ -1997,7 +2075,7 @@ document.addEventListener("DOMContentLoaded", () => {
{% for i in range(row_count) %} - {% set item = line_items[i] if i < line_items|length else None %} + {% set item = line_items[i] if i < line_items|length else none %}