codesoft指定打印机打印_巧用win32print来控制windows系统打印机并推送打印任务
小爬最近接到的一個(gè)需求是:將windows系統(tǒng)下的打印任務(wù)批量有序傳輸給網(wǎng)絡(luò)打印機(jī),實(shí)現(xiàn)批量有序打印。
????用戶先從公司的OA(B/S模式)系統(tǒng)下 打印指定內(nèi)容的表單以及表單中的附件內(nèi)容。這個(gè)問(wèn)題可以這樣分解:
1、抓包,得到OA對(duì)應(yīng)的任務(wù)接口,然后利用python requests模擬post請(qǐng)求,獲取所有的表單的URL并進(jìn)行必要的去重處理;
2、打印OA表單的過(guò)程,需要瀏覽器在前臺(tái),這個(gè)時(shí)候可以結(jié)合selenium的driver.get(url)方法,打開(kāi)每一個(gè)表單,同時(shí)解析網(wǎng)頁(yè)內(nèi)容,拿到所有附件的相關(guān)信息(名稱、后綴、下載地址),利用requests再度保存這些附件至本地;
3、打開(kāi)表單后,利用 win32api.keybd_event,模擬鍵盤快捷鍵“Ctrl + Shift + P”調(diào)出系統(tǒng)的打印窗口;
4、選中“PDF打印機(jī)”,需要電腦中有“Microsoft Print to Pdf”或者“Foxit Reader PDF Printer”等;
5、利用pywin32中的相關(guān)方法,驅(qū)動(dòng)打印過(guò)程,將每個(gè)OA表單(網(wǎng)頁(yè))打印成PDF文件并格式化命名&存儲(chǔ),與前面的附件內(nèi)容存儲(chǔ)到同一個(gè)文件夾;
6、附件文件和OA生成的PDF文件均格式化存儲(chǔ),用OA單號(hào)作為文件名的一部分,將兩者關(guān)聯(lián)起來(lái);
7、將本地對(duì)應(yīng)文件夾的所有內(nèi)容有序推送給打印機(jī),指定打印機(jī)為某一臺(tái)網(wǎng)絡(luò)打印機(jī)。同時(shí)要確保打印過(guò)程中,不亂序;
針對(duì)步驟3,可以自定義函數(shù)來(lái)實(shí)現(xiàn):
#鍵盤按下def?key_down(keyname):
????win32api.keybd_event(vk_code[keyname],0,0,0)
#鍵盤抬起
def?key_up(key_name):
????win32api.keybd_event(vk_code[key_name],0,win32con.KEYEVENTF_KEYUP,0)
#按鍵組合操作
def?simulate_three_key(firstkey,sencondkey,lastkey):
????key_down(firstkey)
????key_down(sencondkey)
????key_down(lastkey)
????key_up(lastkey)
????key_up(sencondkey)
????key_up(firstkey)
#按鍵組合操作
def?simulate_two_key(firstkey,sencondkey):
????key_down(firstkey)
????key_down(sencondkey)
????key_up(sencondkey)
????key_up(firstkey)
????然后利用 simulate_three_key('ctrl',"shift",'p') 即可呼出系統(tǒng)的默認(rèn)打印窗口:
那么步驟4,也就是上圖的打印窗口,如何選中某一個(gè)打印機(jī)呢?直接利用win32gui.SendMessage
????來(lái)選中某個(gè)打印機(jī)是非常困難的。一種可行的方法是,利用pywin32下的win32print模塊,也就是本文的重點(diǎn)。
比如,用下面的代碼可以遍歷并獲取到當(dāng)前計(jì)算機(jī)的所有打印機(jī)信息:
for?it?in?win32print.EnumPrinters(6):????print(it[1])
????我們甚至可以知道某臺(tái)打印機(jī)的當(dāng)前狀態(tài),假定某臺(tái)打印機(jī)名為printerName,則可以這樣獲取打印機(jī)狀態(tài):
hPrinter?=?win32print.OpenPrinter?(printerName)dic?=?hex(win32print.GetPrinter(hPrinter,2)['Status'])
if?dic[-2]=="8":
????print("The?printer?is?offline.")
if?dic[-5]=="4":
???print("The?printer?is?out?of?toner.")
elif?dic[-5]=="2":
???print("The?printer?is?low?on?toner.")
PRINTER_STATUS_BUSY 0x00000200 | The printer is busy. |
PRINTER_STATUS_DOOR_OPEN 0x00400000 | The printer door is open. |
PRINTER_STATUS_ERROR 0x00000002 | The printer is in an error state. |
PRINTER_STATUS_INITIALIZING 0x00008000 | The printer is initializing. |
PRINTER_STATUS_IO_ACTIVE 0x00000100 | The printer is in an active input or output state. |
PRINTER_STATUS_MANUAL_FEED 0x00000020 | The printer is in a manual feed state. |
PRINTER_STATUS_NOT_AVAILABLE 0x00001000 | The printer is not available for printing. |
PRINTER_STATUS_NO_TONER 0x00040000 | The printer is out of toner. |
PRINTER_STATUS_OFFLINE 0x00000080 | The printer is offline. |
PRINTER_STATUS_OUTPUT_BIN_FULL 0x00000800 | The printer's output bin is full. |
PRINTER_STATUS_OUT_OF_MEMORY 0x00200000 | The printer has run out of memory. |
PRINTER_STATUS_PAGE_PUNT 0x00080000 | The printer cannot print the current page. |
PRINTER_STATUS_PAPER_JAM 0x00000008 | Paper is stuck in the printer. |
PRINTER_STATUS_PAPER_OUT 0x00000010 | The printer is out of paper. |
PRINTER_STATUS_PAPER_PROBLEM 0x00000040 | The printer has an unspecified paper problem. |
PRINTER_STATUS_PAUSED 0x00000001 | The printer is paused. |
PRINTER_STATUS_PENDING_DELETION 0x00000004 | The printer is being deleted as a result of a client's call to?RpcDeletePrinter. No new jobs can be submitted on existing printer objects for that printer. |
PRINTER_STATUS_POWER_SAVE 0x01000000 | The printer is in power-save mode.<182> |
PRINTER_STATUS_PRINTING 0x00000400 | The printer is printing. |
PRINTER_STATUS_PROCESSING 0x00004000 | The printer is processing a?print job. |
PRINTER_STATUS_SERVER_OFFLINE 0x02000000 | The printer is offline.<183> |
PRINTER_STATUS_SERVER_UNKNOWN 0x00800000 | The printer status is unknown.<184> |
PRINTER_STATUS_TONER_LOW 0x00020000 | The printer is low on toner. |
PRINTER_STATUS_USER_INTERVENTION 0x00100000 | The printer has an error that requires the user to do something. |
PRINTER_STATUS_WAITING 0x00002000 | The printer is waiting. |
PRINTER_STATUS_WARMING_UP 0x00010000 | The printer is warming up. |
????????更多的打印機(jī)接口信息,可查詢微軟的開(kāi)發(fā)文檔:https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rprn/1625e9d9-29e4-48f4-b83d-3bd0fdaea787?redirectedfrom=MSDN
我們也可以得到當(dāng)前默認(rèn)的打印機(jī),設(shè)置默認(rèn)打印機(jī):
currentPrinter=win32print.GetDefaultPrinterW()win32print.SetDefaultPrinterW(printer)
????????我們利用上面兩個(gè)函數(shù),可以先得到系統(tǒng)當(dāng)前的打印機(jī),用變量存儲(chǔ)后,再設(shè)置默認(rèn)打印機(jī)至 PDF打印機(jī),待執(zhí)行完所有任務(wù)后,再設(shè)置默認(rèn)打印機(jī)為用戶一開(kāi)始的默認(rèn)打印機(jī),整個(gè)過(guò)程用戶不需要更多的干預(yù);
重點(diǎn)說(shuō)步驟7:我們需要以O(shè)A表單+附件的形式,逐一給打印機(jī)分配任務(wù),且不能亂序:
如果附件是圖片性質(zhì),我們可以結(jié)合Pillow庫(kù)來(lái)處理,示例代碼如下:
import?win32printimport?win32ui
from?PIL?import?Image,?ImageWin
#?Constants?for?GetDeviceCaps
#
#
#?HORZRES?/?VERTRES?=?printable?area
#
HORZRES?=?8
VERTRES?=?10
#
#?LOGPIXELS?=?dots?per?inch
#
LOGPIXELSX?=?88
LOGPIXELSY?=?90
#
#?PHYSICALWIDTH/HEIGHT?=?total?area
#
PHYSICALWIDTH?=?110
PHYSICALHEIGHT?=?111
#
#?PHYSICALOFFSETX/Y?=?left?/?top?margin
#
PHYSICALOFFSETX?=?112
PHYSICALOFFSETY?=?113
def?print_image(file_name):
?
????printer_name?=?win32print.GetDefaultPrinterW()?#?獲得默認(rèn)打印機(jī)
????
????#
????#?You?can?only?write?a?Device-independent?bitmap
????#?directly?to?a?Windows?device?context;?therefore
????#?we?need?(for?ease)?to?use?the?Python?Imaging
????#?Library?to?manipulate?the?image.
????#
????#?Create?a?device?context?from?a?named?printer
????#?and?assess?the?printable?size?of?the?paper.
????#
????hDC?=?win32ui.CreateDC?()
????hDC.CreatePrinterDC?(printer_name)
????printable_area?=?hDC.GetDeviceCaps?(HORZRES),?hDC.GetDeviceCaps?(VERTRES)
????printer_size?=?hDC.GetDeviceCaps?(PHYSICALWIDTH),?hDC.GetDeviceCaps?(PHYSICALHEIGHT)
????printer_margins?=?hDC.GetDeviceCaps?(PHYSICALOFFSETX),?hDC.GetDeviceCaps?(PHYSICALOFFSETY)
????
????#
????#?Open?the?image,?rotate?it?if?it's?wider?than
????#?it?is?high,?and?work?out?how?much?to?multiply
????#?each?pixel?by?to?get?it?as?big?as?possible?on
????#?the?page?without?distorting.
????#
????bmp?=?Image.open?(file_name)
????#?bmp?=?bmp.rotate?(90)
????#?bmp.save("test1.png")
????if?bmp.size[0]?>?bmp.size[1]:
????????#?bmp?=?bmp.rotate?(90)
????????bmp=bmp.transpose(Image.ROTATE_90)
????
????ratios?=?[1.0?*?printable_area[0]?/?bmp.size[0],?1.0?*?printable_area[1]?/?bmp.size[1]]
????scale?=?min?(ratios)*0.85?#這個(gè)0.85的系數(shù)是不希望圖片被打印太大,缺少margin,不方便文檔的裝訂
????file_name=file_name.split("\\")[-1]?#這一步是為了提取fullpath中的filename部分
????
????#
????#?Start?the?print?job,?and?draw?the?bitmap?to
????#?the?printer?device?at?the?scaled?size.
????#
????hDC.StartDoc?(file_name)
????hDC.StartPage?()
????
????dib?=?ImageWin.Dib?(bmp)
????scaled_width,?scaled_height?=?[int?(scale?*?i)?for?i?in?bmp.size]
????x1?=?int?((printer_size[0]?-?scaled_width)?/?2)
????y1?=?int?((printer_size[1]?-?scaled_height)?/?2)
????x2?=?x1?+?scaled_width
????y2?=?y1?+?scaled_height
????dib.draw?(hDC.GetHandleOutput?(),?(x1,?y1,?x2,?y2))
????
????hDC.EndPage?()
????hDC.EndDoc?()
????hDC.DeleteDC?()
????????需要強(qiáng)調(diào)的是,如果我們對(duì)圖片進(jìn)行后臺(tái)旋轉(zhuǎn)90度時(shí),一定要用transpose(Image.ROTATE_90),不要使用 rotate (90),否則打印的圖片很有可能顯示不完整,且有黑邊;
具體的transpose用法見(jiàn)Pillow官網(wǎng)文檔:
????如果我們要打印的任務(wù)是PDF或者其他office類型的文檔,可以利用win32api.ShellExecute方法,示例如下:
def?printer_loading(filename):????#?open?(filename,?"r")
????currentPrinter=win32print.GetDefaultPrinterW()
????win32api.ShellExecute?(0,"print",filename,'/d:"%s"'?%?currentPrinter,".",0)
該方法有一個(gè)缺陷,win32api.ShellExecute 會(huì)在指令發(fā)出后,立即返回值,而不是等打印任務(wù)真正傳輸?shù)酱蛴C(jī)后再返回。這就意味著,附件中的圖片用win32ui的方法走后臺(tái)已經(jīng)傳輸給打印機(jī),而PDF等其他文件可能還沒(méi)及時(shí)發(fā)送給打印機(jī),造成打印任務(wù)亂序。
????????可行的解決方法是,利用win32print.EnumJobs,定時(shí)獲取打印機(jī)當(dāng)前的任務(wù)隊(duì)列,確保隊(duì)列中出現(xiàn)剛推送的任務(wù)后,再來(lái)推送下一個(gè)打印任務(wù)。示例如下:
handle?=?win32print.OpenPrinter(printer_name).handletasks=win32print.EnumJobs(handle,0,?-1,?1)
for?task?in?tasks:
????taskName=task["pDocument"]
????????由于打印任務(wù)是動(dòng)態(tài)增減的,每次得到的tasks可能都不同,且由于打印機(jī)可能有很多人共同使用,不能保證某個(gè)用戶的某次打印任務(wù)一定會(huì)出現(xiàn)在打印隊(duì)列的最上方。所以要盡可能拿到所有的任務(wù);
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
????????至此,這個(gè)項(xiàng)目中的難點(diǎn)都逐一有了解決方案,希望小爬以上的思路,對(duì)喜歡自動(dòng)化的你,能有所借鑒~~
總結(jié)
以上是生活随笔為你收集整理的codesoft指定打印机打印_巧用win32print来控制windows系统打印机并推送打印任务的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: php树形数据结构是什么,数据结构 之
- 下一篇: mfc 对话框透明 控件不透明_你不知道