How to render WebBrowser to device context?

2020-07-06 08:02发布

问题:

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, ...);

回答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.