1#!/usr/bin/env python3 2# This file is part of Xpra. 3# Copyright (C) 2017-2020 Antoine Martin <antoine@xpra.org> 4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 5# later version. See the file COPYING for details. 6 7import os 8import sys 9from ctypes.wintypes import HDC 10from ctypes import WinDLL, c_void_p, Structure, c_int, c_uint, c_ulong, c_char_p, cast, pointer, POINTER 11 12from xpra.util import ellipsizer 13from xpra.os_util import strtobytes 14from xpra.platform.win32.common import GetDeviceCaps 15from xpra.platform.win32 import win32con 16from xpra.platform.win32.win32_printing import GDIPrinterContext, DOCINFO, StartDocA, EndDoc, LPCSTR 17 18LIBPDFIUMDLL = os.environ.get("XPRA_LIBPDFIUMDLL", "pdfium.dll") 19try: 20 pdfium = WinDLL(LIBPDFIUMDLL, use_last_error=True) 21except WindowsError as e: #@UndefinedVariable 22 raise ImportError("cannot load %s: %s" % (LIBPDFIUMDLL, e)) from None 23 24class FPDF_LIBRARY_CONFIG(Structure): 25 _fields_ = [ 26 ("m_pUserFontPaths", c_void_p), 27 ("version", c_int), 28 ("m_pIsolate", c_void_p), 29 ("m_v8EmbedderSlot", c_uint), 30 ] 31 32FPDF_DOCUMENT = c_void_p 33FPDF_PAGE = c_void_p 34 35FPDF_DestroyLibrary = pdfium.FPDF_DestroyLibrary 36FPDF_InitLibraryWithConfig = pdfium.FPDF_InitLibraryWithConfig 37FPDF_InitLibraryWithConfig.argtypes = [POINTER(FPDF_LIBRARY_CONFIG)] 38FPDF_GetLastError = pdfium.FPDF_GetLastError 39FPDF_GetLastError.restype = c_ulong 40FPDF_GetPageCount = pdfium.FPDF_GetPageCount 41FPDF_GetPageCount.argtypes = [FPDF_DOCUMENT] 42FPDF_GetPageCount.restype = c_int 43FPDF_LoadPage = pdfium.FPDF_LoadPage 44FPDF_LoadPage.argtypes = [FPDF_DOCUMENT, c_int] 45FPDF_RenderPage = pdfium.FPDF_RenderPage 46FPDF_RenderPage.argtypes = [HDC, FPDF_PAGE, c_int, c_int, c_int, c_int, c_int, c_int] 47FPDF_LoadMemDocument = pdfium.FPDF_LoadMemDocument 48FPDF_LoadMemDocument.restype = FPDF_DOCUMENT 49FPDF_LoadMemDocument.argtypes = [c_void_p, c_int, c_void_p] 50FPDF_CloseDocument = pdfium.FPDF_CloseDocument 51FPDF_CloseDocument.argtypes = [FPDF_DOCUMENT] 52 53FPDF_ERR_SUCCESS = 0 # No error. 54FPDF_ERR_UNKNOWN = 1 # Unknown error. 55FPDF_ERR_FILE = 2 # File not found or could not be opened. 56FPDF_ERR_FORMAT = 3 # File not in PDF format or corrupted. 57FPDF_ERR_PASSWORD = 4 # Password required or incorrect password. 58FPDF_ERR_SECURITY = 5 # Unsupported security scheme. 59FPDF_ERR_PAGE = 6 # Page not found or content error. 60FPDF_ERR_XFALOAD = 7 # Load XFA error. 61FPDF_ERR_XFALAYOUT = 8 # Layout XFA error. 62 63ERROR_STR = { 64 #FPDF_ERR_SUCCESS : No error. 65 FPDF_ERR_UNKNOWN : "Unknown error", 66 FPDF_ERR_FILE : "File not found or could not be opened", 67 FPDF_ERR_FORMAT : "File not in PDF format or corrupted", 68 FPDF_ERR_PASSWORD : "Password required or incorrect password", 69 FPDF_ERR_SECURITY : "Unsupported security scheme", 70 FPDF_ERR_PAGE : "Page not found or content error", 71 FPDF_ERR_XFALOAD : "Load XFA error", 72 FPDF_ERR_XFALAYOUT : "Layout XFA error", 73 } 74 75FPDF_ANNOT = 0x01 76FPDF_LCD_TEXT = 0x02 77FPDF_NO_NATIVETEXT = 0x04 78FPDF_GRAYSCALE = 0x08 79FPDF_DEBUG_INFO = 0x80 80FPDF_NO_CATCH = 0x100 81FPDF_RENDER_LIMITEDIMAGECACHE = 0x200 82FPDF_RENDER_FORCEHALFTONE = 0x400 83FPDF_PRINTING = 0x800 84FPDF_RENDER_NO_SMOOTHTEXT = 0x1000 85FPDF_RENDER_NO_SMOOTHIMAGE = 0x2000 86FPDF_RENDER_NO_SMOOTHPATH = 0x4000 87FPDF_REVERSE_BYTE_ORDER = 0x10 88 89def get_error(): 90 global ERROR_STR 91 v = FPDF_GetLastError() 92 return ERROR_STR.get(v, v) 93 94def do_print_pdf(hdc, title=b"PDF Print Test", pdf_data=None): 95 assert pdf_data, "no pdf data" 96 from xpra.log import Logger 97 log = Logger("printing", "win32") 98 log("pdfium=%s", pdfium) 99 buf = c_char_p(pdf_data) 100 log("pdf data buffer: %s", ellipsizer(pdf_data)) 101 log("FPDF_InitLibraryWithConfig=%s", FPDF_InitLibraryWithConfig) 102 config = FPDF_LIBRARY_CONFIG() 103 config.m_pUserFontPaths = None 104 config.version = 2 105 config.m_pIsolate = None 106 config.m_v8EmbedderSlot = 0 107 FPDF_InitLibraryWithConfig(config) 108 x = 0 109 y = 0 110 w = GetDeviceCaps(hdc, win32con.HORZRES) 111 h = GetDeviceCaps(hdc, win32con.VERTRES) 112 rotate = 0 113 log("printer device size: %ix%i", w, h) 114 flags = FPDF_PRINTING | FPDF_DEBUG_INFO 115 try: 116 doc = FPDF_LoadMemDocument(cast(buf, c_void_p), len(pdf_data), None) 117 if not doc: 118 log.error("Error: FPDF_LoadMemDocument failed, error: %s", get_error()) 119 return -1 120 log("FPDF_LoadMemDocument(..)=%s", doc) 121 count = FPDF_GetPageCount(doc) 122 log("FPDF_GetPageCount(%s)=%s", doc, count) 123 docinfo = DOCINFO() 124 docinfo.lpszDocName = LPCSTR(b"%s\0" % title) 125 jobid = StartDocA(hdc, pointer(docinfo)) 126 if jobid<0: 127 log.error("Error: StartDocA failed: %i", jobid) 128 return jobid 129 log("StartDocA()=%i", jobid) 130 try: 131 for i in range(count): 132 page = FPDF_LoadPage(doc, i) 133 if not page: 134 log.error("Error: FPDF_LoadPage failed for page %i, error: %s", i, get_error()) 135 return -2 136 log("FPDF_LoadPage()=%s page %i loaded", page, i) 137 FPDF_RenderPage(hdc, page, x, y, w, h, rotate, flags) 138 log("FPDF_RenderPage page %i rendered", i) 139 finally: 140 EndDoc(hdc) 141 finally: 142 FPDF_DestroyLibrary() 143 return jobid 144 145def print_pdf(printer_name, title, pdf_data): 146 with GDIPrinterContext(printer_name) as hdc: 147 return do_print_pdf(hdc, title, pdf_data) 148 149 150EXIT = False 151JOBS_INFO = {} 152def watch_print_job_status(): 153 global JOBS_INFO, EXIT 154 from xpra.log import Logger 155 log = Logger("printing", "win32") 156 log("wait_for_print_job_end()") 157 #log("wait_for_print_job_end(%i)", print_job_id) 158 from xpra.platform.win32.printer_notify import wait_for_print_job_info, job_status 159 while not EXIT: 160 info = wait_for_print_job_info(timeout=1.0) 161 if not info: 162 continue 163 log("wait_for_print_job_info()=%s", info) 164 for nd in info: 165 job_id, key, value = nd 166 if key=='job_status': 167 value = job_status(value) 168 log("job_id=%s, key=%s, value=%s", job_id, key, value) 169 JOBS_INFO.setdefault(job_id, {})[key] = value 170 171 172def main(): 173 global JOBS_INFO, EXIT 174 if len(sys.argv) not in (2, 3, 4): 175 print("usage: %s /path/to/document.pdf [printer-name] [document-title]" % sys.argv[0]) 176 return -3 177 filename = sys.argv[1] 178 with open(filename, 'rb') as f: 179 pdf_data = f.read() 180 181 if len(sys.argv)==2: 182 from xpra.platform.win32.printing import get_printers 183 printers = get_printers() 184 printer_name = strtobytes(printers.keys()[0]) 185 if len(sys.argv) in (3, 4): 186 printer_name = strtobytes(sys.argv[2]) 187 if len(sys.argv)==4: 188 title = strtobytes(sys.argv[3]) 189 else: 190 title = strtobytes(os.path.basename(filename)) 191 192 import time 193 from xpra.util import csv 194 from xpra.log import Logger 195 log = Logger("printing", "win32") 196 197 #start a new thread before submitting the document, 198 #because otherwise the job may complete before we can get its status 199 from threading import Thread 200 t = Thread(target=watch_print_job_status, name="watch print job status") 201 t.daemon = True 202 t.start() 203 204 job_id = print_pdf(printer_name, title, pdf_data) 205 if job_id<0: 206 return job_id 207 #wait for job to end: 208 job_status = None 209 while True: 210 job_info = JOBS_INFO.get(job_id, {}) 211 log("job_info[%i]=%s", job_id, job_info) 212 v = job_info.get("job_status") 213 if v!=job_status: 214 log.info("print job status: %s", csv(v)) 215 job_status = v 216 if "OFFLINE" in job_status or "DELETING" in job_status: 217 EXIT = True 218 break 219 time.sleep(1.0) 220 return 0 221 222 223if __name__ == "__main__": 224 sys.exit(main()) 225