Chapter 6 of the XSL-FO Tutorial
6. More Complex Structures
6.1. Lists
Lists in XSL FO are much more than just a bulleted sequence of paragraphs: it is a general-purpose mechanism to align two blocks adjacent to each other. It may be used to format ordinary lists, footnotes, image lists, and even to produce some table-like layout patterns.
A list is created by a <fo:list-block> object. Inside it, there are one or more <fo:list-item> elements. Each list item contains one <fo:list-item-label> followed by one <fo:list-item-body>. These two elements contain blocks that are aligned vertically and placed side-by-side.
Let's start with an ordinary bulleted list:
<fo:list-block provisional-distance-between-starts="18pt"❶
provisional-label-separation="3pt"❷>
<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>First item</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>Second item</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
| ❶ | This property specifies how far the left side of the label is distant from the left side of the body. |
| ❷ | This property specifies the separation between the right side of the label and the left edge of the body. |
| ❸ | end-indent attribute specifies the offset of the right edge of <fo:list-item-label> from the right edge of the reference area (i.e. page). A special label-end() function sets it to the value calculated from provisional-distance-between-starts and provisional-label-separation values. However, this is not a default value: you have to specify end-indent="label-end()" on each <fo:list-item-label> in the list. Alternatively, you can use an explicit value of end-indent. |
| ❹ | This is a Unicode for a round bullet. |
| ❺ | start-indent attribute specifies the left offset of the <fo:list-item-body> from the left. A special body-start() function sets it to the value calculated from provisional-distance-between-starts. Like for the <fo:list-item-label>, this is not a default value; don't forget to specify it on each <fo:list-item-body>. |
Another example — using <fo:list-block> to align text in a header:
<fo:static-content flow-name="xsl-region-before">
<fo:list-block border-bottom="1pt gray ridge"❶ padding-after="6pt">
<fo:list-item>
<fo:list-item-label end-indent="3in">❷
<fo:block text-align="start">RenderX XSL FO Manual</fo:block>
</fo:list-item-label>
<fo:list-item-body start-indent="3in">❸
<fo:block text-align="end">List Example</fo:block>
</fo:list-item-body>
</fo:list-item>
</fo:list-block>
</fo:static-content>
| ❶ | <fo:list-block> and <fo:list-item> are block-level elements, and can get margins (including space-before/space-after and indents), keep & break constraints, borders, padding, and background. Please note that <fo:list-item-body> and <fo:list-item-label> aren't considered blocks in the spec, and cannot have borders or background. |
| ❷ ❸ | Instead of using label-end()/body-start() technique, you may also specify indents explicitly like in this example. To use this method, you should know the width of the parent reference area to avoid overlapping of item labels to item bodies. |
6.2. Tables
Tables in XSL FO resemble HTML ones: they are made of cells grouped into rows; rows are further grouped into row groups — table header, table footer, and table bodies (one or more). There are also column descriptors. Table model is rather evolved: please refer to the spec for further details. Here, I limit myself to a couple of core examples.
A basic 2×2 table:
<fo:table border="0.5pt solid black" text-align="center">
<fo:table-body>
<fo:table-row>
<fo:table-cell padding="6pt" border="0.5pt solid black">❶
<fo:block> upper left </fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> upper right </fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> lower left </fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> lower right </fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
| ❶ | Table cells can have borders, padding, and background; they cannot have margins. The table itself may have all attributes a normal block may have, except for padding. |
A more complicated example includes explicit column width assignment and cells spanning multiple grid units:
<fo:table border="0.5pt solid black"
text-align="center"
border-spacing="3pt"❶>
<fo:table-column column-width="1in"/>
<fo:table-column column-width="0.5in" number-columns-repeated="2"❷/>
<fo:table-header>❸
<fo:table-row>
<fo:table-cell padding="6pt"
border="1pt solid blue"
background-color="silver"
number-columns-spanned="3"❹>
<fo:block text-align="center" font-weight="bold">
Header
</fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-header>
<fo:table-body>
<fo:table-row>
<fo:table-cell padding="6pt"
border="1pt solid blue"
background-color="silver"
number-rows-spanned="2"❺>
<fo:block text-align="end" font-weight="bold">
Items:
</fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> 1 : 1 </fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> 1 : 2 </fo:block>
</fo:table-cell>
</fo:table-row>
<fo:table-row>❻
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> 2 : 1 </fo:block>
</fo:table-cell>
<fo:table-cell padding="6pt" border="0.5pt solid black">
<fo:block> 2 : 2 </fo:block>
</fo:table-cell>
</fo:table-row>
</fo:table-body>
</fo:table>
| ❶ | border-spacing corresponds to CELLSPACING attribute of the HTML table model. |
| ❷ | This is a column specifier that sets the width for the second and the third column. Instead of repeating the same specifier for two consecutive columns, I have used an number-columns-repeated attribute: it just clones the description the specified number of times. |
| ❸ | Table header is a row group like <fo:table-body>. It is repeated at the top of page after page breaks unless you specify table-omit-header-at-break="true" on the embracing <fo:table>. |
| ❹ | This cell will span three columns horizontally. |
| ❺ | This cell will span two rows vertically. |
| ❻ | This row contains only two cells; the first grid unit is already occupied by the cell spanning from the previous row. |