发票缩印下也是可以收藏的嘛,还不用担心褪色(
写了个程序,利用poppler将多张电子发票缩印到一张A4纸中。(想打包成客户端程序的,但好像有点问题),输出和对比效果如图所示:
# layout_invoices.py
from PIL import Image, ImageOps
import os
import math
import glob
# 如果需要从 PDF 转页面图像,启用以下(可选)
try:
from pdf2image import convert_from_path
PDF2IMAGE_AVAILABLE = True
except Exception:
PDF2IMAGE_AVAILABLE = False
# ---------- 配置区 ----------
INPUT_FOLDER = "invoices" # 存放待处理发票(图片或 PDF)的文件夹
OUTPUT_PDF = "invoices_layout.pdf"
TARGET_WIDTH_CM = 9.0 # 目标宽(cm)
TARGET_HEIGHT_CM = 6.0 # 目标高(cm)
DPI = 300 # 打印/导出分辨率(可改为 150/300 等)
A4_WIDTH_MM = 210
A4_HEIGHT_MM = 297
MARGIN_MM = 10 # 页面四周留白(mm)
SPACING_MM = 5 # 发票之间间距(mm)
FILL_METHOD = "fill" # "fill"(裁切并填满目标框), "fit"(等比缩放并留白)
# -----------------------------
def mm_to_px(mm, dpi=DPI):
return int(round(mm / 25.4 * dpi))
def cm_to_px(cm, dpi=DPI):
return mm_to_px(cm * 10.0, dpi)
def load_files_from_folder(folder):
files = []
exts = ("*.jpg","*.jpeg","*.png","*.bmp","*.tiff","*.tif","*.pdf")
for e in exts:
files.extend(glob.glob(os.path.join(folder, e)))
files = sorted(files)
return files
def pdf_to_images(pdf_path, dpi=DPI):
from pdf2image import convert_from_path
poppler_path = r"D:\poppler\Library\bin" # ← 改成你的实际路径
images = convert_from_path(pdf_path, dpi=dpi, poppler_path=poppler_path)
return images
def resize_and_fill(img: Image.Image, target_w_px, target_h_px, method="fill"):
"""
method:
- 'fill': 等比放大再中心裁切,确保填满 target 精确尺寸(常用于发票)
- 'fit' : 等比缩放到目标内框,并用白色背景居中填充
"""
if method == "fill":
# 使用 ImageOps.fit 做中心裁剪以填满
out = ImageOps.fit(img, (target_w_px, target_h_px), method=Image.LANCZOS, centering=(0.5,0.5))
return out
else:
# fit: 保持长宽比缩放,放到目标框中心,周围留白
img.thumbnail((target_w_px, target_h_px), Image.LANCZOS)
background = Image.new("RGB", (target_w_px, target_h_px), (255,255,255))
x = (target_w_px - img.width) // 2
y = (target_h_px - img.height) // 2
background.paste(img, (x,y))
return background
def process_all(input_folder):
files = load_files_from_folder(input_folder)
pages_images = [] # 每个元素是已处理的 PIL.Image(目标尺寸)
target_w_px = cm_to_px(TARGET_WIDTH_CM, DPI)
target_h_px = cm_to_px(TARGET_HEIGHT_CM, DPI)
for fp in files:
ext = os.path.splitext(fp)[1].lower()
if ext == ".pdf":
if not PDF2IMAGE_AVAILABLE:
raise RuntimeError(f"Found PDF {fp} but pdf2image not available.")
pdf_imgs = pdf_to_images(fp, dpi=DPI)
for pimg in pdf_imgs:
pimg = pimg.convert("RGB")
pages_images.append(resize_and_fill(pimg, target_w_px, target_h_px, method=FILL_METHOD))
else:
img = Image.open(fp).convert("RGB")
pages_images.append(resize_and_fill(img, target_w_px, target_h_px, method=FILL_METHOD))
return pages_images, target_w_px, target_h_px
def compose_a4_pages(item_images, target_w_px, target_h_px):
# A4 大小(像素)
a4_w_px = mm_to_px(A4_WIDTH_MM, DPI)
a4_h_px = mm_to_px(A4_HEIGHT_MM, DPI)
margin_px = mm_to_px(MARGIN_MM, DPI)
spacing_px = mm_to_px(SPACING_MM, DPI)
# 计算可放置列/行数(在可用区域内)
avail_w = a4_w_px - 2 * margin_px + spacing_px # +spacing 用于整除计算
avail_h = a4_h_px - 2 * margin_px + spacing_px
cols = max(1, avail_w // (target_w_px + spacing_px))
rows = max(1, avail_h // (target_h_px + spacing_px))
# 若 cols*rows == 0,强制至少 1
if cols * rows == 0:
cols, rows = 1, 1
per_page = int(cols * rows)
pages = []
total = len(item_images)
for page_idx in range(math.ceil(total / per_page)):
page_img = Image.new("RGB", (a4_w_px, a4_h_px), (255,255,255))
# 计算起始左上,让表格居中(在 margin 内)
used_w = cols * target_w_px + (cols - 1) * spacing_px
used_h = rows * target_h_px + (rows - 1) * spacing_px
start_x = (a4_w_px - used_w) // 2
start_y = (a4_h_px - used_h) // 2
for i in range(per_page):
idx = page_idx * per_page + i
if idx >= total:
break
r = i // cols
c = i % cols
x = start_x + c * (target_w_px + spacing_px)
y = start_y + r * (target_h_px + spacing_px)
page_img.paste(item_images[idx], (x, y))
pages.append(page_img)
return pages, cols, rows
def save_pages_as_pdf(pages, out_pdf):
if not pages:
raise RuntimeError("No pages to save.")
# Pillow.save with save_all
first, rest = pages[0], pages[1:]
first.save(out_pdf, "PDF", resolution=DPI, save_all=True, append_images=rest)
print(f"Saved {len(pages)} page(s) to {out_pdf}")
def main():
print("Loading and processing files...")
items, tw, th = process_all(INPUT_FOLDER)
print(f"Total items processed: {len(items)}. Each target size (px): {tw}x{th} at {DPI} DPI.")
pages, cols, rows = compose_a4_pages(items, tw, th)
print(f"Composed into {len(pages)} A4 page(s). Layout: {cols} cols x {rows} rows per page.")
save_pages_as_pdf(pages, OUTPUT_PDF)
print("Done.")
if __name__ == "__main__":
main()



[...]程序复制可进:http://mxzfun.xyz/index.php/archives/939/[...]