简体   繁体   English

尝试使用IViewObject :: Draw()将Web浏览器控件渲染到HDC中会失败,但IE11会成功

[英]Trying to render web browser control using IViewObject::Draw() into HDC fails with IE8 but succeeds with IE11

I have an MFC dialog window where I added a WebBrowser control (that encapsulates the Internet Explorer engine.) 我有一个MFC对话框窗口,我在其中添加了一个WebBrowser控件 (它封装了Internet Explorer引擎)。

The goal of the following code is to render the contents of the said web browser control into a device context that I can later use for printing: 以下代码的目标是将所述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);

This code works fine if I have the latest IE11 installed on my development machine. 如果我的开发机器上安装了最新的IE11此代码可以正常工作。 But if, for instance, someone has IE8 installed on their Windows 7, the IViewObject::Draw method will render only a small part of the document (equal to the size of the web browser control itself.) 但是,例如,如果某人在其Windows 7上安装了IE8 ,则IViewObject :: Draw方法将仅呈现文档的一小部分(等于Web浏览器控件本身的大小。)

The best way to describe it is to illustrate it with the examples: 描述它的最好方法是用例子来说明它:

Normally rendered test page with IE11 installed: 通常使用IE11安装的测试页面:

在此输入图像描述

and here's what happens with IE8 installed: 以下是安装IE8的情况:

在此输入图像描述

Does anyone have any idea what am I doing wrong here that IE8 doesn't like? 有谁知道IE8不喜欢我在这里做错了什么?

EDIT1: Did some more digging into the IViewObject::Draw function with WinDbg and then found the source code for it. EDIT1:使用WinDbg进一步深入了解IViewObject::Draw函数,然后找到它的源代码。 Here's CServer::Draw() that is IViewObject::Draw , and then CDoc::Draw() that is called internally from CServer::Draw() . 这里的CServer ::抽奖()IViewObject::Draw ,然后CDOC ::抽奖()是从内部调用CServer::Draw()

First, thanks for the interesting question. 首先,感谢有趣的问题。 While not so practical - not a lot of people use IE8 today - it was not so trivial to solve. 虽然不那么实用 - 今天很多人都没有使用IE8 - 解决这个问题并不是那么简单。 I'll describe what is the problem and provide a simplistic but working solution that you can improve. 我将描述问题是什么,并提供一个简单但可行的解决方案,您可以改进。


Before I go into IE8 solution, a couple of points: 在我进入IE8解决方案之前,有几点:

  1. The solution with window resize to fit scroll size is not stable if you can encounter large documents. 如果您遇到大型文档,则窗口调整大小以适合滚动大小的解决方案不稳定。 If you don't know what to anticipate, you need to refactor the solution for later explorers as well, to avoid relying on resizing window to scroll size. 如果您不知道预期的内容,则还需要为以后的资源管理器重构解决方案,以避免依赖于调整窗口大小来滚动大小。

  2. Why carry giant bitmap? 为什么要携带巨型位图? Metafiles etc. may fit better. 图元文件等可能更合适。 Any large enough page at this resolution is going to blow memory on PC with the naive DIB creation. 在这个分辨率下任何足够大的页面都会在PC上用大量的DIB创建来释放内存。 Google page in provided sample renders to 100Mb bitmap file while emf, from which rasterization is done, takes less than 1Mb. 提供的示例中的Google页面呈现为100Mb位图文件,而执行光栅化的emf则需要不到1Mb。

  3. While I don't know exact requirements and limitations of your project, I'm 99% sure that drawing into gigantic DIB is not the best solution. 虽然我不知道您的项目的确切要求和限制,但我99%确信吸入巨大的DIB并不是最好的解决方案。 Even EMF, while better, is not the best either. 甚至EMF虽然更好,但也不是最好的。 If you need, for example, add a signature and then print, there are better ways to handle this. 例如,如果您需要添加签名然后打印,则有更好的方法来处理此问题。 This, of course, just a side note, not related to the question itself. 当然,这只是一个旁注,与问题本身无关。


IE8 rendering problem IE8渲染问题

In IE8 renderer there is a bug. 在IE8渲染器中有一个错误。 Draw() will be clipped at pixel dimensions of actual display area (the visible rectangle you see is the original display area in the scale of rendering context). Draw()将被剪切在实际显示区域的像素尺寸上(您看到的可见矩形是渲染上下文比例中的原始显示区域)。

So, if you have a scaled target that is larger than the actual size, while scaled, it will be clipped to the size in scaled pixels anyway (so it has much less content than original rectangle). 因此,如果您的缩放目标大于实际大小,则在缩放时,它将被缩放到缩放像素的大小(因此它的内容比原始矩形少得多)。

If it doesn't clip for somebody on genuine IE8, then there are remainders of later IE in the system or there is other non-scratch setup, system update or alike. 如果它没有为真正的IE8上的某人剪辑,那么系统中存在后续IE的剩余部分,或者存在其他非临时设置,系统更新等。

Workaround possibilities 解决方法可能性

Good news it is possible to workaround, bad news workarounds are a bit nasty. 好消息是可以解决方法,坏消息的解决方法有点令人讨厌。

First, it is still possible to workaround with IViewObject. 首先,仍然可以使用IViewObject进行解决。 But, because there is arbitrary scaling involved and accessible source rectangle is very small, this solution has some complexities that I think are beyong an SO answer. 但是,因为涉及任意缩放并且可访问的源矩形非常小,所以这种解决方案具有一些复杂性,我认为这些都是SO答案。 So I would not dive into this path. 所以我不会潜入这条道路。

Instead, we can render through another, now outdated API: IHTMLElementRender . 相反,我们可以渲染另一个现在过时的API: IHTMLElementRender It allows to render the page with DrawToDC into arbitrary context. 它允许使用DrawToDC将页面呈现为任意上下文。 Unfortunately, it is not so simple as it may appear and goes beyond just providing device context. 不幸的是,它并不像看起来那么简单,不仅仅是提供设备上下文。

First, there is similar bug of clipping. 首先,有类似的剪辑错误。 It can be handled easier because clipping occurs at large values beyond screen dimensions. 它可以更容易处理,因为剪切发生在超出屏幕尺寸的大值。 Second, when using device context transformations it either will not work or will mess the rendered html, so you can't actually rely on scale or translate. 其次,当使用设备上下文转换时,它将无法工作或将混乱渲染的html,因此您实际上不能依赖于缩放或转换。 Both problems require relatively non-trivial handling and complicate one another. 这两个问题都需要相对不重要的处理并且彼此复杂化。

The solution 解决方案

I'll describe and provide sample code for non-optimal but working on most simple pages solution. 我将描述并提供非最佳的示例代码,但是在大多数简单页面解决方案上工作。 In general, it is possible to achieve a perfect and more efficient solution, but, again, this goes beyond the scope of an answer. 通常,可以实现完美且更有效的解决方案,但是,这又超出了答案的范围。 Obviously, it is IE8 only, so you'll need to check browser version and execute different handlers for IE8 vs. IE9 or higher, but you can take some ideas to improve other browsers rendering too. 显然,它只是IE8,所以你需要检查浏览器版本并为IE8和IE9或更高版本执行不同的处理程序,但你也可以采取一些想法来改进其他浏览器的渲染。

There are two interrelated workarounds here. 这里有两个相互关联的解决方法。

Up-scaling 向上扩展

First, how do we upscale the vector content to the printer quality if we can't transform? 首先,如果我们无法转换,我们如何将矢量内容升级到打印机质量? The workaround here is to render to a context compatible with printer dc. 此处的解决方法是渲染到与打印机DC兼容的上下文。 What will happen is that content will be rendered at printer DPI. 会发生什么是内容将在打印机DPI上呈现。 Note it will not fit exactly printer width, it will scale to printerDPI/screenDPI. 请注意,它不能完全适合打印机宽度,它将扩展到printerDPI / screenDPI。

Later, on rasterization, we downscale to fit the printer width. 之后,在光栅化时,我们缩小尺寸以适应打印机宽度。 We render initially to EMF, so there is no much of a quality loss (that will occur on the printer itself anyway). 我们最初渲染到EMF,因此没有太多质量损失(无论如何都会在打印机本身上发生)。 If you need higher quality (I doubt it), there are two possibilities - modify the solution to render for target width (this is not trivial) or work with resulting emf instead of bitmap and let printer to make the downscale fit. 如果你需要更高的质量(我怀疑它),有两种可能性 - 修改解决方案以渲染目标宽度(这不是微不足道)或使用生成的emf而不是位图,让打印机使缩小比例适合。

Another note is that you currently use just printer width, but there may be non-printable margins that you need to query from printer and account for them. 另一个注意事项是,您目前只使用打印机宽度,但您可能需要从打印机查询并为帐户进行查询,这些页边距可能不可打印。 So it may re-scaled by printer even if you provide bitmap in exact printer dimensions. 因此,即使您提供精确打印机尺寸的位图,它也可能会被打印机重新缩放。 But again, I doubt this resolution disparity will make any difference for your project. 但同样,我怀疑这种解决方案的差异会对你的项目产生任何影响。

Clipping 剪裁

Second to overcome is the clipping. 要克服的第二个是削减。 To overcome this limitation, we render content in small chunks so they are not clipped by the renderer. 为了克服这个限制,我们以小块的形式呈现内容,这样它们就不会被渲染器剪切掉。 After rendering a chunk, we change the scroll position of the document and render next chunk to the appropriate position in the target DC. 渲染块后,我们更改文档的滚动位置,并将下一个块渲染到目标DC中的适当位置。 This can be optimized to use larger chunks eg nearest DPI multiple to 1024 (using window resize), but I didn't implement that (it is just a speed optimization). 这可以优化为使用更大的块,例如最近的DPI倍数到1024(使用窗口调整大小),但我没有实现它(它只是速度优化)。 If you don't make this optimization, ensure that minimum browser window size is not too small. 如果不进行此优化,请确保最小浏览器窗口大小不会太小。

Note, doing this scroll on an arbitrary fractional scale will be an approximation and is not so simple to implement in generic case. 注意,在任意小数刻度上执行此滚动将是近似值,并且在通用情况下实现起来并不那么简单。 But with regular printer and screen we can make integer chunk steps in multiplies of DPI, eg if screen is 96 DPI and printer is 600DPI, we make steps in the same multiple of 96 and 600 on each context and everything is much simpler. 但是使用常规打印机和屏幕,我们可以在DPI的倍数中进行整数块步骤,例如,如果屏幕是96 DPI而打印机是600DPI,我们在每个上下文中以96和600的相同倍数执行步骤,一切都更简单。 However, the remainder from top or bottom after processing all whole chunks will not be in DPI multiplies so we can't scroll as easily there. 但是,处理完所有整块后,从顶部或底部开始的余数不会在DPI中增加,因此我们无法在那里轻松滚动。

In general, we can approximate scroll position in printer space and hope there will be no misfit between final chunks. 通常,我们可以在打印机空间中近似滚动位置,并希望最终块之间不会出现错误配置。 What I did instead is appending an absolutely positioned div with chunk size at the right bottom of the page. 我所做的是在页面的右下方添加一个绝对定位的div,块大小。

Note, this can interfere with some pages and change the layout (probably not the case with simple reports). 请注意,这可能会干扰某些页面并更改布局(可能不是简单报告的情况)。 If that is a problem, you'll need to add remainder handling after loops instead of adding an element. 如果这是一个问题,您需要在循环后添加余数处理而不是添加元素。 Most simple solution in that case is still to pad with div but not with the full chunk size but just to make content width multiple of screen DPI. 在这种情况下,最简单的解决方案仍然是使用div填充,但不是使用完整的块大小,而只是使内容宽度为屏幕DPI的倍数。

Even simpler idea, as I've realized later, is just to resize window to the nearest multiple of DPI and take this window size as a chunk size. 更简单的想法,正如我后面所意识到的,只是将窗口调整到最接近的DPI倍数,并将此窗口大小作为块大小。 You can try that instead of div, this will simplify the code and fix pages that may interfere with the injected div. 您可以尝试使用而不是div,这将简化代码并修复可能会干扰注入的div的页面。

The code 代码

This is just a sample. 这只是一个样本。

  • No error handling. 没有错误处理。 You need to add checks for every COM and API call, etc. 您需要为每个COM和API调用添加检查等。

  • No code style, just quick and dirty. 没有代码风格,只是快速和肮脏。

  • Not sure all acquired resources are released as needed, do your checks 不确定所有获得的资源是否按需发布,请进行检查

  • You must disable page borders on browser control for this sample to work (if you need borders around browser, just render them separately, built-in are not consistent anyway). 必须在浏览器控件上禁用页面边框才能使此示例正常工作(如果您需要浏览器边框,只需单独渲染它们,内置程序就不一致了)。 On IE8 this is not so trivial but there are many answers here or on the web. 在IE8上,这不是那么微不足道,但在这里或网上有很多答案。 Anyway, I will include this patch in sample solution project. 无论如何,我将在示例解决方案项目中包含此补丁。 You can render with borders as well and exclude them, but this will be unnecessary complication for a problem that has simple solution. 您也可以使用边框进行渲染并将其排除,但对于具有简单解决方案的问题,这将是不必要的复杂化。

  • The full solution project can be found at this link , I'll post only the relevant code here. 完整的解决方案项目可以在这个链接找到,我只会在这里发布相关的代码。

The code below renders the page and saves in c:\\temp\\test.emf + c:\\temp\\test.bmp 下面的代码呈现页面并保存在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);
}

Sample page output (4958x7656) 样本页输出(4958x7656)

样本输出

I have taken your code and run it on IE11 when the WebBrowser control is smaller then the page size. WebBrowser控件小于页面大小时,我已经使用了您的代码并在IE11上运行它。 It rendered a portion of the page equal to control's size. 它使页面的一部分等于控件的大小。 Not sure why you say IE8 and IE11 are any different. 不知道为什么你说IE8和IE11有任何不同。

It seems that common approach to taking full page screenshots is adjusting WebBrowser size before taking screenshot, like this: 看起来采用整页截图的常用方法是在截取屏幕截图之前调整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);

This seems to work well, even on large pages such as the one you're currently reading (I have taken screenshot 1920x8477). 这似乎运作良好,即使在你正在阅读的大页面上也是如此(我已截屏1920x8477)。 It works both on my IE11 and on IE8 virtual machine 它适用于我的IE11和IE8 虚拟机

It has a side effect of resetting scrollbars, but that can be solved, for example by using an invisible copy of WebBrowser for screenshots. 它具有重置滚动条的副作用,但这可以解决,例如通过使用WebBrowser的不可见副本进行屏幕截图。

PS: You could have done a better job by providing example code that can be compiled, at least ;) PS:你可以通过提供可以编译的示例代码来做得更好,至少;)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM