11

I'm trying to print a pdf file from Python with module win32print but the only way I can print success is a text.

hPrinter = win32print.OpenPrinter("\\\\Server\Printer")
filename = "test.pdf"
try:
    hJob = win32print.StartDocPrinter(hPrinter, 1, ('PrintJobName', None, 'RAW'))
    try:
        win32api.ShellExecute(0, "print", filename, None, ".", 0)
        win32print.StartPagePrinter(hPrinter)
        win32print.WritePrinter(hPrinter, "test")  # Instead of raw text is there a way to print PDF File ?
        win32print.EndPagePrinter(hPrinter)
    finally:
        win32print.EndDocPrinter(hPrinter)
finally:
    win32print.ClosePrinter(hPrinter)

So instead of printing a text I need to print the "test.pdf" file.

I also tried with win32api.ShellExecute(0, "print", filename, None, ".", 0) but it's not working, after some test like (getprinter, getdefault, setprinter, setdefaultprinter) it seems not to be attaching the printer. So at this way I can't get working.

This is the code I used !

win32print.SetDefaultPrinter(hPrinter)
win32api.ShellExecute(0, "print", filename, None,  ".",  0)
RMPR
  • 3,368
  • 4
  • 19
  • 31
ndAR
  • 371
  • 2
  • 4
  • 15

3 Answers3

17

I'd like a solution which is able to print about 100 pdf files daily every about 200 pages long. Files are stored in certain directory.

import win32api
import win32print
from glob import glob

# A List containing the system printers
all_printers = [printer[2] for printer in win32print.EnumPrinters(2)]
# Ask the user to select a printer
printer_num = int(input("Choose a printer:\n"+"\n".join([f"{n} {p}" for n, p in enumerate(all_printers)])+"\n"))
# set the default printer
win32print.SetDefaultPrinter(all_printers[printer_num])
pdf_dir = "D:/path/to/pdf_dir/**/*"
for f in glob(pdf_dir, recursive=True):
    win32api.ShellExecute(0, "print", f, None,  ".",  0)

input("press any key to exit")

Notes:

  1. You can change the printer attributes, such as color/black, scaling, quality, etc, by using the DevMode object (example on the GUI code).
  2. Provide the absolute paths of the filenames
  3. Give enough time to process the job before exiting the script.

While not direct answer to the question, I couldn't resist the opportunity to make a GUI using tkinter. It can be easily changed to select a dir - and all the files inside - instead of a single file.

import win32api
import win32print
import traceback

from tkinter.filedialog import askopenfilename
from tkinter import *
from tkinter import font # * doesn't import font or messagebox
from tkinter import messagebox

root = Tk()
root.title("Python Printer")
root.geometry("410x310")
root.resizable(False, False)
root.tk.call('encoding', 'system', 'utf-8')

def font_size(fs):
    return font.Font(family='Helvetica', size=fs, weight='bold')

# Add a grid
mainframe = Frame(root)
#mainframe.grid(column=0,row=0, sticky=(N,W,E,S) )
mainframe.grid(column=0,row=0, sticky=(N) )
mainframe.columnconfigure(0, weight = 1)
mainframe.rowconfigure(0, weight = 1)
mainframe.pack(pady = 10, padx = 0)

# Create a _printer variable
_printer = StringVar(root)
# Create a _color variable
_color = StringVar(root)
_filename = ""

# on change dropdown value
def sel_printer(*args):
    print( _printer.get() )
# link function to change dropdown
_printer.trace('w', sel_printer)

def sel_color(*args):
    print( _color.get() )
# link function to change dropdown
_color.trace('w', sel_color)

def UploadAction(event=None):
    global _filename
    _filename = filedialog.askopenfilename()
    #print('Selected:', _filename)

def PrintAction(event=None):

    PRINTER_DEFAULTS = {"DesiredAccess":win32print.PRINTER_ALL_ACCESS} 
    pHandle = win32print.OpenPrinter(_printer.get(), PRINTER_DEFAULTS)
    properties = win32print.GetPrinter(pHandle, 2)
    properties['pDevMode'].Color = 1 if str(_color.get()) == "Color" else 2
    properties['pDevMode'].Copies = 1
    win32print.SetPrinter(pHandle, 2, properties, 0)

    if not _filename:
        messagebox.showerror("Error", "No File Selected")
        return
    elif not _printer.get():
        messagebox.showerror("Error", "No Printer Selected")
        return

    try:
        #win32print.SetDefaultPrinter(_printer.get())
        win32api.ShellExecute(0, "print", _filename, None,  ".",  0)
        win32print.ClosePrinter(pHandle)
    except:
        pass
        messagebox.showerror("Error", "There was an error printing the file :(")

choices = [printer[2] for printer in win32print.EnumPrinters(2)]
_printer.set(win32print.GetDefaultPrinter()) # set the default option

popupMenu = OptionMenu(mainframe, _printer, *choices)
popupMenu['font'] = font_size(12)
Label(mainframe, text="SELECT PRINTER").grid(row = 1, column = 1)
popupMenu.grid(row = 2, column =1)

# Dictionary with options
choices = ["COLOR", "MONOCHROME"]
_color.set("COLOR") # set the default option

popupMenu2 = OptionMenu(mainframe, _color, *choices)
popupMenu2['font'] = font_size(12)
Label(mainframe, text="COLOR MODE").grid(row = 3, column = 1)
popupMenu2.grid(row = 4, column =1)

Label(mainframe, text="SELECT FILE").grid(row = 5, column = 1)
button = Button(mainframe, text=u"\uD83D\uDCC1" ' BROWSE', command=UploadAction)
button['font'] = font_size(12)
button.grid(row = 6, column =1)


_copies = IntVar()
_copies.set(1)

def copies_increase(event=None):
    _copies.set(_copies.get() + 1)

def copies_decrease(event=None):
    _copies.set(_copies.get() - 1)
    if _copies.get() < 1 :
        _copies.set(1)

Label(mainframe, textvariable=_copies).grid(columnspan=2)
button_frame = Frame(mainframe)
button_frame.grid(columnspan=2)


dec_button = Button(button_frame, text=u"\u2212", command=copies_decrease, fg="dark green", bg = "white", height=1, width=3 )
dec_button['font'] = font_size(10)

inc_button = Button(button_frame, text=u"\uFF0B", command=copies_increase, fg="dark green", bg = "white", height=1, width=3 )
inc_button['font'] = font_size(10)

button_frame.columnconfigure(0, weight=1)
button_frame.columnconfigure(1, weight=1)

dec_button.grid(row=0, column=0, sticky=W+E)
inc_button.grid(row=0, column=1, sticky=W+E)

Label(mainframe).grid(row = 10, column = 1)
p_button = Button(mainframe, text=u'\uD83D\uDDB6' + " PRINT", command=PrintAction, fg="dark green", bg = "white")
p_button['font'] = font_size(18)
p_button.grid(row = 11, column =1)

root.mainloop()

GIST

enter image description here enter image description here


Sources:

  1. Python printing a pdf file on my Brother Laser printer (Duplex print on/off)
  2. Python print pdf file with win32print
  3. http://docs.activestate.com/activepython/3.1/pywin32/PyDEVMODE.html
  4. https://www.thecodingforums.com/threads/printing-using-python.502484/
  5. https://stackoverflow.com/a/37586129/797495
  6. https://stackoverflow.com/a/56638762/797495
  7. https://t.codebug.vip/questions-712908.htm
  8. How do I create a button in Python Tkinter to increase integer variable by 1 and display that variable?
  9. https://gregcaporale.wordpress.com/2012/01/18/powershell-to-print-files-automatically/ (this may also be a solution)

PS: I may not win the bounty, but I certainly enjoyed making the GUI. tkinter layout was the hardest part!

Pedro Lobito
  • 94,083
  • 31
  • 258
  • 268
  • 1
    Thank you for such a complete answer. I had problem with Shellexecute to print pdf. So I used rather [this](https://superuser.com/a/1529950/71797) for printing. I've mistaked that I said that gscript is obsolete. gsprint.exe has problems but gswin64.exe is OK. – xralf Mar 07 '20 at 11:08
  • You're welcome, I'm glad you found a solution. Thank you for the bounty! – Pedro Lobito Mar 07 '20 at 13:47
  • I love the look of this and it is exactly what I want to add to my script. My only problem is that I keep getting "pywintypes.error: (5, 'OpenPrinter', 'Access is denied.')" in PrintAction on the pHandle. Any idea why this would happen? – NMALM Nov 24 '20 at 23:01
  • 1
    @NMALM Please have a look at https://stackoverflow.com/a/37586129/797495 – Pedro Lobito Nov 26 '20 at 21:35
  • @PedroLobito I'm afraid I don't see the answer there. I can't tell what they're doing differently than the script here. ```PRINTER_DEFAULTS = {"DesiredAccess":win32print.PRINTER_ALL_ACCESS} pHandle = win32print.OpenPrinter(_printer.get(), PRINTER_DEFAULTS) ``` I don't see how this is any different. – NMALM Nov 30 '20 at 19:45
  • `PRINTER_DEFAULTS = {"DesiredAccess":win32print.PRINTER_ACCESS_ADMINISTER} ` VS `PRINTER_DEFAULTS = {"DesiredAccess":win32print.PRINTER_ALL_ACCESS} `, try changing this. – Pedro Lobito Nov 30 '20 at 19:54
  • @PedroLobito Your Tkinter GUI had a little error in https://gist.github.com/OttomanZ/9e33fde4e00f3189eaa0ac0e5a0734cf . I have fixed it please update the source code there because editing queue is full. You need to import `askopenfilename()` instead of `filedialog.askopenfilename()` because of importing structure. – Muneeb Ahmad Khurram Sep 12 '22 at 17:40
  • @MuneebAhmadKhurram I've just tested the code on python 3.6.8 and it works as expected. We start by importing `askopenfilename` like this: `from tkinter.filedialog import askopenfilename` and calling `filedialog.askopenfilename()` as you proposed is redundant. You're the 1st user to report a "_editing queue is full_" error. The error is probably related to your python version or printer setup, but thanks anyway. – Pedro Lobito Sep 12 '22 at 19:42
5

This is the code I have used and it works correctly.

name = win32print.GetDefaultPrinter() # verify that it matches with the name of your printer
printdefaults = {"DesiredAccess": win32print.PRINTER_ALL_ACCESS} # Doesn't work with PRINTER_ACCESS_USE
handle = win32print.OpenPrinter(name, printdefaults)
level = 2
attributes = win32print.GetPrinter(handle, level)
#attributes['pDevMode'].Duplex = 1  #no flip
#attributes['pDevMode'].Duplex = 2  #flip up
attributes['pDevMode'].Duplex = 3   #flip over
win32print.SetPrinter(handle, level, attributes, 0)
win32print.GetPrinter(handle, level)['pDevMode'].Duplex
win32api.ShellExecute(0,'print','manual1.pdf','.','/manualstoprint',0)
1

You can try

win32print.SetDefaultPrinter("\\\\Server\Printer")

This method accepts a String, not the printer object you tried to pass it.

WJVDP
  • 62
  • 5