Beyond Print Preview: Print Customization for Internet Explorer 5.5

Chuck Ainslie
Microsoft Corporation

August 2000

Summary: This article explains how to write a print template, what you need to do to use the print template you've written, and some ideas and applications that can exploit print templates. (16 printed pages)

Contents

Introduction
What Can I Do with Custom Print Templates?
How Do I Use These Print Template Things?
Writing a Print Template
Safety Considerations When Using Print Templates
Cool Things You Can Do
Endless Printing Possibilities

Introduction

I'd like to introduce you to the new printing architecture for Microsoft® Internet Explorer 5.5. The new architecture is very cool for a couple of reasons. The most visible reason is that the new architecture is the foundation for the new Internet Explorer print preview facility. Print preview in itself is an exciting new feature—relatively speaking, of course. I'd give it up in a minute for time travel, which I'd give up for world peace—reluctantly. (Well, maybe not.) However, print preview is only one part of a bigger story. The new printing architecture offers much more than a single new feature. The new architecture has been designed to be highly flexible and extensible, and it is exposed here for your use. In other words, you can use the Internet Explorer 5.5 printing architecture to customize how Internet Explorer and the WebBrowser control handle printing.

In this article, you'll learn how to write a print template, which is the mechanism Internet Explorer 5.5 uses to control print/preview behavior and page layout for printing/previewing. You'll learn what you need to do to use a print template you've written. You'll also see some ideas and applications that could exploit print templates. A future article will explore the possibilities for creating a custom print preview user interface and customizing the Page Setup and Print dialog boxes.

What Can I Do with Custom Print Templates?

The new print customization possibilities add a new layer of possibilities to Internet Explorer as a platform for application development. By creating a print template, you can control:

  • The layout of pages when printed/previewed, and the content that is printed/previewed on them.
  • How print jobs are handled—for instance, which pages are printed in what order.
  • The look of the Print Preview window and controls available on the print preview user interface.

Because you now have extensive layout and handling control over printing, Internet Explorer can fit many new application scenarios, such as:

  • Adding boilerplate text to print jobs, including company logos, legal notices, and advertisements.
  • Customized placement and styling for header and footer elements, such as page numbers or chapter headings.
  • Customized printing for scheduling or appointment applications, which gives the user different layout or binding options.
  • Multi-fold brochure printing.
  • Book-style printing using mirrored margins for the odd and even pages.

How Do I Use These Print Template Things?

A print template is written using standard HTML, script, and four element behaviors specific to print templates:

  1. LAYOUTRECT
  2. DEVICERECT
  3. TEMPLATEPRINTER
  4. HEADERFOOTER

For more specific information about these elements, see the Print Template Reference documentation. I've also written a spiffy sample application you can download that demonstrates the print template behaviors.

Download spiffy sample application Download spiffy sample application
(Note: You must choose the "Save this program to disk" option to run this application successfully.)

First, let's assume you've got a print template you want to use. How do you get Internet Explorer or the WebBrowser control to recognize your template and use it in place of the default print template provided with Internet Explorer? The most important requirement is that you work in C++. Even though print templates are HTML files, they can only be used from C++ in an application, Microsoft ActiveX® control, binary behavior, or other binary executable file that hosts the WebBrowser control. There is currently no scriptable mechanism for using print templates, nor any mechanism to use them in Microsoft Visual Basic®. To pass a print template to the WebBrowser control, you must be able to issue or intercept the IDM_PRINT and IDM_PRINTPREVIEW commands through IOleCommandTarget::Exec. The print template path is passed to the WebBrowser control in the pVarArgIn VARIANT argument to IOleCommandTarget::Exec.

Let's say, for instance, that you have a print template called MyTemplate.htm whose path is c:\MyTemplate.htm. Assume also that you have instantiated the WebBrowser control and obtained an IWebBrowser2 pointer, or if you're in an ActiveX control, you've acquired a pointer to Internet Explorer's IWebBrowser2 interface from IOleClientSite and IOleContainer. (An IHTMLDocument2 interface pointer will work, too.) Let's assume the IWebBrowser2 interface pointer is named pWB. Query pWB for an IOleCommandTarget pointer:

IOleCommandTarget* pCmdTarg;
pWB->QueryInterface(IID_IOleCommandTarget, (void**)&pCmdTarg);

From pCmdTarg, you can now call IOleCommandTarget::Exec with either the IDM_PRINT or IDM_PRINTPREVIEW commands and a VARIANT containing the path to your print template.

VARIANT vTemplatePath;
V_VT(&vTemplatePath) = VT_BSTR;
V_BSTR(&vTemplatePath) = SysAllocString(L"c:/MyTemplate.htm");
pCmdTarg->Exec(&CGID_MSHTML,
               IDM_PRINT,
               OLECMDEXECOPT_PROMPTUSER,
               &vTemplatePath,
               NULL);

For IDM_PRINT, the third parameter can be OLECMDEXECOPT_PROMPTUSER, OLECMDEXECOPT_DONTPROMPTUSER, or OLECMDEXECOPT_DODEFAULT. The default action for the IDM PRINT command will be to prompt the user with the Print dialog box before printing. For IDM_PRINTPREVIEW, there is always a user interface—that's the whole point, isn't it?—so the third parameter is ignored. Just use OLECMDEXECOPT_DONTPROMPTUSER.

Writing a Print Template

It's time for the "main event"—writing a print template itself. We'll start at the beginning, showing the four print template element behaviors you use to write a print template and the basics of their use. When we get to the TemplatePrinter behavior, we'll discuss the dialogArguments object passed to a print template from the WebBrowser control or Internet Explorer. We'll also discuss how to dynamically create LAYOUTRECT and DEVICERECT elements, which is the best way to ensure that the entire source document is displayed or printed. At various points in our discussion, we'll look at thread synchronization issues that need to be addressed when a print template prints a document.

Laying Out a Page: the LayoutRect and DeviceRect Element Behaviors

The LayoutRect and DeviceRect element behaviors provide the visual formatting to make a page look the way you want. A DEVICERECT element represents a single page for printing. LAYOUTRECT elements go inside DEVICERECT elements and establish the area of each page containing the source document's content. With this in mind, take a look at the syntax for a pair of very basic pages:

<IE:DEVICERECT ID="page1" CLASS="pagestyle" MEDIA="print">
    <IE:LAYOUTRECT ID="layoutrect1" CLASS="lorstyle" 
            CONTENTSRC="document" NEXTRECT="layoutrect2"/>
</IE:DEVICERECT>

<IE:DEVICERECT ID="page2" CLASS="pagestyle" MEDIA="print">
    <IE:LAYOUTRECT ID="layoutrect2" CLASS="lorstyle"/>
</IE:DEVICERECT>

First, notice that each of the tags begins with a namespace declaration (the IE:). A print template declares the namespace in its first lines like this:

<HTML XMLNS:IE>
<HEAD>
<?IMPORT NAMESPACE="IE" IMPLEMENTATION="#default">

Here, the XMLNS attribute on the HTML tag declares a namespace called "IE" for the template. You can call the namespace anything you want, so long as you substitute your namespace name for the "IE" wherever it occurs. The IMPORT tag then imports the default Internet Explorer behavior implementations to the IE namespace, making the print template element behaviors available to the template.

Second, notice that the LAYOUTRECT elements are scopeless. They do not have a closing tag, and their closing bracket is preceded by a forward slash. This is standard XML syntax for an element without a closing tag. A LAYOUTRECT cannot contain any HTML. For instance, the following code would have no effect:

layoutrect1.innerHTML = "<B>Hi there!</B>"; 

Now, look at the attributes on each tag. The ID attribute is well known. It gives an element an identifying name. The CLASS attribute points to style classes defined somewhere else on the page. The styles for the LAYOUTRECT element must include a width and height or they will not display. Their default values are zero. The width and height styles for DEVICERECT elements should correspond to the paper settings for the current printer. We'll see how you can set these styles when we get to the TemplatePrinter behavior. The MEDIA attribute is specific to the DEVICERECT element. When set to "print," this attribute specifies that the page should be printed at the highest resolution possible for the printer. You should always include this attribute and set it to "print."

Where this example gets interesting is the CONTENTSRC and NEXTRECT attributes on the first LAYOUTRECT. The CONTENTSRC attribute is important. It tells a LAYOUTRECT what content to use to fill itself. The word "document" is a keyword that tells the LAYOUTRECT to fill itself with the current document displayed in the WebBrowser control. This is usually true, unless the current document specifies an alternate URL for printing. See the LINK element for additional details. The displayed document and print document can also be different when the user right-clicks a link and selects Print Target. If you don't want to print the current document, you can set the CONTENTSRC to a URL or other address to load other content into a LAYOUTRECT. The NEXTRECT attribute specifies the next LAYOUTRECT to fill once the current one is full. Perhaps you can see the idea behind LAYOUTRECT elements: you specify a CONTENTSRC attribute for the first LAYOUTRECT in a series of LAYOUTRECT elements. Then you chain a bunch of them together with the NEXTRECT attribute to provide enough area to completely render the source document.

Now the question is:

How can you be sure you have enough LAYOUTRECT elements to hold a source document's content?

You might create a print template with a million LAYOUTRECT elements all connected together, but such a template would have lousy performance. And what about that two million-page document you want to print? The way to go in a print template is to create DEVICERECT and LAYOUTRECT elements dynamically when the print template first loads or as needed after the print template loads—you'll see this when it comes time to look at user interfaces in the next article.

The onlayoutcomplete event

The key to the dynamic creation of LAYOUTRECT elements is the onlayoutcomplete event. This event occurs when a LAYOUTRECT is done filling. A LAYOUTRECT finishes filling either when there is no more content from the source to fill the LAYOUTRECT or when there is no more space in the LAYOUTRECT for content. You can determine which of these conditions occurred by checking a new property on the event object called contentOverflow. When the property is false, no further content needs to be rendered. When the property is true, the source document has not been completely rendered, and you need another LAYOUTRECT to fill.

With this in mind, let's look at the next example:

<SCRIPT LANGUAGE="JScript">
var iNextPageToCreate = 1;

function AddFirstPage()
{
    newHTML  = "<IE:DEVICERECT ID='page1' MEDIA='print' CLASS='pagestyle'>";
    newHTML += "<IE:LAYOUTRECT ID='layoutrect1' CONTENTSRC='document'" +
               "ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect2'" +
               "CLASS='lorstyle'/>";
    newHTML += "</IE:DEVICERECT>";

    pagecontainer.insertAdjacentHTML("afterBegin", newHTML);
}

function OnRectComplete()
{
    if (event.contentOverflow == true)
    {
        document.all("layoutrect" + iPageToCreate).onlayoutcomplete = null;

        newHTML  = "<IE:DEVICERECT ID='page" + (iPageToCreate + 1) +  
                   "' MEDIA='print' CLASS='pagestyle'>";
        newHTML += "<IE:LAYOUTRECT ID='layoutrect" + (iPageToCreate + 1) + 
                   "' ONLAYOUTCOMPLETE='OnRectComplete()' NEXTRECT='layoutrect" + 
                   (iPageToCreate + 2) + "'  CLASS='lorstyle'/>";
        newHTML += "</IE:DEVICERECT>";

        pagecontainer.insertAdjacentHTML("beforeEnd", newHTML);
        iPageToCreate++;
    }
}
</SCRIPT>

<BODY ONLOAD="AddFirstPage()">

<DIV ID="pagecontainer">
<!-- Dynamically created pages go here. -->
</DIV>

</BODY>
</HTML>

Here we have an (initially) empty DIV element named pagecontainer. We also have two functions, AddFirstPage and OnRectComplete. AddFirstPage is called once when the document has finished loading, and simply adds the first page to pagecontainer using the insertAdjacentHTML method. Notice the declaration for the first LAYOUTRECT. It contains the attribute ONLAYOUTCOMPLETE='OnRectComplete()'. When this LAYOUTRECT has finished filling, the OnRectComplete function will be called.

OnRectComplete checks the contentOverflow property on the event object and adds a new LAYOUTRECT if there is more content to render from the source document. Each of the LAYOUTRECT elements it adds has the same ONLAYOUTCOMPLETE event handler declaration pointing to OnRectComplete. The variable iNextPageToCreate tracks how many pages have been created and is used to generate the IDs for new LAYOUTRECT and DEVICERECT elements.

Notice that when the OnRectComplete function adds a new page, it sets the onlayoutcomplete event handler for the current LAYOUTRECT element to null. You want to do this because a print template periodically re-flows the source document into the LAYOUTRECT elements. For instance, when the Print Preview window is resized, or when the styles on LAYOUTRECT elements change. If you don't unset the onlayoutcomplete handler, you'll end up with repeated calls to OnRectComplete that generate a bunch of pages you don't need.

At this point, we've covered the basics of the LAYOUTRECT and DEVICERECT elements. This knowledge is enough to get a print template to display a document in the Print Preview dialog window, but you still may not know how to make sure the page styles correspond to the Page Setup and printer settings. More important though, you can't print with the knowledge you've gained here so far. For that, we need to discuss the TemplatePrinter behavior.

Making a Print Template Print: the TemplatePrinter Element Behavior

The TemplatePrinter element behavior is a print template's interface to the printer and to Page Setup settings. Adding this element behavior to a print template is simple—just add the following line to the BODY element of the template:

<IE:TEMPLATEPRINTER ID="printer"/>

From here on out, all methods and properties of the TemplatePrinter behavior can be accessed using the ID for the TEMPLATEPRINTER element:

printer.showPageSetupDialog();

Check out the reference documentation for the TemplatePrinter behavior. You'll see that it has quite a few properties and methods you can use.

Let's run through a printing scenario. In your C++ application, you write a call to IOleCommandTarget:Exec with IDM_PRINT or IDM_PRINTPREVIEW, passing it OLECMDEXECOPT_PROMPTUSER, OLECMDEXECOPT_DONTPROMPTUSER, or OLECMDEXECOPT_DODEFAULT and the path to the print template. The print template is instantiated.

How does the print template know whether to prompt the user for printing, print without a prompt, or display the Print Preview window?

The answer is that when the print template is instantiated, the window object for a print template has a dialogArguments object attached to it. The dialogArguments object has a property named __IE_PrintType that contains the value "Prompt," " NoPrompt," or "Preview," depending on the OLECMDEXECOPT value from the IOleCommandTarget::Exec call. A function in the print template can query for this property and direct the template to act accordingly.

function CheckPrint()
{
    switch (dialogArguments.__IE_PrintType)
    {
        case "Prompt":
            if (printer.showPrintDialog())
                PrintPrep();
            break;
        case "NoPrompt":
            PrintPrep();
            break;
        case "Preview":
        default:
            break;
    }
}

This example has a simple switch statement that responds to each of the possible values. When appropriate, the example calls a function named PrintPrep that begins this template's printing process. Note that the case for "Preview" is empty because a print template's default behavior displays the Print Preview window. The case block for "Prompt" calls a method of the TemplatePrinter behavior called showPrintDialog before calling PrintPrep. If the user clicks the Cancel button in the Print dialog box, PrintPrep is not called. Note that OLECMDEXECOPT_PROMPTUSER does not automatically cause the Print dialog box to open. It is the script in the print template that displays the Print dialog box and causes the template to respond appropriately to the IOleCommandTarget::Exec call.

What's this function PrintPrep, you may ask? What does it look like and what's its purpose?

Here it is:

function PrintPrep()
{
    if (layoutrect1.contentDocument.readyState == "complete")
        PrintNow();
    else
        layoutrect1.contentDocument.onreadystatechange = 
                                               PrintWhenContentDocComplete;
}

PrintPrep is an important function, mostly when printing without a user prompt. The printing process runs as a separate thread from the document loading process. A print job could be sent to the printer before the document has finished loading into the print template—if this happened, the template would print blank pages or an incorrect number of pages. To make the print process wait for the source document, PrintPrep sets an onreadystatechange event handler on the document object of the source used to fill the chain of LAYOUTRECT elements. The document object of the source is referenced by the contentDocument property on the first LAYOUTRECT in the chain. The onreadystatechange event handler just waits for the readyState to be "complete," then continues the printing process by calling a function named PrintNow that's defined elsewhere in the template.

function PrintWhenContentDocComplete()
{
    if (layoutrect1.contentDocument.readyState == "complete")
    {
        layoutrect1.contentDocument.onreadystatechange = null;
        PrintNow();
    }
}

Let's get on with it and actually print something.

Now, let's look at the PrintNow function, where the printing actually occurs in this particular print template example:

function PrintNow()
{
    var startPage;
    var endPage;
    var oDeviceRectCollection = document.all.tags("DEVICERECT");
    
    // Check dialogArguments
    if (dialogArguments.__IE_PrintType == "NoPrompt" || 
        printer.selectedPages == false)
    {
        // Printing w/o prompt, so set startPage and endPage to print all pages
        startPage = 1;
        endPage = oDeviceRectCollection.length;
    }
    else
    {
        // Printing w/prompt, so set startPage and endPage
        // from TemplatePrinter and do some error checking
        startPage = printer.pageFrom;
        endPage = printer.pageTo;
        if (startPage > endPage)
        {
            alert("Error: Start page greater than end page");
            return;
        }
        if (startPage > oDeviceRectCollection.length)
        {
            alert("Error: Start page greater than number of pages in print job.");
            return;
        }
        if (endPage > oDeviceRectCollection.length)
        {
            alert("Warning: End page greater than number of pages in print job." +
                  "Continuing Print Job.");
            endPage = oDeviceRectCollection.length;
        }
    }

    // Now that startPage and endPage are set, process the print job
    printer.startDoc("Printing from Custom Print Template");

    for (i = startPage - 1; i < endPage; i++)
        printer.printPage(oDeviceRectCollection[i]);
        
    printer.stopDoc();
}

The heart of PrintNow is in the last four lines. All the rest is preparation to determine which pages to print. These lines show the TemplatePrinter methods startDoc, printPage, and stopDoc. You always start the printing process with the startDoc command and finish with the stopDoc command. These two commands coincide roughly with the appearance and disappearance of the print icon in the system tray. The argument to startPage becomes the document name as displayed in the printer's document queue. The function printPage prints a single DEVICERECT element. As you can see, PrintNow uses a for statement to loop through the DEVICERECT collection for the pages that need to be printed.

There's one question that I haven't addressed yet. Remember the CheckPrint function at the beginning of this section, where the print template first determines how to handle the print job?

When should a print template check the print job type?

The timing of this call is important when a print template generates its LAYOUTRECT and DEVICERECT elements dynamically, as most should do. You might think CheckPrint could be the onload event handler, or could be called from the onload event handler. However, it's not quite that simple. When a print template loads, there are two threads involved. One is the thread loading the print template and creating the DEVICERECT and LAYOUTRECT elements. The other is the thread loading the source document into the LAYOUTRECT elements. In general, the thread loading the template will finish after the thread loading the source document, but both threads must finish before the template can print properly.

During print preview or when printing with a prompt, this is usually not a problem; enough delay is built into each of these processes that the template will preview or print correctly. The issue crops up when printing without a prompt, when each thread is racing to completion. In the example presented by this article, a good place to put the call to CheckPrint is in the onlayoutcomplete handler, OnRectComplete. The call to CheckPrint could come as an else alternative in the if statement—when the contentOverflow property is false. While this is almost enough for a solution, there's one further complication. The print job can't be processed from within the onlayoutcomplete event handler because the printer or screen's device context (DC) can't render while the LAYOUTRECT elements are still being measured out. For this reason, you must let the event handler finish before calling CheckPrint. One simple way to do this is to set a timer that delays the call to CheckPrint slightly. The OnRectComplete function then looks like this:

function OnRectComplete()
{
    if (event.contentOverflow == true)
    {
        // Add another LAYOUTRECT and DEVICERECT
    }
    else
    {
        setTimeout("CheckPrint()", 100);
    }

How long a delay is necessary?

A delay of as little as one millisecond can be enough. For safety, 100 milliseconds should be ample.

Keep in mind that the processing of CheckPrint in this example includes a check of the source document's ready state in its PrintPrep function calls. The example makes sure that both the LAYOUTRECT/DEVICERECT creation thread and the source document loading thread are complete before printing.

You've seen so far how the LayoutRect, DeviceRect, and TemplatePrinter behaviors work. There's one more print template behavior to cover before we're done—the HeaderFooter behavior.

Adding Headers and Footers with the HeaderFooter Behavior

The HeaderFooter behavior is a conversion tool. It takes header and footer formatting strings, typically from the Page Setup dialog box, and generates HTML that you can insert in the DEVICERECT elements of a print template. You don't have to use the HeaderFooter behavior to add headers and footers. In fact, you don't have to add headers and footers at all, or you can add them using your own custom scheme. The HeaderFooter behavior is useful for generating HTML based on the user's header/footer settings in the Page Setup dialog box and in conformance with standard Internet Explorer style.

To use the HeaderFooter behavior, you must first include it in a print template. The syntax is the same as the TemplatePrinter:

<IE:HEADERFOOTER ID="headfoot"/>

To generate the HTML you need for the headers and footers, set the HeaderFooter behavior's textHead and textFoot properties to the TemplatePrinter behavior's header and footer properties. These two properties of the TemplatePrinter behavior contain the header and footer formatting strings from the Page Setup dialog box. These strings will create headers and footers based on the user's specifications. If you want to override the user's header and footer settings, set the textHead and textFoot properties with your own header and footer formatting strings. Here is how you might prepare the HeaderFooter behavior to generate the header and footer HTML for the first page of a document. The date and time are set automatically by the HeaderFooter behavior.

headfoot.textHead = printer.header;
headfoot.textFoot = printer.footer;
headfoot.url = dialogArguments.__IE_BrowseDocument.URL;
headfoot.title = dialogArguments.__IE_BrowseDocument.title;
headfoot.page = 1;

Now you can retrieve the generated HTML by accessing the htmlHead and htmlFoot properties of the HeaderFooter behavior—then insert them in the appropriate DEVICERECT.

newHeader = "<DIV CLASS='headerstyle'>" + headfoot.htmlHead + "</DIV>";
newFooter = "<DIV CLASS='footerstyle'>" + headfoot.htmlFoot + "</DIV>";
    
document.all("page1").insertAdjacentHTML("afterBegin", newHeader); 
document.all("page1").insertAdjacentHTML("beforeEnd", newFooter);

You can see that this example wraps the generated HTML in a DIV with an attached style. Typically, the styles for headers and footers are absolutely positioned so they can be placed precisely on the pages of a document. The placement is usually determined by the unprintableXxx and marginXxx properties of the TemplatePrinter behavior.

You might notice one thing is missing from the HeaderFooter settings, the pageTotal. To deal with the page total, we have to answer a question first:

When is the best time to add headers and footers to the DEVICERECT elements of a document?

The page total is not known until all the DEVICERECT elements you need have been created. It might seem, therefore, that you should add the headers and footers after the pages are all created and the page total is known. The problem is that even if DEVICERECT elements are absolutely positioned, adding headers and footers to them after they've been created will cause the LAYOUTRECT elements to re-flow the source document, which is somewhat inefficient. The bigger problem, however, is that the onlayoutcomplete event will fire again during the re-flow. Since adding the headers and footers will probably be initiated from the onlayoutcomplete handler (once the contentOverflow property is false), you can end up in an infinite loop if you're not careful. It's easy enough to prevent this, but you can also avoid the issue entirely by adding the headers and footers—without the page total—when the DEVICERECT elements are first created. Once all the DEVICERECT elements have been generated and the page total is known, the template can insert the page total in the header or footer without causing a LAYOUTRECT re-flow. The following function shows one way you might do this:

function AddPageTotalToPages()
{
    oDeviceRectCollection = document.all.tags("DEVICERECT");
    headfoot.pageTotal = oDeviceRectCollection.length;

    oSpanCollection = document.all.tags("SPAN");
    for (i = 0; i < oSpanCollection.length; i++)
    {
        if (oSpanCollection[i].className == "hfPageTotal")
            oSpanCollection[i].innerText = headfoot.pageTotal;
    }
}

The HeaderFooter behavior generates HTML that uses SPAN elements to separate the different parts of the headers and footers. The SPAN element for the page total has a class called "hfPageTotal" attached to it. The function AddPageTotalToPages first retrieves a collection of the document's DEVICERECT elements and stores the collection's length in the pageTotal property of the HeaderFooter behavior. Then the function retrieves a collection of the document's SPAN elements and checks each SPAN to see if it has the class hfPageTotal attached. If so, the function sets the SPAN's innerText to the pageTotal.

Safety Considerations When Using Print Templates

You're now familiar with the four print template behaviors and can use them. Before you do, however, spend a few moments considering the important topic of security as it applies to print templates.

Security precautions are built into the four print template behaviors. The behaviors have been implemented so that they only work in the context of a print template. If they are used on an ordinary Web page, their functionality is disabled. You cannot build a Web page that will print or preview through a print template without the help of a binary control of some kind embedded on the page.

Typically, an application or ActiveX control has already been granted considerable access to a user's computer and file system. Since print templates can only be used from C++ through a control or application, using them does not add any security issues above those normally associated with application or control development. However, you should guard against one scenario. An ActiveX control, binary behavior or other embedded binary object on a Web page should not provide scriptable methods that can load an arbitrary print template for use by the control. Such a control could be co-opted to load unsafe or malicious print templates.

Cool Things You Can Do

Now that you have an idea of print template basics, it's time to look at where you can go with them, including some of the additional properties the print template architecture provides and some ideas for using print templates.

Printing the Current Selection

In case you haven't realized it already, the attributes on the LAYOUTRECT and DEVICERECT elements are also properties that can be set from script. This gives you the chance to do some neat things. For one, the dialogArguments object passed to a print template when it loads includes an __IE_ContentSelectionUrl property. This property provides a path to a temporary HTML file that contains the current selection in Internet Explorer or the WebBrowser control. You can use this temporary HTML file to print the current selection, as the following code shows.

layoutrect1.contentSrc = dialogArguments.__IE_ContentSelectionUrl;

Boilerplate Content and Multiple Content Sources

The content of DEVICERECT elements isn't limited to LAYOUTRECT elements—you can add other HTML elements to them as well. You could use this feature to add boilerplate content, like a company logo or a copyright notice, to printing. You' re not limited to a single LAYOUTRECT element chain in a print template either. You can have more than one if you choose. Use this feature, for instance, to print multiple frames in a customized layout, or as another way to add boilerplate content to a printed page.

Access to the Source Document

The __IE_BrowseDocument property of the dialogArguments object gives a print template access to the document object for the current document in Internet Explorer or the WebBrowser control. In some circumstances, this property may be null, for instance, if the document specifies an alternate URL for printing or if the browser navigates away from the page being printed. You can drill down into this object to access any information in the document. You could, for instance, extract information about the headings in a document to construct a table of contents.

Using Zoom

You can use the CSS zoom property to change how pages are displayed in print preview mode and how the contents are scaled when printed.

If you want to change the scale of pages in print preview, apply the zoom property to a DIV or other containing element that holds the DEVICERECT elements of the print template. Don't apply the zoom property directly to the DEVICERECT elements. Doing so will cause them to scale in print preview, but will also cause them to scale during printing, which is probably what you don't want (assuming that the DEVICERECT elements' style has been set to the paper size).

If you want to scale the actual printing, it's better to apply the zoom property to the LAYOUTRECT elements directly or indirectly with a containing element. This will change the scale of the actual printed content and alter how that content is displayed in print preview.

Endless Printing Possibilities

This article has covered most of what you need to know to use print templates. However, it hasn't described how to create a print preview user interface. Because a print template doesn't have a default user interface, you must write the template to generate whatever user controls you want to provide. The next article, Print Preview 2: The Continuing Adventures of Internet Explorer 5.5 Print Customization, looks at some of the requirements and performance issues that come up while developing the UI controls for print preview. It also looks at creative ways to extract content from a source document for printing.

Chuck Ainslie is a Programmer/Writer on the Internet Explorer SDK documentation team, and seldom complains about the food at the Microsoft cafeterias. When he's not tormenting his officemates with Wagnerian opera, you can find him at one of Seattle's tango venues (he's very smooth on the dance floor).