Recorrection GUI step ; LaTeX issues debugging

This commit is contained in:
sebastien committed 2026-10-10 20:40:48 +02:00
1 parent 2d0f7a219f
commit 48602c24f1
12 files changed
+306 -8

No files matched your search

+47 -5
View File
@@ -4,6 +4,7 @@ import argparse
import io
import os
import re
import shutil
import subprocess
import tempfile
from collections.abc import Sequence
@@ -175,6 +176,10 @@ def render_latex_text(text, width_px, bg_color=(255, 255, 255, 255), max_lines=N
final_img.alpha_composite(img)
return final_img
class AnnotationLatexError(RuntimeError):
"""A feedback comment failed to render; diagnostics have been retained."""
def render_real_latex_text(text, width_px, bg_color=(255, 255, 255, 255), max_lines=None, fontsize=19):
dpi = 100
width_in = width_px / dpi
@@ -194,16 +199,37 @@ def render_real_latex_text(text, width_px, bg_color=(255, 255, 255, 255), max_li
f.write(latex_template)
# Compile to PDF
subprocess.run(
compilation = subprocess.run(
['pdflatex', '-interaction=nonstopmode', 'text.tex'],
cwd=temp_dir,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=False,
)
if not os.path.exists(pdf_path):
raise RuntimeError("LaTeX compilation failed. Check your LaTeX syntax.")
diagnostics = Path(tempfile.mkdtemp(prefix="copienator-latex-error-"))
shutil.copy2(tex_path, diagnostics / "text.tex")
log_path = Path(temp_dir) / "text.log"
output = (compilation.stdout or b"").decode("utf-8", errors="replace")
(diagnostics / "compiler-output.txt").write_text(output, encoding="utf-8")
if log_path.exists():
shutil.copy2(log_path, diagnostics / "text.log")
log = log_path.read_text(encoding="utf-8", errors="replace")
else:
log = output
lines = log.splitlines()
first_error = next((i for i, line in enumerate(lines) if line.startswith("!")), max(0, len(lines) - 20))
excerpt = "\n".join(lines[first_error:first_error + 25])
raise AnnotationLatexError(
f"Échec de compilation LaTeX (code {compilation.returncode}).\n"
f"Commentaire LaTeX fautif :\n{text}\n\n"
f"Extrait du journal :\n{excerpt}\n\n"
f"Source LaTeX complète : {diagnostics / 'text.tex'}\n"
f"Diagnostics conservés dans : {diagnostics}\n"
"Modifiez le commentaire concerné dans correction.json, enregistrez le fichier, "
"puis relancez cette étape de génération des annotations."
)
# Convert PDF to grayscale (ignoring pdf2image's broken transparency)
images = convert_from_path(pdf_path, dpi=dpi)
@@ -295,7 +321,7 @@ def compose_label_image(base_img, label, result, hmin,
with_error=True,
with_empty=False,
more_right=False,
with_id=None):
with_id=None, source_pdf=None):
"""
Composes the final image with annotations.
@@ -309,6 +335,21 @@ def compose_label_image(base_img, label, result, hmin,
called when elements are placed. Used for checkboxes.
"""
original_render = render_fn
def render_fn(text, *args, **kwargs):
try:
return original_render(text, *args, **kwargs)
except AnnotationLatexError as exc:
context = f"Question : {label}"
if source_pdf is not None:
source = Path(source_pdf)
context = f"Copie : {source.parent.name} — {context}\nPDF source : {source}"
context += f"\nFichier à modifier : {source.parent.parent.parent / 'correction.json'}"
elif with_id is not None:
context = f"Copie : {with_id} — {context}"
raise AnnotationLatexError(f"{context}\n{exc}") from exc
left_pad = 0
if base_img.width < TARGET_MIN_WIDTH:
total_missing = TARGET_MIN_WIDTH - base_img.width
@@ -489,6 +530,7 @@ def process_student(student_id, labels_data, root_dir, all_labels, overwrite):
coordinates[0],
with_empty=True,
render_fn=render_real_latex_text,
source_pdf=pdf_full_path,
)
final_img.save(staging / f"{label}.jpg")
if result.get('error', "") != "empty-answer":
@@ -47,6 +47,7 @@ def render_item(item):
draw_callback=checkbox_renderer.callback,
more_right=True,
with_id=student_id,
source_pdf=pdf_path,
)
if final_image is None:
return None
@@ -184,6 +184,7 @@ def _render_student(
content["result"],
content["coordinates"][0],
draw_callback=checkbox_renderer.callback,
source_pdf=pdf_path,
)
if final_image is None:
problems = True
+8
View File
@@ -23,6 +23,14 @@ def read_json(path: str | Path, *, default: T | object = _MISSING) -> T:
target = Path(path)
try:
with target.open("r", encoding="utf-8") as stream:
if target.name == "refaire.json":
# This user-edited selection supports whole-line // comments.
# Preserve line numbers in JSON diagnostics and keep other JSON strict.
text = "".join(
"\n" if line.lstrip().startswith("//") else line
for line in stream
)
return cast(T, json.loads(text))
return cast(T, json.load(stream))
except FileNotFoundError:
if default is _MISSING: