尝试使用 IViewObject::Draw() 将 Web 浏览器控件呈现到 HDC 中,但在 IE8 中失败,但在 IE11 中成功
Trying to render web browser control using IViewObject::Draw() into HDC fails with IE8 but succeeds with IE11
我有一个 MFC 对话框 window,我在其中添加了一个 WebBrowser control(封装了 Internet Explorer
引擎。)
以下代码的目标是将所述 web browser control
的内容呈现为 device context
以便稍后用于打印:
//MFC code, error checks are omitted for brevity
//'m_browser' = is a web browser control of type `CExplorer1`
IDispatch* pHtmlDoc = m_browser.get_Document();
CComPtr<IHTMLDocument2> pHtmlDocument2;
pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pHtmlDocument2));
//Get IViewObject2 for the entire document that we will use to render into a DC
CComPtr<IViewObject2> pViewObject;
pHtmlDocument2->QueryInterface(IID_IViewObject2, (void **)&pViewObject));
CComPtr<IHTMLElement> pBody;
pHtmlDocument2->get_body(&pBody));
CComPtr<IHTMLElement2> pBody2;
pBody->QueryInterface(IID_IHTMLElement2, (void **)&pBody2));
//Get default printer DC
CPrintDialog pd(TRUE, PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION);
pd.m_pd.Flags |= PD_RETURNDC | PD_RETURNDEFAULT;
pd.DoModal(); //corrected later
HDC hPrintDC = pd.CreatePrinterDC();
//Calc bitmap width based on printer DC specs
//Note that this width will be larger than
//the width of the WebControl window itself due to
//printer's much higher DPI setting...
int n_bitmapWidth = ::GetDeviceCaps(hPrintDC, HORZRES); //Use entire printable area
//Get full size of the document
long n_scrollWidth;
long n_scrollHeight;
pBody2->get_scrollWidth(&n_scrollWidth);
pBody2->get_scrollHeight(&n_scrollHeight);
//Calc proportional size of the bitmap in the DC to render
int nWidth = n_bitmapWidth;
int nHeight = n_bitmapWidth * n_scrollHeight / n_scrollWidth;
//Create memory DC to render into
HDC hDc = ::GetDC(hWnd);
HDC hCompDc = ::CreateCompatibleDC(hDC);
//I'm using a raw DIB section here as I'll need to access
//its bitmap bits directly later in my code...
BITMAPINFOHEADER infoHeader = {0};
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = nWidth;
infoHeader.biHeight = -nHeight;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
BITMAPINFO info;
info.bmiHeader = infoHeader;
//Create a bitmap as DIB section of size `nWidth` by `nHeight` pixels
BYTE* pMemory = 0;
HBITMAP hBitmap = ::CreateDIBSection(hDc, &info, DIB_RGB_COLORS, (void**)&pMemory, 0, 0);
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hCompDc, hBitmap);
RECT rcAll = {0, 0, nWidth, nHeight};
::FillRect(hCompDc, &rcAll, (HBRUSH)::GetStockObject(WHITE_BRUSH));
RECTL rectPrnt = {0, 0, nWidth, nHeight};
//Do the upscaling & render -- note that IE8 fails to render it here!!!!
pViewObject->Draw(DVASPECT_CONTENT, //DVASPECT_DOCPRINT
-1, NULL, NULL, NULL, hCompDc,
&rectPrnt,
NULL,
NULL, 0));
::SelectObject(hCompDc, hOldBmp);
//Now the bitmap in `hCompDc` contains the resulting pixels
//For debugging purposes, save it as .bmp file
BITMAPFILEHEADER fileHeader = {0};
fileHeader.bfType = 0x4d42;
fileHeader.bfSize = 0;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
CFile file(
L"path-to\test.bmp",
CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone);
file.Write((char*)&fileHeader, sizeof(fileHeader));
file.Write((char*)&infoHeader, sizeof(infoHeader));
int bytes = (((24 * nWidth + 31) & (~31)) / 8) * nHeight;
file.Write(pMemory, bytes);
//Clean up
::DeleteObject(hBitmap);
::DeleteDC(hCompDc);
::ReleaseDC(hWnd, hDc);
::DeleteDC(hPrintDC);
如果我在我的开发机器上安装了最新的 IE11
,此代码可以正常工作。但是,例如,如果某人在他们的 Windows 7 上安装了 IE8
,则 IViewObject::Draw 方法将只呈现文档的一小部分(等于 Web 浏览器控件本身的大小) .)
描述它的最好方法是用例子来说明它:
安装了 IE11
的正常呈现测试页:
这是安装 IE8
后发生的情况:
有人知道我在这里做错了什么 IE8
不喜欢吗?
EDIT1: 用 WinDbg
深入研究了 IViewObject::Draw
函数,然后找到了它的源代码。这是从 CServer::Draw()
.
内部调用的 CServer::Draw() that is IViewObject::Draw
, and then CDoc::Draw()
当 WebBrowser
控件小于页面大小时,我已将您的代码 运行 用于 IE11。它呈现页面的一部分等于控件的大小。不知道为什么你说 IE8 和 IE11 有什么不同。
好像一般的全屏截图都是先调整WebBrowser
大小再截图,像这样:
const long oldH = m_browser.get_Height();
const long oldW = m_browser.get_Width();
m_browser.put_Height(n_scrollHeight);
m_browser.put_Width(n_scrollWidth);
//Do the upscaling & render -- note that IE8 fails to render it here!!!!
pViewObject->Draw(DVASPECT_CONTENT, //DVASPECT_DOCPRINT
-1, NULL, NULL, NULL, hCompDc,
&rectPrnt,
NULL,
NULL, 0);
m_browser.put_Height(oldH);
m_browser.put_Width(oldW);
这似乎效果很好,即使在像您当前正在阅读的页面这样的大页面上也是如此(我截取了 1920x8477 的屏幕截图)。它适用于我的 IE11 和 IE8 virtual machine
它有重置滚动条的副作用,但这可以解决,例如使用 WebBrowser 的不可见副本进行屏幕截图。
PS:至少通过提供可以编译的示例代码,您本可以做得更好;)
首先,感谢您提出有趣的问题。虽然不是很实用——今天使用 IE8 的人并不多——但解决起来并不是那么简单。我将描述问题所在,并提供一个您可以改进的简单但可行的解决方案。
在我进入 IE8 解决方案之前,有两点:
如果遇到大型文档,window 调整大小以适应滚动大小的解决方案并不稳定。如果您不知道会发生什么,您也需要为以后的浏览器重构解决方案,以避免依赖调整大小 window 来滚动大小。
为什么要携带巨型位图?图元文件等可能更适合。在这种分辨率下,任何足够大的页面都会因简单的 DIB 创建而耗尽 PC 上的内存。 Google 提供的示例中的页面呈现为 100Mb 位图文件,而完成光栅化的 emf 占用的空间不到 1Mb。
虽然我不知道您的项目的确切要求和限制,但我 99% 确定绘制巨大的 DIB 不是最佳解决方案。即使是 EMF,虽然更好,但也不是最好的。例如,如果您需要添加签名然后打印,则有更好的方法来处理此问题。当然,这只是旁注,与问题本身无关。
IE8 渲染问题
在 IE8 渲染器中有一个错误。 Draw() 将在实际显示区域的像素尺寸处进行裁剪(您看到的可见矩形是渲染上下文比例中的原始显示区域)。
所以,如果你有一个比实际尺寸大的缩放目标,在缩放时,无论如何它都会被裁剪到缩放像素的大小(所以它的内容比原始矩形少得多)。
如果它不能在正版 IE8 上剪辑,则说明系统中存在更高版本的 IE 的剩余部分,或者存在其他非临时设置、系统更新等。
解决方法的可能性
好消息是解决方法是可能的,坏消息是解决方法有点讨厌。
首先,仍然可以使用 IViewObject 进行变通。但是,因为涉及任意缩放并且可访问的源矩形非常小,所以这个解决方案具有一些我认为超出 SO 答案的复杂性。所以我不会div进入这条路。
相反,我们可以通过另一个现已过时的 API:IHTMLElementRender 进行渲染。它允许使用 DrawToDC 将页面渲染到任意上下文中。不幸的是,它并不像看起来那么简单,而且不仅仅是提供设备上下文。
首先,裁剪也有类似的bug。它可以更容易地处理,因为剪裁发生在超出屏幕尺寸的大值处。其次,当使用设备上下文转换时,它要么不起作用,要么会弄乱渲染的 html,因此您实际上不能依赖缩放或转换。这两个问题都需要相对复杂的处理,并且彼此复杂化。
解决方案
我将描述并提供非最佳但适用于最简单页面解决方案的示例代码。通常,可以实现完美且更有效的解决方案,但这同样超出了答案的范围。显然,它仅适用于 IE8,因此您需要检查浏览器版本并针对 IE8 与 IE9 或更高版本执行不同的处理程序,但您也可以采纳一些想法来改进其他浏览器的渲染。
这里有两个相互关联的解决方法。
扩大规模
首先,如果我们不能转换,我们如何将矢量内容升级到打印机质量?这里的解决方法是渲染到与打印机 dc 兼容的上下文。将会发生的是内容将以打印机 DPI 呈现。请注意,它不会完全适合打印机宽度,它会缩放到 printerDPI/screenDPI.
稍后,在光栅化时,我们缩小以适应打印机宽度。我们最初渲染为 EMF,因此没有太大的质量损失(无论如何都会发生在打印机本身上)。如果您需要更高的质量(我对此表示怀疑),有两种可能性 - 修改解决方案以针对目标宽度进行渲染(这不是微不足道的)或使用生成的 emf 而不是位图并让打印机缩小尺寸。
另一个注意事项是您目前只使用打印机宽度,但可能有不可打印的边距,您需要从打印机查询并考虑它们。因此,即使您以精确的打印机尺寸提供位图,它也可能会被打印机重新缩放。但同样,我怀疑这种分辨率差异是否会对您的项目产生任何影响。
剪辑
第二个要克服的是裁剪。为了克服这个限制,我们以小块的形式渲染内容,这样它们就不会被渲染器裁剪掉。渲染一个块后,我们更改文档的滚动位置并将下一个块渲染到目标 DC 中的适当位置。这可以优化以使用更大的块,例如最近的 DPI 倍数为 1024(使用 window 调整大小),但我没有实现它(这只是速度优化)。如果您不进行此优化,请确保最小浏览器 window 大小不会太小。
请注意,在任意分数比例上执行此滚动将是一个近似值,在一般情况下实现起来并不那么简单。但是使用常规打印机和屏幕,我们可以按 DPI 的倍数制作整数块步骤,例如如果屏幕是 96 DPI 而打印机是 600DPI,我们在每个上下文中以 96 和 600 的相同倍数进行步骤,一切都会简单得多。但是,在处理完所有整块之后,顶部或底部的剩余部分不会以 DPI 倍增,因此我们无法轻松地滚动到那里。
一般来说,我们可以估计打印机中的滚动位置 space 并希望最终块之间没有错位。我所做的是在页面右下角附加一个绝对定位的 div 和块大小。
请注意,这可能会干扰某些页面并更改布局(简单报告可能不会出现这种情况)。如果这是一个问题,您将需要在循环后添加余数处理而不是添加元素。在这种情况下,最简单的解决方案仍然是用 div 填充,但不是用完整的块大小填充,而只是使内容宽度成为屏幕 DPI 的倍数。
更简单的想法,正如我后来意识到的那样,只是将 window 的大小调整为最接近的 DPI 倍数,并将此 window 大小作为块大小。您可以尝试使用 div,这将简化代码并修复可能干扰注入的 div.
的页面
密码
这只是一个示例。
没有错误处理。您需要为每个 COM 和 API 调用等添加检查。
没有代码风格,只是又快又脏。
不确定是否已根据需要释放所有获取的资源,请进行检查
您必须禁用浏览器控件上的页面边框才能使此示例正常工作(如果您需要浏览器周围的边框,只需单独呈现它们,内置的是反正不一致)。在 IE8 上这不是那么微不足道,但这里或网上有很多答案。无论如何,我将把这个补丁包含在示例解决方案项目中。您也可以使用边框进行渲染并排除它们,但这对于具有简单解决方案的问题来说是不必要的复杂化。
完整的解决方案项目可以在 this link 找到,我 post 这里只提供相关代码。
下面的代码呈现页面并保存在 c:\temp\test.emf + c:\temp\test.bmp
void convertEmfToBitmap(const RECT& fitRect, HDC hTargetDC, HENHMETAFILE hMetafile, LPCTSTR fileName);
CComPtr<IHTMLDOMNode> appendPadElement(IHTMLDocument2* pDoc, IHTMLElement* pBody, long left, long top, long width, long height);
void removeElement(IHTMLElement* pParent, IHTMLDOMNode* pChild);
void CMFCApplication1Dlg::OnBnClickedButton2()
{
COleVariant varNull;
COleVariant varUrl = L"http://www.google.com/search?q=ie+8+must+die";
m_browser.Navigate2(varUrl, varNull, varNull, varNull, varNull);
}
void CMFCApplication1Dlg::OnBnClickedButton1()
{
//get html interfaces
IDispatch* pHtmlDoc = m_browser.get_Document();
CComPtr<IHTMLDocument2> pHtmlDocument2;
pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pHtmlDocument2);
CComPtr<IHTMLElement> pBody;
pHtmlDocument2->get_body(&pBody);
CComPtr<IHTMLElement2> pBody2;
pBody->QueryInterface(IID_IHTMLElement2, (void**)&pBody2);
CComPtr<IHTMLBodyElement> pBodyElement;
pBody->QueryInterface(IID_IHTMLBodyElement, (void**)&pBodyElement);
CComPtr<IHTMLElement> pHtml;
pBody->get_parentElement(&pHtml);
CComPtr<IHTMLElement2> pHtml2;
pHtml->QueryInterface(IID_IHTMLElement2, (void**)&pHtml2);
CComPtr<IHTMLStyle> pHtmlStyle;
pHtml->get_style(&pHtmlStyle);
CComPtr<IHTMLStyle> pBodyStyle;
pBody->get_style(&pBodyStyle);
//get screen info
HDC hWndDc = ::GetDC(m_hWnd);
const int wndLogPx = GetDeviceCaps(hWndDc, LOGPIXELSX);
const int wndLogPy = GetDeviceCaps(hWndDc, LOGPIXELSY);
//keep current values
SIZE keptBrowserSize = { m_browser.get_Width(), m_browser.get_Height() };
SIZE keptScrollPos;
//set reasonable viewport size
//m_browser.put_Width(docSize.cx);
//m_browser.put_Height(docSize.cy*2);
pHtml2->get_scrollLeft(&keptScrollPos.cx);
pHtml2->get_scrollTop(&keptScrollPos.cy);
COleVariant keptOverflow;
pBodyStyle->get_overflow(&keptOverflow.bstrVal);
//setup style and hide scroll bars
pHtmlStyle->put_border(L"0px;");
pHtmlStyle->put_overflow(L"hidden");
pBodyStyle->put_border(L"0px;");
pBodyStyle->put_overflow(L"hidden");
//get document size and visible area in screen pixels
SIZE docSize;
pBody2->get_scrollWidth(&docSize.cx);
pBody2->get_scrollHeight(&docSize.cy);
RECT clientRect = { 0 };
pHtml2->get_clientWidth(&clientRect.right);
pHtml2->get_clientHeight(&clientRect.bottom);
//derive chunk size
const SIZE clientChunkSize = {
clientRect.right - clientRect.right % wndLogPx,
clientRect.bottom - clientRect.bottom % wndLogPy };
//pad with absolutely positioned element to have enough scroll area for all chunks
//alternatively, browser can be resized to chunk multiplies (simplest), to DPI multiplies (more work).
//This pad also can be made smaller, to modulus DPI, but then need more work in the loops below
CComPtr<IHTMLDOMNode> pPadNode =
appendPadElement(pHtmlDocument2, pBody, docSize.cx, docSize.cy, clientChunkSize.cx, clientChunkSize.cy);
//get printer info
CPrintDialog pd(TRUE, PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION);
pd.m_pd.Flags |= PD_RETURNDC | PD_RETURNDEFAULT;
pd.DoModal();
HDC hPrintDC = pd.CreatePrinterDC();
const int printLogPx = GetDeviceCaps(hPrintDC, LOGPIXELSX);
const int printLogPy = GetDeviceCaps(hPrintDC, LOGPIXELSY);
const int printHorRes = ::GetDeviceCaps(hPrintDC, HORZRES);
const SIZE printChunkSize = { printLogPx * clientChunkSize.cx / wndLogPx, printLogPy * clientChunkSize.cy / wndLogPy };
//browser total unscaled print area in printer pixel space
const RECT printRectPx = { 0, 0, docSize.cx* printLogPx / wndLogPx, docSize.cy*printLogPy / wndLogPy };
//unscaled target EMF size in 0.01 mm with printer resolution
const RECT outRect001Mm = { 0, 0, 2540 * docSize.cx / wndLogPx, 2540 * docSize.cy / wndLogPy };
HDC hMetaDC = CreateEnhMetaFile(hPrintDC, L"c:\temp\test.emf", &outRect001Mm, NULL);
::FillRect(hMetaDC, &printRectPx, (HBRUSH)::GetStockObject(BLACK_BRUSH));
//unscaled chunk EMF size in pixels with printer resolution
const RECT chunkRectPx = { 0, 0, printChunkSize.cx, printChunkSize.cy };
//unscaled chunk EMF size in 0.01 mm with printer resolution
const RECT chunkRect001Mm = { 0, 0, 2540 * clientChunkSize.cx / wndLogPx, 2540 * clientChunkSize.cy / wndLogPy };
////////
//render page content to metafile by small chunks
//get renderer interface
CComPtr<IHTMLElementRender> pRender;
pHtml->QueryInterface(IID_IHTMLElementRender, (void**)&pRender);
COleVariant printName = L"EMF";
pRender->SetDocumentPrinter(printName.bstrVal, hMetaDC);
//current positions and target area
RECT chunkDestRectPx = { 0, 0, printChunkSize.cx, printChunkSize.cy };
POINT clientPos = { 0, 0 };
POINT printPos = { 0, 0 };
//loop over chunks left to right top to bottom until scroll area is completely covered
const SIZE lastScroll = { docSize.cx, docSize.cy};
while (clientPos.y < lastScroll.cy)
{
while (clientPos.x < lastScroll.cx)
{
//update horizontal scroll position and set target area
pHtml2->put_scrollLeft(clientPos.x);
chunkDestRectPx.left = printPos.x;
chunkDestRectPx.right = printPos.x + printChunkSize.cx;
//render to new emf, can be optimized to avoid recreation
HDC hChunkDC = CreateEnhMetaFile(hPrintDC, NULL, &chunkRect001Mm, NULL);
::FillRect(hChunkDC, &chunkRectPx, (HBRUSH)::GetStockObject(WHITE_BRUSH));
pRender->DrawToDC(hChunkDC);
HENHMETAFILE hChunkMetafile = CloseEnhMetaFile(hChunkDC);
//copy chunk to the main metafile
PlayEnhMetaFile(hMetaDC, hChunkMetafile, &chunkDestRectPx);
DeleteEnhMetaFile(hChunkMetafile);
//update horizontal positions
clientPos.x += clientChunkSize.cx;
printPos.x += printChunkSize.cx;
}
//reset horizontal positions
clientPos.x = 0;
printPos.x = 0;
//update vertical positions
clientPos.y += clientChunkSize.cy;
printPos.y += printChunkSize.cy;
pHtml2->put_scrollTop(clientPos.y);
chunkDestRectPx.top = printPos.y;
chunkDestRectPx.bottom = printPos.y + printChunkSize.cy;
}
//restore changed values on browser
//if for large pages on slow PC you get content scrolling during rendering and it is a problem,
//you can either hide the browser and show "working" or place on top first chunk content
pBodyStyle->put_overflow(keptOverflow.bstrVal);
pHtml2->put_scrollLeft(keptScrollPos.cx);
pHtml2->put_scrollTop(keptScrollPos.cy);
m_browser.put_Width(keptBrowserSize.cx);
m_browser.put_Height(keptBrowserSize.cy);
removeElement(pBody, pPadNode);
//draw to bitmap and close metafile
HENHMETAFILE hMetafile = CloseEnhMetaFile(hMetaDC);
RECT fitRect = { 0, 0, printHorRes, docSize.cy * printHorRes / docSize.cx };
convertEmfToBitmap(fitRect, hWndDc, hMetafile, L"c:\temp\test.bmp");
DeleteEnhMetaFile(hMetafile);
//cleanup - probably more here
::ReleaseDC(m_hWnd, hWndDc);
::DeleteDC(hPrintDC);
//{
// std::stringstream ss;
// ss << "====" << docSize.cx << "x" << docSize.cy << " -> " << fitRect.right << "x" << fitRect.bottom << "" << "\n";
// OutputDebugStringA(ss.str().c_str());
//}
}
///////////////
////some util
void convertEmfToBitmap(const RECT& fitRect, HDC hTargetDC, HENHMETAFILE hMetafile, LPCTSTR fileName)
{
//Create memory DC to render into
HDC hCompDc = ::CreateCompatibleDC(hTargetDC);
//NOTE this
BITMAPINFOHEADER infoHeader = { 0 };
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = fitRect.right;
infoHeader.biHeight = -fitRect.bottom;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
BITMAPINFO info;
info.bmiHeader = infoHeader;
//create bitmap
BYTE* pMemory = 0;
HBITMAP hBitmap = ::CreateDIBSection(hCompDc, &info, DIB_RGB_COLORS, (void**)&pMemory, 0, 0);
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hCompDc, hBitmap);
PlayEnhMetaFile(hCompDc, hMetafile, &fitRect);
BITMAPFILEHEADER fileHeader = { 0 };
fileHeader.bfType = 0x4d42;
fileHeader.bfSize = 0;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
CFile file(
fileName,
CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone);
file.Write((char*)&fileHeader, sizeof(fileHeader));
file.Write((char*)&infoHeader, sizeof(infoHeader));
int bytes = (((24 * infoHeader.biWidth + 31) & (~31)) / 8) * abs(infoHeader.biHeight);
file.Write(pMemory, bytes);
::SelectObject(hCompDc, hOldBmp);
//Clean up
if (hBitmap)
::DeleteObject(hBitmap);
::DeleteDC(hCompDc);
}
CComPtr<IHTMLDOMNode> appendPadElement(IHTMLDocument2* pDoc, IHTMLElement* pBody, long left, long top, long width, long height)
{
CComPtr<IHTMLElement> pPadElement;
pDoc->createElement(L"DIV", &pPadElement);
CComPtr<IHTMLStyle> pPadStyle;
pPadElement->get_style(&pPadStyle);
CComPtr<IHTMLStyle2> pPadStyle2;
pPadStyle->QueryInterface(IID_IHTMLStyle2, (void**)&pPadStyle2);
pPadStyle2->put_position(L"absolute");
CComVariant value = width;
pPadStyle->put_width(value);
value = height;
pPadStyle->put_height(value);
pPadStyle->put_posLeft((float)left);
pPadStyle->put_posTop((float)top);
CComPtr<IHTMLDOMNode> pPadNode;
pPadElement->QueryInterface(IID_IHTMLDOMNode, (void**)&pPadNode);
CComPtr<IHTMLDOMNode> pBodyNode;
pBody->QueryInterface(IID_IHTMLDOMNode, (void **)&pBodyNode);
pBodyNode->appendChild(pPadNode, NULL);
return pPadNode;
}
void removeElement(IHTMLElement* pParent, IHTMLDOMNode* pChild)
{
CComPtr<IHTMLDOMNode> pNode;
pParent->QueryInterface(IID_IHTMLDOMNode, (void **)&pNode);
pNode->removeChild(pChild, NULL);
}
示例页面输出 (4958x7656)
我有一个 MFC 对话框 window,我在其中添加了一个 WebBrowser control(封装了 Internet Explorer
引擎。)
以下代码的目标是将所述 web browser control
的内容呈现为 device context
以便稍后用于打印:
//MFC code, error checks are omitted for brevity
//'m_browser' = is a web browser control of type `CExplorer1`
IDispatch* pHtmlDoc = m_browser.get_Document();
CComPtr<IHTMLDocument2> pHtmlDocument2;
pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pHtmlDocument2));
//Get IViewObject2 for the entire document that we will use to render into a DC
CComPtr<IViewObject2> pViewObject;
pHtmlDocument2->QueryInterface(IID_IViewObject2, (void **)&pViewObject));
CComPtr<IHTMLElement> pBody;
pHtmlDocument2->get_body(&pBody));
CComPtr<IHTMLElement2> pBody2;
pBody->QueryInterface(IID_IHTMLElement2, (void **)&pBody2));
//Get default printer DC
CPrintDialog pd(TRUE, PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION);
pd.m_pd.Flags |= PD_RETURNDC | PD_RETURNDEFAULT;
pd.DoModal(); //corrected later
HDC hPrintDC = pd.CreatePrinterDC();
//Calc bitmap width based on printer DC specs
//Note that this width will be larger than
//the width of the WebControl window itself due to
//printer's much higher DPI setting...
int n_bitmapWidth = ::GetDeviceCaps(hPrintDC, HORZRES); //Use entire printable area
//Get full size of the document
long n_scrollWidth;
long n_scrollHeight;
pBody2->get_scrollWidth(&n_scrollWidth);
pBody2->get_scrollHeight(&n_scrollHeight);
//Calc proportional size of the bitmap in the DC to render
int nWidth = n_bitmapWidth;
int nHeight = n_bitmapWidth * n_scrollHeight / n_scrollWidth;
//Create memory DC to render into
HDC hDc = ::GetDC(hWnd);
HDC hCompDc = ::CreateCompatibleDC(hDC);
//I'm using a raw DIB section here as I'll need to access
//its bitmap bits directly later in my code...
BITMAPINFOHEADER infoHeader = {0};
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = nWidth;
infoHeader.biHeight = -nHeight;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
BITMAPINFO info;
info.bmiHeader = infoHeader;
//Create a bitmap as DIB section of size `nWidth` by `nHeight` pixels
BYTE* pMemory = 0;
HBITMAP hBitmap = ::CreateDIBSection(hDc, &info, DIB_RGB_COLORS, (void**)&pMemory, 0, 0);
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hCompDc, hBitmap);
RECT rcAll = {0, 0, nWidth, nHeight};
::FillRect(hCompDc, &rcAll, (HBRUSH)::GetStockObject(WHITE_BRUSH));
RECTL rectPrnt = {0, 0, nWidth, nHeight};
//Do the upscaling & render -- note that IE8 fails to render it here!!!!
pViewObject->Draw(DVASPECT_CONTENT, //DVASPECT_DOCPRINT
-1, NULL, NULL, NULL, hCompDc,
&rectPrnt,
NULL,
NULL, 0));
::SelectObject(hCompDc, hOldBmp);
//Now the bitmap in `hCompDc` contains the resulting pixels
//For debugging purposes, save it as .bmp file
BITMAPFILEHEADER fileHeader = {0};
fileHeader.bfType = 0x4d42;
fileHeader.bfSize = 0;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
CFile file(
L"path-to\test.bmp",
CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone);
file.Write((char*)&fileHeader, sizeof(fileHeader));
file.Write((char*)&infoHeader, sizeof(infoHeader));
int bytes = (((24 * nWidth + 31) & (~31)) / 8) * nHeight;
file.Write(pMemory, bytes);
//Clean up
::DeleteObject(hBitmap);
::DeleteDC(hCompDc);
::ReleaseDC(hWnd, hDc);
::DeleteDC(hPrintDC);
如果我在我的开发机器上安装了最新的 IE11
,此代码可以正常工作。但是,例如,如果某人在他们的 Windows 7 上安装了 IE8
,则 IViewObject::Draw 方法将只呈现文档的一小部分(等于 Web 浏览器控件本身的大小) .)
描述它的最好方法是用例子来说明它:
安装了 IE11
的正常呈现测试页:
这是安装 IE8
后发生的情况:
有人知道我在这里做错了什么 IE8
不喜欢吗?
EDIT1: 用 WinDbg
深入研究了 IViewObject::Draw
函数,然后找到了它的源代码。这是从 CServer::Draw()
.
IViewObject::Draw
, and then CDoc::Draw()
当 WebBrowser
控件小于页面大小时,我已将您的代码 运行 用于 IE11。它呈现页面的一部分等于控件的大小。不知道为什么你说 IE8 和 IE11 有什么不同。
好像一般的全屏截图都是先调整WebBrowser
大小再截图,像这样:
const long oldH = m_browser.get_Height();
const long oldW = m_browser.get_Width();
m_browser.put_Height(n_scrollHeight);
m_browser.put_Width(n_scrollWidth);
//Do the upscaling & render -- note that IE8 fails to render it here!!!!
pViewObject->Draw(DVASPECT_CONTENT, //DVASPECT_DOCPRINT
-1, NULL, NULL, NULL, hCompDc,
&rectPrnt,
NULL,
NULL, 0);
m_browser.put_Height(oldH);
m_browser.put_Width(oldW);
这似乎效果很好,即使在像您当前正在阅读的页面这样的大页面上也是如此(我截取了 1920x8477 的屏幕截图)。它适用于我的 IE11 和 IE8 virtual machine
它有重置滚动条的副作用,但这可以解决,例如使用 WebBrowser 的不可见副本进行屏幕截图。
PS:至少通过提供可以编译的示例代码,您本可以做得更好;)
首先,感谢您提出有趣的问题。虽然不是很实用——今天使用 IE8 的人并不多——但解决起来并不是那么简单。我将描述问题所在,并提供一个您可以改进的简单但可行的解决方案。
在我进入 IE8 解决方案之前,有两点:
如果遇到大型文档,window 调整大小以适应滚动大小的解决方案并不稳定。如果您不知道会发生什么,您也需要为以后的浏览器重构解决方案,以避免依赖调整大小 window 来滚动大小。
为什么要携带巨型位图?图元文件等可能更适合。在这种分辨率下,任何足够大的页面都会因简单的 DIB 创建而耗尽 PC 上的内存。 Google 提供的示例中的页面呈现为 100Mb 位图文件,而完成光栅化的 emf 占用的空间不到 1Mb。
虽然我不知道您的项目的确切要求和限制,但我 99% 确定绘制巨大的 DIB 不是最佳解决方案。即使是 EMF,虽然更好,但也不是最好的。例如,如果您需要添加签名然后打印,则有更好的方法来处理此问题。当然,这只是旁注,与问题本身无关。
IE8 渲染问题
在 IE8 渲染器中有一个错误。 Draw() 将在实际显示区域的像素尺寸处进行裁剪(您看到的可见矩形是渲染上下文比例中的原始显示区域)。
所以,如果你有一个比实际尺寸大的缩放目标,在缩放时,无论如何它都会被裁剪到缩放像素的大小(所以它的内容比原始矩形少得多)。
如果它不能在正版 IE8 上剪辑,则说明系统中存在更高版本的 IE 的剩余部分,或者存在其他非临时设置、系统更新等。
解决方法的可能性
好消息是解决方法是可能的,坏消息是解决方法有点讨厌。
首先,仍然可以使用 IViewObject 进行变通。但是,因为涉及任意缩放并且可访问的源矩形非常小,所以这个解决方案具有一些我认为超出 SO 答案的复杂性。所以我不会div进入这条路。
相反,我们可以通过另一个现已过时的 API:IHTMLElementRender 进行渲染。它允许使用 DrawToDC 将页面渲染到任意上下文中。不幸的是,它并不像看起来那么简单,而且不仅仅是提供设备上下文。
首先,裁剪也有类似的bug。它可以更容易地处理,因为剪裁发生在超出屏幕尺寸的大值处。其次,当使用设备上下文转换时,它要么不起作用,要么会弄乱渲染的 html,因此您实际上不能依赖缩放或转换。这两个问题都需要相对复杂的处理,并且彼此复杂化。
解决方案
我将描述并提供非最佳但适用于最简单页面解决方案的示例代码。通常,可以实现完美且更有效的解决方案,但这同样超出了答案的范围。显然,它仅适用于 IE8,因此您需要检查浏览器版本并针对 IE8 与 IE9 或更高版本执行不同的处理程序,但您也可以采纳一些想法来改进其他浏览器的渲染。
这里有两个相互关联的解决方法。
扩大规模
首先,如果我们不能转换,我们如何将矢量内容升级到打印机质量?这里的解决方法是渲染到与打印机 dc 兼容的上下文。将会发生的是内容将以打印机 DPI 呈现。请注意,它不会完全适合打印机宽度,它会缩放到 printerDPI/screenDPI.
稍后,在光栅化时,我们缩小以适应打印机宽度。我们最初渲染为 EMF,因此没有太大的质量损失(无论如何都会发生在打印机本身上)。如果您需要更高的质量(我对此表示怀疑),有两种可能性 - 修改解决方案以针对目标宽度进行渲染(这不是微不足道的)或使用生成的 emf 而不是位图并让打印机缩小尺寸。
另一个注意事项是您目前只使用打印机宽度,但可能有不可打印的边距,您需要从打印机查询并考虑它们。因此,即使您以精确的打印机尺寸提供位图,它也可能会被打印机重新缩放。但同样,我怀疑这种分辨率差异是否会对您的项目产生任何影响。
剪辑
第二个要克服的是裁剪。为了克服这个限制,我们以小块的形式渲染内容,这样它们就不会被渲染器裁剪掉。渲染一个块后,我们更改文档的滚动位置并将下一个块渲染到目标 DC 中的适当位置。这可以优化以使用更大的块,例如最近的 DPI 倍数为 1024(使用 window 调整大小),但我没有实现它(这只是速度优化)。如果您不进行此优化,请确保最小浏览器 window 大小不会太小。
请注意,在任意分数比例上执行此滚动将是一个近似值,在一般情况下实现起来并不那么简单。但是使用常规打印机和屏幕,我们可以按 DPI 的倍数制作整数块步骤,例如如果屏幕是 96 DPI 而打印机是 600DPI,我们在每个上下文中以 96 和 600 的相同倍数进行步骤,一切都会简单得多。但是,在处理完所有整块之后,顶部或底部的剩余部分不会以 DPI 倍增,因此我们无法轻松地滚动到那里。
一般来说,我们可以估计打印机中的滚动位置 space 并希望最终块之间没有错位。我所做的是在页面右下角附加一个绝对定位的 div 和块大小。
请注意,这可能会干扰某些页面并更改布局(简单报告可能不会出现这种情况)。如果这是一个问题,您将需要在循环后添加余数处理而不是添加元素。在这种情况下,最简单的解决方案仍然是用 div 填充,但不是用完整的块大小填充,而只是使内容宽度成为屏幕 DPI 的倍数。
更简单的想法,正如我后来意识到的那样,只是将 window 的大小调整为最接近的 DPI 倍数,并将此 window 大小作为块大小。您可以尝试使用 div,这将简化代码并修复可能干扰注入的 div.
的页面密码
这只是一个示例。
没有错误处理。您需要为每个 COM 和 API 调用等添加检查。
没有代码风格,只是又快又脏。
不确定是否已根据需要释放所有获取的资源,请进行检查
您必须禁用浏览器控件上的页面边框才能使此示例正常工作(如果您需要浏览器周围的边框,只需单独呈现它们,内置的是反正不一致)。在 IE8 上这不是那么微不足道,但这里或网上有很多答案。无论如何,我将把这个补丁包含在示例解决方案项目中。您也可以使用边框进行渲染并排除它们,但这对于具有简单解决方案的问题来说是不必要的复杂化。
完整的解决方案项目可以在 this link 找到,我 post 这里只提供相关代码。
下面的代码呈现页面并保存在 c:\temp\test.emf + c:\temp\test.bmp
void convertEmfToBitmap(const RECT& fitRect, HDC hTargetDC, HENHMETAFILE hMetafile, LPCTSTR fileName);
CComPtr<IHTMLDOMNode> appendPadElement(IHTMLDocument2* pDoc, IHTMLElement* pBody, long left, long top, long width, long height);
void removeElement(IHTMLElement* pParent, IHTMLDOMNode* pChild);
void CMFCApplication1Dlg::OnBnClickedButton2()
{
COleVariant varNull;
COleVariant varUrl = L"http://www.google.com/search?q=ie+8+must+die";
m_browser.Navigate2(varUrl, varNull, varNull, varNull, varNull);
}
void CMFCApplication1Dlg::OnBnClickedButton1()
{
//get html interfaces
IDispatch* pHtmlDoc = m_browser.get_Document();
CComPtr<IHTMLDocument2> pHtmlDocument2;
pHtmlDoc->QueryInterface(IID_IHTMLDocument2, (void**)&pHtmlDocument2);
CComPtr<IHTMLElement> pBody;
pHtmlDocument2->get_body(&pBody);
CComPtr<IHTMLElement2> pBody2;
pBody->QueryInterface(IID_IHTMLElement2, (void**)&pBody2);
CComPtr<IHTMLBodyElement> pBodyElement;
pBody->QueryInterface(IID_IHTMLBodyElement, (void**)&pBodyElement);
CComPtr<IHTMLElement> pHtml;
pBody->get_parentElement(&pHtml);
CComPtr<IHTMLElement2> pHtml2;
pHtml->QueryInterface(IID_IHTMLElement2, (void**)&pHtml2);
CComPtr<IHTMLStyle> pHtmlStyle;
pHtml->get_style(&pHtmlStyle);
CComPtr<IHTMLStyle> pBodyStyle;
pBody->get_style(&pBodyStyle);
//get screen info
HDC hWndDc = ::GetDC(m_hWnd);
const int wndLogPx = GetDeviceCaps(hWndDc, LOGPIXELSX);
const int wndLogPy = GetDeviceCaps(hWndDc, LOGPIXELSY);
//keep current values
SIZE keptBrowserSize = { m_browser.get_Width(), m_browser.get_Height() };
SIZE keptScrollPos;
//set reasonable viewport size
//m_browser.put_Width(docSize.cx);
//m_browser.put_Height(docSize.cy*2);
pHtml2->get_scrollLeft(&keptScrollPos.cx);
pHtml2->get_scrollTop(&keptScrollPos.cy);
COleVariant keptOverflow;
pBodyStyle->get_overflow(&keptOverflow.bstrVal);
//setup style and hide scroll bars
pHtmlStyle->put_border(L"0px;");
pHtmlStyle->put_overflow(L"hidden");
pBodyStyle->put_border(L"0px;");
pBodyStyle->put_overflow(L"hidden");
//get document size and visible area in screen pixels
SIZE docSize;
pBody2->get_scrollWidth(&docSize.cx);
pBody2->get_scrollHeight(&docSize.cy);
RECT clientRect = { 0 };
pHtml2->get_clientWidth(&clientRect.right);
pHtml2->get_clientHeight(&clientRect.bottom);
//derive chunk size
const SIZE clientChunkSize = {
clientRect.right - clientRect.right % wndLogPx,
clientRect.bottom - clientRect.bottom % wndLogPy };
//pad with absolutely positioned element to have enough scroll area for all chunks
//alternatively, browser can be resized to chunk multiplies (simplest), to DPI multiplies (more work).
//This pad also can be made smaller, to modulus DPI, but then need more work in the loops below
CComPtr<IHTMLDOMNode> pPadNode =
appendPadElement(pHtmlDocument2, pBody, docSize.cx, docSize.cy, clientChunkSize.cx, clientChunkSize.cy);
//get printer info
CPrintDialog pd(TRUE, PD_ALLPAGES | PD_USEDEVMODECOPIES | PD_NOPAGENUMS | PD_HIDEPRINTTOFILE | PD_NOSELECTION);
pd.m_pd.Flags |= PD_RETURNDC | PD_RETURNDEFAULT;
pd.DoModal();
HDC hPrintDC = pd.CreatePrinterDC();
const int printLogPx = GetDeviceCaps(hPrintDC, LOGPIXELSX);
const int printLogPy = GetDeviceCaps(hPrintDC, LOGPIXELSY);
const int printHorRes = ::GetDeviceCaps(hPrintDC, HORZRES);
const SIZE printChunkSize = { printLogPx * clientChunkSize.cx / wndLogPx, printLogPy * clientChunkSize.cy / wndLogPy };
//browser total unscaled print area in printer pixel space
const RECT printRectPx = { 0, 0, docSize.cx* printLogPx / wndLogPx, docSize.cy*printLogPy / wndLogPy };
//unscaled target EMF size in 0.01 mm with printer resolution
const RECT outRect001Mm = { 0, 0, 2540 * docSize.cx / wndLogPx, 2540 * docSize.cy / wndLogPy };
HDC hMetaDC = CreateEnhMetaFile(hPrintDC, L"c:\temp\test.emf", &outRect001Mm, NULL);
::FillRect(hMetaDC, &printRectPx, (HBRUSH)::GetStockObject(BLACK_BRUSH));
//unscaled chunk EMF size in pixels with printer resolution
const RECT chunkRectPx = { 0, 0, printChunkSize.cx, printChunkSize.cy };
//unscaled chunk EMF size in 0.01 mm with printer resolution
const RECT chunkRect001Mm = { 0, 0, 2540 * clientChunkSize.cx / wndLogPx, 2540 * clientChunkSize.cy / wndLogPy };
////////
//render page content to metafile by small chunks
//get renderer interface
CComPtr<IHTMLElementRender> pRender;
pHtml->QueryInterface(IID_IHTMLElementRender, (void**)&pRender);
COleVariant printName = L"EMF";
pRender->SetDocumentPrinter(printName.bstrVal, hMetaDC);
//current positions and target area
RECT chunkDestRectPx = { 0, 0, printChunkSize.cx, printChunkSize.cy };
POINT clientPos = { 0, 0 };
POINT printPos = { 0, 0 };
//loop over chunks left to right top to bottom until scroll area is completely covered
const SIZE lastScroll = { docSize.cx, docSize.cy};
while (clientPos.y < lastScroll.cy)
{
while (clientPos.x < lastScroll.cx)
{
//update horizontal scroll position and set target area
pHtml2->put_scrollLeft(clientPos.x);
chunkDestRectPx.left = printPos.x;
chunkDestRectPx.right = printPos.x + printChunkSize.cx;
//render to new emf, can be optimized to avoid recreation
HDC hChunkDC = CreateEnhMetaFile(hPrintDC, NULL, &chunkRect001Mm, NULL);
::FillRect(hChunkDC, &chunkRectPx, (HBRUSH)::GetStockObject(WHITE_BRUSH));
pRender->DrawToDC(hChunkDC);
HENHMETAFILE hChunkMetafile = CloseEnhMetaFile(hChunkDC);
//copy chunk to the main metafile
PlayEnhMetaFile(hMetaDC, hChunkMetafile, &chunkDestRectPx);
DeleteEnhMetaFile(hChunkMetafile);
//update horizontal positions
clientPos.x += clientChunkSize.cx;
printPos.x += printChunkSize.cx;
}
//reset horizontal positions
clientPos.x = 0;
printPos.x = 0;
//update vertical positions
clientPos.y += clientChunkSize.cy;
printPos.y += printChunkSize.cy;
pHtml2->put_scrollTop(clientPos.y);
chunkDestRectPx.top = printPos.y;
chunkDestRectPx.bottom = printPos.y + printChunkSize.cy;
}
//restore changed values on browser
//if for large pages on slow PC you get content scrolling during rendering and it is a problem,
//you can either hide the browser and show "working" or place on top first chunk content
pBodyStyle->put_overflow(keptOverflow.bstrVal);
pHtml2->put_scrollLeft(keptScrollPos.cx);
pHtml2->put_scrollTop(keptScrollPos.cy);
m_browser.put_Width(keptBrowserSize.cx);
m_browser.put_Height(keptBrowserSize.cy);
removeElement(pBody, pPadNode);
//draw to bitmap and close metafile
HENHMETAFILE hMetafile = CloseEnhMetaFile(hMetaDC);
RECT fitRect = { 0, 0, printHorRes, docSize.cy * printHorRes / docSize.cx };
convertEmfToBitmap(fitRect, hWndDc, hMetafile, L"c:\temp\test.bmp");
DeleteEnhMetaFile(hMetafile);
//cleanup - probably more here
::ReleaseDC(m_hWnd, hWndDc);
::DeleteDC(hPrintDC);
//{
// std::stringstream ss;
// ss << "====" << docSize.cx << "x" << docSize.cy << " -> " << fitRect.right << "x" << fitRect.bottom << "" << "\n";
// OutputDebugStringA(ss.str().c_str());
//}
}
///////////////
////some util
void convertEmfToBitmap(const RECT& fitRect, HDC hTargetDC, HENHMETAFILE hMetafile, LPCTSTR fileName)
{
//Create memory DC to render into
HDC hCompDc = ::CreateCompatibleDC(hTargetDC);
//NOTE this
BITMAPINFOHEADER infoHeader = { 0 };
infoHeader.biSize = sizeof(infoHeader);
infoHeader.biWidth = fitRect.right;
infoHeader.biHeight = -fitRect.bottom;
infoHeader.biPlanes = 1;
infoHeader.biBitCount = 24;
infoHeader.biCompression = BI_RGB;
BITMAPINFO info;
info.bmiHeader = infoHeader;
//create bitmap
BYTE* pMemory = 0;
HBITMAP hBitmap = ::CreateDIBSection(hCompDc, &info, DIB_RGB_COLORS, (void**)&pMemory, 0, 0);
HBITMAP hOldBmp = (HBITMAP)::SelectObject(hCompDc, hBitmap);
PlayEnhMetaFile(hCompDc, hMetafile, &fitRect);
BITMAPFILEHEADER fileHeader = { 0 };
fileHeader.bfType = 0x4d42;
fileHeader.bfSize = 0;
fileHeader.bfReserved1 = 0;
fileHeader.bfReserved2 = 0;
fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
CFile file(
fileName,
CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone);
file.Write((char*)&fileHeader, sizeof(fileHeader));
file.Write((char*)&infoHeader, sizeof(infoHeader));
int bytes = (((24 * infoHeader.biWidth + 31) & (~31)) / 8) * abs(infoHeader.biHeight);
file.Write(pMemory, bytes);
::SelectObject(hCompDc, hOldBmp);
//Clean up
if (hBitmap)
::DeleteObject(hBitmap);
::DeleteDC(hCompDc);
}
CComPtr<IHTMLDOMNode> appendPadElement(IHTMLDocument2* pDoc, IHTMLElement* pBody, long left, long top, long width, long height)
{
CComPtr<IHTMLElement> pPadElement;
pDoc->createElement(L"DIV", &pPadElement);
CComPtr<IHTMLStyle> pPadStyle;
pPadElement->get_style(&pPadStyle);
CComPtr<IHTMLStyle2> pPadStyle2;
pPadStyle->QueryInterface(IID_IHTMLStyle2, (void**)&pPadStyle2);
pPadStyle2->put_position(L"absolute");
CComVariant value = width;
pPadStyle->put_width(value);
value = height;
pPadStyle->put_height(value);
pPadStyle->put_posLeft((float)left);
pPadStyle->put_posTop((float)top);
CComPtr<IHTMLDOMNode> pPadNode;
pPadElement->QueryInterface(IID_IHTMLDOMNode, (void**)&pPadNode);
CComPtr<IHTMLDOMNode> pBodyNode;
pBody->QueryInterface(IID_IHTMLDOMNode, (void **)&pBodyNode);
pBodyNode->appendChild(pPadNode, NULL);
return pPadNode;
}
void removeElement(IHTMLElement* pParent, IHTMLDOMNode* pChild)
{
CComPtr<IHTMLDOMNode> pNode;
pParent->QueryInterface(IID_IHTMLDOMNode, (void **)&pNode);
pNode->removeChild(pChild, NULL);
}
示例页面输出 (4958x7656)