Working with HTML

PDF vs. HTML

Before we discuss how to define page layouts with xhtml2pdf style sheets, it helps to understand some of the inherent differences between PDF and HTML.

PDF is specifically designed around pages of a specific width and height. PDF page elements (such as paragraphs, tables and images) are positioned at absolute (X,Y) coordinates.

Note

While true PDF files use (0,0) to denote the bottom left of a page, xhtml2pdf uses (0,0) to denote the top left of a page, partly to maintain similarity with the HTML coordinate system.

HTML, by itself, does not have the concept of pagination or of pages with a specific width and height. An HTML page can be as wide as your browser width (and even wider), and it can be as long as the page content itself. HTML page elements are positioned in relationship to each other and may change when the browser window is resized.

In order to bridge the differences between HTML and PDFs, xhtml2pdf makes use of the concept of Pages and Frames. Pages define the size, orientation and margins of pages. Frames are rectangular regions with in each page.

The Frame location is specified in absolute (X,Y) coordinates, while the Frame content is used to flow HTML content using the relative positioning rules of HTML. This is the essence from which the power of xhtml2pdf stems.

HTML content will start printing in the first available content frame. Once the first frame is filled up, the content will continue to print in the next frame, and the next, and so on, until the entire HTML content has been printed.

Defining Page Layouts

xhtml2pdf facilitates the conversion of HTML content into a PDF document by flowing the continuous HTML content through one or more pages using Pages and Frames. A page represents a page layout within a PDF document. A Frame represents a rectangular region within a page through which the HTML content will flow.

Pages

The @page object defines a Page template by defining the properties of a page, such as page type (letter or A4), page orientation (portrait or landscape), and page margins. The @page definition follows the style sheet convention of ordinary CSS style sheets:

<style>
    @page {
        size: letter landscape;
        margin: 2cm;
    }
</style>

Frames

The @frame object defines the position and size of rectangular region within a page. A @page object can hold one or more @frame objects. The @frame definition follows the style sheet convention of ordinary CSS style sheets:

Here’s a definition of a page template with one Content Frame. It makes use of the Letter page size of 612pt x 792pt.

<style>
    @page {
        size: letter portrait;
        @frame content_frame {
            left: 50pt;
            width: 512pt;
            top: 50pt;
            height: 692pt;
            -pdf-frame-border: 1;    /* for debugging the layout */
        }
    }
</style>

This will result in the following page layout:

+-page----------------+
|                     |
|  +-content_frame-+  |
|  |               |  |
|  |               |  |
|  |               |  |
|  |               |  |
|  +---------------+  |
|                     |
+---------------------+

Note

To visualize the page layout you are defining, add the -pdf-frame-border: 1; property to your each of your @frame objects.

Static frames vs Content frames

xhtml2pdf uses the concept of Static Frames to define content that remains the same across different pages (like headers and footers), and uses Content Frames to position the to-be-converted HTML content.

Static Frames are defined through use of the @frame property -pdf-frame-content. Regular HTML content will not flow through Static Frames.

Content Frames are @frame objects without this property defined. Regular HTML content will flow through Content Frames.

Note

A Static Frame is repainted from scratch on every page and has no continuation, so content taller than the frame has nowhere to go. Rather than dropping it – which used to lose the logo at the end of a header without a word – xhtml2pdf scales the whole frame down to fit and logs a warning naming the frame:

The content of the static frame 'header_frame' needs 122.3 pt of
height and the @frame is 113.4 pt tall, so it was fitted with
-pdf-keep-in-frame-mode: shrink. Give the @frame more height.

Shrinking is a rescue, not a layout tool: a frame that warns is a frame whose declared height is too small. Declare -pdf-keep-in-frame-mode on the @frame to ask for something else – truncate clips at the frame boundary, overflow draws past it, and error leaves the frame unpainted.

Note

There is no page: name property. The page template is chosen with <pdf:nextpage name=""> or with the -pdf-next-page property; page appears often in stylesheets written for other engines and does nothing here.

Example with 2 Static Frames and 1 Content Frame

<html>
<head>
<style>
    @page {
        size: a4 portrait;
        @frame header_frame {           /* Static Frame */
            -pdf-frame-content: header_content;
            left: 50pt; width: 512pt; top: 50pt; height: 40pt;
        }
        @frame content_frame {          /* Content Frame */
            left: 50pt; width: 512pt; top: 90pt; height: 632pt;
        }
        @frame footer_frame {           /* Another static Frame */
            -pdf-frame-content: footer_content;
            left: 50pt; width: 512pt; top: 772pt; height: 20pt;
        }
    }
</style>
</head>

<body>
    <!-- Content for Static Frame 'header_frame' -->
    <div id="header_content">Lyrics-R-Us</div>

    <!-- Content for Static Frame 'footer_frame' -->
    <div id="footer_content">(c) - page <pdf:pagenumber>
        of <pdf:pagecount>
    </div>

    <!-- HTML Content -->
    To PDF or not to PDF
</body>
</html>

In the example above, the vendor-specific tags <pdf:pagenumber> and <pdf:pagecount> are used to display page numbers and the total page count. This example will produce the following PDF Document:

+-page------------------+
| +-header_frame------+ |
| | Lyrics-R-Us       | |
| +-------------------+ |
| +-content_frame-----+ |
| | To PDF or not to  | |
| | PDF               | |
| |                   | |
| |                   | |
| +-------------------+ |
| +-footer_frame------+ |
| | (c) - page 1 of 1 | |
| +-------------------+ |
+-----------------------+
# Developer's note:
# To avoid a problem where duplicate numbers are printed,
# make sure that these tags are immediately followed by a newline.

A Static Frame on the last page only

-pdf-frame-pages on a Static Frame says which pages it is painted on: all (the default), last for the last page only, or not-last for every page but the last. A signature block or a closing footer goes on the last page whatever the length of the content, and a “continued on the next page” note on the others:

<style>
    @page {
        margin: 2cm 2cm 4cm 2cm;
        @frame signature_frame {
            -pdf-frame-content: signature;
            -pdf-frame-pages: last;
            left: 2cm; width: 10cm; bottom: 1cm; height: 2.5cm;
        }
        @frame continued_frame {
            -pdf-frame-content: continued;
            -pdf-frame-pages: not-last;
            left: 2cm; width: 10cm; bottom: 1cm; height: 1cm;
        }
    }
</style>
<div id="signature">Signed: ______________________</div>
<div id="continued">Continued on the next page.</div>

Which page is the last is only known once the document has been laid out, so a frame with last or not-last makes xhtml2pdf lay it out twice, as <pdf:pagecount> does.

A block at the bottom of the page

-pdf-frame-align: bottom on a block puts it at the foot of the frame it lands in, after the content before it. It is the other way to end a document with a signature: written last, the block sits at the bottom of the last page, and when it does not fit below the content it moves to the bottom of the next page, whole. The block takes the room above it too, so whatever follows it starts on the next page.

<style>
    .signature { -pdf-frame-align: bottom; border-top: 1px solid black; }
</style>
<p>... the contract ...</p>
<div class="signature">
    <p>Signed: ______________________</p>
    <p>Date: ______________________</p>
</div>

Flowing HTML content through Content Frames

Content frames are used to position the HTML content across multiple pages. HTML content will start printing in the first available Content Frame. Once the first frame is filled up, the content will continue to print in the next frame, and the next, and so on, until the entire HTML content has been printed. This concept is illustrated by the example below.

Advanced concepts

Keeping text and tables together

You can prevent a block of text from being split across separate frames through the use of the vendor-specific -pdf-keep-with-next property.

Here’s an example where paragraphs and tables are kept together until a ‘separator paragraph’ appears in the HTML content flow.

<style>
    table { -pdf-keep-with-next: true; }
    p { margin: 0; -pdf-keep-with-next: true; }
    p.separator { -pdf-keep-with-next: false; font-size: 6pt; }
</style>
  ...
<body>
    <p>Keep these lines</p>
    <table><tr><td>And this table</td></tr></table>
    <p>together in one frame</p>

    <p class="separator">&nbsp;<p>

    <p>Keep these sets of lines</p>
    <p>may appear in a different frame</p>
    <p class="separator">&nbsp;<p>
</body>

Named Page templates

Page templates can be named by providing the name after the @page keyword

@page my_page {
    margin: 40pt;
}

Switching between multiple Page templates

PDF documents sometimes requires a different page layout across different sections of the document. xhtml2pdf allows you to define multiple @page templates and a way to switch between them using the vendor-specific tag <pdf:nexttemplate>.

As an illustration, consider the following example for a title page with large 5cm margins and regular pages with regular 2cm margins.

<html>
<head>
<style>
    @page title_template { margin: 5cm; }
    @page regular_template { margin: 2cm; }
</style>
</head>

<body>
    <!-- the first page -->
    <pdf:nexttemplate name="title_template" />
    <h1>Title Page</h1>
    This is a title page with a large 5cm margin.

    <!-- switch page templates: from the next page on -->
    <pdf:nexttemplate name="regular_template" />
    <pdf:nextpage />

    <h1>Chapter 1</h1>
    This is a regular page with a regular 2cm margin.
</body>
</html>

<pdf:nexttemplate> takes effect at the next page break, which is why the switch is followed by <pdf:nextpage />; without one, the new template waits for the page the content overflows onto.

What the first page is:

  • With an unnamed @page, the first page is that one. A <pdf:nexttemplate> before any content then means what it says, the next page – the way to have a cover on the unnamed template and every page after it on another. @page body is the unnamed @page, written by its name: with it, a leading <pdf:nexttemplate> also waits for the second page, and a warning says so. Give the rule another name for the template a leading <pdf:nexttemplate> names to be the first page’s.

  • Without one, a <pdf:nexttemplate> written before any content, as above, names the first page’s template. Otherwise the first page is a default one, A4 with 1cm margins.

Each @page starts from the size of the unnamed one (A4 if there is none), so a named template’s size changes that template only.

Demonstration

Pages, frames and static content, over several pages.

format_html.html
<html>
<head>
    <title>Pages, frames and static content</title>
    <style>
        /* The first page: a static header, a static footer and a content
           frame between them. Content flows through the content frame only;
           the static frames repeat unchanged on every page that uses this
           template. */
        @page {
            size: a4 portrait;
            @frame header_frame {
                -pdf-frame-content: header_content;
                left: 50pt; width: 495pt; top: 40pt; height: 40pt;
            }
            @frame content_frame {
                left: 50pt; width: 495pt; top: 90pt; height: 660pt;
                -pdf-frame-border: 1;      /* draw the frame, to show where it is */
            }
            @frame footer_frame {
                -pdf-frame-content: footer_content;
                left: 50pt; width: 495pt; top: 772pt; height: 20pt;
            }
        }

        /* A second, landscape template, reached with <pdf:nexttemplate>. */
        @page wide {
            size: a4 landscape;
            @frame content_frame {
                left: 50pt; width: 742pt; top: 50pt; height: 495pt;
                -pdf-frame-border: 1;
            }
        }

        body { font-family: Helvetica; font-size: 11pt; }
        h1 { font-size: 150%; margin-bottom: 0.4em; }
        #header_content { border-bottom: 1px solid #888; font-weight: bold; }
        #footer_content { border-top: 1px solid #888; font-size: 8pt; color: #555; }
        .filler { color: #666; }
    </style>
</head>
<body>
    <div id="header_content">Pages and Frames</div>
    <div id="footer_content">page <pdf:pagenumber> of <pdf:pagecount></div>

    <h1>One frame, filled until it overflows</h1>

    <p>
        The bordered rectangle is the content frame. Everything on this page is
        inside it, and the header and footer above and below are separate
        static frames that this text never enters.
    </p>

    <p>
        When the content frame runs out of room the converter starts a new page
        using the same template, so the header and footer come back with it.
        The paragraphs below are here to make that happen.
    </p>

    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>
    <p class="filler">
        To PDF or not to PDF, that is the question. Whether 'tis nobler in the
        mind to suffer the slings and arrows of outrageous pagination, or to
        take arms against a sea of frames, and by opposing end them.
    </p>

    <pdf:nexttemplate name="wide">
    <pdf:nextpage>

    <h1>A second template</h1>

    <p>
        This page uses the <code>wide</code> template: landscape, one frame,
        and no static header or footer, because that template declares none.
        <code>&lt;pdf:nexttemplate&gt;</code> chose it and
        <code>&lt;pdf:nextpage&gt;</code> moved here.
    </p>
</body>
</html>