Skip to content
XSL-FO — XML Formatting Objects for PDF Publishing, Layout, Tables, Page Masters

XSL-FO — XML Formatting Objects for PDF Publishing, Layout, Tables, Page Masters

DodaTech Updated Jun 20, 2026 8 min read

XML documents need to be rendered as PDFs, print-ready documents, or high-quality formatted output. XSL-FO (XML Formatting Objects) is the standard for XML-to-PDF publishing. This guide teaches XSL-FO page layout, block/inline formatting, tables, lists, and PDF generation with Apache FOP.

What You’ll Learn

You will design XSL-FO page masters, format text with block and inline elements, create tables with headers and spanning, build lists, generate PDFs from XML using Apache FOP, and apply XSL-FO in real-world document publishing. DodaZIP uses XSL-FO for generating formatted documentation and reports from XML configuration data.

Learning Path

    flowchart LR
  A[XSLT] --> B[XML Signatures]
  B --> C[XSL-FO<br/>You are here]
  C --> D[XML Publishing Pipeline]
  style C fill:#f90,color:#fff
  

XSL-FO Document Structure

An XSL-FO document defines pages, flows, and formatting:

<?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="page"
            page-width="8.5in" page-height="11in"
            margin-top="0.75in" margin-bottom="0.75in"
            margin-left="1in" margin-right="1in">
            <fo:region-body margin-top="0.5in"/>
            <fo:region-before extent="0.5in"/>
            <fo:region-after extent="0.5in"/>
        </fo:simple-page-master>
    </fo:layout-master-set>

    <fo:page-sequence master-reference="page">
        <fo:static-content flow-name="xsl-region-before">
            <fo:block text-align="center"
                font-size="10pt" font-style="italic">
                DodaTech Documentation
            </fo:block>
        </fo:static-content>
        <fo:static-content flow-name="xsl-region-after">
            <fo:block text-align="center" font-size="9pt">
                Page <fo:page-number/>
            </fo:block>
        </fo:static-content>

        <fo:flow flow-name="xsl-region-body">
            <fo:block font-size="24pt" font-weight="bold"
                space-after="12pt">
                XML Publishing Guide
            </fo:block>
            <fo:block font-size="12pt" line-height="1.4"
                space-after="6pt">
                XSL-FO transforms XML into formatted pages
                suitable for PDF generation.
            </fo:block>
        </fo:flow>
    </fo:page-sequence>
</fo:root>

Page Masters

Multiple pages with different layouts:

<fo:layout-master-set>
    <!-- Cover page: no margins -->
    <fo:simple-page-master master-name="cover"
        page-width="8.5in" page-height="11in"
        margin="0in">
        <fo:region-body margin="0in"/>
    </fo:simple-page-master>

    <!-- Normal page: with margins -->
    <fo:simple-page-master master-name="normal"
        page-width="8.5in" page-height="11in"
        margin-top="0.75in" margin-bottom="0.75in"
        margin-left="1in" margin-right="1in">
        <fo:region-body margin-top="0.5in"/>
        <fo:region-before extent="0.5in"/>
        <fo:region-after extent="0.5in"/>
    </fo:simple-page-master>

    <!-- Landscape page for tables -->
    <fo:simple-page-master master-name="landscape"
        page-width="11in" page-height="8.5in"
        margin="0.75in">
        <fo:region-body/>
    </fo:simple-page-master>

    <!-- Page sequence master for alternating -->
    <fo:page-sequence-master master-name="document">
        <fo:repeatable-page-master-alternatives>
            <fo:conditional-page-master-reference
                master-reference="cover"
                page-position="first"/>
            <fo:conditional-page-master-reference
                master-reference="normal"
                page-position="rest"/>
            <fo:conditional-page-master-reference
                master-reference="landscape"
                odd-or-even="even"/>
        </fo:repeatable-page-master-alternatives>
    </fo:page-sequence-master>
</fo:layout-master-set>

Block Elements

<!-- Paragraph with indentation -->
<fo:block text-indent="0.25in"
    space-before="6pt" space-after="6pt"
    text-align="justify">
    This paragraph has first-line indentation and
    justified alignment with spacing before and after.
</fo:block>

<!-- Headings with varying sizes -->
<fo:block font-size="18pt" font-weight="bold"
    font-family="Helvetica" space-before="12pt"
    space-after="6pt" keep-with-next="always">
    Section Heading
</fo:block>

<fo:block font-size="14pt" font-weight="bold"
    space-before="8pt" space-after="4pt">
    Subsection Heading
</fo:block>

<!-- Code block with monospace font -->
<fo:block font-family="Courier" font-size="9pt"
    background-color="#f0f0f0" padding="6pt"
    border="1pt solid #ccc" white-space="pre"
    linefeed-treatment="preserve">
    &lt;?xml version="1.0"?&gt;
    &lt;root&gt;
        &lt;element/&gt;
    &lt;/root&gt;
</fo:block>

Inline Elements

<fo:block>
    Text with
    <fo:inline font-weight="bold">bold</fo:inline>,
    <fo:inline font-style="italic">italic</fo:inline>,
    <fo:inline font-size="8pt">small</fo:inline>,
    <fo:inline color="red">colored</fo:inline>,
    <fo:inline text-decoration="underline">underlined</fo:inline>,
    and
    <fo:inline background-color="yellow">highlighted</fo:inline>
    formatting.
</fo:block>

Tables

<fo:table table-layout="fixed" width="100%"
    border-collapse="collapse">
    <!-- Column widths -->
    <fo:table-column column-width="30%"/>
    <fo:table-column column-width="50%"/>
    <fo:table-column column-width="20%"/>

    <!-- Table header -->
    <fo:table-header font-weight="bold"
        background-color="#e0e0e0">
        <fo:table-row>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>Feature</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>Description</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999"
                text-align="center">
                <fo:block>Version</fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table-header>

    <!-- Table body -->
    <fo:table-body>
        <fo:table-row>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>XML Signing</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>Digital signature support</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999"
                text-align="center">
                <fo:block>2.0+</fo:block>
            </fo:table-cell>
        </fo:table-row>
        <fo:table-row>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>XSL-FO</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999">
                <fo:block>PDF rendering engine</fo:block>
            </fo:table-cell>
            <fo:table-cell padding="4pt"
                border="0.5pt solid #999"
                text-align="center">
                <fo:block>1.1</fo:block>
            </fo:table-cell>
        </fo:table-row>
    </fo:table-body>
</fo:table>

<!-- Cell spanning multiple columns -->
<fo:table-cell number-columns-spanned="3"
    padding="4pt" border="0.5pt solid #999"
    background-color="#fffde7">
    <fo:block font-weight="bold" text-align="center">
        This cell spans all three columns
    </fo:block>
</fo:table-cell>

<!-- Cell spanning multiple rows -->
<fo:table-cell number-rows-spanned="2">
    <fo:block>Vertical merge</fo:block>
</fo:table-cell>

Lists

<fo:list-block space-before="6pt" space-after="6pt"
    provisional-distance-between-starts="0.3in"
    provisional-label-separation="0.1in">

    <!-- Bullet 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>First item in the bullet list</fo:block>
        </fo:list-item-body>
    </fo:list-item>

    <!-- Numbered list item -->
    <fo:list-item>
        <fo:list-item-label end-indent="label-end()">
            <fo:block>1.</fo:block>
        </fo:list-item-label>
        <fo:list-item-body start-indent="body-start()">
            <fo:block>Second item with numbering</fo:block>
        </fo:list-item-body>
    </fo:list-item>

    <fo:list-item>
        <fo:list-item-label end-indent="label-end()">
            <fo:block>2.</fo:block>
        </fo:list-item-label>
        <fo:list-item-body start-indent="body-start()">
            <fo:block>Third item in this list</fo:block>
        </fo:list-item-body>
    </fo:list-item>
</fo:list-block>

Generating PDF with Apache FOP

import org.apache.fop.apps.*;
import javax.xml.transform.*;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;

public class FopPdfGenerator {
    public static void generatePdf(File foFile, File pdfFile)
            throws Exception {

        // Configure FOP
        FopFactory fopFactory = FopFactory.newInstance(
            new File(".").toURI());
        FOUserAgent foUserAgent = fopFactory.newFOUserAgent();

        // Create output stream
        try (OutputStream out = new FileOutputStream(pdfFile)) {
            // Construct FOP with PDF output
            Fop fop = fopFactory.newFop(
                MimeConstants.MIME_PDF, foUserAgent, out);

            // Transform FO file to PDF
            TransformerFactory factory =
                TransformerFactory.newInstance();
            Transformer transformer = factory.newTransformer();

            Source src = new StreamSource(foFile);
            Result res = new SAXResult(fop.getDefaultHandler());
            transformer.transform(src, res);

            System.out.println("PDF generated: " + pdfFile);
        }
    }

    public static void main(String[] args) throws Exception {
        if (args.length < 2) {
            System.out.println("Usage: java FopPdfGenerator in.fo out.pdf");
            return;
        }
        generatePdf(new File(args[0]), new File(args[1]));
    }
}

XML to PDF Pipeline

Combine XSLT and FOP to transform raw XML into PDF:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:template match="/">
        <fo:root>
            <fo:layout-master-set>
                <fo:simple-page-master master-name="page"
                    page-width="8.5in" page-height="11in"
                    margin="1in">
                    <fo:region-body/>
                    <fo:region-before extent="0.5in"/>
                    <fo:region-after extent="0.5in"/>
                </fo:simple-page-master>
            </fo:layout-master-set>

            <fo:page-sequence master-reference="page">
                <fo:static-content flow-name="xsl-region-before">
                    <fo:block text-align="right"
                        font-size="9pt" color="#666">
                        <xsl:value-of select="document/title"/>
                    </fo:block>
                </fo:static-content>

                <fo:flow flow-name="xsl-region-body">
                    <fo:block font-size="20pt" font-weight="bold"
                        space-after="12pt">
                        <xsl:value-of select="document/title"/>
                    </fo:block>

                    <xsl:apply-templates select="document/section"/>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>

    <xsl:template match="section">
        <fo:block font-size="14pt" font-weight="bold"
            space-before="12pt" space-after="6pt">
            <xsl:value-of select="@title"/>
        </fo:block>
        <xsl:apply-templates select="paragraph"/>
    </xsl:template>

    <xsl:template match="paragraph">
        <fo:block space-after="6pt" text-align="justify">
            <xsl:value-of select="."/>
        </fo:block>
    </xsl:template>
</xsl:stylesheet>

Run the pipeline:

# Step 1: Transform XML to XSL-FO
java -cp saxon9he.jar net.sf.saxon.Transform \
    -s:input.xml -xsl:xml-to-fo.xsl -o:output.fo

# Step 2: Generate PDF from XSL-FO
java -cp fop.jar:lib/* org.apache.fop.cli.Main \
    -fo output.fo -pdf output.pdf

# Combined one-liner
java -cp saxon9he.jar net.sf.saxon.Transform \
    -s:input.xml -xsl:xml-to-fo.xsl \
    | java -cp fop.jar:lib/* org.apache.fop.cli.Main \
    -fo - -pdf output.pdf

Common XSL-FO Mistakes

1. Not Setting white-space for Code Blocks

Without white-space="pre" and linefeed-treatment="preserve", code blocks collapse whitespace and lose indentation and line breaks.

2. Missing Column Definitions

Tables without <fo:table-column> widths default to equal-width columns, which rarely produces the desired layout. Always specify column widths.

3. Overlapping Regions

If <fo:region-body> margin-top overlaps with <fo:region-before> extent, content collides. Ensure region extents are within the body margin.

4. Forgetting keep-with-next on Headings

Headings that appear alone at the bottom of a page confuse readers. Add keep-with-next="always" to headings to keep them with their following paragraph.

5. Incorrect Namespace URI

The XSL-FO namespace is http://www.w3.org/1999/XSL/Format. The xsl prefix is for XSLT (http://www.w3.org/1999/XSL/Transform). Confusing them causes rendering failures.

6. Using Unsupportable CSS Properties

XSL-FO is not CSS. Properties like display, margin, or padding don’t work. Use XSL-FO equivalents: space-before, space-after, padding, border.

7. Page Number References Across Sequences

Using <fo:page-number-citation> to reference page numbers in different page sequences requires careful setup. Ensure the referenced ID is unique and accessible.

Practice Questions

1. What namespace does XSL-FO use? http://www.w3.org/1999/XSL/Format. The prefix fo: is conventional.

2. How do you create a multi-column layout in XSL-FO? Use <fo:region-body column-count="2" column-gap="0.25in"/> on the page master, or use <fo:block span="all"> for full-width elements.

3. What is the difference between fo:region-body, fo:region-before, fo:region-after? region-body is the main content area. region-before is the top/header area. region-after is the bottom/footer area. Each has its own flow name.

4. How do you handle page breaks in XSL-FO? Use page-break-before="always", page-break-after="always", or page-break-inside="avoid" on blocks to control pagination.

5. Challenge: Design an XSL-FO template for a product catalog with a cover page, table of contents, product listings with images and prices, and page numbers. Answer: Use a page sequence master with cover (first), table of contents (roman numerals), and body (arabic numerals) page masters. Apply XSLT templates for each section. Use keep-with-next for headings and avoid orphans/widows with widows="2" orphans="2".

Mini Project: XML Invoice to PDF

<?xml version="1.0" encoding="UTF-8"?>
<invoice number="INV-2026-001" date="2026-06-20">
    <bill-to>
        <name>Acme Corp</name>
        <address>123 Main St, City</address>
    </bill-to>
    <items>
        <item>
            <description>DodaZIP Enterprise License</description>
            <quantity>5</quantity>
            <unit-price>299.00</unit-price>
        </item>
        <item>
            <description>Durga Antivirus Pro - 1yr</description>
            <quantity>10</quantity>
            <unit-price>49.99</unit-price>
        </item>
    </items>
</invoice>

XSL-FO output:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:fo="http://www.w3.org/1999/XSL/Format">

    <xsl:template match="/">
        <fo:root>
            <fo:layout-master-set>
                <fo:simple-page-master master-name="invoice"
                    page-width="8.5in" page-height="11in"
                    margin="0.75in">
                    <fo:region-body/>
                </fo:simple-page-master>
            </fo:layout-master-set>

            <fo:page-sequence master-reference="invoice">
                <fo:flow flow-name="xsl-region-body">
                    <!-- Header -->
                    <fo:block font-size="24pt" font-weight="bold"
                        color="#1a237e" space-after="4pt">
                        INVOICE
                    </fo:block>
                    <fo:block font-size="10pt" color="#666"
                        space-after="12pt">
                        Number: <xsl:value-of select="invoice/@number"/>
                        | Date: <xsl:value-of select="invoice/@date"/>
                    </fo:block>

                    <!-- Bill To -->
                    <fo:block font-weight="bold"
                        space-before="12pt" space-after="4pt">
                        Bill To:
                    </fo:block>
                    <fo:block>
                        <xsl:value-of select="invoice/bill-to/name"/>
                    </fo:block>
                    <fo:block space-after="12pt">
                        <xsl:value-of select="invoice/bill-to/address"/>
                    </fo:block>

                    <!-- Items Table -->
                    <fo:table width="100%" border-collapse="collapse">
                        <fo:table-column column-width="60%"/>
                        <fo:table-column column-width="15%"
                            text-align="center"/>
                        <fo:table-column column-width="25%"
                            text-align="right"/>

                        <fo:table-header font-weight="bold"
                            background-color="#1a237e" color="white">
                            <fo:table-row>
                                <fo:table-cell padding="6pt"
                                    border="0.5pt solid #1a237e">
                                    <fo:block>Description</fo:block>
                                </fo:table-cell>
                                <fo:table-cell padding="6pt"
                                    border="0.5pt solid #1a237e">
                                    <fo:block>Qty</fo:block>
                                </fo:table-cell>
                                <fo:table-cell padding="6pt"
                                    border="0.5pt solid #1a237e">
                                    <fo:block>Amount</fo:block>
                                </fo:table-cell>
                            </fo:table-row>
                        </fo:table-header>

                        <fo:table-body>
                            <xsl:for-each select="invoice/items/item">
                                <fo:table-row>
                                    <fo:table-cell padding="4pt"
                                        border="0.5pt solid #ccc">
                                        <fo:block>
                                            <xsl:value-of
                                                select="description"/>
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell padding="4pt"
                                        border="0.5pt solid #ccc"
                                        text-align="center">
                                        <fo:block>
                                            <xsl:value-of
                                                select="quantity"/>
                                        </fo:block>
                                    </fo:table-cell>
                                    <fo:table-cell padding="4pt"
                                        border="0.5pt solid #ccc"
                                        text-align="right">
                                        <fo:block>
                                            $<xsl:value-of
                                                select="format-number(
                                                    quantity * unit-price,
                                                    '#,##0.00')"/>
                                        </fo:block>
                                    </fo:table-cell>
                                </fo:table-row>
                            </xsl:for-each>

                            <!-- Total row -->
                            <fo:table-row font-weight="bold">
                                <fo:table-cell padding="4pt"
                                    border="0.5pt solid #ccc">
                                    <fo:block>Total</fo:block>
                                </fo:table-cell>
                                <fo:table-cell padding="4pt"
                                    border="0.5pt solid #ccc"/>
                                <fo:table-cell padding="4pt"
                                    border="0.5pt solid #ccc"
                                    text-align="right">
                                    <fo:block font-size="14pt">
                                        $<xsl:value-of
                                            select="format-number(
                                                sum(invoice/items/item/
                                                    quantity *
                                                    unit-price),
                                                '#,##0.00')"/>
                                    </fo:block>
                                </fo:table-cell>
                            </fo:table-row>
                        </fo:table-body>
                    </fo:table>

                    <!-- Footer -->
                    <fo:block text-align="center"
                        space-before="24pt" font-size="8pt"
                        color="#999">
                        Generated by DodaTech Publishing System
                    </fo:block>
                </fo:flow>
            </fo:page-sequence>
        </fo:root>
    </xsl:template>
</xsl:stylesheet>

FAQ

What’s the difference between XSL-FO and CSS for print?
XSL-FO is purpose-built for paginated media (PDF, print). CSS Paged Media is newer and simpler but less powerful. XSL-FO handles complex layouts (running headers/footers, page-number citations, table spanning better than CSS).
What tools support XSL-FO?
Apache FOP (free), Antenna House Formatter, RenderX XEP, PrinceXML (CSS-based but handles FO). Apache FOP is the most widely used open-source option.
Can I convert HTML to XSL-FO?
Yes — tools like html2fo exist, but HTML-to-FO conversion is lossy due to fundamental differences between flowing HTML and paginated FO. It’s easier to start from XML with XSLT.
Is XSL-FO still relevant in 2026?
Yes — for enterprise document generation (invoices, reports, statements, catalogs) where paginated PDF output is required and HTML-to-PDF rendering is insufficient.
How do I handle images in XSL-FO?
Use <fo:external-graphic src="url('image.png')" content-width="6in"/>. For SVG, use <fo:instream-foreign-object> with the SVG namespace.
Can I embed fonts in the PDF?
Yes — use Apache FOP’s font configuration. Register TTF or OTF fonts in a fop.xconf file and reference them by family name in the FO.

What’s Next

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. Updated 2026-06-20.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro