Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Printed parent window of iframed pages causes odd scaling

I was tasked with combining several HTML documents into a single page for printing. My first attempt failed miserably - I tried to isolate the page contents each within their own <div> tag and stylesheet rules modified to match. My second attempt, using iframes to isolate each document, looks significantly better, but the pages are enlarged when printing from the parent frame of the iframe stack versus printing the documents individually in their own windows.

Here's an example of the documents I'm working with: http://dl.dropbox.com/u/291229/print-test/index.html

You can do a print preview in Firefox to see what I mean. If you open the first frame in its own tab/window and print preview, the outlined boxes fit well within the page. Doing the same in the parent window of the iframe stack shows the cells flowing outside the page bounds.

Disclaimer: I did not code these pages. Yes, I know they are horrendous. Unfortunately, the project doesn't have the time or budget to redo the pages in such a way that would fulfill the goal of a single page for printing a set of documents. I may temporarily require users to print each page separately for proper scaling, but I'd still like to understand what might be causing this issue.

like image 392
Kevin Avatar asked Jul 11 '11 06:07

Kevin


1 Answers

The problem is that browsers use the width of the container iframe to determine how to scale the contents of that frame for printing. Therefore, the frames need to have their widths explicitly determined.

Unfortunately, dynamically figuring out the print widths is pretty tricky and involves some hackery. You might be better off just styling a fixed width of component pages and hard-coding the iframe width to that.

However, if you really need to dynamically size them, you can run the following code after the document has loaded (tested in Chrome):

var iframes = document.getElementsByTagName("iframe");
for(var i = 0; i < iframes.length; ++i) {
    var curFrame = iframes[i];
    var curBody = curFrame.contentDocument.body;
    curBody.innerHTML = '<div id="iframe-print-content" style="display:inline-block; overflow:hidden; white-space: nowrap;">'
                      +  curBody.innerHTML + '</div>';
    var printContent = curFrame.contentDocument.getElementById("iframe-print-content");
    var curWidth = printContent.offsetWidth + printContent.offsetLeft;
    iframes[i].style.width=(curWidth + "px");
}

After running this, you can print normally or call window.print() or do whatever you want.


Note: This method won't quite work on ie6 or ie7, since they don't support inline-block. (There are also a few other older browsers that also do not). You can, of course, try

display:-moz-inline-stack; display:inline-block; zoom:1; *display:inline; overflow:hidden; white-space: nowrap;

instead, which will probably take care of most of these cases, but I make no promises for older browsers.

Good luck, sorry to hear you got stuck with the code on those pages.




Edit: The above solution is a bit wonky in Firefox.

Solution for Firefox (tested & working in Firefox 5):

var iframes = document.getElementsByTagName("iframe");
for(var i = 0; i < iframes.length; ++i) {
    var curFrame = iframes[i];
    var curBody = curFrame.contentDocument.body;
    var oldHTML = curBody.innerHTML;
    curBody.innerHTML = '<div id="iframe-print-content" style="display:inline-block; overflow:hidden;">' +  oldHTML + '</div>';
    var printContent = curFrame.contentDocument.getElementById("iframe-print-content");
    var curWidth = printContent.offsetWidth + printContent.offsetLeft + 25;
    var curHeight = printContent.offsetHeight + printContent.offsetTop + 25;
    curBody.innerHTML = oldHTML;
    iframes[i].style.width=(curWidth + "px");
    iframes[i].style.height=(curHeight + "px");
}

For a complete explanation of why this general approach works, see my answer above.

What is different from the solution above, and why?

First, the Firefox specifics:
There are a few key differences. The first is noticing that Firefox handles display: inline-block and white-space: nowrap differently than Chromium/WebKit for fixed-width elements (which is why one document looked really wonky with the previous code). Next is noticing that Firefox likes to give a little bit of margin inside its iframes. I'm not very familiar with the Firefox code that does this (spend my days hacking on Chromium) but my usual solution (aside from avoiding frames) is to just give an extra 25px on the margins. A little sum like this seems to be a popular hack to fix this kind of problem.

Now, the parts of the code that are actually better:
First, this now stores the old body, lifts the body onto a div to take measurements, then restores the old body. This should avoid problems with inflexible CSS and should guarantee more predictable behavior. Second, this now does the same thing for the height as the width (my previous assumption that the hard-coded widths would just work was no good, and this is a more flexible solution anyway).

Why is this still bad?

Taking away the white-space: nowrap will make text wrap in printing on Chromium/WebKit. It still prints fine, but it isn't exactly right, since some extra text will wrap. To make this a truly workable solution, you still have to do browser-detect code.

Again, I recommend using this code to quickly figure out the appropriate widths and heights, then to test and hard-code the right sizes for each browser. It's an ugly solution, but it is ultimately the only truly robust solution in this situation.

like image 137
Alex Churchill Avatar answered Nov 15 '22 00:11

Alex Churchill