In the
previous article, I made a short review, giving an overview of the publication of DITA in PDF. Now I decided to tell you how XSL-FO does the layout of the pages of a future document and how it is implemented in the basic DITA-OT configuration.
Used materials from the book "Dave Pawson, XSL-FO - Making XML Look Good in Print, 2002".
Document structure
Any document in XSL-FO can be divided into three logical parts:
- front matter - the front part, which includes the title page, annotation, table of contents, list of illustrations, etc.
- main matter (or body ) - the main part, which contains the text of the document, tables, illustrations, etc.
- back matter - back part that contains indexes, glossaries, bibliography, etc.
It is worth noting that this is the structure of the final document from the point of view of XSL-FO. The original DITA document usually contains only text (tables and illustrations). DITA elements contain attributes whose values also play a role in publishing (the language used, alignment, snapping to neighboring elements, etc.). Table of contents, lists and indexes, title page - these parts of the document are automatically generated based on the original content.
The final document may be missing the front and / or back, or they can be combined with the main part. The main part always exists.
Files that describe the markup
For each component of the document can use its own page markup. All markup (page types) are described in the
layout-masters.xsl file located in the folder
.. \ org.dita.pdf2 \ cfg \ fo .
Parameter sets for each markup are set in the
layout-masters-attr.xsl file , which is located in the folder
.. \ org.dita.pdf2 \ cfg \ fo \ attrs .
The values of the parameters in the sets are either specified explicitly or as a link to another parameter. The parameter referenced must be explicitly specified in any of the parameter files.
Markup Elements
The root element of the document XSL-FO is
fo: root . The conversion for it is located in the file
.. \ org.dita.pdf2 \ xsl \ fo \ root-processing.xsl . Its child element, which contains the
layout of all page types, is
fo: layout-master-set (see figure).

The
fo: layout-master-set element in turn contains two child elements:
- fo: simple-page-master - contains a description of the page markup of a specific part of the document (for example, a table of contents)
- fo: page-sequence-master - generates a sequence of pages with specific markup. When a document is formed, the initial content of DITA is distributed to these sequences.
Both elements have a unique name, which is specified in the
master-name parameter. According to it, when forming a document, the required page markup is called. The call is made from the
fo: page-sequence elements, which are intended to distribute the contents of the DITA among the pages with the required markup.
The fo: page-sequence refers to the
fo: simple-page-master or the
fo: page-sequence-master via the
master-reference parameter.
')
The question arises - why use
fo: page-sequence-master if you can refer to
fo: simple-page-master directly. The fact is that for the same type of markup, you can use subtypes: for the first page of the section, for the last page, for even pages, for odd pages. For each subtype, as well as for the main type, its own set of parameters is specified. And
fo: page-sequence-master allows for input parameters to dynamically determine which type of markup should be used. If we call
fo: simple-page-master directly, then we must know in advance which markup to choose, which is not always feasible.
In DITA-OT, the subtype names are given as follows:
- for the first page, add -first to the name of the main type;
- for the latter - -last ;
- for odd ones, -odd ;
- for even - -even ;
- for blank pages - -blank .
Simple page master
As I said above, the
fo: simple-page-master element is intended to describe the page markup of a specific part of a document.
In XSL-FO, any page contains five work areas (see picture):
- region-body - the main area of the page in which the document content is placed;
- region-before - header;
- region-after - footer;
- region-start - left margin (for the direction of the text from left to right);
- region-end - right margin (for the direction of the text from left to right).
Each region has a name. By default, it corresponds to the name of the element. The name can be set in the
region-name parameter. The name of the area is used to indicate in which document the data is to be output to which area. For example, static
fo: static-content data is most often displayed in footers or fields.

Each region in XSL-FO corresponds to the same-name element (in the
fo namespace). All of these elements are children of
fo: simple-page-master .
Parameters Simple Page Master
The
fo: simple-page-master element has the following parameters:
- master-name is a unique markup identifier.
- page-height - page height.
- page-width - page width.
- margin-top , margin-bottom , margin-left and margin-right are the dimensions of the page margins. You should distinguish them from the header and footer, which are workspaces.
- reference-orientation - the orientation of the top of the page (angle in degrees counterclockwise) relative to the default value. It can take values: -270, -180, -90, 0, 90, 180, 270. The default is 0.
- writing-mode - a mode that determines the location of areas and blocks (paragraphs, tables, illustrations, etc.) on a page. There are three modes: lr-tb (from left to right, top to bottom), rl-tb (right to left, top to bottom), tb-rl (top to bottom, right to left). The first part of the mode name indicates the direction of the text in lines, the second - the direction of following the blocks within the region.
The figure below shows the location of areas on the page, depending on the parameters of the
reference-orientation and
writing-mode .

Sizes of areas
Each of the regions (except for the
region-body ) has the
extent parameter, which determines the size of the region in the direction perpendicular to the corresponding side of the page. For headers and footers - this is the height, for fields - the width. Set in units of length, or as a percentage of the corresponding page size (
page-height and
page-width parameters).
Region-body
The size of the
region-body is determined in another way. First, they are limited by the size of the page margins. Secondly, it has 4 parameters:
margin-top ,
margin-bottom ,
margin-left and
margin-right , which define the indents of the border of the
region-body region from the
content rectangle of the page reference area , indicated by the dash in the second figure. These indents can be specified in units of length or as a percentage of the size of the
content rectangle of the page reference area .
Here one should draw a rather significant conclusion that the boundaries of the
region-body region do not depend on the sizes and parameters of the neighboring regions.
The indents of the
region-body are arranged in accordance with the orientation of the page. For example, if the page has an orientation of 90, the upper indent is measured from this direction. If the
region-body has its own orientation (
reference-orientation parameter), this does not affect the position of the indents. They are still counted according to the orientation of the page. Orientation of the
region-body affects only how the child blocks will be located in it.
For the
region-body region, the number of columns in which the contents of the document will be placed can be specified. Two parameters are responsible for this:
column-count - sets the number of columns (must be greater than or equal to 1);
column-gap - the distance between adjacent columns.
Alignment in areas
For alignment in areas, the
display-align parameter is used, which can take values:
auto ,
before ,
center ,
after . It determines the alignment of the child blocks in the region in the direction of their sequence, which is specified in the parameter
writing-mode .
And what about the corners?
The two upper corners of the page can be occupied by either a header or left and right margins. Similarly, the bottom corners - footer or left and right margins. The
precedence parameter specified for headers and footers is responsible for this. If the parameter is
true , then the corners are occupied by the footer (the one for which the parameter is specified). If the parameter is
false , then the corners occupy the fields. The default setting is
false .
Page Sequence Master
The
fo: page-sequence-master element, as I mentioned, is intended to create a sequence of pages into which the initial content of the DITA will be distributed when the document is created.
This element has three child elements:
- fo: single-page-master-reference - used when you want to form a sequence of one page. For example, the title page of the document. When working with the fo: single-page-master-reference element, it should be remembered that if one page is not enough to hold the original content, this will cause an error when publishing.
- fo: repeatable-page-master-reference - used to form limited or unlimited sequences of pages with the same markup. When using the fo: repeatable-page-master-reference element, the maximum number of pages can be specified. To do this, use the parameter maximum-repeats . The default number of pages is unlimited.
- fo: repeatable-page-master-alternatives - used to form several sequences with different markup depending on the specified parameters. It is the most flexible tool.
The conditions for the selection of markup in the
fo: repeatable-page-master-alternatives element are set using the
fo elements
: conditional-page-master-reference . Each condition can contain from one to three parameters, which together determine whether the condition is fulfilled or not.
These parameters are:
- page-position - page layout in section - first (first), last (last), rest (not the first and not the last), any (any).
- odd-or-even - page number evenness - odd (odd), even (even), any (any).
- blank-or-not-blank - whether the page has the data transferred from the fo: flow block - blank (not empty), not-blank (not empty), any (any).
- for fo: repeatable-page-master-alternatives, the maximum number of pages in the maximum-repeats parameter can also be set.
A little bit about fo: page-sequence
So, the markup is described, the selection conditions are set, how now to call them from the XSLT transformation?
To do this, use the
fo element
: page-sequence . It has the required
master-reference parameter, which specifies the name of the markup being called.
Also in the element can be specified the parameters responsible for the pagination.
The
initial-page-number parameter fixes the number of the first page of the generated sequence of pages:
- auto - continues the numbering of the previous section;
- auto-odd - continues numbering from the first odd number;
- auto-even - continues numbering from the first even number;
- specific number from which numbering will begin.
The
force-page-count parameter imposes a condition on the number of pages of the generated sequence:
- auto - sets the number of pages in accordance with the number of the first page of the next sequence;
- even - hard sets an even number of pages;
- odd - hard sets an odd number of pages;
- end-on-even - the last page number is even;
- end-on-odd - the last page number is odd;
- no-force - not regulated.
And how is it in DITA-OT?
A lot of theory, but how is all this implemented in the DITA Open Toolkit.
If in general terms, all types of
simple-page-master markup are described first (for covers, tables of contents, etc.). Further, according to the theory, the sequence of pages
page-sequence-master for each markup should be described.
DITA-OT uses the
generate-page-sequence-master XSL template for this, which generates a sequence of pages using the specified parameters.
The template works out several times from the
call-template . Each call generates a sequence for one type of markup in accordance with the specified parameters.
All this we can see in the file
layout-masters.xsl , located in the folder
.. \ org.dita.pdf2 \ cfg \ fo .
In line 43 we see the beginning of the
fo: layout-master-set element, in which the
fo: simple-page-master elements are located, each of which contains a description of a separate markup.
For example, the first four pieces are for the cover:
<fo:simple-page-master master-name="front-matter-first" xsl:use-attribute-sets="simple-page-master"> <fo:region-body xsl:use-attribute-sets="region-body__frontmatter.odd"/> </fo:simple-page-master> <fo:simple-page-master master-name="front-matter-last" xsl:use-attribute-sets="simple-page-master"> <fo:region-body xsl:use-attribute-sets="region-body__frontmatter.even"/> <fo:region-before region-name="last-frontmatter-header" xsl:use-attribute-sets="region-before"/> <fo:region-after region-name="last-frontmatter-footer" xsl:use-attribute-sets="region-after"/> </fo:simple-page-master> <xsl:if test="$mirror-page-margins"> <fo:simple-page-master master-name="front-matter-even" xsl:use-attribute-sets="simple-page-master"> <fo:region-body xsl:use-attribute-sets="region-body__frontmatter.even"/> <fo:region-before region-name="even-frontmatter-header" xsl:use-attribute-sets="region-before"/> <fo:region-after region-name="even-frontmatter-footer" xsl:use-attribute-sets="region-after"/> </fo:simple-page-master> </xsl:if> <fo:simple-page-master master-name="front-matter-odd" xsl:use-attribute-sets="simple-page-master"> <fo:region-body xsl:use-attribute-sets="region-body__frontmatter.odd"/> <fo:region-before region-name="odd-frontmatter-header" xsl:use-attribute-sets="region-before"/> <fo:region-after region-name="odd-frontmatter-footer" xsl:use-attribute-sets="region-after"/> </fo:simple-page-master>
- The first markup, based on the title, is for the first page of the cover. It has only the main region-body region , for which the set of region-body__frontmatter.odd attributes is applied . Although this set is designed for odd pages (as the name suggests), nothing prevents you from using it here. The set of attributes is described in the layout-masters-attr.xsl file .
- The second markup is for the last page of the cover. It has a main area, a header and a footer. In this case, the headers and footers have their own names, last-frontmatter-header and last-frontmatter-footer , the main region has a default name - region-body .
- The third markup - for even cover pages - is used only when the condition specified in the xsl: if element is met. If the boolean variable $ mirror-page-margins is true , then for odd and even pages different markup is used (as a rule, it is mirrored).
- The fourth markup - for odd cover pages - is not limited to the parity condition of the page. However, if $ mirror-page-margins is true , then it is called only for odd pages. This can be seen in the fo: page-sequence-master below.
Further, after the declaration of all the markup, the sequences of the pages
page-sequence-master (line 169) are generated, into which during the formation of the document the data flow from the DITA topics will be distributed.
For example, the following code generates a sequence of pages for a table of contents (toc - table of content):
<xsl:call-template name="generate-page-sequence-master"> <xsl:with-param name="master-name" select="'toc-sequence'"/> <xsl:with-param name="master-reference" select="'toc'"/> </xsl:call-template>
To create each sequence, the
generate-page-sequence-master XSL template is invoked with the appropriate parameters in the
xsl: with-param elements. In the parameters:
name - the name of the parameter,
select - the value of the parameter.
At the end of the file is described the XSL template
generate-page-sequence-master , which generates a sequence of pages
page-sequence-master in accordance with the input parameters (see the last listing). In the basic DITA-OT configuration, only the universal
fo: repeatable-page-master-alternatives is used , and depending on the input parameters one or another markup is chosen.
The template has four parameters:
- master-name - sets the name for the generated sequence. By this name, the sequence will be selected when distributing the original content of the DITA.
- master-reference is the markup name that will be called to generate a sequence.
- first is a sign that this is the first page. The default is true .
- last is a sign that this is the last page. The default is true .
Next, the template creates a
page-sequence-master with the name specified in the template
master-name parameter. The parameter value is passed as
$ master-name variable. The
page-sequence-master element contains one child
fo: repeatable-page-master-alternatives , which contains three conditions:
- xsl: if test = "$ first" - is the page first in the sequence;
- xsl: if test = "$ last" - is the page last in the sequence;
- xsl: when test = "$ mirror-page-margins" - whether different markup is used for even and odd pages.
If the page is the first, then the
conditional-page-master-reference calls the markup by the name specified in the
$ master-reference variable for the first page (
-first is added to the name), with parameters:
odd-or-even = "odd" (odd page ) and
page-position = "first" (first page).
If the page is last, then
conditional-page-master-reference invokes markup by the name specified in the
$ master-reference variable for the last page (
-last is added to the name), with parameters:
odd-or-even = "even" (even page ),
page-position = “last” (last page) and
blank-or-not-blank = “blank” (empty page).
If the parity of the pages is taken into account, two
conditional-page-master-reference : for even and odd pages are used. Otherwise (
xsl: otherwise ), markup for odd pages is used.
<xsl:template name="generate-page-sequence-master"> <xsl:param name="master-name"/> <xsl:param name="master-reference"/> <xsl:param name="first" select="true()"/> <xsl:param name="last" select="true()"/> <fo:page-sequence-master master-name="{$master-name}"> <fo:repeatable-page-master-alternatives> <xsl:if test="$first"> <fo:conditional-page-master-reference master-reference="{$master-reference}-first" odd-or-even="odd" page-position="first"/> </xsl:if> <xsl:if test="$last"> <fo:conditional-page-master-reference master-reference="{$master-reference}-last" odd-or-even="even" page-position="last" blank-or-not-blank="blank"/> </xsl:if> <xsl:choose> <xsl:when test="$mirror-page-margins"> <fo:conditional-page-master-reference master-reference="{$master-reference}-odd" odd-or-even="odd"/> <fo:conditional-page-master-reference master-reference="{$master-reference}-even" odd-or-even="even"/> </xsl:when> <xsl:otherwise> <fo:conditional-page-master-reference master-reference="{$master-reference}-odd"/> </xsl:otherwise> </xsl:choose> </fo:repeatable-page-master-alternatives> </fo:page-sequence-master> </xsl:template>