Fix layout review save flow and respect word font sizes in replica rendering
This commit is contained in:
parent
fbdef46220
commit
9fcef4cacd
|
|
@ -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],
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -557,7 +557,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
<button type="button" class="layout-tool-btn" id="layout-fit-width">Fit</button>
|
||||
<button type="button" class="layout-tool-btn" id="layout-zoom-in">+</button>
|
||||
<span id="layout-zoom-label">100%</span>
|
||||
<button type="submit" form="layout-review-save-form" class="layout-tool-btn primary">Save</button>
|
||||
<button type="submit" form="layout-review-save-form" class="layout-tool-btn primary" onclick="window.prepareLayoutReviewSubmit && window.prepareLayoutReviewSubmit()">Save</button>
|
||||
<span id="layout-review-status">Ready</span>
|
||||
</div>
|
||||
|
||||
|
|
@ -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", () => {
|
|||
</thead>
|
||||
<tbody>
|
||||
{% 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 %}
|
||||
<tr class="line-item-row">
|
||||
<td style="padding:0.35rem; white-space:nowrap;">
|
||||
<span class="line-item-row-number">{{ i + 1 }}</span>
|
||||
|
|
|
|||
Loading…
Reference in New Issue