Introduction to XSL-FO
This chapter provides an overview of formatting objects and provides some suggestions on how to create PDF documents from XML files.
We also look at the techniques for using XSLT transformation to create FO files.
Layout of an FO file
A very simple FO file is shown in Figure 4-1:
<?xml version='1.0' encoding='UTF-8'?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple">
<fo:region-body margin="2.5cm" region-name="body" background-color='#eeeeee'/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
This file is logically in three parts, namely the root, layout-master-set and page-sequence parts. All FO files share this structure.
The fo:root element
The fo:root element shown in Figure 4-2 contains the whole content of the file and establishes the XSL-FO namespace as the default namespace. This element is the same for all FO files.
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
Additional namespaces can be added to the fo:root element as shown in Figure 4-3.
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format"
xmlns:ibex="http://www.xmlpdf.com/2003/ibex/Format"
xmlns:svg="xmlns="http://www.w3.org/2000/svg"
>
If you want to use SVG diagrams or Ibex extensions you should define the namespaces as shown.
The layout-master-set element
The fo:layout-master-set element show in Figure 4-4 defines the shape and layout of pages in the document.
Within the fo:layout-master-set we have
a fo:simple-page-master element which in turn contains the fo:region-body element.
The fo:simple-page-master defines the layout of one type of page and is uniquely identified by its master-name attribute. The fo:region-body element defines an area of the page where content will be placed. A page can have more than one region so we give the region a unique name "body" using the region-name attribute. This value is used with fo:flow elements to specify which content goes into which region on the page.
<fo:layout-master-set>
<fo:simple-page-master master-name="simple">
<fo:region-body margin="2.5cm" region-name="body" background-color="#eeeeee"/>
</fo:simple-page-master>
</fo:layout-master-set>
A FO file contains one or more fo:simple-page-master elements, each with a unique master-name. In this simple example we have only one. Each fo:simple-page-master element creates a formatting object known as a page master.
The page-sequence element
The fo:page-sequence element shown in Figure 4-5 defines a sequence of pages that will appear in the PDF document.
The master-reference attribute is used to tie the content of the fo:page-sequence
to a particular page layout, in this case one defined previously using a fo:simple-page-master.
When finds a fo:page-sequence element it looks at the list of known fo:simple-page-master
and fo:page-sequence-master elements (we have no fo:page-sequence-master elements in this example) and finds one with a
master-name attribute which equals the [master-reference** attribute on the fo:page-sequence.
If does not find a matching page master the FO file is invalid and Ibex will throw an exception.
<fo:page-sequence master-reference="simple">
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
</fo:flow>
</fo:page-sequence>
Within the fo:page-sequence element we have a fo:flow element. This holds the content which will appear on one or more pages.
A page can have multiple regions. To associate content with
a region we use the flow-name attribute on the fo:flow element. In order for the content contained in the fo:flow to appear on
the page, the flow-name of the fo:flow should match a region-name of one of the regions (in this example the fo:region-body) on the page.
If the [flow-name** of the fo:flow does not match a region-name of one of the regions on the page the content is not displayed on that page. This is not an error. It is a useful feature and we show how to use it later in this chapter.
Looking at the FO in Figure 4-6, the names identified by "**" names must match each other, and the names identified by "++" should match if you want the content to appear.
<fo:layout-master-set>
<fo:simple-page-master master-name="**simple**">
<fo:region-body margin="2.5cm" region-name="++body++" background-color='#eeeeee'/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="**simple**">
<fo:flow flow-name="++body++">
<fo:block>Hello World</fo:block>
</fo:flow>
</fo:page-sequence>
Within the fo:flow element we can have one or more "block level" elements. These are elements such as fo:list-block, fo:block and fo:table which define the content to appear on the page. In this example we have a single fo:block element containing the text "Hello World". This produces a page like the one shown in Figure 4-7. The region created by the fo:region-body element has a shaded background so you can see how large it is.
Adding a page footer region
In our example so far all the text contained in the fo:flow element goes into the body region in the center of the page. To add a page footer we need to define a new region on the page and then define some new content to go into that region. We define a footer region by adding a fo:region-after element into the existing fo:simple-page-master as shown in Figure 4-8.
<fo:layout-master-set>
<fo:simple-page-master master-name="
simple
">
...
<fo:region-after extent='1cm' region-name="footer" background-color='#dddddd'/>
...
</fo:simple-page-master>
</fo:layout-master-set>
The fo:region-after element defines an area on the page which extends the full width of the page. If we had side regions (fo:region-start and fo:region-end) this might change, but in this example we have no side regions. The height of the region created by the fo:region-after element is defined by the extent attribute. In this example we have extent="1cm", so the region will be 1cm high and end at the bottom of the page. Even without any content the footer region is still rendered on the page. Our page now looks like the one in Figure 4-9.
In its current position on the page the footer region will not print on most printers because they do not print right to the edge of the paper. We can define a margin around the whole page by setting the margin attribute on the fo:simple-page-master element of the fo:page-sequence as shown in Figure 4-10.
<fo:layout-master-set>
<fo:simple-page-master master-name="simple" margin="2.5cm"
>
<fo:region-body margin="2.5cm" region-name="body" background-color="#eeeeee"/>
<fo:region-after extent="1cm" region-name="footer" background-color="#dddddd"/>
</fo:simple-page-master>
</fo:layout-master-set>
The area inside the margins of the fo:simple-page-master is called the "content area".
The area covered by the regions (defined by the fo:region-body and fo:region-end) is measured from the inside of the page's content area, so when we add
margins to the fo:simple-page-master we reduce the size of the regions correspondingly.
Our page now appears as shown in Figure 4-11.
Now that we have some space on the sides of the body region we can remove the side margins from the body by changing the definition from that shown in Figure 4-12 to the one shown in Figure 4-13, resulting in the page layout shown in Figure 4-14.
<fo:region-body margin="2.5cm" region-name="body" background-color="#eeeeee"/>
<fo:region-body margin-top="2.5cm" margin-bottom="2.5cm" region-name="body" background-color="#eeeeee"/>
The last thing we need to do to get a working page layout is to make the footer region narrower by adding side regions. The left side region is created with a fo:region-start element and the right side with a fo:region-end element as in Figure 4-15.
We can also specify the bottom-margin attribute of the body region so that it ends just where the footer starts, by setting margin-bottom= "1cm" on the fo:region-body element.
<fo:layout-master-set>
<fo:simple-page-master master-name="simple" margin='2.5cm'>
<fo:region-body margin="2.5cm" margin-bottom='1cm' region-name="body" background-color='#eeeeee'/>
<fo:region-after extent='1cm' region-name="footer" background-color='#dddddd'/>
<fo:region-start extent='2.5cm'/>
<fo:region-end extent='2.5cm'/>
</fo:simple-page-master>
</fo:layout-master-set>
By default the side regions take precedence over the top and bottom regions so the top and bottom regions become narrower. This gives us the page layout shown in Figure 4-16, to which we can start adding some content.
Attribute processing
The FO above also illustrates one of the ways in which XSL-FO handles attributes. We can specify a shorthand attribute such as "margin",
which has the effect of setting the specific values margin-left, margin-right, margin-top and margin-bottom, and then override just the
specific value we want (for example by setting margin-bottom="1cm"). The order in which the attributes are specified in the FO file has no effect.
A more specific setting
will always override a more general one.
So the two examples in Figure 4-17
and Figure 4-18
produce the same result.
<fo:layout-master-set>
<fo:simple-page-master master-name="simple">
<fo:region-body margin="2.5cm" margin-bottom="1cm">
</fo:simple-page-master>
</fo:layout-master-set>
<fo:layout-master-set>
<fo:simple-page-master master-name="simple">
<fo:region-body margin-bottom="1cm" margin="2.5cm">
</fo:simple-page-master>
</fo:layout-master-set>
Adding content to the footer
While content is added to the body of the page using the fo:flow element, content is added to other regions using the fo:static-content element.
The "static" part of the fo:static-content name refers to the fact that the content defined in this element stays within the region specified on this page.
It does not flow
from one page to the next. If the content exceeds the size of the region it will not flow to the next page.
The content of the fo:static-content is repeated on every page which has a region with a matching flow-name (such as "footer"), and is
typically different on every page as the page number changes.
To insert a simple footer with the words "XSL-FO Example" we add a fo:static-content element as shown
in Figure 4-19.
<?xml version='1.0' encoding='UTF-8'?>
<fo:root xmlns="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple"
margin='2.5cm'>
<fo:region-body margin="2.5cm" margin-bottom='1cm' region-name="body" background-color='#eeeeee'/>
<fo:region-after extent='1cm' region-name="footer" background-color='#dddddd'/>
<fo:region-start extent='2.5cm'/>
<fo:region-end extent='2.5cm'/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:static-content flow-name="footer">
<fo:block text-align='center'>XSL-FO Example</fo:block>
</fo:static-content>
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
Note that the order of the fo:static-content and fo:flow elements is important. All fo:static-content elements must come before
any fo:flow elements.
This FO produces the page shown in Figure 4-20.
Note that the flow-name of the fo:static-content element and the region-name of the fo:region-after element must match for the content to appear. This feature makes it possible to have many fo:static-content elements within the same fo:page-sequence, and only those which match regions in the current fo:simple-page-master will be rendered.
Adding the page number to the footer
To insert the current page number into the document use the fo:page-number element inside the fo:static-content element as shown in Figure 4-21.
<?xml version='1.0' encoding='UTF-8'?>
<fo:root xmlns="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple" margin='2.5cm'>
<fo:region-body margin="2.5cm" margin-bottom='1cm' region-name="body" background-color='#eeeeee'/>
<fo:region-after extent='1cm' region-name="footer" background-color='#dddddd'/>
<fo:region-start extent='2.5cm'/>
<fo:region-end extent='2.5cm'/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:static-content flow-name="footer">
<fo:block text-align='center'>
XSL-FO Example, page <fo:page-number/>
</fo:block>
</fo:static-content>
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
</fo:flow>
</fo:page-sequence>
</fo:root>
This FO produces the page shown in Figure 4-22.
Adding the total page count to the footer
Adding the total page count (so we can have "page 3 of 5") is a two step process, based on the use of the "id" attribute which uniquely identifies an FO element. We place a block on the last page with the id of "last-page", and then we use the fo:page-number-citation element to get the number of the page on which that block appears as our total number of pages.
Typically the block with the id of "last-page" is empty so a new page is not created at the end of the document.
The FO for the last block in the document is shown in Figure 4-23, and the FO to retrieve the last page number and put it in the footer is shown in Figure 4-24.
<fo:block id="last-page"/>
<fo:page-number-citation ref-id="last-page"/>
You can see how the id and ref-id values match. This is how Ibex associates the two elements and knows from which block to retrieve the page number. So bringing all these elements together we have the FO shown in Figure 4-25.
<?xml version='1.0' encoding='UTF-8'?>
<fo:root xmlns="http://www.w3.org/1999/XSL/Format">
<fo:layout-master-set>
<fo:simple-page-master master-name="simple"
margin='2.5cm'>
<fo:region-body margin="2.5cm" margin-bottom='1cm' region-name="body" background-color='#eeeeee'/>
<fo:region-after extent='1cm' region-name="footer"background-color='#dddddd'/>
<fo:region-start extent='2.5cm'/>
<fo:region-end extent='2.5cm'/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="simple">
<fo:static-content flow-name="footer">
<fo:block text-align='center'>
XSL-FO Example, page <fo:page-number/>
of <fo:page-number-citation ref-id='last-page'/>
</fo:block>
</fo:static-content>
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
<fo:block id='last-page'/>
</fo:flow>
</fo:page-sequence>
</fo:root>
This FO produces the page shown in Figure 4-26.
Adding text content
Text is added to the body region of the page by using the fo:block element.
A fo:block element can contain any amount of text and has attributes which define how the text will appear. These attributes are described in more detail later in the manual.
A fo:block can contain text as shown in Figure 4-27.
<fo:flow flow-name="body">
<fo:block>Hello World</fo:block>
</fo:flow>
A fo:block element can also contain other fo:block elements which in turn contain text or more nested elements.
Figure 4-28 shows a fo:block
which contains another block with a different font, set using the font attribute.
<fo:flow flow-name="body">
<fo:block>
Hello World
<fo:block font-size="16pt">
this is a nested block
</fo:block>
</fo:block>
</fo:flow>
There is no limit to the nesting of fo:block elements.
Using borders and padding
Many FO elements can have a border around the area they create on the page. If the border around an element is the same on all four sides then it can be defined with a single use of the border attribute. The space between a border and the content of the block (in this case the text) is controlled using the padding attribute. Figure 4-29 shows FO for a block with border and padding.
<fo:flow flow-name="body">
<fo:block background-color='#eeeeee'>
<fo:block>
Hello World
</fo:block>
<fo:block border='1pt solid red' padding='3pt'>
Hello World
</fo:block>
</fo:block>
</fo:flow>
This example has two fo:block elements nested inside an outer element, with a background color set on the outer element to highlight the area created by the outer block. The block created from this FO is shown in Figure 4-30.
Ibex positions the content of the block (in this case the text) relative to the edge of the region. After positioning the content, the padding and borders are positioned relative to the position of the content. This places the padding and borders outside the content area of the block. The contents of the block are not indented, rather the padding and borders extend outside the block. This is the default behavior of XSL-FO formatters. If you prefer Cascading Style Sheets (CSS) compatible behavior where adding a border to a block indents its content, you can specify the left-margin and right-margin attributes to force this to happen. Even if the left-margin and right-margin values are zero, CSS type indentation will still occur. The XML for this is shown in Figure 4-31 and the resulting output is shown in Figure 4-32.
<fo:flow flow-name="body">
<fo:block background-color='#eeeeee'>
<fo:block>
Hello World
</fo:block>
<fo:block border='1pt solid red' padding='3pt' margin-left="0" margin-right="0">
Hello World
</fo:block>
</fo:block>
</fo:flow>
Creating lists
A list is content divided into two columns. Each item in the list is in two parts, called the label and the body respectively.
A list is created with the fo:list-block element. A fo:list-block contains one or more fo:list-item elements, each of which contains exactly one fo:list-item-label element and exactly one fo:list-item-body element.
<fo:list-block
margin-left='3cm' margin-right='3cm' padding='3pt'
border='.1pt solid blue'
provisional-distance-between-starts='0.5cm'
provisional-label-separation='0.1cm'>
<fo:list-item>
<fo:list-item-label end-indent='label-end()'>
<fo:block>•</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent='body-start()'>
<fo:block>this is item one</fo:block>
</fo:list-item-body>
</fo:list-item>
<fo:list-item>
<fo:list-item-label end-indent='label-end()'>
<fo:block>•</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent='body-start()'>
<fo:block>this is item two</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
A list sets the two columns to widths specified using the attributes of the fo:list-block elements. The
provisional-distance-between-starts attribute
specifies the distance between the start of the label column and the start of the body column.
The provisional-label-separation attribute sets how much of the label column should be left empty to provide a blank space between the columns.
For more information on lists see page Lists.
Creating tables
A table is created using the fo:table element. Within the fo:table element there can be one fo:table-header element, any number of fo:table-body elements (which contain the rows) and one fo:table-footer element. Each of the fo:table-header, fo:table-body and fo:table-footer elements contains one or more fo:table-row elements, each containing one or more fo:table-cell elements which in turn contain block-level elements such as fo:block, fo:table and fo:list-block.
Since a fo:table-cell can contain any block-level element you can easily create tables which contain text, nested tables or lists.
Table headers and footers defined with the fo:table-header and fo:table-footer elements
are automatically repeated at each page break, although this can be suppressed if required.
Figure 4-34 shows the FO for a simple table and Figure 4-35 shows the table created from the FO.
<fo:table>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
The padding and border attributes are not inherited from containing elements so must be defined on the fo:table-cell elements.
Setting table column widths
The width of a table column is set using the fo:table-column element. A fo:table element contains zero or more fo:table-column elements each of which defines properties such as width and background-color for a column in the table.
To make the first column 30% of the table width we would add fo:table-column elements as shown in Figure 4-36, which creates the output shown in Figure 4-37.
<fo:table>
<fo:table-column column-width='30%' column-number='1'/>
<fo:table-column column-width='70%' column-number='2'/>
<fo:table-body>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 1</fo:block>
</fo:table-cell>
<fo:table-cell border='1pt solid blue' padding='2pt'>
<fo:block>row 1 column 2</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
For more information on tables see Tables.