Enforce configurable height and pixel limits for tablet documents

This commit is contained in:
sebastien committed 2026-10-26 15:09:05 +01:00
1 parent 8918d64452
commit a38c1ff4b3
10 files changed
+187 -57

No files matched your search

+49 -19
View File
@@ -23,7 +23,7 @@ from copienator import (
workspace_from_args,
)
from copienator.annotation_data import load_annotation_data
from copienator.annotation_documents import MAX_HEIGHT_PX
from copienator.annotation_documents import MAX_HEIGHT_PX, MAX_PIXELS, document_fits
from copienator.commands import annotating, annotating_with_checks
from copienator.commands.import_annotations import clear_import_directory
from copienator.filesystem import staged_directory
@@ -64,10 +64,11 @@ def save_batch(batch, prefix, group_id, output_root: Path) -> None:
print(f"Generating group PDF: {prefix} G{group_id} ({len(batch)} elements)")
max_width = max(item[2].width for item in batch)
total_height = sum(item[2].height for item in batch)
if total_height > MAX_HEIGHT_PX and len({item[0] for item in batch}) > 1:
if not document_fits(max_width, total_height, MAX_HEIGHT_PX, MAX_PIXELS):
raise CliError(
f"{prefix} G{group_id} would be {total_height} px high; "
f"only a single student's block may exceed {MAX_HEIGHT_PX} px."
f"{prefix} G{group_id} would be {max_width} × {total_height} px "
f"({max_width * total_height} pixels); document limits are "
f"{MAX_HEIGHT_PX} px high and {MAX_PIXELS} pixels."
)
concatenated = Image.new("RGB", (max_width, total_height), "white")
draw = ImageDraw.Draw(concatenated)
@@ -281,6 +282,26 @@ def _existing_group_state(
def split_batches(rendered):
batches = []
heights = []
widths = []
def fits(width, height):
return document_fits(width, height, MAX_HEIGHT_PX, MAX_PIXELS)
def add_block(items):
width = max(item[2].width for item in items)
height = sum(item[2].height for item in items)
# Reuse space, accounting for padding to the widest answer in a group.
for batch_index in range(len(batches)):
combined_width = max(widths[batch_index], width)
combined_height = heights[batch_index] + height
if fits(combined_width, combined_height):
batches[batch_index].extend(items)
widths[batch_index] = combined_width
heights[batch_index] = combined_height
return
batches.append(list(items))
widths.append(width)
heights.append(height)
index = 0
while index < len(rendered):
@@ -291,22 +312,31 @@ def split_batches(rendered):
student_items = rendered[index:end]
student_height = sum(item[2].height for item in student_items)
if student_height > MAX_HEIGHT_PX:
# Oversized blocks remain intact in a group of their own.
batches.append(list(student_items))
heights.append(student_height)
index = end
continue
# Reuse space in earlier groups, keeping each student's block intact.
for batch_index, height in enumerate(heights):
if height + student_height <= MAX_HEIGHT_PX:
batches[batch_index].extend(student_items)
heights[batch_index] += student_height
break
student_width = max(item[2].width for item in student_items)
if fits(student_width, student_height):
add_block(student_items)
else:
batches.append(list(student_items))
heights.append(student_height)
# Split oversized student blocks only between complete answers.
block = []
width = height = 0
for item in student_items:
image = item[2]
if not fits(image.width, image.height):
raise CliError(
f"Copie{student_id} ({item[1]}) is {image.width} × "
f"{image.height} px ({image.width * image.height} pixels); "
f"a single answer cannot be split. Document limits are "
f"{MAX_HEIGHT_PX} px high and {MAX_PIXELS} pixels."
)
if block and not fits(max(width, image.width), height + image.height):
add_block(block)
block = []
width = height = 0
block.append(item)
width = max(width, image.width)
height += image.height
if block:
add_block(block)
index = end
return batches