1

I have been working on a printer macro that uses Python to paste a letterhead onto a PDF message and then send it to the printer to print. In most of the companies' offices I set it up in, it works fine. Some of them, the macro won't print. The printer will receive the print job, and you can see it in the printer queue, but then it will silently cancel the job, and nothing will print. It works by converting the output PDF to a bunch of bitmaps and then sending those to the printer. I initially reduced the DPI from 300 to 150, thinking that would fix the issue because the printers that were having issues were older models, and I thought it might be a memory issue. That did not fix it. The printer I tested it on was a HP LaserJet M608, and the issue was on a HP LaserJet 600 M601. The macro also sends print jobs to a HP LaserJet 600 M603.

Libraries: sys os json win32print win32ui win32con win32gui fitz shutil logging uuid PIL pypdf

The program is distributed as a PyInstaller executable.

This is the code that converts the PDF to a bitmap:

def convert_to_bitmap(pdf_path, input_name):
print_queue = []

# cycle through pages in doc for bitmap conversions
doc = fitz.open(pdf_path)
i = 0
logger.info("Converting PDF file pages to bitmaps")
for page in doc:
    pixmap = page.get_pixmap(dpi=150) # convert page to pixel map
    img = Image.frombytes("RGB", [pixmap.width, pixmap.height], pixmap.samples) # converts pixel map to PIL image

    # creates appropriate path and saves bit map
    uuid4 = uuid.uuid4()
    bitmap_name = f"bitmap({i})-" + str(uuid4) + ".bmp"
    bitmap_path = get_resource_path(f"temp\\{bitmap_name}")
    try:
        img.save(bitmap_path)
    except PermissionError as e:
        logger.error(f"User lacks permission to save bitmap to {bitmap_path}:\n{e} \nExiting program")
        input_path = get_resource_path(os.path.join("temp\\", input_name))
        clean_up(print_queue, input_path, pdf_path)
        sys.exit(1)
    except FileNotFoundError as e:
        logger.error(f"{bitmap_path} is an invalid directory:\n{e} \nExiting program")
        input_path = get_resource_path(os.path.join("temp\\", input_name))
        clean_up(print_queue, input_path, pdf_path)
        sys.exit(1)
    except ValueError as e:
        logger.error(f"Unsupported file format or specifier:\n{e} \nExiting program")
        input_path = get_resource_path(os.path.join("temp\\", input_name))
        clean_up(print_queue, input_path, pdf_path)
        sys.exit(1)
    except OSError as e:
        logger.error(f"OS error encountered:\n{e} \nExiting program")
        input_path = get_resource_path(os.path.join("temp\\", input_name))
        clean_up(print_queue, input_path, pdf_path)
        sys.exit(1)
    except Exception as e:
        logger.error(f"Unexpected error encountered:\n{e} \nExiting program")
        input_path = get_resource_path(os.path.join("temp\\", input_name))
        clean_up(print_queue, input_path, pdf_path)
        sys.exit(1)
    print_queue.append(bitmap_path) # queues up bitmap for printing
    i = i + 1
    logger.info(f"Page {i + 1}: converted to bitmap")
logger.info("All PDF file pages converted to bitmaps")
return print_queue

This is the code that sends the print job to the printer (printe_queue is the bitmaps to be printed):

def print_pdf(printer, tray, print_queue):
# cyclce through printers to see if passed printer is valid
printers = win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL | win32print.PRINTER_ENUM_CONNECTIONS)
valid_printer = False
for p in printers:
    if (p[2] == printer):
        valid_printer = True
        logger.info(f"Printer: {printer} found")
if (not(valid_printer)): # printer not found setting to default
    logger.warning(f"Printer: {printer} not found")
    printer = win32print.GetDefaultPrinter()
    logger.info(f"Printing from default: {printer}")

# creates handler, device context, memory device context, and starts print job
printer_handler = win32print.OpenPrinter(printer)
# acceses printer settings
devmode = win32print.GetPrinter(printer_handler, 2)["pDevMode"]
if tray is not None: # sets tray
    if tray >= 0:
        devmode.DefaultSource = tray
        logger.info(f"Tray set to: {tray}")
    else:
        logger.warning(f"Tray set to default: {devmode.DefaultSource}")

win32print.DocumentProperties(0, printer_handler, printer, devmode, devmode, # loads printer setting onto printer
                              win32con.DM_IN_BUFFER | win32con.DM_OUT_BUFFER)
dc = win32ui.CreateDC()
dc.CreatePrinterDC(printer)
memory_dc = dc.CreateCompatibleDC()
dc.StartDoc("Letterhead on Message")

# gets page dimensions
page_width = dc.GetDeviceCaps(win32con.HORZRES)
page_height = dc.GetDeviceCaps(win32con.VERTRES)

# cycles bitmaps for each page
for bm in print_queue:
    dc.StartPage()

    # load bitmap object
    bitmap_handle = win32gui.LoadImage(0, bm, win32con.IMAGE_BITMAP, 0, 0, win32con.LR_LOADFROMFILE)
    bitmap = win32ui.CreateBitmapFromHandle(bitmap_handle)
    old_bitmap = memory_dc.SelectObject(bitmap)

    # gets bitmap info
    bitmap_info = bitmap.GetInfo()
    bitmap_width = bitmap_info['bmWidth']
    bitmap_height = bitmap_info['bmHeight']

    # stretch bitmap from memory device context to printer device context
    win32gui.StretchBlt(dc.GetSafeHdc(), 0, 0, page_width, page_height, memory_dc.GetSafeHdc(), 0, 0, bitmap_width, bitmap_height, win32con.SRCCOPY)

    # clean up
    memory_dc.SelectObject(old_bitmap)
    win32gui.DeleteObject(bitmap.GetHandle())
    dc.EndPage()
    logger.info(f"{bm} added to print job")

# finishes print job
memory_dc.DeleteDC()
dc.EndDoc()
win32print.ClosePrinter(printer_handler)
logger.info(f"Print job sent to {printer}")
return True

Does anyone have any ideas on what could be causing this issue and how to fix it? Do I need to reduce the memory more, or is it something else?

1
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a minimal reproducible example. Commented Jul 9 at 18:33

1 Answer 1

1

WIthout looking much at your code (sorry) - th ehigh level answer is: the modified PDF version you are generating is compatible with some of the printers - but it may either be non fully compliant, or may be using PDF features with which the non-functioning printers don't comply.

The workaround will be simplifying the PDF after you generate it - instead of sending the file directly to the printer. One tool which can do that from the command line (and therefore, it can be called from your Python script) is ghostscript - I suggest you take a look at it, and the "PDF device" output, and then sending the resulting filtered PDF to the faulty printers.

I apologize if I won't show you the needed changes in your code for that - but I hope yu can figure it out given this.

Sign up to request clarification or add additional context in comments.

1 Comment

I appreciate the advice. Ghostscript worked well for converting PDF files to PCL, but I am still running into the issue of it silently canceling when I send the PCL files to the printer. I've tried sending just the Ghostscript output to the printer and adding on some PCL job description data at the start, such as resetting the printer, proper job initialization, and job description specifics, which still didn't work. I'm looking into converting from PDF to EMF, but I haven't been able to find tools to do that conversion in python. Do you know of any, or have any other ideas?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.