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>

Figure 4-1: Simple FO file

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">

Figure 4-2: The root element

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"
		>

Figure 4-3: The root element with additional namespaces

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>

Figure 4-4: The master-layout element

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>

Figure 4-5: The page-sequence element

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>

Figure 4-6: Matching master-name and master-reference

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.

Figure 4-7: A basic page with a region-body and some text

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>

Figure 4-8: Simple page master with footer region

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.

Figure 4-9: A basic page with a footer region

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>

Figure 4-10: Simple page master with margin added

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.

Figure 4-11: After adding margins to the simple-page-master

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"/>

Figure 4-12: Body with side margins

<fo:region-body margin-top="2.5cm" margin-bottom="2.5cm" region-name="body"  background-color="#eeeeee"/>

Figure 4-13: Body without side margins

Figure 4-14: After removing the left and right margins from the region-body

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>

Figure 4-15: side regions

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.

Figure 4-16: With side regions to reduce the width of the footer

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>

Figure 4-17: Shorthand and specific attributes

<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>

Figure 4-18: Shorthand and specific attributes

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>

Figure 4-19: Adding a static-content element

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.

Figure 4-20: FO with static-content

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.

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>

Figure 4-21: Adding a page number

This FO produces the page shown in Figure 4-22.

Figure 4-22: Page with page number

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"/>

Figure 4-23: Block with id for last page

<fo:page-number-citation ref-id="last-page"/>

Figure 4-24: FO to retrieve the page number of the identified block

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>

Figure 4-25: Complete FO to display total page count

This FO produces the page shown in Figure 4-26.

Figure 4-26: Page with page number and total page count

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>

Figure 4-27: Text in a block

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>

Figure 4-28: Nested blocks

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>

Figure 4-29: Block with border and padding

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.

Figure 4-30: Default indentation of nested blocks

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>

Figure 4-31: Block with margins specified

Figure 4-32: Indentation of nested blocks with margins

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>&#x2022;</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>&#x2022;</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>

Figure 4-33: FO for the 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>

Figure 4-34: FO for a 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>

Figure 4-36: Table with table-column elements

For more information on tables see Tables.