Semi-complex Layout

We know now how to work with master & regions and what tools we have at our disposal to put them into context using page sequences. In this section, we shall create a semi-complex layout pattern.

Layout definition

One of the challenges of creating multiple XSl style sheets is deciding what should be shared and what should be unique to each and every style sheet. To some extent, this is a highly subjective topic, of course. However, in many situations, page layout is an example of a one of those bits that can be comfortably shared, significantly shortening your individual style sheets. Let's assume you would like to create such shared page layout.

<!-- layout variable definitions -->
<xsl:variable name="pageWidthFull" select="595"/>
<xsl:variable name="pageHeightFull" select="842"/>

<xsl:variable name="pageMarginTop" select="25"/>
<xsl:variable name="pageMarginRight" select="35"/>
<xsl:variable name="pageMarginBottom" select="25"/>
<xsl:variable name="pageMarginLeft" select="35"/>
<xsl:variable
  name="pageMargin"
  select="concat($pageMarginTop, ' ', $pageMarginRight, ' ', $pageMarginBottom, ' ', $pageMarginLeft)" />

<xsl:variable name="headerHeight" select="50"/>
<xsl:variable name="headerFirstHeight" select="$headerHeight"/>
<xsl:variable name="bodyMarginTop" select="0" />
<xsl:variable name="footerHeight" select="50"/>
<xsl:variable name="footerLastHeight" select="$footerHeight"/>

<!-- compute available width automatically; currently: 595 - 35 - 35 = 520 -->
<xsl:variable
  name="pageWidthAvailable"
  select="($pageWidthFull - $pageMarginLeft - $pageMarginRight)"/>

<xsl:template name="page-layout">
  <fo:layout-master-set>

    <!-- the only page (first page header & last page footer) -->
    <fo:simple-page-master
      master-name="onlyPage"
      margin="{$pageMargin}"
      page-height="{$pageHeightFull}"
      page-width="{$pageWidthFull}">
      <fo:region-before
        region-name="header-first"
        extent="{$headerFirstHeight}"/>
      <fo:region-body
        region-name="body"
        margin-top="{$bodyMarginTop + $headerFirstHeight}"
        margin-bottom="{$footerLastHeight}"/>
      <fo:region-after
        region-name="footer-last"
        extent="{$footerLastHeight}"/>
    </fo:simple-page-master>

    <!-- first page (first page header & general footer) -->
    <fo:simple-page-master
      master-name="firstPage"
      margin="{$pageMargin}"
      page-height="{$pageHeightFull}"
      page-width="{$pageWidthFull}">
      <fo:region-before
        region-name="header-first"
        extent="{$headerFirstHeight}"/>
      <fo:region-body
        region-name="body"
        margin-top="{$bodyMarginTop + $headerFirstHeight}"
        margin-bottom="{$footerHeight}"/>
      <fo:region-after
        region-name="footer"
        extent="{$footerHeight}"/>
    </fo:simple-page-master>

    <!-- last page (general header & last page footer) -->
    <fo:simple-page-master
      master-name="lastPage"
      margin="{$pageMargin}"
      page-height="{$pageHeightFull}"
      page-width="{$pageWidthFull}">
      <fo:region-before
        region-name="header"
        extent="{$headerHeight}"/>
      <fo:region-body
        region-name="body"
        margin-top="{$bodyMarginTop + $headerHeight}"
        margin-bottom="{$footerLastHeight}"/>
      <fo:region-after
        region-name="footer-last"
        extent="{$footerLastHeight}"/>
    </fo:simple-page-master>

    <!-- non-specific page (general header & general footer) -->
    <fo:simple-page-master
      master-name="middlePage"
      margin="{$pageMargin}"
      page-height="{$pageHeightFull}"
      page-width="{$pageWidthFull}">
      <fo:region-before
        region-name="header"
        extent="{$headerHeight}"/>
        <fo:region-body
          region-name="body"
          margin-top="{$bodyMarginTop + $headerHeight}"
          margin-bottom="{$footerHeight}"/>
      <fo:region-after
        region-name="footer"
        extent="{$footerHeight}"/>
    </fo:simple-page-master>

    <!-- page sequence master for all pages -->
    <fo:page-sequence-master master-name="pages">
      <fo:repeatable-page-master-alternatives>
        <fo:conditional-page-master-reference
          page-position="only"
          master-reference="onlyPage"/>
        <fo:conditional-page-master-reference
          page-position="first"
          master-reference="firstPage"/>
        <fo:conditional-page-master-reference
          page-position="last"
          master-reference="lastPage"/>
        <fo:conditional-page-master-reference
          page-position="rest"
          master-reference="middlePage"/>
      </fo:repeatable-page-master-alternatives>
    </fo:page-sequence-master>

  </fo:layout-master-set>
</xsl:template>

One of the great properties of the approach demonstrated above is its reusability. You can have the code in a separate file you include (more on that in Organization & Reusability) in all apposite style sheets. The page layout can then be applied via <xsl:call-template name="page-layout">.

We will look at a demonstration of its usage in a short while. First, a quick explanation of the code above might be in order:

  • Using common variables provides a great starting point for building any style sheet. That way, you can have default values of all common document properties such as margins, heights, etc. while preserving the possibility of adjusting their values for specific templates.
  • The layout master set contains four master definitions. While using the common variables declared above to set important page properties, each one of them has a unique name (onlyPage, firstPage, lastPage and middlePage). Each one of them also contains block definitions.
  • Let's take the firstPage master as an example. Apart from setting common page properties, it declares 3 regions:
    • region-before referencing a header-first, i.e. a special version of a header featured on the first page
    • region-body, declaration of the body of the document
    • region-footer, referencing a footer, i.e. a general version of a footer put on every page apart from the last one
  • The other regions are very similar in nature. Make sure to read through the code to understand why specific regions are referenced from each and every master.
  • The <fo:page-sequence-master> assigns "roles" to the defined masters. The master called onlyPage should be used when there is only one page in the document, firstPage is to be used as the first page of a document (given it contains more than one page), etc.

Usage

The above reusable layout could be invoked as follows:

<?xml version="1.0" encoding="UTF-8"?>

<xsl:stylesheet
  version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:fo="http://www.w3.org/1999/XSL/Format">

  <!-- import the code above (replace with acutal file name!) -->
  <xsl:import href="layout.xsl" />

  <!-- override to provide a custom `margin-top` for every page -->
  <xsl:variable name="pageMarginTop" select="40" />

  <!-- main template -->
  <xsl:template match="/">
    <fo:root font-family="Georgia" font-size="10">
      <!-- apply the layout -->
      <xsl:call-template name="page-layout" />

      <fo:page-sequence master-reference="pages">

        <!-- first header -->
        <fo:static-content flow-name="header-first">
          <fo:block
            border-bottom="1 solid black"
            text-align="center">First page header</fo:block>
        </fo:static-content>

        <!-- general header -->
        <fo:static-content flow-name="header">
          <fo:block
            border-bottom="1 solid black"
            text-align="center">General header</fo:block>
        </fo:static-content>

        <!-- general footer -->
        <fo:static-content flow-name="footer">
          <fo:block
            border-top="1 dotted black"
            text-align="right">General footer</fo:block>
        </fo:static-content>

        <!-- last footer  -->
        <fo:static-content flow-name="footer-last">
          <fo:block
            border-top="1 dotted black"
            text-align="right">Last page footer</fo:block>
        </fo:static-content>

        <!-- body -->
        <fo:flow flow-name="body">
          <fo:block page-break-after="always">First page</fo:block>
          <fo:block page-break-after="always">Second page</fo:block>
          <fo:block page-break-after="always">Third page</fo:block>
          <fo:block>Last page</fo:block>
        </fo:flow>
      </fo:page-sequence>
    </fo:root>
  </xsl:template>
</xsl:stylesheet>

And that's it! The re-usable layout is simply included and then applied using the <xsl:call-template name="page-layout" /> tag. Note the flow-name attributes of the <fo:static-content> declarations: their values correspond to the region-name's values in the re-usable layout definition.

results matching ""

    No results matching ""