fop-layout 0.1.1

Layout engine for Apache FOP Rust implementation
Documentation

fop-layout

Crates.io docs.rs License

Layout engine for the Apache FOP Rust implementation. Transforms the FO tree (from fop-core) into an area tree that can be rendered to PDF or other formats.

Version: 0.1.1 | Release Date: 2026-04-20

Pipeline

FO Tree (fop-core)
    |
    v
LayoutEngine        -- coordinates the process
    |
    +-- BlockLayout  -- stacks blocks vertically
    +-- InlineLayout -- positions text horizontally, breaks lines
    +-- TableLayout  -- computes column widths, positions cells
    +-- ListLayout   -- positions labels and bodies side-by-side
    +-- PageBreaker  -- splits content across pages
    |
    v
Area Tree            -- positioned rectangles ready for rendering

Installation

Add fop-layout to your Cargo.toml:

[dependencies]
fop-layout = "0.1"

Or with cargo add:

cargo add fop-layout

Usage

use fop_core::FoTreeBuilder;
use fop_layout::LayoutEngine;
use std::io::Cursor;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let xml = r#"<?xml version="1.0"?>
    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
        <fo:layout-master-set>
            <fo:simple-page-master master-name="A4"
                page-width="210mm" page-height="297mm">
                <fo:region-body margin="1in"/>
            </fo:simple-page-master>
        </fo:layout-master-set>
        <fo:page-sequence master-reference="A4">
            <fo:flow flow-name="xsl-region-body">
                <fo:block>Hello, Layout!</fo:block>
            </fo:flow>
        </fo:page-sequence>
    </fo:root>"#;

    let fo_tree = FoTreeBuilder::new().parse(Cursor::new(xml))?;
    let engine = LayoutEngine::new();
    let area_tree = engine.layout(&fo_tree)?;

    println!("Generated {} areas", area_tree.len());
    Ok(())
}

Architecture

area/ — Area Tree Data Structures

File Lines Description
types.rs 205 Area, AreaType (8 types), TraitSet, FontStyle
area_tree.rs 241 AreaTree, AreaNode, AreaId (arena-allocated)

Area Types: Page, Region, Block, Inline, Line, Table, ListItem, Image

Each area has a Rect (position + size) and an optional TraitSet for styling (font, color, borders).

layout/ — Layout Algorithms

File Lines Description
engine.rs 533 LayoutEngine — main coordinator, wires all algorithms
block.rs 100 BlockLayoutContext — vertical block stacking
inline.rs 238 InlineArea, InlineLayoutContext, LineBreaker
knuth_plass.rs 336 KnuthPlassBreaker — optimal line breaking algorithm
table.rs 421 TableLayout, ColumnWidth — table column computation
list.rs 362 ListLayout, ListMarkerStyle — list positioning
page_break.rs 283 PageBreaker — multi-page content splitting
properties.rs 210 Property extraction (FO properties → area traits)

Total: 2,994 lines across 11 files

Key Algorithms

Knuth-Plass Line Breaking

Optimal line breaking using the Knuth-Plass algorithm (same as TeX). Minimizes total "badness" across all lines rather than greedily filling each line.

use fop_layout::KnuthPlassBreaker;
use fop_types::Length;

fn break_lines() -> Result<(), Box<dyn std::error::Error>> {
    let breaker = KnuthPlassBreaker::new(Length::from_pt(300.0));
    let items = breaker.text_to_items("Long paragraph text...", 12.0);
    let breaks = breaker.find_breaks(&items);
    println!("Break points: {:?}", breaks);
    Ok(())
}

Table Layout

Computes column widths using fixed, proportional, or automatic sizing.

use fop_layout::{TableLayout, ColumnWidth};
use fop_types::Length;

fn compute_table() -> Result<(), Box<dyn std::error::Error>> {
    let layout = TableLayout::new(Length::from_pt(500.0))
        .with_border_spacing(Length::from_pt(2.0));

    let widths = layout.compute_fixed_widths(&[
        ColumnWidth::Fixed(Length::from_pt(100.0)),
        ColumnWidth::Proportional(2.0),
        ColumnWidth::Proportional(1.0),
    ]);
    println!("Column widths: {:?}", widths);
    Ok(())
}

List Layout

Positions list labels (markers) and bodies side-by-side with 9 marker styles.

use fop_layout::{ListLayout, ListMarkerStyle};
use fop_types::Length;

fn layout_list() -> Result<(), Box<dyn std::error::Error>> {
    let layout = ListLayout::new(Length::from_pt(400.0))
        .with_label_width(Length::from_pt(30.0))
        .with_label_separation(Length::from_pt(10.0));

    let marker = layout.generate_marker(1, ListMarkerStyle::Decimal); // "1."
    let bullet = layout.generate_marker(1, ListMarkerStyle::Disc);    // "•"
    println!("{marker} {bullet}");
    Ok(())
}

Marker Styles: Disc, Circle, Square, Decimal, LowerAlpha, UpperAlpha, LowerRoman, UpperRoman, None

Page Breaking

Splits content across multiple pages when it overflows.

use fop_layout::PageBreaker;
use fop_types::Length;

fn page_break_demo() -> Result<(), Box<dyn std::error::Error>> {
    let breaker = PageBreaker::new(
        Length::from_mm(210.0),  // A4 width
        Length::from_mm(297.0),  // A4 height
        [Length::from_inch(1.0); 4],  // 1-inch margins
    );

    assert_eq!(breaker.content_height(), Length::from_pt(698.0));
    assert!(breaker.fits_on_page(Length::from_pt(100.0), Length::from_pt(200.0)));
    Ok(())
}

Tests

52 unit tests covering:

  • Block layout vertical stacking
  • Inline layout horizontal positioning
  • Knuth-Plass line breaking (various paragraph widths)
  • Table layout (fixed, proportional, auto, mixed columns)
  • List layout (all 9 marker styles, positioning)
  • Page breaking (overflow detection, multi-page, area splitting)
  • Area tree construction and traversal
  • Property extraction from FO nodes

Dependencies

Crate Version Kind
fop-types workspace Internal
fop-core workspace Internal
thiserror 2.0 Error handling
log 0.4 Logging

Related Crates

Crate Description
fop-types Core XSL-FO types (lengths, colors, properties)
fop-core FO tree parser and builder
fop-render Rendering traits and SVG/PostScript output
fop-pdf-renderer PDF output renderer
fop-cli Command-line interface

License

Licensed under the Apache License, Version 2.0. See LICENSE for details.

Author

Copyright 2024–2026 COOLJAPAN OU (Team Kitasan)