ChristopherJKoen commited on
Commit
4bed098
·
1 Parent(s): 1f0784e

Paginate photo-only PDF pages by capacity

Browse files

Compute the max number of images that can fit on a photo-only page based on available height and minimum cell size.
Chunk overflow photos across additional continued pages instead of shrinking frames too far.
Align spacing constants for photo grids so continued pages match the main template.

Files changed (1) hide show
  1. server/app/services/pdf_reportlab.py +47 -21
server/app/services/pdf_reportlab.py CHANGED
@@ -224,9 +224,12 @@ def render_report_pdf(
224
  footer_h = 8 * mm
225
  gap = 4 * mm
226
  photo_col_gap = 6 * mm
 
 
227
  min_photo_cell_w = 80 * mm
 
228
  two_col_cell_w = (width - 2 * margin - photo_col_gap) / 2
229
- max_photos_per_page = 6 if two_col_cell_w >= min_photo_cell_w else 2
230
 
231
  gray_50 = colors.HexColor("#f9fafb")
232
  gray_200 = colors.HexColor("#e5e7eb")
@@ -260,6 +263,15 @@ def render_report_pdf(
260
  uploads = (session.get("uploads") or {}).get("photos") or []
261
  by_id = {item.get("id"): item for item in uploads if item.get("id")}
262
 
 
 
 
 
 
 
 
 
 
263
  sections: List[dict]
264
  if sections_or_pages and isinstance(sections_or_pages[0], dict) and "pages" in sections_or_pages[0]:
265
  sections = sections_or_pages
@@ -286,21 +298,35 @@ def render_report_pdf(
286
  if path and path.exists():
287
  label = _safe_text(item.get("name") or path.name)
288
  photo_entries.append({"path": path, "label": label})
289
- chunks = _chunk(photo_entries, max_photos_per_page) or [[]]
290
- for chunk_index, chunk in enumerate(chunks):
291
- if base_variant == "photos":
292
- variant = "photos"
293
- else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
294
  variant = "full" if chunk_index == 0 else "photos"
295
- print_pages.append(
296
- {
297
- "page_index": page_index,
298
- "template": template,
299
- "photos": chunk,
300
- "variant": variant,
301
- "section_index": section_index,
302
- }
303
- )
304
 
305
  if not print_pages:
306
  print_pages = [
@@ -378,7 +404,7 @@ def render_report_pdf(
378
  pdf.drawString(margin, y, "Observations and Findings")
379
  pdf.setStrokeColor(gray_200)
380
  pdf.line(margin, y - 2, width - margin, y - 2)
381
- y -= 8 * mm
382
 
383
  ref = _safe_text(template.get("reference"))
384
  area = _safe_text(template.get("area"))
@@ -578,7 +604,7 @@ def render_report_pdf(
578
  pdf.drawString(margin, y, "Photo Documentation (continued)")
579
  pdf.setStrokeColor(gray_200)
580
  pdf.line(margin, y - 2, width - margin, y - 2)
581
- y -= 8 * mm
582
 
583
  if variant == "full":
584
  y -= 2 * mm
@@ -595,16 +621,16 @@ def render_report_pdf(
595
  if photos:
596
  columns = 1 if len(photos) == 1 else 2
597
  rows = math.ceil(len(photos) / columns)
598
- cell_w = (width - 2 * margin - (columns - 1) * 6 * mm) / columns
599
- cell_h = (photo_area_height - (rows - 1) * 6 * mm) / rows
600
 
601
  for idx, photo in enumerate(photos):
602
  photo_path = photo["path"]
603
  label = photo.get("label") or photo_path.name
604
  row = idx // columns
605
  col = idx % columns
606
- x = margin + col * (cell_w + 6 * mm)
607
- y = photo_area_top - (row + 1) * cell_h - row * 6 * mm
608
  pdf.setStrokeColor(gray_200)
609
  pdf.setFillColor(gray_50)
610
  pdf.roundRect(x, y, cell_w, cell_h, 3 * mm, stroke=1, fill=1)
 
224
  footer_h = 8 * mm
225
  gap = 4 * mm
226
  photo_col_gap = 6 * mm
227
+ photo_row_gap = 6 * mm
228
+ photos_header_gap = 8 * mm
229
  min_photo_cell_w = 80 * mm
230
+ min_photo_cell_h = 60 * mm
231
  two_col_cell_w = (width - 2 * margin - photo_col_gap) / 2
232
+ columns_for_photos = 2 if two_col_cell_w >= min_photo_cell_w else 1
233
 
234
  gray_50 = colors.HexColor("#f9fafb")
235
  gray_200 = colors.HexColor("#e5e7eb")
 
263
  uploads = (session.get("uploads") or {}).get("photos") or []
264
  by_id = {item.get("id"): item for item in uploads if item.get("id")}
265
 
266
+ content_top = height - margin - header_h - gap
267
+ content_bottom = margin + footer_h + gap
268
+ photo_area_height_photos = max(0, content_top - photos_header_gap - content_bottom)
269
+ max_rows_photos = max(
270
+ 1,
271
+ int((photo_area_height_photos + photo_row_gap) // (min_photo_cell_h + photo_row_gap)),
272
+ )
273
+ max_photos_photos = max(1, max_rows_photos * columns_for_photos)
274
+
275
  sections: List[dict]
276
  if sections_or_pages and isinstance(sections_or_pages[0], dict) and "pages" in sections_or_pages[0]:
277
  sections = sections_or_pages
 
298
  if path and path.exists():
299
  label = _safe_text(item.get("name") or path.name)
300
  photo_entries.append({"path": path, "label": label})
301
+ if base_variant == "photos":
302
+ chunks = _chunk(photo_entries, max_photos_photos) or [[]]
303
+ for chunk in chunks:
304
+ print_pages.append(
305
+ {
306
+ "page_index": page_index,
307
+ "template": template,
308
+ "photos": chunk,
309
+ "variant": "photos",
310
+ "section_index": section_index,
311
+ }
312
+ )
313
+ else:
314
+ first_chunk = photo_entries[:2]
315
+ remainder = photo_entries[2:]
316
+ chunks = [first_chunk]
317
+ if remainder:
318
+ chunks.extend(_chunk(remainder, max_photos_photos))
319
+ for chunk_index, chunk in enumerate(chunks):
320
  variant = "full" if chunk_index == 0 else "photos"
321
+ print_pages.append(
322
+ {
323
+ "page_index": page_index,
324
+ "template": template,
325
+ "photos": chunk,
326
+ "variant": variant,
327
+ "section_index": section_index,
328
+ }
329
+ )
330
 
331
  if not print_pages:
332
  print_pages = [
 
404
  pdf.drawString(margin, y, "Observations and Findings")
405
  pdf.setStrokeColor(gray_200)
406
  pdf.line(margin, y - 2, width - margin, y - 2)
407
+ y -= photos_header_gap
408
 
409
  ref = _safe_text(template.get("reference"))
410
  area = _safe_text(template.get("area"))
 
604
  pdf.drawString(margin, y, "Photo Documentation (continued)")
605
  pdf.setStrokeColor(gray_200)
606
  pdf.line(margin, y - 2, width - margin, y - 2)
607
+ y -= photos_header_gap
608
 
609
  if variant == "full":
610
  y -= 2 * mm
 
621
  if photos:
622
  columns = 1 if len(photos) == 1 else 2
623
  rows = math.ceil(len(photos) / columns)
624
+ cell_w = (width - 2 * margin - (columns - 1) * photo_col_gap) / columns
625
+ cell_h = (photo_area_height - (rows - 1) * photo_row_gap) / rows
626
 
627
  for idx, photo in enumerate(photos):
628
  photo_path = photo["path"]
629
  label = photo.get("label") or photo_path.name
630
  row = idx // columns
631
  col = idx % columns
632
+ x = margin + col * (cell_w + photo_col_gap)
633
+ y = photo_area_top - (row + 1) * cell_h - row * photo_row_gap
634
  pdf.setStrokeColor(gray_200)
635
  pdf.setFillColor(gray_50)
636
  pdf.roundRect(x, y, cell_w, cell_h, 3 * mm, stroke=1, fill=1)