Custom page design

If you find that the stylesheet parameters used in the DocBook stylesheets are not sufficient for meeting your page design needs, you can create your own custom page masters and use them in your documents.

Default page masters

Before creating custom page masters, it helps to understand the set up of the default page masters so you can see how to fit your customizations in.

In DocBook XSL-FO, a standard set of FO simple-page-master elements are declared and used. The page masters are grouped into page classes, one for each specialized type of page such as title page or body. The six page classes are listed in the following table. Within each class, there are different page masters to handle the sequence of pages of that type, such as first, left, and right pages. There is a page-sequence-master element for each page class to set the conditions for sequencing the group of page masters. The following is a list of the built-in page masters.

Table 13.4. DocBook XSL-FO page master names

Page ClassUsed bysimple-page-master nameDescription
titlepageTitle pages for set, part and book elements.titlepage-firstFirst title page
titlepage-oddRight-hand title pages
titlepage-evenLeft-hand title pages
lotLists of titles, including table of contents and lists of figures, tables, examples, or procedures.lot-firstFirst page of table of contents or list of titles such as figures, tables, examples or procedures that appear at front of book.
lot-oddRight-hand list of titles
lot-evenLeft-hand list of titles
frontUsed by these elements:
dedication
preface
front-firstFirst page of new page sequence in book front matter, used for dedication and preface.
front-oddRight-hand front matter page.
front-evenLeft-hand front matter page.
bodyUsed by these elements:
chapter
article
reference
refentry
section (if root element)
sect1   (if root element)
body-firstFirst page of new page sequence in body of document, such as chapters.
body-oddRight-hand body page.
body-evenLeft-hand body page.
backUsed by these elements:
appendix
bibliography
colophon
glossary
back-firstFirst page of new page sequence in book back matter, which includes appendix, glossary, bibliography, and colophon (but not index).
back-oddRight-hand back matter page.
back-evenLeft-hand back matter page.
indexBook or set indexindex-firstFirst page of index.
index-oddRight-hand index page.
index-evenLeft-hand index page.
  blankBlank page that may be automatically inserted at the end of a page sequence to even out the page count. It can be used in all page-sequence-masters.

Note

In each page class, there is a second set of page masters with the same names except the names all end in -draft, such as titlepage-first-draft. These are used when a document is processed in draft mode, which happens when you set the draft.mode parameter to a value other than no.

Each of the page-master-sequence and simple-page-master elements is defined in the fo/pagesetup.xsl stylesheet file. The page-master-sequence elements are used to set the conditions under which different page masters are selected, such as the first page or even page number. The simple-page-master elements define the margins of the page, using the stylesheet parameters. The following example shows a complete page master declaration:

Example 13.10. Page master declaration

<fo:simple-page-master master-name="titlepage-even" 1
                       page-width="{$page.width}"  2
                       page-height="{$page.height}"
                       margin-top="{$page.margin.top}"
                       margin-bottom="{$page.margin.bottom}"
                       margin-left="{$page.margin.inner}"
                       margin-right="{$page.margin.outer}">
    <fo:region-body margin-bottom="{$body.margin.bottom}"  3
                    margin-top="{$body.margin.top}"
                    column-count="{$column.count.titlepage}">
    </fo:region-body>
    <fo:region-before region-name="xsl-region-before-even"  4
                      extent="{$region.before.extent}"
                      display-align="before"/>
    <fo:region-after region-name="xsl-region-after-even"  5
                     extent="{$region.after.extent}"
                     display-align="after"/>
</fo:simple-page-master>

1

The master-name attribute indicates that this page master is for even-numbered title pages.

2

The attributes set the page size and margins using stylesheet parameter values.

3

The region-body child sets the margins for the body area on the page.

4

The region-before child element sets the height of the header area. The display-align attribute puts the header text at the top of the header area.

5

The region-after child element sets the height of the footer area, with the text aligned at the bottom of the footer area.


The DocBook FO stylesheets do not call the page masters directly, but instead call the page sequence masters, which then call the appropriate page masters as needed. The following is an example of a complete page sequence declaration:

Example 13.11. Page sequence declaration

<fo:page-sequence-master master-name="titlepage">  1
    <fo:repeatable-page-master-alternatives>  2
        <fo:conditional-page-master-reference 
                  master-reference="blank"  3
                  blank-or-not-blank="blank"/>  4
        <fo:conditional-page-master-reference 
                  master-reference="titlepage-first"  5
                  page-position="first"/>  6
        <fo:conditional-page-master-reference 
                  master-reference="titlepage-odd"
                  odd-or-even="odd"/>
        <fo:conditional-page-master-reference 
                  master-reference="titlepage-even"
                  odd-or-even="even"/>
    </fo:repeatable-page-master-alternatives>
</fo:page-sequence-master>

1

The stylesheet calls this master-name when setting up a page sequence for title pages.

2

Wrapper element for a set of conditions that select a particular page master when its conditions are met.

3

Calls the page master named blank when its conditions are met.

4

The value of blank for this property indicates that this page master is to be used if a page must be generated but there is no content from the flow to put on it. This occurs when the page sequence properties are set to end on an even page number, but the last of content happens to be on an odd page number.

5

Calls the page master named titlepage-first when its conditions are met.

6

The value of first for the page-position property indicates that this page master is to be used for the first page of a new titlepage sequence. The other conditions follow a similar pattern for odd-numbered and even-numbered pages.


Declaring custom page masters

The FO stylesheets let you set up your own page masters while leaving the original default page masters in place. To do so, you create an XSL template named user.pagemasters in your customization layer. In it, you add as many fo:simple-page-master and fo:page-sequence-master elements as you need. The names must be different from the default names, which means each master-name attribute must be unique and different from those already declared. Then you add any page properties you need as attributes to the simple-page-master element. The user.pagemasters template is automatically called by the template that sets up the default page masters. So all you have to do is define it and your page masters will become part of your FO output files, ready to be used in page sequences.

The easiest way to make your own page masters is to cut and paste from the original fo/pagesetup.xsl stylesheet file to your customization, and then change the name and property attributes. You will most likely want to copy a page-sequence-master and its related simple-page-master elements so you can have proper handling of first, left, and right pages.

In your property attributes for your custom page masters, you can refer to any of the existing stylesheet parameter values, or define your own parameters in your customization layer. You can also use any of them in expressions, so you can adjust the regular value by increments. There are many simple-page-master property attributes that you can add as well. You will need a good FO reference or the XSL-FO specification to know what attribute names and values can be used. Keep in mind that not all property attributes are supported by all XSL-FO processors.

Using custom page masters

Declaring custom page masters just makes them available to be used, but does not employ them in page sequences. To do that, you have to add some logic to the stylesheets so your custom page masters are selected instead of the default page masters. That logic is put into a template named select.user.pagemaster that you add to your customization layer. The way this works is as follows:

  1. The DocBook elements that start a page sequence call the template named select.pagemaster. It is supposed to return a master-name for a page-sequence-master such as titlepage or body. That template's first step is to select the appropriate default page master name for that element.

  2. Then the select.pagemaster template automatically calls the select.user.pagemaster template, passing the selected default master name as a parameter.

  3. The default version of the select.user.pagemaster template simply returns the default page master. But if you customize that template, then it can return the master name of one of your customized page-sequence-masters in place of the default.

The following is an example of a customized template that selects a new name if for titlepage, otherwise returns the default:

<xsl:template name="select.user.pagemaster">
  <xsl:param name="element"/>
  <xsl:param name="pageclass"/>
  <xsl:param name="default-pagemaster"/>

  <!-- Return my customized title page master name if for titlepage,
       otherwise return the default -->

  <xsl:choose>
    <xsl:when test="$default-pagemaster = 'titlepage'">
      <xsl:value-of select="'my-new-titlepage'" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$default-pagemaster"/>
    </xsl:otherwise>
  </xsl:choose>
  </xsl:template>

You could also use the element name or the pageclass parameter value in the conditions for choosing a page master.

Landscape pages

There are three levels at which landscape (horizontally oriented) pages can be used:

Landscape page sequence

If you want an individual chapter or appendix to be presented in landscape mode, then you can accomplish that with a new page master. In fact, this can be done for any element that generates a page-sequence in XSL-FO:

appendix
article
bibliography*
chapter
colophon
dedication
glossary*
index
preface
refentry*
reference

* Except when it  is contained inside an element that generates a page-sequence.

To put an element into a landscape page-sequence, you have to do two things:

  1. Set up a new XSL-FO page-sequence-master with landscape specifications.

  2. Apply the page-sequence-master to your selected element.

To set up a new page-sequence-master, you define it in your customization layer inside a template named user.pagemasters. Then you customize the template named select.user.pagemaster to apply it.

There are two methods to handle landscape in a page sequence:

  • Add a reference-orientation="90" attribute to the fo:region-body of custom page-masters. This option works best for printed output.

  • Swap the vertical and horizontal page dimensions in custom page-masters. This option works best for viewing output in a PDF viewer.

See the following sections for details of each method.

Rotated body-region

This method of rotating the content of a page-sequence is the easiest. It works best for printed out. That's because it leaves the headers and footers in their portait positions and rotates only the body content on the page. When printed, page numbers and running header and footer titles are in consistent positions on all pages. But when viewed in a PDF browser, the body content will be rotated, and the user will have to select an option in the PDF viewer to unrotate the pages for reading.

In XSL-FO, a page has five primary regions that can hold content. These are the top and bottom margin areas, the left and right margin areas, and the center body area. See the following table.

LocationRegion name
Top margin areafo:region-before
Bottom margin areafo:region-after
Left margin areafo:region-start
Right margin areafo:region-end
Center body areafo:region-body

Note

If your document is set up for text that reads right-to-left as in Hebrew or Arabic, the left area is fo:region-end and the right area is fo:region-start.

To use this method, you add a reference-orientation="90" attribute to the fo:region-body element in a custom page-master declaration. That rotates only the center body area, leaving the headers and footers in their original locations.

  1. Create a template named user.pagemasters in your customization layer.

  2. Locate the set of fo:simple-page-master elements for body in fo/pagesetup.xsl. You will need those with master-name of body-first, body-odd, and body-even. Copy these elements into your user.pagemasters template.

  3. Change the master-name values in your copies so they do not conflict with the originals. For example, use landscape-first instead of body-first, etc.

  4. In each fo:region-body element, add the reference-orientation="90" attribute.

    <fo:simple-page-master master-name="landscape-odd"
                               page-width="{$page.width}"
                               page-height="{$page.height}"
                               margin-top="{$page.margin.top}"
                               margin-bottom="{$page.margin.bottom}"
                               margin-left="{$margin.left.inner}"
                               margin-right="{$page.margin.outer}">
          <fo:region-body margin-bottom="{$body.margin.bottom}"
                          margin-top="{$body.margin.top}"
                          reference-orientation="90"
                          column-gap="{$column.gap.body}"
                          column-count="{$column.count.body}">
          </fo:region-body>
          ...
    </fo:simple-page-master>
  5. Also in your user.pagemasters template, copy the fo:page-sequence-master for body, and edit it to change the master-name and to use your new simple page-master names, as follows:

        <fo:page-sequence-master master-name="landscape">
          <fo:repeatable-page-master-alternatives>
            <fo:conditional-page-master-reference master-reference="blank"
                                                  blank-or-not-blank="blank"/>
            <fo:conditional-page-master-reference master-reference="landscape-first"
                                                  page-position="first"/>
            <fo:conditional-page-master-reference master-reference="landscape-odd"
                                                  odd-or-even="odd"/>
            <fo:conditional-page-master-reference
                                                  odd-or-even="even">
              <xsl:attribute name="master-reference">
                <xsl:choose>
                  <xsl:when test="$double.sided != 0">landscape-even</xsl:when>
                  <xsl:otherwise>landscape-odd</xsl:otherwise>
                </xsl:choose>
              </xsl:attribute>
            </fo:conditional-page-master-reference>
          </fo:repeatable-page-master-alternatives>
        </fo:page-sequence-master>
    
  6. Once the page-sequence-master has been set up this way, you customize the template named select.user.pagemaster to employ it for certain elements. For example, if you intend to use role="landscape" on a chapter or appendix element to rotate it, then you would use the following:

    <xsl:template name="select.user.pagemaster">
      <xsl:param name="element"/>
      <xsl:param name="pageclass"/>
      <xsl:param name="default-pagemaster"/>
    
      <xsl:choose>
        <xsl:when test="@role = 'landscape'">landscape</xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="$default-pagemaster"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>
    

The select.user.pagemaster template is automatically called when the stylesheet is selecting a page-master for an element. Normally it just selects the default, but in this case it will select your custom page-master and rotate the content in the body region.

Landscape page dimensions

A different approach to landscape output is to change the page dimensions to landscape, without rotating any content. Of course, you can change an entire document to landscape by setting the stylesheet parameter page.orientation="landscape". But if you only want a chapter or appendix in landscape, you have to do it with a custom page-master.

When you use this method, none of the content is rotated. When you view the output in a PDF browser, the pages will simply switch from tall to wide, with all the content remaining upright. The headers and footers will still be at the top and bottom, but they will be longer. That output style makes this method the most convenient for users consuming the output using a PDF reader instead of through printed output.

If you print such a PDF document, most (perhaps not all) PDF browsers know to rotate the landscape pages to fit a portrait-oriented printed page. But the headers and footers will also rotate. When the printed pages are assembled into a portrait document, the headers and footers on the landscape pages will be on the sides instead of top and bottom.

To use this method, copy the declaration for body pages into a template named user.pagemasters in your customization layer and swap the horizontal and vertical dimensions. The following example highlights the changes necessary.

<xsl:template name="user.pagemasters">
  <!-- landscape body pages -->
  <fo:simple-page-master master-name="body-first-landscape"
                         page-width="{$page.height}"
                         page-height="{$page.width}"
                         margin-top="{$margin.left.inner}"
                         margin-bottom="{$page.margin.outer}"
                         margin-left="{$page.margin.bottom}"
                         margin-right="{$page.margin.top}">
    <fo:region-body margin-bottom="{$body.margin.bottom}"
                    margin-top="{$body.margin.top}"
                    column-gap="{$column.gap.body}"
                    column-count="{$column.count.body}">
    </fo:region-body>
    <fo:region-before region-name="xsl-region-before-first"
                      extent="{$region.before.extent}"
                      display-align="before"/>
    <fo:region-after region-name="xsl-region-after-first"
                     extent="{$region.after.extent}"
                     display-align="after"/>
  </fo:simple-page-master>
  <fo:simple-page-master master-name="body-odd-landscape"
                         page-width="{$page.height}"
                         page-height="{$page.width}"
                         margin-top="{$margin.left.inner}"
                         margin-bottom="{$page.margin.outer}"
                         margin-left="{$page.margin.bottom}"
                         margin-right="{$page.margin.top}">
    <fo:region-body margin-bottom="{$body.margin.bottom}"
                    margin-top="{$body.margin.top}"
                    column-gap="{$column.gap.body}"
                    column-count="{$column.count.body}">
    </fo:region-body>
    <fo:region-before region-name="xsl-region-before-odd"
                      extent="{$region.before.extent}"
                      display-align="before"/>
    <fo:region-after region-name="xsl-region-after-odd"
                     extent="{$region.after.extent}"
                     display-align="after"/>
  </fo:simple-page-master>
  <fo:simple-page-master master-name="body-even-landscape"
                         page-width="{$page.height}"
                         page-height="{$page.width}"
                         margin-top="{$page.margin.outer}"
                         margin-bottom="{$margin.left.inner}"
                         margin-left="$page.margin.bottom}"
                         margin-right="{$page.margin.top}">
    <fo:region-body margin-bottom="{$body.margin.bottom}"
                    margin-top="{$body.margin.top}"
                    column-gap="{$column.gap.body}"
                    column-count="{$column.count.body}">
    </fo:region-body>
    <fo:region-before region-name="xsl-region-before-even"
                      extent="{$region.before.extent}"
                      display-align="before"/>
    <fo:region-after region-name="xsl-region-after-even"
                     extent="{$region.after.extent}"
                     display-align="after"/>
  </fo:simple-page-master>
  <!-- blank pages -->
  <fo:simple-page-master master-name="blank-landscape"
                         page-width="{$page.height}"
                         page-height="{$page.width}"
                         margin-top="{$page.margin.outer}"
                         margin-bottom="{$margin.left.inner}"
                         margin-left="{$page.margin.bottom}"
                         margin-right="{$page.margin.top}">
    <fo:region-body display-align="center"
                    margin-bottom="{$body.margin.bottom}"
                    margin-top="{$body.margin.top}">
      <xsl:if test="$fop.extensions = 0 and $fop1.extensions = 0">
        <xsl:attribute name="region-name">blank-body</xsl:attribute>
      </xsl:if>
    </fo:region-body>
    <fo:region-before region-name="xsl-region-before-blank"
                      extent="{$region.before.extent}"
                      display-align="before"/>
    <fo:region-after region-name="xsl-region-after-blank"
                     extent="{$region.after.extent}"
                      display-align="after"/>
  </fo:simple-page-master>

  <fo:page-sequence-master master-name="body-landscape">
    <fo:repeatable-page-master-alternatives>
      <fo:conditional-page-master-reference master-reference="blank-landscape"
                                            blank-or-not-blank="blank"/        
      <fo:conditional-page-master-reference master-reference="body-first-landscape"
                                            page-position="first"
      <fo:conditional-page-master-reference master-reference="body-odd-landscape"
                                            odd-or-even="odd"/>
      <fo:conditional-page-master-reference
                                            odd-or-even="even">
        <xsl:attribute name="master-reference">
          <xsl:choose>
            <xsl:when test="$double.sided != 0">body-even-landscape</xsl:when>
            <xsl:otherwise>body-odd-landscape</xsl:otherwise>
          </xsl:choose>
        </xsl:attribute>
      </fo:conditional-page-master-reference>
    </fo:repeatable-page-master-alternatives>
  </fo:page-sequence-master>
</xsl:template>

To apply the new body-landscape master-name, you customize the template named select.user.pagemaster as described in the section “Rotated body-region”.

Then when you add role="landscape" to a chapter or appendix element, it will be output in landscape mode to the PDF file. A PDF viewer should display all the pages upright, with the landscape pages shown more wide than tall.

Landscape elements

The XSL-FO method of rotating a given element in an otherwise portrait page-sequence is to use the fo:block-container element. That element accepts a reference-orientation="90" property to rotate the content relative to the page. This method is used for the following:

Sometimes the result may not be quite what you expected. When a block-container with rotated content is placed on a portrait page, it is the width rather than the height of the content that determines how much vertical space it takes up. You may need to manually set the width on the DocBook element to get correct formatting.

Long landscape tables are a special problem in XSL-FO. The standard does not support flowing the content of a block-container from one page to the next. It is likely that a table longer than a page will simply be truncated. There is a workaround for this problem, though. G. Ken Holman of Crane Softwrights Ltd. has published a method for creating multipage landscape tables in XSL-FO. His Page Sequence Master Interleave (PSMI) method uses two passes to rearrange the pages in an FO file. PSMI is described at http://www.cranesoftwrights.com/resources/psmi/index.htm.

Custom page sequences

The top-level elements in DocBook generate one or more page sequences that contain their content. For example, the book template creates page sequences for the titlepage information, the table of contents, and then lets its chapter and appendix elements generate their own page sequences.

You may want to change how a top-level element creates page sequences. For example, an article element by default creates a single page sequence for all of its content, but you may want to divide that into more than one sequence. You could create a landscape sequence, or a multicolumn sequence for part of the output.

You can use a utility template named page.sequence to create your own page sequences. You can specify the page master name, the content, and the page numbering style as parameters passed to the template. The following example customizes the template for article to output the article body in a two-column layout while the titlepage and bibliography are in single column full page width. It uses the existing page master names titlepage, body, and back, but you could substitute your own custom page masters.

Example 13.12. Custom page sequences

<xsl:param name="column.count.titlepage" select="1" />
<xsl:param name="column.count.body" select="2" />
<xsl:param name="column.count.back" select="1" />

<xsl:template match="article">
  <xsl:variable name="id">
    <xsl:call-template name="object.id"/>
  </xsl:variable>

  <xsl:call-template name="page.sequence">
    <xsl:with-param name="master-reference">titlepage</xsl:with-param>
    <xsl:with-param name="content">
      <fo:block id="{$id}"
                xsl:use-attribute-sets="component.titlepage.properties">
        <xsl:call-template name="article.titlepage"/>
      </fo:block>

      <xsl:variable name="toc.params">
        <xsl:call-template name="find.path.params">
          <xsl:with-param name="table" select="normalize-space($generate.toc)"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:if test="contains($toc.params, 'toc')">
        <xsl:call-template name="component.toc">
          <xsl:with-param name="toc.title.p"
                          select="contains($toc.params, 'title')"/>
        </xsl:call-template>
        <xsl:call-template name="component.toc.separator"/>
      </xsl:if>
    </xsl:with-param>
  </xsl:call-template>

  <xsl:call-template name="page.sequence">
    <xsl:with-param name="master-reference">body</xsl:with-param>
    <xsl:with-param name="content">
      <xsl:apply-templates select="*[not(self::bibliography)]"/>
    </xsl:with-param>
  </xsl:call-template>
  <xsl:if test="bibliography">
    <xsl:call-template name="page.sequence">
      <xsl:with-param name="master-reference">back</xsl:with-param>
      <xsl:with-param name="content">
        <xsl:apply-templates select="bibliography"/>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:if>
</xsl:template>