12306电子发票缩印输出


发票缩印下也是可以收藏的嘛,还不用担心褪色(

写了个程序,利用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()

声明:三二一的一的二|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 12306电子发票缩印输出


三二一的一的二