Firstly, thanks for the interesting question. Although it is not so practical - today many people use IE8 - it was not so difficult. I will describe what the problem is and provide a simplified but working solution that you can improve.
Before moving on to the IE8 solution, a few points:
A solution with a window size with a scroll size is unstable if you can run into large documents. If you don't know what to expect, you also need to reorganize the solution for later researchers to avoid using the resize window to scroll.
Why transfer a giant bitmap? Metaphiles, etc. Could fit better. Any sufficiently large page with this resolution is going to explode the memory on a PC with the naive creation of DIB. The Google page in the presented sample displays a 100 MB raster file, while the emf from which rasterization is performed takes less than 1 MB.
While I do not know the exact requirements and limitations of your project, I am 99% sure that drawing in a giant DIB is not the best solution. Even EMF, although better, is not the best either. If you need to, for example, add a signature and then print, there are better ways to handle this. This, of course, is just a note, not related to the question itself.
IE8 rendering problem
There is an error in IE8 renderer. Draw () will be cropped in the pixel dimensions of the actual display area (the visible rectangle that you see is the original display area in the scale of the rendering context).
So, if you have a scaled target, the size of which is larger than the actual one, then when scaling it will be cropped to the size in scaled pixels anyway (therefore, it has much less content than the original rectangle).
If this is not a clip for someone in genuine IE8, then the system has the remnants of a later IE, or there is another setting without scratches, a system update, or the like.
Workarounds
Good news can be circumvented; bad news workarounds are a bit unpleasant.
First, you can still get along with IViewObject. But since there is arbitrary scaling, and the available source rectangle is very small, this solution has some difficulties, which I think are SO's answer. Therefore, I would not plunge into this path.
Instead, we can run another, deprecated API: IHTMLElementRender . This allows you to display the page using DrawToDC in any context. Unfortunately, this is not as simple as it might seem, and goes beyond the simple context of the device.
Firstly, there is a similar clipping error. It is easier to handle because clipping occurs at large values ββoutside the screen size. Secondly, when using device context transformations, it either will not work or it will ruin the displayed html, so you cannot actually rely on scale or translate. Both problems require relatively non-trivial processing and complicate each other.
Decision
I will describe and provide sample code for a suboptimal but working on most simple pages. In general, an ideal and more effective solution can be achieved, but, again, this goes beyond the answer. Obviously, this is only IE8, so you need to check the browser version and run different handlers for IE8 compared to IE9 or higher, but you can take some ideas to improve the display of other browsers.
There are two interlocking workarounds here.
Upscaling
First, how do we scale vector content to printer quality if we cannot convert it? The workaround here is to render in a dc compatible printer environment. What will happen is that the content will be displayed in the DPI of the printer. Please note that it will not exactly match the width of the printer; it will scale to the printer DPI / screenDPI.
Later, when rasterized, we zoom out to fit the width of the printer. First, we visualize EMF, so there is a loss of quality (which will not happen on the printer itself). If you need higher quality (I doubt it), there are two possibilities: change the rendering solution for the target width (this is not trivial) or work with the resulting emf instead of a bitmap, and also allow the printer to make a reduced size.
Another remark is that at the moment you are only using the width of the printer, but there may be non-printable fields that you need to request from the printer and the account for them. Therefore, the printer can be scaled by the printer even if you provide a bitmap in the exact size of the printer. But then again, I doubt that this inconsistency of the resolution will be relevant to your project.
Clipping
The second to overcome is clipping. To overcome this limitation, we process the content in small pieces so that they are not clipped by the renderer. After rendering the fragment, we change the scroll position of the document and output the next fragment to the corresponding position in the target DC. This can be optimized to use large chunks, for example. the closest DPI to 1024 (using window resizing), but I haven't implemented this (it's just speed optimization). If you do not do this optimization, make sure that the minimum browser window size is not too small.
Please note: performing this scroll on an arbitrary fractional scale will be approximate and not so easy to implement in the general case. But with a regular printer and screen, we can perform whole block steps in DPI multiplications, for example. if the screen is 96 DPI and the printer is 600DPI, we take steps in the same multiple of 96 and 600 in each context, and everything is much simpler. However, the rest of the top or bottom after processing all the whole pieces will not be multiplied by DPI, so we can not scroll it as easily.
In general, we can approximate the scroll position in the printer space and hope that there will be no discrepancy between the final pieces. Instead, I add an absolutely positioned div with the block size at the bottom right of the page.
Please note that this may interfere with some pages and change the layout (perhaps not in the case of simple reports). If this is a problem, you need to add handling of the remainder after the loops instead of adding an element. The simplest solution in this case, as before, is to fill in the div, but not with the full size of the chunk, but just to make the width of the contents of the multiple DPI screen.
An even simpler idea, as I understood later, is simply to resize the window to the nearest multiple DPI and accept the size of this window as the block size. You can try this instead of a div, this will simplify the code and fix pages that might interfere with the nested div.
Code
This is just a sample.
No error handling. You need to add checks for each COM and API call, etc.
No code style, just fast and dirty.
Not sure if all purchased resources will be released as needed, complete your checks.
You must disable the page borders in the browser control in order for this pattern to work (if you need borders around the browser, just visualize them separately, the built-in ones are not consistent anyway). In IE8 this is not so trivial, but there are many answers here or on the Internet. In any case, I will include this patch in the draft sample solution. You can visualize with borders and exclude them, but this will be an unnecessary complication for a problem that has a simple solution.
The complete draft decision can be found on this link , here I have posted only the corresponding code.
The code below displays the page and saves to 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); }
Page Output Example (4958x7656)
