| """ |
| PDF Generator for Edited Comics |
| Generates PDF from comic with user edits preserved |
| """ |
|
|
| import os |
| from reportlab.lib.pagesizes import A4 |
| from reportlab.pdfgen import canvas |
| from reportlab.lib.utils import ImageReader |
| from PIL import Image, ImageDraw, ImageFont |
| import json |
|
|
| class ComicPDFGenerator: |
| """Generate PDF from edited comic data""" |
| |
| def __init__(self): |
| self.page_width = A4[0] |
| self.page_height = A4[1] |
| self.margin = 20 |
| |
| def generate_pdf(self, pages_data, edited_bubbles, output_path="output/comic_edited.pdf"): |
| """ |
| Generate PDF with edited text and positions |
| |
| Args: |
| pages_data: Original comic pages data |
| edited_bubbles: List of edited bubble data (text, position) |
| output_path: Output PDF path |
| """ |
| |
| |
| c = canvas.Canvas(output_path, pagesize=A4) |
| |
| bubble_index = 0 |
| |
| for page_num, page in enumerate(pages_data): |
| if page_num > 0: |
| c.showPage() |
| |
| |
| c.setFont("Helvetica-Bold", 16) |
| c.drawString(self.margin, self.page_height - 30, f"Page {page_num + 1}") |
| |
| |
| panels_per_page = len(page['panels']) |
| if panels_per_page <= 4: |
| cols, rows = 2, 2 |
| else: |
| cols, rows = 3, 2 |
| |
| panel_width = (self.page_width - self.margin * 2 - 10 * (cols - 1)) / cols |
| panel_height = (self.page_height - 100 - 10 * (rows - 1)) / rows |
| |
| |
| for i, panel in enumerate(page['panels']): |
| row = i // cols |
| col = i % cols |
| |
| x = self.margin + col * (panel_width + 10) |
| y = self.page_height - 60 - (row + 1) * (panel_height + 10) |
| |
| |
| img_path = os.path.join('frames/final', panel['image']) |
| if os.path.exists(img_path): |
| img = Image.open(img_path) |
| img_reader = ImageReader(img) |
| c.drawImage(img_reader, x, y, width=panel_width, height=panel_height, preserveAspectRatio=True) |
| |
| |
| c.setStrokeColorRGB(0, 0, 0) |
| c.setLineWidth(2) |
| c.rect(x, y, panel_width, panel_height) |
| |
| |
| for bubble in page.get('bubbles', []): |
| if bubble_index < len(edited_bubbles): |
| edited_bubble = edited_bubbles[bubble_index] |
| |
| |
| text = edited_bubble.get('text', bubble['dialog']) |
| |
| |
| if edited_bubble.get('left') and edited_bubble.get('top'): |
| |
| bubble_x = x + self._parse_position(edited_bubble['left'], panel_width) |
| bubble_y = y + panel_height - self._parse_position(edited_bubble['top'], panel_height) - 30 |
| else: |
| |
| bubble_x = x + bubble['bubble_offset_x'] |
| bubble_y = y + panel_height - bubble['bubble_offset_y'] - 30 |
| |
| |
| self._draw_speech_bubble(c, bubble_x, bubble_y, text) |
| |
| bubble_index += 1 |
| |
| |
| c.save() |
| return output_path |
| |
| def _parse_position(self, css_value, max_value): |
| """Convert CSS position (e.g., '50px') to numeric value""" |
| if isinstance(css_value, str) and css_value.endswith('px'): |
| return float(css_value[:-2]) |
| return 0 |
| |
| def _draw_speech_bubble(self, canvas, x, y, text): |
| """Draw a speech bubble with text""" |
| |
| canvas.setFont("Helvetica-Bold", 10) |
| text_width = canvas.stringWidth(text, "Helvetica-Bold", 10) |
| |
| |
| words = text.split() |
| lines = [] |
| current_line = [] |
| max_width = 150 |
| |
| for word in words: |
| test_line = ' '.join(current_line + [word]) |
| if canvas.stringWidth(test_line, "Helvetica-Bold", 10) > max_width: |
| if current_line: |
| lines.append(' '.join(current_line)) |
| current_line = [word] |
| else: |
| lines.append(word) |
| else: |
| current_line.append(word) |
| |
| if current_line: |
| lines.append(' '.join(current_line)) |
| |
| |
| bubble_width = min(max_width + 20, 180) |
| bubble_height = len(lines) * 15 + 20 |
| |
| |
| canvas.setFillColorRGB(1, 1, 1) |
| canvas.setStrokeColorRGB(0, 0, 0) |
| canvas.setLineWidth(2) |
| |
| |
| canvas.roundRect(x, y, bubble_width, bubble_height, 10, fill=1, stroke=1) |
| |
| |
| canvas.setFillColorRGB(0, 0, 0) |
| text_y = y + bubble_height - 15 |
| for line in lines: |
| canvas.drawString(x + 10, text_y, line) |
| text_y -= 15 |
| |
| def generate_from_html(self, html_path, edited_data, output_path="output/comic_edited.pdf"): |
| """ |
| Alternative: Generate PDF from edited HTML |
| This would require parsing the HTML and extracting positions |
| """ |
| |
| |
| pass |
|
|
|
|
| def generate_edited_pdf(request_data): |
| """ |
| Generate PDF from edit request |
| |
| Args: |
| request_data: Dict with edited bubble data |
| """ |
| generator = ComicPDFGenerator() |
| |
| |
| with open('output/pages.json', 'r') as f: |
| pages_data = json.load(f) |
| |
| |
| edited_bubbles = request_data.get('bubbles', []) |
| |
| |
| output_path = generator.generate_pdf(pages_data, edited_bubbles) |
| |
| return output_path |