工作需要实现一个基于IE内核的打印方案。本文将对于此方案的进行一个整体解析。由于此方案涉及自己以前未涉及的很多方面,在研究过程中学习了很多,因此在此也是做一次总结。

本文主要分为以下几个部分:

(一) 整体方案方案分析

(二) 技术难点方案解析:

1. 如何解决发起打印请求时自动跳出的打印提示对话框;

2. 如何通过自定义打印模板配置打印页眉页脚、页边距、打印纸张方向等;

3. 如何解决发起打印请求时自动跳出的打印文件保存对话框。

(一) 整体方案方案分析

目前web打印主要有以下几种方案:

1. 使用通用报表组件来完成打印。但是很多都是商用付费。

2. 使用IE自带的打印功能,加上CSS的支持。功能太弱,不支持自定义配置。需要用户手动选择打印配置。

3. 使用第三方的打印组件。免费组件比较少,而且实现不透明。对于我们来说打印组件是黑盒的。

4. 生成PDF文件。相关的库都是商用付费的。

综上几种方案都不适用于本项目,因此需要自己动手了。我们的主要需求在于可以控制页边距、页面布局(横向、纵向)、页眉页脚以及支持打印背景颜色和图像。

后来通过在msdn上找到两篇文章,《Beyond Print Preview: Print Customization for Internet Explorer 5.5》、《Print Preview 2: The Continuing Adventures of Internet Explorer 5.5 Print Customization》。经过艰难的努力,才终于解决所有的需求。

首先我们看一下IE的架构:

IExplore.exe位于最上层,当IE装载时被实例化。这个EXE使用IE的各种组件来执行HTML解析和渲染,以及其他相关功能,同时为独立的浏览器提供工具栏和框架。IExplore.exe是Shdocvw.dll组件的直接宿主。

Shdocvw.dll依次寄宿Mshtml.dll,当有其他的活动文档组件(例如MS Office应用),当用户导航到这些特定的文档时,可以就地装入浏览器。Shdocvw.dll提供这些和导航联系在一起的功能:就地链接、收藏夹和历史记录管理、PICS支持。改动态连接库也想宿主暴露了一些接口,以允许这些宿主可以把它当作ActiveX控件来分别寄宿。

在Shdocvw.dll中有一个接口叫做IWebBrowser2,我们所见到的IE,其实上就是对该接口的一个封装。他有一个很重要的成员函数“ExecWB”,其原型如下

通过这个函数不同cmdID和cmdexecopt指定不同的值,我们可以做IE界面上几乎所有的事。其中就有我们需要的XPS文件生成功能。我们的主要方案就是利用这个接口来实现我们的打印功能。

ExecWB方法实际上是调用IOleCommandTarget接口的Exec函数,其函数原型如下:

首先,我们需要一个MicroSoft XPS document Writer,并将其设置为操作系统的默认打印机。这个xps虚拟打印机在win7之后会默认安装。

其次我们需要实现一个HTMLView对象,并通过打印命令调用ExecWB接口。MFC提供了一个CHtmlView的类,我们可以继承这个类,然后调用其ExecWB方法通过XPS虚拟打印机生成一个个XPS打印文件。

最后将生成的XPS打印文件发往真实打印机,即可实现打印。

但是实际运用时,会发现,调用接口后跳出一个打印页面提示对话框,然后跳出xps文件保存对话框。

此外,我们还需要想办法控制打印配置,以实现我们自己的打印页边距等需求。

(页面提示对话框以及文件保存对话框)

因此我们需要解决的就是以上三个难点。

(二) 技术难点方案分析

1. 如何解决发起打印请求时自动跳出的打印提示对话框。接下来我们仔细观察了execWB这个接口的各个参数。

第一个参数cmdID,表示执行的命令。可以在MSDN上搜索MSHTML Command Identifiers来查阅这些命令。我们在这里需要的命令参数是 IDM_PRINT——使用默认打印模板或者自定义打印模板来打印当前文档内容。我们稍后来解释打印模板。

第二个参数是命令选项,在MSDN搜索OLECMDEXECOPT可以查阅。它是一个枚举,包括以下几个值:

其中OLECMDEXECOPT_DONTPROMPTUSER是指执行命令时不提示用户。当使用打印命令时,点击打印时将文档直接打印,不提示用户。

至此,我们就可以解决第一个问题。

2. 如何通过自定义打印模板配置打印页眉页脚、页边距、打印纸张方向等。我们继续来看execWB的参数。

请注意标红的一段,如果pvaIn标识的Varaiant是VT_BSTR类型的,那么表示的是一个全路径,路径指向的文件是一个自定义的打印模板。当pvaIn为空时,使用的是IE的默认打印模板。

我们可以使用打印模板做什么事情呢?

1、控制打印和预览时页面的版面,打印以及预览的内容;

2、控制打印任务如何被处理,例如以何种顺序打印页面;

3、控制打印预览窗口的外观,在打印用户界面上放置定制的控件。

有了打印模板,我们可以加入一些非常酷的功能,例如加入公司的Logo,法律声明,广告;定制页眉页脚的位置和样式;安排打印计划进行定时打印等等。

一个打印模板其实就是一个标准的HTML文件,与其他的HTML文件不同的是,他有4个声明模板的基本元素:LAYOUTRECT,DEVICERECT,TEMPLATEPRINTER,以及HEADERFOOTER。下面是这四个模板元素的作用:

LAYOUTRECT:为打印和预览模板中的文档内容生成容器。

DEVICERECT:为LAYOUTRECT元素和打印模板中的其他内容提供一个容器。位于DEVICERECT之外的内容都不会被打印。

TEMPLATEPRINTER:提供一个打印模板以获取页面设置和打印设置,并且可以控制从打印模板初始化出来的打印作业。

HEADERFOOTER:提供一个工具一边打印模板可以把页眉和页脚的格式化字符串转换为格式化的HTML文本。

下面我们展示一个最小的支持打印的模板,此模板来自于MSDN。增加了部分说明。

<!-- Template2.htm: 一个很小的模板,它支持打印
这个模板展示了一个最小的打印模板,他可以打印我们在预览时见到的内容。增加了TEMPLATEPRINTER元素和一些支持打印的脚本。这些脚本以函数CheckPrint开始。该函数处理第二个LAYOUTRECT元素的onlayoutcomplete事件,由一个时间是100毫秒的定时器触发。CheckPrint检查dialogArguments.__IE_PrintType 属性来决定是否是预览文档,或者是在打印之前需要以一个打印对话框来提示用户,还是不提示用户而直接打印。使用定时器来延时非常重要,因为设备上下文(DC)在onlayoutcomplete时间没有完成之前,不能渲染打印机或者屏幕。延时让使得打印发生在onlayoutcomplete事件完成后再开始。1毫秒就足够让onlayoutcomplete时间完成,但为了安全起见,在本例中,把定时器设为100毫秒。
PrintPrep是一个重要的函数,因为设置了一个onreadystatechange处理函数,以确定模板的源文档在打印之前被完全装入。
-->
<HTML XMLNS:IE>
<HEAD>
<?IMPORT NAMESPACE="IE" IMPLEMENTATION="#default">
<STYLE TYPE="text/css">
.lorstyle
{width:5.5in;height:8in;margin:1in;background:white;   border:1 dashed gray;
}
.pagestyle
{ width:8.5in;height:11in;background:#FFFF99;   border-left:1 solid black;border-top:1 solid black;border-right:4 solid black;border-bottom:4 solid black;margin:10px;
}
</STYLE>
<SCRIPT LANGUAGE="JScript">
function CheckPrint()
{switch (dialogArguments.__IE_PrintType){case "Prompt":if (printer.showPrintDialog()) PrintPrep();break;case "NoPrompt":PrintPrep();break;case "Preview":default:break;}
}function PrintPrep()
{if (layoutrect1.contentDocument.readyState == "complete"){//这一块会被调用当提示用户后再打印的时候,因为打印对话框会给出时间,//让文档内容被完全装入。PrintNow();}else{   layoutrect1.contentDocument.onreadystatechange = PrintWhenContentDocComplete;}
}function PrintWhenContentDocComplete()
{if (layoutrect1.contentDocument.readyState == "complete"){layoutrect1.contentDocument.onreadystatechange = null;PrintNow();}
}function PrintNow()
{printer.startDoc("Printing from Tmplt2.htm");printer.printPage(page1);   printer.printPage(page2);printer.stopDoc();
}
</SCRIPT><IE:TEMPLATEPRINTER ID="printer"/>
</HEAD><BODY>
<IE:DEVICERECT ID="page1" CLASS="pagestyle" MEDIA="print"><IE:LAYOUTRECT ID="layoutrect1" CONTENTSRC="document" CLASS="lorstyle" NEXTRECT="layoutrect2"/>
</IE:DEVICERECT>
<IE:DEVICERECT ID="page2" CLASS="pagestyle" MEDIA="print"><IE:LAYOUTRECT ID="layoutrect2" CLASS="lorstyle" ONLAYOUTCOMPLETE="setTimeout('CheckPrint()', 100)"/>
</IE:DEVICERECT>
</BODY>
</HTML>

这个例子还不是很完善,首先只能打印两页,无论有多少页,此模板只能打印前两页。其次页面大小也是固定设置为8×11.5英寸。

我们注意到,每个标签都以一个命名空间开始(IE:),一个打印模板必须在第一行以如下的形式声明他的命名空间:

<HTML XMLNS:IE>

<HEAD>

<?IMPORT NAMESPACE=”IE” IMPLEMENTATION=”#default”>

在这里HTML标签的XMLNS属性为模板声明了一个叫做IE的命名空间,你也可以把”IE”替换成任意名字,模板后面相关命名空间都需要修改。IMPORT标签然后导入了Internet Explore对IE命名空间的实现,使得打印模板上的元素的行为对模板有效。

现在我们来看看属性,需要注意的是,CLASS属性指定的样式中必须包含Width和Height。另外一个属性是MEDIA,这里必须指定为print,这是强制性的。另外一个是CONTENTSRC,这里的值是document,表示打印当前IE显示的页面。如果不想打印当前界面可以为其指定另外一个URL。最后一个是NEXTRECT属性,表示当前页满了之后的下一个LAYOUTRECT的名字。例如本例中layoutrect1的nextrect为layoutrect2。本例中只能打印两页就是由于这里只定义了两个LAYOUTRECT。

接下来请大家注意下脚本函数。

我们需要注意一个重要的事件,就是ONLAYOUOTCOMPLETE。当打印内容完全填充满当前页时触发此事件。此事件还有一个重要的属性,contentOverFlow,如果为true,表示文档尚未显示完成,否则文档就显示完全。我们可以根据这个值来判断总共打印了多少页,并动态打印。因此我们可以获取页码,并显示在页脚。

此外,还需要注意TEMPLATEPRINTER对象的属性。此对象拥有一个orientation属性,可以将其设置为"landscape"表示纸张方向为横向打印,设置为"portrait"表示为纵向打印。

至此我们可以根据自定义模板实现打印页面控制。

3. 如何解决发起打印请求时自动跳出的打印文件保存对话框

首先我们尝试着打印一份文档,当弹出文件保存对话框时,查看此时的调用栈如下:

# ChildEBP RetAddr  Args to Child

WARNING: Stack unwind information not available. Following frames may be wrong.

00 08afc088 77d452ed 000a0ed0 08afc0a4 00040ef8 KERNELBASE!CompareStringW+0x11c

01 08afc0c4 77d45511 000a0ed0 00040ef8 00000000 USER32!DialogBox2+0x144

02 08afc0e8 77d45553 6f050000 08d44a80 00040ef8 USER32!InternalDialogBox+0xcb

03 08afc108 77d44ac2 6f050000 08d44a80 00040ef8 USER32!DialogBoxIndirectParamAorW+0x37

04 08afc128 6f0559a1 6f050000 08d44a80 00040ef8 USER32!DialogBoxIndirectParamW+0x1b

05 08afc174 6f066536 08d44a80 00040ef8 00000000 COMDLG32!DllCanUnloadNow+0x2c6c

06 08afc1a0 6f06648c 08d2a234 00000611 00040ef8 COMDLG32!GetSaveFileNameW+0x397

07 08afc1cc 6f0663cd 00000611 00040ef8 08d2a234 COMDLG32!GetSaveFileNameW+0x2ed

08 08afc208 6f0662cf 00000611 00000000 08afc244 COMDLG32!GetSaveFileNameW+0x22e

09 08afc218 6f0662ad 08afc258 00000000 08afd7d0 COMDLG32!GetSaveFileNameW+0x130

0a 08afc244 6f0661fa 08afc258 6f08ac51 0024f52c COMDLG32!GetSaveFileNameW+0x10e

0b 08afd2c4 0b249852 08afd7d0 00000000 00000000 COMDLG32!GetSaveFileNameW+0x5b

0c 08afe288 0b248e27 08fd2e78 08afe2b4 0b24cca3 mxdwdui+0x9852

0d 08afe2d8 0b248913 08afe30c 08afe8dc 08db0154 mxdwdui+0x8e27

0e 08afe754 702891d8 08fd2e78 08db0154 20211c9f mxdwdui+0x8913

0f 08afe780 7028920f 0024f52c 08db0154 20211c9f unidrvui!DrvDocumentPropertySheets+0x9b4

10 08afe7c4 6d93fe1d 08db0154 20211c9f 00000005 unidrvui!DrvDocumentPropertySheets+0x9eb

11 08afe824 6d946864 08db0154 20211c9f 00000005 WINSPOOL!CallDrvDocumentEventNative+0x6d

12 08afe858 77b71450 00000001 20211c9f 00000005 WINSPOOL!DocumentEvent+0x1a6

13 08afe880 77b95d96 068db868 08db0154 20211c9f GDI32!DocumentEventEx+0x7e

14 08afe9bc 6ca15a4f 20211c9f 08afe9d8 6ca03070 GDI32!StartDocW+0x1e0

15 08afea7c 6fc43e75 0267efd8 068a5504 08afeae0 iepeers!DllEnumClassObjects+0x6a55

16 08afea9c 6fc43cef 0267efd8 0000001c 00000004 OLEAUT32!DispCallFunc+0xa6

17 08afeb2c 6fc4052f 08dbdd6c 0267efd8 00000000 OLEAUT32!LoadRegTypeLib+0xac1

18 08afebbc 6fc4052f 08dbdd14 0267efd8 00000001 OLEAUT32!CreateErrorInfo+0x7c4

19 08afec4c 6ca0226b 08dbdcbc 0267efd8 00000001 OLEAUT32!CreateErrorInfo+0x7c4

1a 08afec80 6ca0f276 6ca20c94 0267efd8 00000001 iepeers+0x226b

1b 08afecac 74e69ef0 0267efd8 00000001 74c2a200 iepeers!DllEnumClassObjects+0x27c

1c 08afece8 74e5ff87 0267efd8 00000001 00000001 mshtml!DllCanUnloadNow+0x2274e

1d 08afed24 74e5feb8 00000001 00000001 00000003 mshtml!DllCanUnloadNow+0x187e5

1e 08afed90 74e5fe21 00000001 00000003 08afef58 mshtml!DllCanUnloadNow+0x18716

1f 08afedd4 74e5cfaa 06802958 007c1201 00000001 mshtml!DllCanUnloadNow+0x1867f

20 08afee00 74e03994 06802958 007c1201 00000001 mshtml!DllCanUnloadNow+0x15808

21 08afee54 7034a1bc 08d37c40 007c1201 00000001 mshtml!Ordinal103+0x6122f

22 08afee90 7034a107 0120f8f0 007c1201 00000409 jscript+0xa1bc

23 08afeecc 7034a388 0120f8f0 00000409 00000003 jscript+0xa107

24 08afef8c 7034a432 007c1201 00000003 08aff18c jscript+0xa388

这里希望大家注意到的是以上标红的调用。我们发现在DialogBox2之前的两个调用比较有意思。一个是startDoc调用。一个是GetSaveFileName调用。startDoc是向打印机发起一个打印任务。而GetSaveFileName方法是获取文件保存路径。那我们可不可以将GetSaveFileName方法在进程内存中换掉,换成我们自己的实现方法呢?

答案是可以的。

这里我们总共尝试了两种办法。

1.尝试改写进程导入地址表。优点是不需要修改函数实现,在函数跳转前就可以实现拦截;缺点是拦截功能不够强大,有可能出现拦截不到的现象。

2.使用微软detours库实现修改函数实现。优点是肯定可以拦截到。缺点是如果修改函数实现过程中,有此函数调用可能会不安全。

首先尝试了第一种办法,我们知道进程地址中有一个地址表叫做导入地址表,里面存放的是从其他模块引入的导入方法地址。我们所想修改的就是这个地址表中GetSaveFileName的所指向的地址,使其指向我们自己的实现方法。

代码如下:

#include <windows.h>// 实现重定向API
// 参数pDllName:目标API所在的DLL名称
// 参数pFunName:目标API名称
// 参数dwNewProc:自定义的函数地址
// 参数pItem:用于保存IAT项信息
BOOL WINAPI RedirectApi ( PCHAR pDllName, PCHAR pFunName, DWORD dwNewProc)
{// 检查参数是否合法if ( pDllName == NULL || pFunName == NULL || !dwNewProc)return FALSE ;// 检测目标模块是否存在char szTempDllName[256] = {0} ;DWORD dwBaseImage = (DWORD)GetModuleHandle(NULL) ;if ( dwBaseImage == 0 )return FALSE ;// 取得PE文件头信息指针PIMAGE_DOS_HEADER   pDosHeader = (PIMAGE_DOS_HEADER)dwBaseImage ;PIMAGE_NT_HEADERS   pNtHeader = (PIMAGE_NT_HEADERS)(dwBaseImage + (pDosHeader->e_lfanew)) ;PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = &(pNtHeader->OptionalHeader) ;PIMAGE_SECTION_HEADER  pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + 0x18 + pNtHeader->FileHeader.SizeOfOptionalHeader ) ;// 遍历导入表PIMAGE_THUNK_DATA pThunk, pIAT ;PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwBaseImage+pOptionalHeader->DataDirectory[1].VirtualAddress ) ;while ( pIID->FirstThunk ){// 检测是否目标模块if ( strcmp ( (PCHAR)(dwBaseImage+pIID->Name), pDllName ) ){pIID++ ;continue ;}pIAT = (PIMAGE_THUNK_DATA)( dwBaseImage + pIID->FirstThunk ) ;if ( pIID->OriginalFirstThunk )pThunk = (PIMAGE_THUNK_DATA)( dwBaseImage + pIID->OriginalFirstThunk ) ;elsepThunk = pIAT ;// 遍历IATDWORD dwThunkValue = 0 ;while ( ( dwThunkValue = *((DWORD*)pThunk) ) != 0 ){if ( ( dwThunkValue & IMAGE_ORDINAL_FLAG32 ) == 0 ){// 检测是否目标函数if ( strcmp ( (PCHAR)(dwBaseImage+dwThunkValue+2), pFunName ) == 0 ){// 修改IAT项DWORD dwOldProtect = 0 ;VirtualProtect ( pIAT, 4, PAGE_READWRITE, &dwOldProtect ) ;*((DWORD*)pIAT) = dwNewProc ;VirtualProtect ( pIAT, 4, PAGE_READWRITE, &dwOldProtect ) ;return TRUE ;}}pThunk ++ ;pIAT ++ ;}pIID ++ ;}return FALSE ;
}

经过测试,此方案在自行调用GetSaveFileName时可以拦截,但是由于打印时是通过JavaScript调用GetSaveFileName获取保存路径,因此拦截不到。所以只能尝试第二种方案。

Detours库是微软提供的一个拦截API的库。接下来就这个库进行相关说明。

首先我们可以在http://research.microsoft.com/sn/detours免费下载到detours的安装包。安装后可以拿到源码。使用安装完成后得到的makefile编译detours生成库文件。就可以拿到我们需要的detours.lib文件。

Detours是执行时被插入的。内存中的目标函数的代码不是在硬盘上被修改的,而是修改进程内存中函数的实现地址。

Detours定义了三个概念:

1. Target函数:要拦截的函数,通常为Windows的API;

2. Trampoline函数:Target函数的部分复制品。因为Detours将会改写Target函数,所以先把target函数的前5个字节复制保存好,一方面仍然保存Target函数的过程调用语义,另一方面用于恢复。

3. Detour函数:用来替代Target函数的函数。

Detours在Tartget函数的开头加入JMP Address_of_Detour_Function指令(共五个字节)把对Target函数的调用引导到自己的Detour函数,把Target函数的开头的5个字节加上JMP Address_of_Detour_Function+5共10个字节作为Trampoline函数。请参考下图(图片来源于网络):

如何调用detours库呢?

首先在工程中加入三行声明:

#include "detours.h"
#pragma comment(lib,"detoured.lib")
#pragma comment(lib,"detours.lib")

然后通过以下代码进行函数拦截时:

DetourRestoreAfterWith();DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());//这里可以连续多次调用DetourAttach,表明HOOK多个函数DetourAttach(OldApiAddress,pDetour);DetourTransactionCommit();

通过以下代码进行函数拦截恢复:

DetourTransactionBegin();DetourUpdateThread(GetCurrentThread());//这里可以连续多次调用DetourDetach,表明撤销多个函数HOOKDetourDetach(OldApiAddress,pDetour);DetourTransactionCommit();

经测试此方案完全能满足需求。

由于我当前方案中进程不会有其他调用GetSaveFileName的需求,因此进程实现初始化时实现拦截API,进程退出时恢复API拦截。

目前此方案所有问题均已解决,已成功实现自定义web打印。

后来在应用到64位时,由于detours库64位需要收费,因此修改为开源库miniHook库实现API拦截。同时将32位代码也一起修改了。

再次附上自定义打印模板所有代码(仅附上横屏打印模板),如果有问题,欢迎交流:

<HTML XMLNS:IE>
<HEAD>
<?IMPORT NAMESPACE="IE" IMPLEMENTATION="#default">
<STYLE TYPE="text/css">
.lorstyle
{width:5.5in;height:8in;margin:1in;background:white;
}
.pagestyle
{ background:white;border-left:0 solid black;border-top:0 solid black;border-right:4 solid black;border-bottom:4 solid black;width:8.5in;height:11in;margin:10px;overflow:hidden;
}
.headerstyle
{position:absolute;top:.25in;width:6in;left:1in;
}
.footerstyle
{position:absolute;top:7.9in;width:6in;left:1in;
}
</STYLE>
<SCRIPT LANGUAGE="JScript">var iNextPageToCreate = 1;
var oPageStyleClass;
var oLorStyleClass;
var oHeaderStyleClass;
var oFooterStyleClass;var layoutrectTmp;// Returns the object corresponding to a named style rule
function FindStyleRule(styleName)
{for (i = 0; i < document.styleSheets.length; i++){for (j = 0; j < document.styleSheets(i).rules.length; j++){if (document.styleSheets(i).rules(j).selectorText == styleName)return document.styleSheets(i).rules(j);}     }
}function Init()
{var HeadFoot = 40;var LeftRight = 40;var HeadFootLeftSpace = 50;AddFirstPage();oPageStyleClass = FindStyleRule(".pagestyle");oLorStyleClass = FindStyleRule(".lorstyle");oHeaderStyleClass = FindStyleRule(".headerstyle");oFooterStyleClass = FindStyleRule(".footerstyle");oPageStyleClass.style.width =  printer.pageHeight/100 + "in";  oPageStyleClass.style.height = printer.pageWidth/100 + "in";oLorStyleClass.style.marginTop = HeadFoot/100 + "in";    oLorStyleClass.style.marginLeft = LeftRight/100 + "in";oLorStyleClass.style.marginBottom = HeadFoot/100 + "in";    oLorStyleClass.style.marginRight = LeftRight/100 + "in"; //alert("init printer margintop = "+printer.marginTop+", marginLeft= "+printer.marginLeft);//alert("init printer marginright = "+printer.marginRight+", marginBottom= "+printer.marginBottom);//alert("init printer pageWidth = "+printer.pageWidth+", pageHeight= "+printer.pageHeight);//alert("init printer unprintableBottom = "+printer.unprintableBottom+", unprintableTop= "+printer.unprintableTop);//alert("init printer unprintableLeft = "+printer.unprintableLeft+", unprintableRight= "+printer.unprintableRight);oLorStyleClass.style.width = ( printer.pageHeight - (HeadFoot + HeadFoot))/100 + "in";oLorStyleClass.style.height = (printer.pageWidth - (LeftRight + LeftRight))/100 + "in";oHeaderStyleClass.style.left = (printer.pageHeight/2-HeadFootLeftSpace)/100 + "in";oHeaderStyleClass.style.top = printer.unprintableTop/100 + "in";oHeaderStyleClass.style.width = oLorStyleClass.style.width;oFooterStyleClass.style.left = (printer.pageHeight/2-HeadFootLeftSpace)/100 + "in";oFooterStyleClass.style.width = oLorStyleClass.style.width;//oFooterStyleClass.style.bottom = printer.unprintableBottom/100 + "in";//alert("init printer oFooterStyleClass.style.left = "+oFooterStyleClass.style.left+", oFooterStyleClass.style.bottom= "+oFooterStyleClass.style.bottom);printer.orientation = "landscape";
}function CheckPrint()
{switch (dialogArguments.__IE_PrintType){case "Prompt":if (printer.showPrintDialog()) PrintPrep();break;case "NoPrompt":PrintPrep();break;case "Preview":default:break;}
}function AddFirstPage()
{newHTML  = "<IE:DEVICERECT ID='page1' MEDIA='print' CLASS='pagestyle'>";newHTML += "<IE:LAYOUTRECT ID='layoutrect1' CONTENTSRC='document' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect2' CLASS='lorstyle'/>";newHTML += "</IE:DEVICERECT>";pagecontainer.insertAdjacentHTML("afterBegin", newHTML);layoutrectTmp = layoutrect1;headfoot.textHead = "                               www.supcon.com";//printer.header;headfoot.textFoot = "PAGE " + 1;//printer.footer;headfoot.url = dialogArguments.__IE_BrowseDocument.URL;headfoot.title = dialogArguments.__IE_BrowseDocument.title;headfoot.page = 1;AddHeaderAndFooterToPage(1);iNextPageToCreate = 2;
}function OnRectComplete()
{if (event.contentOverflow == true){AddNewPage();}else{//headfoot.pageTotal = document.all.tags("DEVICERECT").length;//alert("OnRectComplete enter iNextPage = " + iNextPageToCreate + "headfoot.pageTotal = " + headfoot.pageTotal);//for (i=1; i<=headfoot.pageTotal; i++)//{//  alert("OnRectComplete enter1 i = " + i );// AddPageTotalToPages(i);//}setTimeout("CheckPrint();",100);}
}function AddNewPage()
{document.all("layoutrect"+(iNextPageToCreate-1)).onlayoutcomplete = null;headfoot.page = iNextPageToCreate;newHTML = "<IE:DEVICERECT ID='page"+iNextPageToCreate+"' MEDIA='print' CLASS='pagestyle'>";newHTML += "<IE:LAYOUTRECT ID='layoutrect" + iNextPageToCreate + "' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect"+(iNextPageToCreate+1)+"' CLASS='lorstyle' />";newHTML += "</IE:DEVICERECT>"pagecontainer.insertAdjacentHTML("beforeEnd",newHTML);eval("layoutrectTmp = layoutrect"+iNextPageToCreate);AddHeaderAndFooterToPage(iNextPageToCreate);iNextPageToCreate++;
}function AddHeaderAndFooterToPage(pageNum)
{headfoot.textFoot = "PAGE " + iNextPageToCreate;newHeader = "<DIV CLASS='headerstyle'>" + headfoot.htmlHead + "</DIV>";newFooter = "<DIV CLASS='footerstyle'>" +headfoot.htmlFoot + "</DIV>";document.all("page" + pageNum).insertAdjacentHTML("afterBegin", newHeader); document.all("page" + pageNum).insertAdjacentHTML("beforeEnd", newFooter);
}function AddPageTotalToPages()
{oSpanCollection = document.all.tags("span");alert("AddPageTotalToPages enter nextpage= " + iNextPageToCreate + ", oSpanCollection.length = " + oSpanCollection.length);for (i = 0; i < oSpanCollection.length; i++){alert("AddPageTotalToPages enter1 i= "+i);if (oSpanCollection[i].className == "hfPageTotal")oSpanCollection[i].innerText = headfoot.pageTotal;}
}function PrintPrep()
{if (layoutrectTmp.contentDocument.readyState == "complete"){//这一块会被调用当提示用户后再打印的时候,因为打印对话框会给出时间,//让文档内容被完全装入。PrintNow();}else{// This block will usually be called when printing w/o user prompt// and sets an event handler that listens for the loading of the content// document before printing. Sometimes, however, the content document// will be loaded in time for the previous block to execute      layoutrectTmp.contentDocument.onreadystatechange = PrintWhenContentDocComplete;}
}function PrintWhenContentDocComplete()
{if (layoutrectTmp.contentDocument.readyState == "complete"){layoutrectTmp.contentDocument.onreadystatechange = null;PrintNow();}
}function PrintNow()
{//alert("Print Now");var startPage;var endPage;var oDeviceRectCollection = document.all.tags("DEVICERECT");if ( dialogArguments.__IE_PrintType == "NoPrompt" || printer.selectedPages == false){startPage = 1;endPage = oDeviceRectCollection.length;}else if ( printer.currentPage == true ){// Determine current page, if necessary.// Normally, the print current page option is disabled because a print// template has no way of determining which section of a document is displayed// in the browser and cannot calculate a current page. In print preview,// a print template can enable the print current page option because the// template can determine which page is currently showing in the print// preview dialog. The print preview window would need a user interface// with a print button so that the user could print from the print preview// window. See template7.htm for an example showing how to add a user// interface to a print preview template}else{startPage = printer.pageFrom;endPage = printer.pageTo;if ( startPage > endPage ){alert("Error:start page greater than end page");return;}if ( startPage > oDeviceRectCollection.length){alert("Error: start page greater than number of pages in print job");return;}if ( endpage > oDeviceRectCollecttion.length){alert("warning: end page greater than number of pages in print job, Continue Print Job");endPage = oDeviceRectCollection.length;}}printer.startDoc("Printing from T_landscape.htm");for (i=startPage-1;i<endPage;i++){try{printer.printPage(oDeviceRectCollection[i]);}catch(e){}}printer.stopDoc();
}
</SCRIPT></HEAD><BODY ONLOAD="Init()"><IE:TEMPLATEPRINTER ID="printer"/>
<IE:HEADERFOOTER ID="headfoot"/><DIV ID="pagecontainer">
<!--动态生成网页在这里 -->
</DIV></BODY>
</HTML>

基于IE内核的一个WEB打印实现方案相关推荐

  1. 2、基于wsgiref模块DIY一个web框架

    一 web框架 Web框架(Web framework)是一种开发框架,用来支持动态网站.网络应用和网络服务的开发.这大多数的web框架提供了一套开发和部署网站的方式,也为web行为提供了一套通用的方 ...

  2. 一种基于浏览器的自动小票机打印实现方案(js版)

    1.使用场景 用户在浏览器做了某项操作后,自动打印小票. 2.测试方式 2.1 JavaScript实现 尝试了很多办法,最终都会出现一个弹出框,让用户选择打印机.不符合我们需求. 2.2 lodop ...

  3. 关于“让我们基于Node.js创建一个Web应用:记事本(三)”

    在我当初学习这个系列的教程的时候,这一部分让我驻足了很久.其他内容都很顺利,能够自己编写出代码并在Node.js中跑出结果,或者在浏览器中得到想要的内容,唯有最后那一部分测试,要使用Exporesso ...

  4. [转]web打印实现方案 Lodop6.034 使用方法总结

    本文转自:https://www.cnblogs.com/tiger8000/archive/2011/09/19/2181365.html 官文下载: http://mtsoftware.v053. ...

  5. web打印实现方案 Lodop 以及条形码

    转 http://www.cnblogs.com/tiger8000/archive/2011/09/19/2181365.html 官文下载: http://mtsoftware.v053.goka ...

  6. web打印实现方案 Lodop6.034 使用方法总结

    官文下载: http://mtsoftware.v053.gokao.net/download.html 本地 Lodop6.034 版本下载:/Files/tiger8000/Lodop6.034. ...

  7. 一种基于浏览器的自动小票机打印实现方案

    1.使用场景 用户在浏览器做了某项操作后,自动打印小票. 2.测试方式 2.1 JavaScript实现尝试了很多办法,最终都会出现一个弹出框,让用户选择打印机.不符合我们需求.2.2 lodop功能 ...

  8. 基于nginx搭建直播,web播放视频方案

    1 流媒体服务器nginx搭建: #nginx源码     git clone https://github.com/nginx/nginx.git #nginx的rtmp模块源码     git c ...

  9. oracle ERP凭证打印样式,Oracle ERP二次开发中特色鲜明的Web打印模式设计与实现

    0背景随着宽带网络的普及和推广,基于浏览器的B/S结构的应用程序越来越多,客户端免安装.免配置.免维护.免升级;服务器端则采用多层模式,将表示层.商业逻辑层和数据层分开,极大的提髙了开发的效率和数据的 ...

最新文章

  1. LLVM与Clang编译图例
  2. 独家 | Python中的SOLID原则(附链接)
  3. 2016012017+小学四则运算练习软件项目报告
  4. Windows使用免费版Kiwisyslog搭建日志服务器
  5. 路由器漏洞:***展示如何攻陷百万台
  6. Intel Edision开发工具简介
  7. 转:org.apache.maven.archiver.MavenArchiver.getManifest错误
  8. 光纤收发器的选购原则介绍
  9. 机器学习的几种方法(knn,逻辑回归,SVM,决策树,随机森林,极限随机树,集成学习,Adaboost,GBDT)
  10. OpenSessionInViewFilter源码分析
  11. 4月份全球新注册39.2万辆电动汽车 榜首并非Model 3
  12. CodeForces 257B Playing Cubes :两人轮流向已有序列后面放红蓝木块,一人想使相邻颜色相同多一人想不想同颜色多,最后得分? :博弈+思维...
  13. mysql之 MHA的binlog-server 创建
  14. 计算机vf知识点总结,计算机等级考试二级VF常用函数总结
  15. Java基础(静态static)
  16. 河海大学计算机考研资料汇总
  17. 倒闭跑路的P2P网贷平台的特征
  18. 使用OpenCV调整图像的亮度和对比度
  19. html5 video cache,手机里的videoCache文件夹什么意思?可以删除吗?
  20. 同城容灾、异地容灾、 双活 数据中心、 两地三中心的区别

热门文章

  1. gee微端服务器系统设置,geem2微端服务器设置
  2. K8S-5--云原生基础/k8s基础及组件/二进制部署k8s集群
  3. c#:使用面向对象的编程思想和窗体实现计算器(二目运算)
  4. 远程启动IDEA时报错Startup Error: Unable to detect graphics environment
  5. 如何成为一名优秀员工
  6. JQuery图片抽奖
  7. 使用PHP的http请求客户端guzzle如何添加请求头
  8. php guzzlehttp,PHP HTTP客户端-Guzzle原理解析
  9. android恢复出厂设置流程
  10. Android UI