63 lines
1.7 KiB
Python
63 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.pdfgen import canvas
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_MARGIN = 72
|
|
_FONT_SIZE = 10
|
|
_LINE_HEIGHT = 14
|
|
|
|
|
|
def generate_text_pdf(text: str, output_path: str) -> None:
|
|
"""Generate a single-page PDF containing the given plain text.
|
|
|
|
Text is rendered with automatic wrapping and page breaks for long chunks.
|
|
Raises on I/O or reportlab errors — caller should handle gracefully.
|
|
"""
|
|
c = canvas.Canvas(output_path, pagesize=A4)
|
|
width, height = A4
|
|
usable_width = width - 2 * _MARGIN
|
|
y = height - _MARGIN
|
|
|
|
for paragraph in text.split("\n"):
|
|
if not paragraph.strip():
|
|
y -= _LINE_HEIGHT
|
|
if y < _MARGIN:
|
|
c.showPage()
|
|
y = height - _MARGIN
|
|
continue
|
|
|
|
lines = _wrap_text(paragraph, usable_width, c)
|
|
for line in lines:
|
|
if y < _MARGIN:
|
|
c.showPage()
|
|
y = height - _MARGIN
|
|
c.drawString(_MARGIN, y, line)
|
|
y -= _LINE_HEIGHT
|
|
|
|
c.save()
|
|
|
|
|
|
def _wrap_text(text: str, max_width: float, canvas_obj: canvas.Canvas) -> list[str]:
|
|
"""Wrap text to fit within max_width using the canvas's stringWidth."""
|
|
words = text.split()
|
|
lines: list[str] = []
|
|
current_line = ""
|
|
|
|
for word in words:
|
|
test_line = f"{current_line} {word}".strip() if current_line else word
|
|
if canvas_obj.stringWidth(test_line, "Helvetica", _FONT_SIZE) <= max_width:
|
|
current_line = test_line
|
|
else:
|
|
if current_line:
|
|
lines.append(current_line)
|
|
current_line = word
|
|
|
|
if current_line:
|
|
lines.append(current_line)
|
|
|
|
return lines
|