|
|
""" |
|
|
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 |