吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1654|回复: 45
上一主题 下一主题
收起左侧

[原创工具] 发票批量打印

  [复制链接]
跳转到指定楼层
楼主
th4c3y 发表于 2025-1-8 15:39 回帖奖励
本帖最后由 th4c3y 于 2025-1-12 18:18 编辑


发票批量打印工具


1月22日更新:
可选A4 四版  和  A4 六版 打印
可选添加裁剪线








PDF/图片打印
文件路径传入方式:拖入文件或者文件夹,双击列表输入路径等。
虚拟打印机推荐合并页面(生成一个文件),否则不合并页面(打印机响应速度会快那么一点)
A5进纸方向为竖向:148mmX210mm(宽X高)
请先少量测试页面方向是否正确,设置是否生效,再批量打印。
64位,不支持win7
蓝奏:https://wwvv.lanzout.com/b00l1hq06d 密码:52pj

图片.png (43.08 KB, 下载次数: 6)

界面

界面

免费评分

参与人数 7吾爱币 +12 热心值 +7 收起 理由
wyt + 1 + 1 谢谢@Thanks!
gqdsc + 1 + 1 谢谢@Thanks!
ms2019 + 1 + 1 谢谢@Thanks!
zhujfxx + 1 + 1 我很赞同!
浩秦 + 1 + 1 谢谢@Thanks!
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Anro + 1 谢谢@Thanks!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
浩秦 发表于 2025-1-9 16:35
[Python] 纯文本查看 复制代码
import sys
import os
import time
import fitz
from print import Ui_PrintForm
from itertools import count
from pathlib import Path
import configparser
from PIL.ImageQt import ImageQt
from PIL import Image
from PySide6.QtGui import QPainter, QPageSize, QPageLayout, QColor
from PySide6.QtPrintSupport import QPrinter, QPrintDialog, QPrinterInfo
from PySide6.QtCore import QRect, QObject, QThread, Signal, QEvent, Qt
from PySide6.QtWidgets import QApplication, QWidget, QListWidgetItem, QFileDialog, QStyleFactory

class printPdfWorker(QObject):
    finished = Signal()
    progress = Signal(str, str)

    def __init__(self, pdf=None, parent=None):
        super().__init__()
        self.win = parent
        self.paths = pdf
        self.cup = self.win.cup
        self.paper = self.win.paper
        self.printname = self.win.printName
        self.dpi = int(str(self.win.dpi)[:3])
        self.orientation = self.win.dirtion()
        ali = self.win.ali
        if '水平居中' in ali:
            self.inter = 2
        else:  # inserted
            if '靠右居中' in ali:
                self.inter = 1
        self.double = self.win.double
        self._printer = QPrinter(QPrinter.PrinterMode.HighResolution)
        self._printer.setPrinterName(self.printname)
        if 'A4' in self.paper:
            self._printer.setPageSize(QPageSize(QPageSize.A4))
            self.height_dpx, self.width_dpx = self.a4_size(self.dpi, 210, 297)
        else:  # inserted
            if 'A5' in self.paper:
                self._printer.setPageSize(QPageSize(QPageSize.A5))
                self.height_dpx, self.width_dpx = self.a4_size(self.dpi, 148, 210)
        self._printer.setPrintRange(QPrinter.PrintRange.AllPages)
        if self.win.checkbox.isChecked():
            self._printer.setColorMode(QPrinter.ColorMode.GrayScale)
        else:  # inserted
            self._printer.setColorMode(QPrinter.ColorMode.Color)
        try:
            self._printer.setDuplex(self.double)
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            if self.orientation:
                self._printer.setPageOrientation(self.orientation)
        self._printer.setCopyCount(self.cup)
            print(e)

    def setprinton(self):
        if 'A4' in self.paper:
            self._printer.setPageSize(QPageSize(QPageSize.A4))
            self.height_dpx, self.width_dpx = self.a4_size(self.dpi, 210, 297)
        else:  # inserted
            if 'A5' in self.paper:
                self._printer.setPageSize(QPageSize(QPageSize.A5))
                self.height_dpx, self.width_dpx = self.a4_size(self.dpi, 148, 210)
        self._printer.setPrintRange(QPrinter.PrintRange.AllPages)
        if self.win.checkbox.isChecked():
            self._printer.setColorMode(QPrinter.ColorMode.GrayScale)
        else:  # inserted
            self._printer.setColorMode(QPrinter.ColorMode.Color)
        try:
            self._printer.setDuplex(self.double)
        except Exception as e:
            pass  # postinserted
        else:  # inserted
            if self.orientation:
                self._printer.setPageOrientation(self.orientation)
        self._printer.setCopyCount(self.cup)
            print(e)
        else:  # inserted
            pass

    def rundialog(self):
        self.dialog = QPrintDialog(self._printer)
        self.dialog.setOptions(QPrintDialog.PrintToFile | QPrintDialog.PrintSelection)
        if self.dialog.exec():
            self.runprint()
        else:  # inserted
            self.finished.emit()

    def runprint(self):
        """长时间运行的打印任务。"""  # inserted
        try:
            self.setprinton()
            if self.paper!= 'A4两版':
                if self.win.mergebox.isChecked():
                    painter = QPainter(self._printer)
                    rect = painter.viewport()
                    images = self.add_image(self.paths)
                    for pil_image, pageNumber in zip(images, count(1)):
                        if pageNumber > 1:
                            self._printer.newPage()
                        self.print_image(pil_image, rect, painter)
                    painter.end()
                else:  # inserted
                    for index, path in enumerate(self.paths):
                        painter = QPainter(self._printer)
                        rect = painter.viewport()
                        images = []
                        path = Path(path)
                        suffix = path.suffix.lower()
                        if suffix == '.pdf':
                            images = self.open_pdf(path, images)
                            for pil_image, pageNumber in zip(images, count(1)):
                                if pageNumber > 1:
                                    self._printer.newPage()
                                self.print_image(pil_image, rect, painter)
                        else:  # inserted
                            with Image.open(path) as image:
                                pass  # postinserted
        except Exception as e:
                                pil_image = image.copy()
                                self.print_image(pil_image, rect, painter)
                        painter.end()
            else:  # inserted
                if self.win.mergebox.isChecked():
                    images = self.add_image(self.paths)
                    self.A4_sep(images)
                else:  # inserted
                    batch_size = 10
                    for i in range(0, len(self.paths), batch_size):
                        batch_paths = self.paths[i:i + batch_size]
                        images = self.add_image(batch_paths)
                        self.A4_sep(images)
            self.progress.emit('文件已发送至打印机', 'green')
        else:  # inserted
            self.finished.emit()
                print(f'打印出错{e}0')

    def open_pdf(self, path, images):
        with fitz.open(path) as pdf:
            num_pages = len(pdf)
            printRange = range(num_pages)
            page_indices = [i for i in printRange]
            for index in page_indices:
                pixmap = pdf[index].get_pixmap(dpi=self.dpi)
                pil_image = Image.frombytes('RGB', [pixmap.width, pixmap.height], pixmap.samples)
                images.append(pil_image)
            return images

    def add_image(self, paths=None):
        images = []
        file_paths = paths
        file_paths = [path for path in file_paths if path.endswith('.pdf')] + [path for path in file_paths if path.endswith(('.jpg', '.jpeg', '.png'))]
        for index, path in enumerate(file_paths):
            path = Path(path)
            suffix = path.suffix.lower()
            if suffix == '.pdf':
                images = self.open_pdf(path, images)
            else:  # inserted
                with Image.open(path) as image:
                    images.append(image.copy())
        return images

    def A4_sep(self, images):
        if len(images) % 2!= 0:
            images.append(None)
        painter = QPainter(self._printer)
        for index, pageNumber in zip(range(0, len(images), 2), count(1)):
            image1 = images[index]
            image2 = images[index + 1]
            if pageNumber > 1:
                self._printer.newPage()
            self.join_pic(image1, image2, painter)
        painter.end()

    def print_image(self, pil_image, rect, painter):
        pilWidth, pilHeight = pil_image.size
        imageRatio = pilHeight / pilWidth
        viewportRatio = rect.height() / rect.width()
        A4Ratio = self.height_dpx / self.width_dpx
        if self.win.up == '自动旋转':
            if viewportRatio < 1 and imageRatio > 1 or (viewportRatio > 1 and imageRatio < 1):
                pil_image = pil_image.transpose(Image.ROTATE_90)
                pilWidth, pilHeight = pil_image.size
                imageRatio = pilHeight / pilWidth
            if A4Ratio < imageRatio:
                x = int(pilHeight / viewportRatio - pilWidth)
                xOffset = int(x / 2)
                yOffset = 0
            else:  # inserted
                xOffset = 0
                y = int(rect.height() - rect.width() / pilWidth * pilHeight)
                yOffset = int(y / self.inter)
        else:  # inserted
            xOffset, yOffset, x, y = (0, 0, 0, 0)
        if viewportRatio > imageRatio:
            y = int(rect.width() / (pilWidth / pilHeight))
            printArea = QRect(xOffset, yOffset, rect.width(), y)
        else:  # inserted
            x = int(pilWidth / pilHeight * rect.height())
            printArea = QRect(xOffset, yOffset, x, rect.height())
        image = ImageQt(pil_image)
        painter.drawImage(printArea, image)
        return painter

    def join_pic(self, image1, image2, painter):
        if image2 == None:
            image2 = Image.new('RGB', image1.size, 'white')
        for image in (image1, image2):
            pilWidth, pilHeight = image.size
            imageRatio = pilHeight / pilWidth
            if imageRatio > 1:
                if image == image1:
                    image1 = image.transpose(Image.ROTATE_90)
                if image == image2:
                    image2 = image.transpose(Image.ROTATE_90)

        def resize_image(image):
            height = int(self.height_dpx / 2)
            ratio = height / image.size[1]
            max_width = int(image.size[0] * ratio)
            max_height = int(height)
            if max_width > self.width_dpx:
                max_width = self.width_dpx
                ratio = self.width_dpx / image.size[0]
                max_height = int(image.size[1] * ratio)
            new_width = max_width
            new_height = max_height
            resized_image = image.resize((new_width, new_height))
            return resized_image
        image1 = resize_image(image1)
        image2 = resize_image(image2)
        half_hight = int(self.height_dpx / 2)
        merged_image = Image.new('RGB', (self.width_dpx, self.height_dpx), 'white')
        if image1.size[0] < self.width_dpx:
            x1 = int((self.width_dpx - image1.size[0]) / self.inter)
        else:  # inserted
            x1 = 0
        if image1.size[1] < half_hight:
            y1 = int((half_hight - image1.size[1]) / 2)
        else:  # inserted
            y1 = 0
        if image2.size[0] < self.width_dpx:
            x2 = int((self.width_dpx - image2.size[0]) / self.inter)
        else:  # inserted
            x2 = 0
        if image2.size[1] < half_hight:
            y2 = int((half_hight - image2.size[1]) / 2)
        else:  # inserted
            y2 = 0
        merged_image.paste(image1, (x1, y1))
        merged_image.paste(image2, (x2, half_hight + y2))
        rect = painter.viewport()
        self.print_image(merged_image, rect, painter)

    def a4_size(self, dpi, width, height):
        a4_width = width / 25.4
        a4_height = height / 25.4
        height_dpx = int(a4_height * dpi)
        width_dpx = int(a4_width * dpi)
        return (height_dpx, width_dpx)

class Window(QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.ui = Ui_PrintForm()
        self.ui_win = self.windowFlags()
        self.ui.setupUi(self)
        self.longRunningBtn = self.ui.pushButton
        self.longRunningBtn.clicked.connect(self.runPrintTask)
        self.addfile = self.ui.pushButton_2
        self.addfile.clicked.connect(self.getFile)
        self.clearfile = self.ui.pushButton_3
        self.clearfile.clicked.connect(self.clearFile)
        self.sysPrint = self.ui.toolButton
        self.sysPrint.clicked.connect(self.rundio)
        self.spinbox = self.ui.doubleSpinBox
        self.paper_box = self.ui.comboBox
        self.dpi_box = self.ui.comboBox_2
        self.double_box = self.ui.comboBox_3
        self.alignment = self.ui.comboBox_4
        self.direction = self.ui.comboBox_6
        self.paper_box.currentIndexChanged.connect(self.setdirection)
        self.bar = self.ui.label_8
        self.listwidget = self.ui.listWidget
        self.checkbox = self.ui.checkBox
        self.mergebox = self.ui.checkBox_2
        self.printbox = self.ui.comboBox_5
        self.load_printers()
        self.small_win = self.ui.dockWidget
        self.small_win.hide()
        self.textbox = self.ui.textEdit
        self.textbox.textChanged.connect(self.changedText)
        self.load_config()
        self.setdirection()
        self.file_path = []
        self.listwidget.viewport().installEventFilter(self)

    def eventFilter(self, source, event):
        if event.type() == QEvent.MouseButtonDblClick and source is self.listwidget.viewport():
            self.small_win.show()
            return True
        return super().eventFilter(source, event)

    def setdirection(self):
        if self.paper_box.currentText()!= 'A4':
            self.direction.setCurrentIndex(0)
            self.direction.setEnabled(False)
        else:  # inserted
            self.direction.setEnabled(True)

    def changedText(self):
        self.clearFile()
        text = self.textbox.toPlainText()
        lines = text.splitlines()
        for line in lines:
            if line.strip():
                self.showListwidget(line)

    def load_config(self):
        config = configparser.ConfigParser()
        config.read('printConfig.ini')
        self.spinbox.setValue(int(config.get('Print', 'Series', fallback=1)))
        self.paper_box.setCurrentIndex(int(config.get('Print', 'Paper', fallback=0)))
        self.dpi_box.setCurrentIndex(int(config.get('Print', 'Dpi', fallback=1)))
        self.double_box.setCurrentIndex(int(config.get('Print', 'Double', fallback=0)))
        self.double_box.setCurrentIndex(int(config.get('Print', 'Center', fallback=0)))
        self.printbox.setCurrentText(config.get('Print', 'PrintName', fallback=''))
        self.direction.setCurrentIndex(int(config.get('Print', 'PageDirection', fallback=0)))
        self.checkbox.setCheckState(Qt.CheckState.Checked if config.getboolean('Print', 'Color', fallback=False) else Qt.CheckState.Unchecked)
        self.mergebox.setCheckState(Qt.CheckState.Checked if config.getboolean('Print', 'Mergebox', fallback=False) else Qt.CheckState.Unchecked)

    def doublePrint(self):
        double = self.double_box.currentIndex()
        if double == 0:
            return QPrinter.DuplexMode.DuplexNone
        if double == 1:
            return QPrinter.DuplexLongSide
        if double == 2:
            return QPrinter.DuplexShortSide
        if double == 3:
            return QPrinter.DuplexAuto

    def dirtion(self):
        self.up = self.direction.currentText()
        if self.up == '纵向':
            return QPageLayout.Portrait
        if self.up == '横向':
            return QPageLayout.Landscape

    def clearFile(self):
        self.file_path = []
        self.listwidget.clear()
        self.runBar('准备就绪......', 'black')

    def getFile(self):
        response = QFileDialog.getOpenFileNames(parent=self, caption='选择文件', filter='文件类型 (*.pdf *.jpg *.png *.jpeg *.bmp);;Images (*.png *.jpg *.jpeg *.bmp);;PDF Files (*.pdf)')
        if response:
            file_paths = response[0]
            for path in file_paths:
                self.showListwidget(path)

    def showListwidget(self, path):
        self.file_path.append(path)
        item_widget = QListWidgetItem(path)
        self.listwidget.addItem(item_widget)
        self.bar.setText(f'已添加文件:{len(self.file_path)}个')
        return self.file_path

    def lianjie(self, paths):
        self.clearFile()
        valid_extensions = {'.jpg', '.png', '.jpeg', 'bmp', '.pdf'}
        for path in paths:
            _, extensions = os.path.splitext(path)
            if extensions.lower() in valid_extensions:
                self.showListwidget(path)

    def load_printers(self):
        printers = QPrinterInfo.availablePrinters()
        printer_names = [printer.printerName() for printer in printers]
        self.printbox.addItems(printer_names)

    def printdata(self):
        self.cup = self.spinbox.value()
        self.paper = self.paper_box.currentText()
        self.dpi = self.dpi_box.currentText()
        self.ali = self.alignment.currentText()
        self.double = self.doublePrint()
        self.printName = self.printbox.currentText()
        self.runBar('正在发送页面到打印机\n请勿关闭程序...', 'red')

    def rundio(self):
        pdf_file = self.file_path
        if not pdf_file:
            self.runBar('没有待打印的文件', 'blue')
            return
        self.printdata()
        self.thread = QThread()
        self.worker = printPdfWorker(pdf_file, self)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.rundialog)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.progress.connect(self.runBar)
        self.thread.start()

    def runPrintTask(self):
        pdf_file = self.file_path
        if not pdf_file:
            self.runBar('没有待打印的文件', 'blue')
            return
        self.printdata()
        self.thread = QThread()
        self.worker = printPdfWorker(pdf_file, self)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.runprint)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.progress.connect(self.runBar)
        self.thread.start()

    def runBar(self, text, color='black'):
        palette = self.bar.palette()
        palette.setColor(self.bar.foregroundRole(), QColor(color))
        self.bar.setPalette(palette)
        self.bar.setText(text)

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:  # inserted
            event.ignore()

    def dropEvent(self, event):
        valid_extensions = {'.jpg', '.png', '.jpeg', 'bmp', '.pdf'}
        dropped_files = []
        for url in event.mimeData().urls():
            file_path = url.toLocalFile()
            if os.path.isdir(file_path):
                for root, dirs, files in os.walk(file_path):
                    for file in files:
                        full_file_path = os.path.join(root, file)
                        _, extension = os.path.splitext(full_file_path)
                        if extension.lower() in valid_extensions:
                            dropped_files.append(full_file_path)
            else:  # inserted
                _, extension = os.path.splitext(file_path)
                if extension.lower() in valid_extensions:
                    dropped_files.append(file_path)
        for file_path in dropped_files:
            self.showListwidget(file_path)

    def closeEvent(self, event):
        self.save_combobox()
        event.accept()

    def save_combobox(self):
        config = configparser.ConfigParser()
        config.read('printConfig.ini')
        if 'Print' not in config:
            config.add_section('Print')
        cup = self.spinbox.value()
        paper = self.paper_box.currentIndex()
        dpi = self.dpi_box.currentIndex()
        double = self.double_box.currentIndex()
        center = self.alignment.currentIndex()
        printName = self.printbox.currentText()
        direction = self.direction.currentIndex()
        mergebox = int(self.mergebox.checkState() == Qt.CheckState.Checked)
        config['Print'] = {'Series': int(cup), 'Paper': str(paper), 'Dpi': str(dpi), 'Double': str(double), 'Center': str(center), 'Color': str(int(self.checkbox.checkState() == Qt.CheckState.Checked)), 'PrintName': str(printName), 'PageDirection': str(direction), 'Mergebox': str(mergebox)}
        with open('printConfig.ini', 'w') as configfile:
            config.write(configfile)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create('Fusion'))
    win = Window()
    win.show()
    sys.exit(app.exec())
推荐
wulin508 发表于 2025-1-13 11:10
请问下我WIN10  64位系统运行不了,出现一个图片提示(Traceback (most recent call last):
  File "main.py", line 1, in <module>
  File "E:\TEMP\_MEI82522\PrintPage.py", line 4, in <module PrintPage>
  File "PyInstaller\loader\pyimod02_importers.py", line 384, in exec_module
  File "print.py", line 11, in <module>
ImportError: DLL load failed while importing QtCore: 找不到指定的程序。
,麻烦楼主看看,谢谢了。前期本站有大大分享的有个(电子发票专用批量打印工具V3.0.exe),到了2025年日期也是用不了,文件自动消失。不知何故。很想用到这样类似的工具,经常打印大数量的发票,省纸省事。感谢感谢!!
沙发
jun269 发表于 2025-1-8 15:54
3#
yrycw 发表于 2025-1-8 16:03
一款不错的发票打印软件
4#
zyx665121 发表于 2025-1-8 16:07
2张发票合并页面打印,并不是选择合并页面,而是纸张大小选A4两版。还有2个问题,能不能自由删除添加进去序列的文件,和输出PDF版自己保存。
5#
ww5270616 发表于 2025-1-8 16:09
感谢,这个是神级需求啊,谢谢
6#
leishe2023 发表于 2025-1-8 16:31
有空用一下谢谢分享
7#
WWsrg7790 发表于 2025-1-8 16:43
合并,A4打印4张发票,这个功能应该比较有需求,
8#
milygy 发表于 2025-1-8 16:44
省时省力,感谢
9#
hbu126 发表于 2025-1-8 16:50
实用工具,多谢分享
10#
myluckyfriend 发表于 2025-1-9 13:21
非常好用,使用简单,感谢楼主分享。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-1-13 23:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表