Add text rendering with auto-fit support

text() renders text into a rectangle with alignment (left/center/right).
Pass size=0 to auto-size the text to fill the rectangle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-04 16:41:20 -04:00
parent ea5b20b3ce
commit db3b3c2558

View File

@@ -1,6 +1,6 @@
from reportlab.lib.units import inch from reportlab.lib.units import inch
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from PIL import Image, ImageDraw from PIL import Image, ImageDraw, ImageFont
import os import os
import tempfile import tempfile
@@ -95,6 +95,32 @@ def ReadJPG(filepath):
DPI = 300 DPI = 300
# Alignment
LEFT = "left"
CENTER = "center"
RIGHT = "right"
def _render_text(page, text, font_path, size_pt, x, y, w, h, align, color):
size_px = int(size_pt * DPI / 72)
font = ImageFont.truetype(font_path, size_px)
draw = ImageDraw.Draw(page)
bbox = draw.textbbox((0, 0), text, font=font)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
xp = int(x * DPI)
yp = int(y * DPI)
wp = int(w * DPI)
hp = int(h * DPI)
if align == CENTER:
tx = xp + (wp - tw) // 2
elif align == RIGHT:
tx = xp + wp - tw
else:
tx = xp
ty = yp + (hp - th) // 2
draw.text((tx, ty - bbox[1]), text, font=font, fill=color + (255,))
class NewPDF: class NewPDF:
def __init__(self, width_inches, height_inches): def __init__(self, width_inches, height_inches):
@@ -116,6 +142,9 @@ class NewPDF:
def line(self, color, x0, y0, x1, y1, width=1): def line(self, color, x0, y0, x1, y1, width=1):
self._ops.append(("line", color, x0, y0, x1, y1, width)) self._ops.append(("line", color, x0, y0, x1, y1, width))
def text(self, string, font, size, x, y, w, h, align=LEFT, color=BLACK):
self._ops.append(("text", string, font, size, x, y, w, h, align, color))
def save(self, filename): def save(self, filename):
pw = int(self.width_inches * DPI) pw = int(self.width_inches * DPI)
ph = int(self.height_inches * DPI) ph = int(self.height_inches * DPI)
@@ -152,6 +181,26 @@ class NewPDF:
fill=color + (255,), width=width, fill=color + (255,), width=width,
) )
elif op[0] == "text":
_, string, font, size, x, y, w, h, align, color = op
if size == 0:
wp = int(w * DPI)
hp = int(h * DPI)
lo, hi = 1, 1000
while lo < hi:
mid = (lo + hi + 1) // 2
size_px = int(mid * DPI / 72)
f = ImageFont.truetype(font, size_px)
bbox = ImageDraw.Draw(page).textbbox((0, 0), string, font=f)
tw = bbox[2] - bbox[0]
th = bbox[3] - bbox[1]
if tw <= wp and th <= hp:
lo = mid
else:
hi = mid - 1
size = lo
_render_text(page, string, font, size, x, y, w, h, align, color)
# Write composited image to PDF # Write composited image to PDF
rgb_page = page.convert("RGB") rgb_page = page.convert("RGB")
c = canvas.Canvas(filename, pagesize=(self.width, self.height)) c = canvas.Canvas(filename, pagesize=(self.width, self.height))