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
andmiddlePage
). 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 aheader-first
, i.e. a special version of a header featured on the first pageregion-body
, declaration of the body of the documentregion-footer
, referencing afooter
, 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 calledonlyPage
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.