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
:
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
:
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
:
self
._printer.setColorMode(QPrinter.ColorMode.Color)
try
:
self
._printer.setDuplex(
self
.double)
except
Exception as e:
pass
else
:
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
:
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
:
self
._printer.setColorMode(QPrinter.ColorMode.Color)
try
:
self
._printer.setDuplex(
self
.double)
except
Exception as e:
pass
else
:
if
self
.orientation:
self
._printer.setPageOrientation(
self
.orientation)
self
._printer.setCopyCount(
self
.cup)
print
(e)
else
:
pass
def
rundialog(
self
):
self
.dialog
=
QPrintDialog(
self
._printer)
self
.dialog.setOptions(QPrintDialog.PrintToFile | QPrintDialog.PrintSelection)
if
self
.dialog.
exec
():
self
.runprint()
else
:
self
.finished.emit()
def
runprint(
self
):
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
:
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
:
with Image.
open
(path) as image:
pass
except
Exception as e:
pil_image
=
image.copy()
self
.print_image(pil_image, rect, painter)
painter.end()
else
:
if
self
.win.mergebox.isChecked():
images
=
self
.add_image(
self
.paths)
self
.A4_sep(images)
else
:
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
:
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
:
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
:
xOffset
=
0
y
=
int
(rect.height()
-
rect.width()
/
pilWidth
*
pilHeight)
yOffset
=
int
(y
/
self
.inter)
else
:
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
:
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
:
x1
=
0
if
image1.size[
1
] < half_hight:
y1
=
int
((half_hight
-
image1.size[
1
])
/
2
)
else
:
y1
=
0
if
image2.size[
0
] <
self
.width_dpx:
x2
=
int
((
self
.width_dpx
-
image2.size[
0
])
/
self
.inter)
else
:
x2
=
0
if
image2.size[
1
] < half_hight:
y2
=
int
((half_hight
-
image2.size[
1
])
/
2
)
else
:
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
:
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
:
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
:
_, 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
())