Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| from io import BytesIO | |
| from pathlib import Path | |
| from typing import Any | |
| from src.agent.types import LkpdContent, MaterialInfo | |
| from src.config import settings | |
| _HEADER_HEIGHT = 74 | |
| _FIRST_PAGE_EXTRAS_HEIGHT = 106 | |
| def _resolve_logo_path() -> str | None: | |
| raw = settings.lkpd_header_logo_path.strip() | |
| if not raw: | |
| return None | |
| candidate = Path(raw) | |
| if not candidate.is_absolute(): | |
| candidate = Path.cwd() / candidate | |
| if candidate.exists() and candidate.is_file(): | |
| return str(candidate) | |
| return None | |
| def _draw_student_identity_block(canvas: Any, doc: Any, y_top: float) -> None: | |
| from reportlab.lib import colors | |
| box_x = doc.leftMargin | |
| box_w = doc.pagesize[0] - doc.leftMargin - doc.rightMargin | |
| box_h = 44 | |
| box_y = y_top - box_h | |
| mid_x = box_x + (box_w / 2) | |
| row_y = box_y + (box_h / 2) | |
| canvas.setStrokeColor(colors.HexColor("#7A869A")) | |
| canvas.setLineWidth(0.8) | |
| canvas.roundRect(box_x, box_y, box_w, box_h, 4, stroke=1, fill=0) | |
| canvas.line(mid_x, box_y, mid_x, box_y + box_h) | |
| canvas.line(box_x, row_y, box_x + box_w, row_y) | |
| canvas.setFillColor(colors.HexColor("#2B2D42")) | |
| canvas.setFont("Helvetica", 9) | |
| canvas.drawString(box_x + 8, row_y + 5, "Nama:") | |
| canvas.drawString(mid_x + 8, row_y + 5, "NIS:") | |
| canvas.drawString(box_x + 8, box_y + 5, "Kelas:") | |
| canvas.drawString(mid_x + 8, box_y + 5, "Tanggal:") | |
| def _draw_header_brand( | |
| canvas: Any, | |
| doc: Any, | |
| *, | |
| logo_path: str | None, | |
| accent_color: Any, | |
| title_line1: str, | |
| title_line2: str, | |
| title_line3: str, | |
| ) -> None: | |
| from reportlab.lib import colors | |
| page_w, page_h = doc.pagesize | |
| header_y = page_h - _HEADER_HEIGHT | |
| canvas.saveState() | |
| canvas.setFillColor(accent_color) | |
| canvas.rect(0, header_y, page_w, _HEADER_HEIGHT, stroke=0, fill=1) | |
| canvas.setStrokeColor(colors.HexColor("#D1D5DB")) | |
| canvas.setLineWidth(0.5) | |
| canvas.line(0, header_y, page_w, header_y) | |
| logo_x = doc.leftMargin | |
| logo_y = header_y + 12 | |
| logo_size = _HEADER_HEIGHT - 24 | |
| if logo_path: | |
| try: | |
| canvas.drawImage( | |
| logo_path, | |
| logo_x, | |
| logo_y, | |
| width=logo_size, | |
| height=logo_size, | |
| preserveAspectRatio=True, | |
| mask="auto", | |
| ) | |
| except Exception: | |
| pass | |
| title_lines = [title_line1.strip(), title_line2.strip()] | |
| third_line = title_line3.strip() | |
| if third_line: | |
| title_lines.append(third_line) | |
| y_start = page_h - 24 | |
| for idx, line in enumerate(title_lines): | |
| if idx == 0: | |
| canvas.setFont("Helvetica-Bold", 12) | |
| elif idx == 1: | |
| canvas.setFont("Helvetica-Bold", 11) | |
| else: | |
| canvas.setFont("Helvetica-Bold", 10) | |
| canvas.setFillColor(colors.white) | |
| canvas.drawCentredString(page_w / 2, y_start - (idx * 15), line) | |
| canvas.restoreState() | |
| def _draw_first_page_extras( | |
| canvas: Any, | |
| doc: Any, | |
| *, | |
| material: MaterialInfo, | |
| document_id: str, | |
| ) -> None: | |
| from reportlab.lib import colors | |
| page_h = doc.pagesize[1] | |
| extras_top = page_h - _HEADER_HEIGHT - 10 | |
| canvas.saveState() | |
| canvas.setFillColor(colors.HexColor("#374151")) | |
| canvas.setFont("Helvetica", 9) | |
| canvas.drawString(doc.leftMargin, extras_top - 12, f"Document ID: {document_id}") | |
| canvas.drawString( | |
| doc.leftMargin, | |
| extras_top - 25, | |
| f"Sumber: {material.filename} ({material.file_type})", | |
| ) | |
| _draw_student_identity_block(canvas, doc, y_top=extras_top - 34) | |
| canvas.restoreState() | |
| def _draw_first_page( | |
| canvas: Any, | |
| doc: Any, | |
| *, | |
| material: MaterialInfo, | |
| document_id: str, | |
| logo_path: str | None, | |
| accent_color: Any, | |
| title_line1: str, | |
| title_line2: str, | |
| title_line3: str, | |
| ) -> None: | |
| _draw_header_brand( | |
| canvas, | |
| doc, | |
| logo_path=logo_path, | |
| accent_color=accent_color, | |
| title_line1=title_line1, | |
| title_line2=title_line2, | |
| title_line3=title_line3, | |
| ) | |
| _draw_first_page_extras(canvas, doc, material=material, document_id=document_id) | |
| def _draw_later_page( | |
| canvas: Any, | |
| doc: Any, | |
| *, | |
| logo_path: str | None, | |
| accent_color: Any, | |
| title_line1: str, | |
| title_line2: str, | |
| title_line3: str, | |
| ) -> None: | |
| _draw_header_brand( | |
| canvas, | |
| doc, | |
| logo_path=logo_path, | |
| accent_color=accent_color, | |
| title_line1=title_line1, | |
| title_line2=title_line2, | |
| title_line3=title_line3, | |
| ) | |
| def render_lkpd_pdf( | |
| *, | |
| lkpd: LkpdContent, | |
| material: MaterialInfo, | |
| document_id: str, | |
| ) -> bytes: | |
| try: | |
| from reportlab.lib import colors | |
| from reportlab.lib.pagesizes import A4 | |
| from reportlab.lib.styles import getSampleStyleSheet | |
| from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Paragraph, Spacer | |
| except ImportError as exc: | |
| raise RuntimeError("reportlab package is required for LKPD PDF rendering.") from exc | |
| try: | |
| accent_color = colors.HexColor(settings.lkpd_header_accent_hex) | |
| except Exception: | |
| accent_color = colors.HexColor("#1F4E79") | |
| logo_path = _resolve_logo_path() | |
| title_line1 = settings.lkpd_header_title_line1.strip() or "LEMBAR KERJA PESERTA DIDIK (LKPD)" | |
| title_line2 = settings.lkpd_header_title_line2.strip() or "SMARTER AI" | |
| title_line3 = settings.lkpd_header_title_line3.strip() | |
| buffer = BytesIO() | |
| doc = BaseDocTemplate( | |
| buffer, | |
| pagesize=A4, | |
| leftMargin=40, | |
| rightMargin=40, | |
| topMargin=40, | |
| bottomMargin=40, | |
| ) | |
| page_w, page_h = A4 | |
| frame_width = page_w - doc.leftMargin - doc.rightMargin | |
| first_frame_top = page_h - _HEADER_HEIGHT - _FIRST_PAGE_EXTRAS_HEIGHT - 12 | |
| first_frame_height = first_frame_top - doc.bottomMargin | |
| first_frame = Frame( | |
| x1=doc.leftMargin, | |
| y1=doc.bottomMargin, | |
| width=frame_width, | |
| height=first_frame_height, | |
| id="lkpd-first-frame", | |
| showBoundary=0, | |
| ) | |
| later_frame_top = page_h - _HEADER_HEIGHT - 12 | |
| later_frame_height = later_frame_top - doc.bottomMargin | |
| later_frame = Frame( | |
| x1=doc.leftMargin, | |
| y1=doc.bottomMargin, | |
| width=frame_width, | |
| height=later_frame_height, | |
| id="lkpd-later-frame", | |
| showBoundary=0, | |
| ) | |
| first_template = PageTemplate( | |
| id="lkpd-first-page", | |
| frames=[first_frame], | |
| autoNextPageTemplate="lkpd-later-page", | |
| onPage=lambda canvas, page_doc: _draw_first_page( | |
| canvas, | |
| page_doc, | |
| material=material, | |
| document_id=document_id, | |
| logo_path=logo_path, | |
| accent_color=accent_color, | |
| title_line1=title_line1, | |
| title_line2=title_line2, | |
| title_line3=title_line3, | |
| ), | |
| ) | |
| later_template = PageTemplate( | |
| id="lkpd-later-page", | |
| frames=[later_frame], | |
| onPage=lambda canvas, page_doc: _draw_later_page( | |
| canvas, | |
| page_doc, | |
| logo_path=logo_path, | |
| accent_color=accent_color, | |
| title_line1=title_line1, | |
| title_line2=title_line2, | |
| title_line3=title_line3, | |
| ), | |
| ) | |
| doc.addPageTemplates([first_template, later_template]) | |
| styles = getSampleStyleSheet() | |
| elements = [ | |
| Paragraph(lkpd.title, styles["Title"]), | |
| Spacer(1, 8), | |
| Paragraph("Tujuan Pembelajaran", styles["Heading2"]), | |
| ] | |
| for idx, item in enumerate(lkpd.learning_objectives, start=1): | |
| elements.append(Paragraph(f"{idx}. {item}", styles["Normal"])) | |
| elements.extend([Spacer(1, 12), Paragraph("Petunjuk", styles["Heading2"])]) | |
| for idx, item in enumerate(lkpd.instructions, start=1): | |
| elements.append(Paragraph(f"{idx}. {item}", styles["Normal"])) | |
| elements.extend([Spacer(1, 12), Paragraph("Aktivitas", styles["Heading2"])]) | |
| for activity in lkpd.activities: | |
| elements.append( | |
| Paragraph( | |
| f"{activity.activity_no}. {activity.task}", | |
| styles["Heading3"], | |
| ) | |
| ) | |
| elements.append(Paragraph(f"Output: {activity.expected_output}", styles["Normal"])) | |
| elements.append(Paragraph(f"Hint Penilaian: {activity.assessment_hint}", styles["Normal"])) | |
| elements.append(Spacer(1, 6)) | |
| elements.extend([Spacer(1, 12), Paragraph("Template Lembar Jawaban", styles["Heading2"])]) | |
| elements.append(Paragraph(lkpd.worksheet_template, styles["Normal"])) | |
| elements.extend([Spacer(1, 12), Paragraph("Rubrik Penilaian", styles["Heading2"])]) | |
| for idx, rubric in enumerate(lkpd.assessment_rubric, start=1): | |
| elements.append( | |
| Paragraph( | |
| f"{idx}. {rubric.aspect} - {rubric.criteria} (Skor: {rubric.score_range})", | |
| styles["Normal"], | |
| ) | |
| ) | |
| doc.build(elements) | |
| return buffer.getvalue() | |