How to render WebBrowser to device context?
Asked Answered
C

1

9

i want to render a web-page (i.e. TWebBrowser) to a device context. i want to use Internet Explorer's layout engine to render content to a device context (i.e. metafile, pdf metafile).


Starting with Internet Explorer 9 the IHTMLElementRender interface is no longer supported:

IHTMLElementRender interface

Use this interface to draw the contents of an element to a specified device context, normally a printer.

Members
The IHTMLElementRender interface inherits from the IUnknown interface but does not have additional members.

To the point that they no longer mention that the DrawToDC method even exists:

IHTMLElementRender::DrawToDC Method

Deprecated. Draws the contents of the element to the specified device context.

Syntax

HRESULT DrawToDC(
     HDC hDC
);

Parameters

  • hDC
    [in] An HDC specifying the device to be drawn to, typically a printer.

Return Value

Returns S_OK if successful, or an error value otherwise.

Remarks

As of Windows Internet Explorer 9, this method is deprecated and should not be used.

With some printers, running IHTMLElementRender::DrawToDC may cause problems. You can ensure that IHTMLElementRender::DrawToDC works properly on all printers by running IHTMLElementRender::SetDocumentPrinter method first, and then passing the modified device context to IHTMLElementRender::DrawToDC.

Note: I'm quoting all the documentation so that it can still be found when Microsoft finally removes it from MSDN altogether. Along with the interface declaration:

IHTMLElementRender = interface(IUnknown)
   ['{3050F669-98B5-11CF-BB82-00AA00BDCE0B}']
   function DrawToDC(hdc: HDC): HResult; stdcall;
   function SetDocumentPrinter(const bstrPrinterName: WideString; hdc: HDC): HResult; stdcall;
end;

Use the IViewObject interface

i've tried converting to use the IViewObject (e.g. How to render HTML element without using web browser?):

procedure RenderWebBrowserToMetafile(Browser: TWebBrowser; Metafilename: string);
var
    view: IViewObject;
    m: TMetafile;
    mc: TMetafileCanvas;
    w, h: Integer;
    r: TRect;
    dpix: Integer;
    dpiy: Integer;
    dc: HDC;
begin
    w := WebBrowserScrollWidth(Browser);
    h := WebBrowserScrollHeightStrict(Browser);

    //96dpi screen to 300dpi metafile (destined for printer)
    dc := GetDC(0);
    try
        dpix := GetDeviceCaps(GetDC(0), LOGPIXELSX);
        dpiy := GetDeviceCaps(GetDC(0), LOGPIXELSY);
    finally
        ReleaseDC(0, dc);
    end;
    w := MulDiv(w, 300, dpix);
    h := MulDiv(h, 300, dpiy);

    view := Browser.Document as IViewObject;

    m := TMetafile.Create;
    try
        m.Width := w;
        m.Height := h;
        r := Rect(0, 0, w, h);

        mc := TMetafileCanvas.Create(m, 0);
        try
            view.Draw(
                    DVASPECT_CONTENT, //draw aspect
                    1, //index
                    nil, //pAspectInfo
                    nil, //pDVTargetDevice
                    0, //Target Device Context
                    mc.handle, //hdcDraw
                    @r, //target Bounds
                    @w, //window bounds (for metafiles)
                    nil, //continue function
                    0); //continue value
        finally
            mc.Free;
        end;
        m.SaveToFile(Metafilename);
    finally
        m.Free;
    end;
end;

The problem with this code is that it only renders the visible area of the browser (i.e. without scrolled content):

i need to render the entire web-page (e.g. for printing).

i tried changing the draw aspect from DVASPECT_CONTENT (as this example does), to DVASPECT_DOCPRINT:

DVASPECT_DOCPRINT
Provides a representation of the object on the screen as though it were printed to a printer using the Print command from the File menu. The described data may represent a sequence of pages.

With the same result; rather than rendering the page, only the visible portion is rendered:

How can i ask the IE9 rendering engine to render?


i also tried using different indexes along with DVASPECT_DOCPRINT. Although IViewObject is not documented, maybe that's how you print different "pages":

view.Draw(DVASPECT_DOCPRINT, 1, ...);
view.Draw(DVASPECT_DOCPRINT, 2, ...);
view.Draw(DVASPECT_DOCPRINT, 3, ...);
...
view.Draw(DVASPECT_DOCPRINT, n, ...);

But the WebBrowser doesn't care what the index is; only rendering the current View (which i suppose makes sense when using IViewObject).

How do i render a browser to a device context?

Note: The question is tagged with delphi, but there's nothing in this question that is delphi specific.


Update: i also tried other combinations of aspect and index:

view.Draw(DVASPECT_CONTENT, -1, ...);
view.Draw(DVASPECT_DOCPRINT, -1, ...);
view.Draw(DVASPECT_CONTENT, 0, ...);
view.Draw(DVASPECT_DOCPRINT, 0, ...);
Cockcroft answered 4/6, 2012 at 15:42 Comment(10)
According to this, you're on the right track and need to resize the browser control (temporarily). Not tested.Cann
TOndrej In this case the browser (control) is aligned/docked to the client form, which is limited to the size of the user's monitor. As Internet Explorer itself is. So i have to wonder how Internet Explorer manages to Print.Cockcroft
Perhaps you can create a temporary invisible browser control and copy the contents to it, or reuse your existing control and try tricks like WM_SETREDRAW, LockWindowUpdate to avoid visible effects.Cann
@TOndrej resizing the browser control might work in some cases, in others it may give you unexpected results in that the HTML content may change its layout if you resize the browser control itself.Unwell
@Unwell Excellent point, I haven't thought about that. Thanks!Cann
Another trick might be to create a bitmap large enough to hold the full image, then scroll the browser control (programmatically) and draw onto the bitmap page by page, producing the resulting combined image. However, the content of the page might change its layout even by scrolling (e.g. by javascript code manipulating the DOM) so it's still not perfect. Maybe it's possible to disable javascript temporarily.Cann
@TOndrej That idea might be correct. i realized that browsers are required to re-render their content in order to use the media: printer css style. But how to decide how wide to make the virtual browser window....Cockcroft
@Ian, have you try index = -1 ? i.e. view.Draw(DVASPECT_DOCPRINT, -1, ...); ? the documentation says -1 identifies all the data.Uke
@Uke i didn't try that! But now i did, and it still doesn't work :(Cockcroft
@Ian, it looks like you can't get the full content without resizing the browser, as TOndrej mentioned. Btw, you may want to include the body left, right, top and bottom margins to your width and height calculations.Uke
T
1

Using IViewObject, you can create a screenshot of the current viewport. I use a trick to create a screenshot of a web page (without any frames) or a frame document.

  1. Calculate the full document size using scrollWidth/scrollHeight/scrollLeft/scrollTop (check IHtmlDocument2 and IHtmlDocument3 interfaces)
  2. Lock window update and adjust the document to calculated full size
  3. Use IViewObject to create a snapshot (your code above seems to be okay)
  4. Restore the document to its original size and unlock window update

Note that you should be careful of doing this when the document is extremely large.

Tellford answered 7/6, 2012 at 12:56 Comment(1)
The second parameter 'index' of IViewObject.Draw(..) seems okay. I am afraid that you cannot create a full size document snapshot using any API directly.Tellford

© 2022 - 2024 — McMap. All rights reserved.