Skip to main content

Apache FOP - XSL-FO to PDF converter (Rust implementation)

Project description

fop-python

Crates.io docs.rs License Python PyO3

Python bindings for FOP — a high-performance, pure-Rust XSL-FO processor that converts XSL-FO (Extensible Stylesheet Language Formatting Objects) documents to PDF, SVG, and plain text output.

This crate is part of the COOLJAPAN FOP ecosystem and is built with PyO3, providing a native Python extension that runs 10–1200x faster than the original Java Apache FOP.


Description

fop-python exposes the full FOP conversion pipeline to Python via a native extension module. It compiles to a shared library (fop.so / fop.pyd) that can be imported directly from Python. The bindings cover:

  • XSL-FO string to PDF bytes
  • XSL-FO string to SVG string
  • XSL-FO string to plain text string
  • File-to-file conversion with automatic format detection
  • XSL-FO document validation
  • Stateful FopConverter class with configurable options
  • Module-level one-shot convenience functions

The implementation delegates to the same Rust pipeline used throughout the FOP workspace:

XSL-FO XML  -->  FO Tree  -->  Area Tree  -->  PDF / SVG / Text
 (fop-core)    (fop-layout)   (fop-render)

Features

Feature Description
FopConverter class Stateful converter object — instantiate once, convert many documents
convert_to_pdf(fo_xml) Convert an XSL-FO string to raw PDF bytes
convert_to_svg(fo_xml) Convert an XSL-FO string to an SVG string
convert_to_text(fo_xml) Convert an XSL-FO string to plain text
convert_file(input, output) Convert a .fo file to PDF/SVG/TXT (format inferred from extension)
validate(fo_xml) Parse and validate an XSL-FO document; returns (valid, node_count, error)
version() Return the crate version string
Module-level one-shot functions fop.convert_to_pdf(), fop.convert_to_svg(), fop.version()
verbose property Enable/disable verbose logging on the converter instance

Installation

Via pip (maturin-built wheel)

If a pre-built wheel is available on PyPI:

pip install fop2

To build and install from source using maturin:

# Install maturin
pip install maturin

# Clone the repository
git clone https://github.com/cool-japan/fop.git
cd fop/crates/fop-python

# Build and install into the active virtual environment
maturin develop --features extension-module

As a Rust dependency

Add the following to your Cargo.toml:

[dependencies]
fop-python = "0.1.0"

Python Usage

Importing the module

import fop

Using the FopConverter class

FopConverter is the primary API. Instantiate it once and reuse it for multiple conversions:

import fop

# Create a converter instance
converter = fop.FopConverter()

# Inspect the object
print(repr(converter))  # FopConverter(verbose=False)

# Enable verbose mode
converter.verbose = True

Converting XSL-FO to PDF

import fop

fo_xml = """<?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="A4"
                           page-width="210mm" page-height="297mm"
                           margin-top="20mm" margin-bottom="20mm"
                           margin-left="25mm" margin-right="25mm">
      <fo:region-body/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference="A4">
    <fo:flow flow-name="xsl-region-body">
      <fo:block font-size="18pt" font-weight="bold">Hello, FOP!</fo:block>
      <fo:block font-size="12pt" space-before="6pt">
        Generated by fop-python — a pure-Rust XSL-FO processor.
      </fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>"""

converter = fop.FopConverter()
pdf_bytes = converter.convert_to_pdf(fo_xml)

with open("output.pdf", "wb") as f:
    f.write(pdf_bytes)

print(f"PDF generated: {len(pdf_bytes)} bytes")

Converting XSL-FO to SVG

import fop

converter = fop.FopConverter()
svg_string = converter.convert_to_svg(fo_xml)

with open("output.svg", "w", encoding="utf-8") as f:
    f.write(svg_string)

Converting XSL-FO to plain text

import fop

converter = fop.FopConverter()
text = converter.convert_to_text(fo_xml)
print(text)

File-to-file conversion

The output format is automatically detected from the file extension:

  • .pdf — PDF output (default)
  • .svg — SVG output
  • .txt — Plain text output
import fop

converter = fop.FopConverter()

# Convert to PDF
converter.convert_file("document.fo", "document.pdf")

# Convert to SVG
converter.convert_file("document.fo", "document.svg")

# Convert to plain text
converter.convert_file("document.fo", "document.txt")

Validating an XSL-FO document

validate() always returns Ok at the Python level; parse errors are reported in the tuple rather than raised as exceptions:

import fop

converter = fop.FopConverter()
valid, node_count, error = converter.validate(fo_xml)

if valid:
    print(f"Document is valid ({node_count} nodes)")
else:
    print(f"Validation failed: {error}")

Module-level convenience functions

For one-shot conversions without creating a FopConverter instance:

import fop

# Convert directly
pdf_bytes = fop.convert_to_pdf(fo_xml)
svg_string = fop.convert_to_svg(fo_xml)

# Get version
print(fop.version())  # fop-python 0.1.0

Error handling

Conversion errors are raised as Python RuntimeError exceptions:

import fop

converter = fop.FopConverter()

try:
    pdf_bytes = converter.convert_to_pdf("<<<not valid XSL-FO>>>")
except RuntimeError as e:
    print(f"Conversion failed: {e}")

try:
    converter.convert_file("/nonexistent/path.fo", "/tmp/out.pdf")
except IOError as e:
    print(f"File error: {e}")

Building from Source

Prerequisites

  • Rust toolchain (1.70+) — install via rustup
  • Python 3.8+ with a virtual environment
  • maturin (pip install maturin)

Development build (editable install)

git clone https://github.com/cool-japan/fop.git
cd fop/crates/fop-python

# Activate your virtual environment first, then:
maturin develop --features extension-module

This builds the native extension and installs it directly into the active Python environment, making import fop available immediately.

Release wheel

# Build a release wheel for the current platform
maturin build --release --features extension-module

# The wheel is placed in target/wheels/
pip install target/wheels/fop_python-*.whl

Cargo build (library only, no Python install)

# Build as a cdylib (shared library) from the workspace root
cargo build --release -p fop-python

# Or from the crate directory
cargo build --release

Rust Usage

You can also use fop-python as a Rust library to access the FopConverter struct directly (e.g., in tests or when embedding):

use fop_python::converter::FopConverter;

fn main() {
    let converter = FopConverter::new();

    let fo_xml = r#"<?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="A4"
                           page-width="210mm" page-height="297mm">
      <fo:region-body/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference="A4">
    <fo:flow flow-name="xsl-region-body">
      <fo:block>Hello from Rust!</fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>"#;

    // Returns PyResult<Vec<u8>> — use pyo3::Python::with_gil to call from Rust
    // For pure Rust usage, prefer the fop-core / fop-render crates directly.
    println!("FopConverter created: {:?}", converter.version());
}

For pure Rust embedding without PyO3 overhead, use the underlying crates directly:

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

let fo_tree = FoTreeBuilder::new().parse(Cursor::new(fo_xml)).unwrap();
let area_tree = LayoutEngine::new().layout(&fo_tree).unwrap();
let pdf_doc = PdfRenderer::new().render(&area_tree).unwrap();
let pdf_bytes = pdf_doc.to_bytes().unwrap();

Feature Flags

Feature Default Description
extension-module No Enables PyO3's extension-module feature, required when building a Python native extension (.so/.pyd). Must be enabled when using maturin build or maturin develop. Do not enable this when using the crate as a pure Rust library dependency, as it links against the Python interpreter in a way that is incompatible with standalone binaries.
# In Cargo.toml when building as a Python extension:
[features]
extension-module = ["pyo3/extension-module"]

Maturin activates this feature automatically when invoked as a build backend.


Architecture

fop-python is a thin PyO3 binding layer over the FOP Rust workspace crates:

fop-python  (PyO3 bindings)
    |
    +-- fop-render  (PdfRenderer, SvgRenderer, TextRenderer)
    |       |
    |       +-- fop-layout  (LayoutEngine, area tree)
    |               |
    |               +-- fop-core   (FoTreeBuilder, XML parsing)
    |                       |
    |                       +-- fop-types  (Length, Color, FopError, ...)

Error propagation: FopError (Rust) is mapped to RuntimeError (Python) via fop_error_to_py. File I/O errors are mapped to IOError.


Supported XSL-FO Elements

The underlying rendering engine supports the following XSL-FO 1.1 elements:

  • fo:root, fo:layout-master-set, fo:simple-page-master
  • fo:region-body, fo:region-before, fo:region-after
  • fo:page-sequence, fo:flow, fo:static-content
  • fo:block, fo:inline
  • fo:table, fo:table-body, fo:table-row, fo:table-cell, fo:table-column
  • fo:list-block, fo:list-item, fo:list-item-label, fo:list-item-body
  • fo:external-graphic, fo:basic-link

294 XSL-FO 1.1 properties are supported with full inheritance semantics.


Performance

Metric Java Apache FOP fop-python (Rust)
Startup time ~2000ms (JVM) <10ms
Parse 1000-page doc ~500ms <50ms
Memory per page ~50KB <5KB
Throughput vs Java 1x 10–1200x faster

Repository


License

Copyright 2024 COOLJAPAN OU (Team Kitasan)

Licensed under the Apache License, Version 2.0.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fop2-0.1.0.tar.gz (347.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

fop2-0.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded PyPymanylinux: glibc 2.17+ x86-64

fop2-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded PyPymanylinux: glibc 2.17+ ARM64

fop2-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded PyPymanylinux: glibc 2.17+ ARM64

fop2-0.1.0-cp38-abi3-win_amd64.whl (1.3 MB view details)

Uploaded CPython 3.8+Windows x86-64

fop2-0.1.0-cp38-abi3-manylinux_2_34_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.34+ x86-64

fop2-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ x86-64

fop2-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (1.3 MB view details)

Uploaded CPython 3.8+manylinux: glibc 2.17+ ARM64

fop2-0.1.0-cp38-abi3-macosx_11_0_arm64.whl (1.2 MB view details)

Uploaded CPython 3.8+macOS 11.0+ ARM64

fop2-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl (1.3 MB view details)

Uploaded CPython 3.8+macOS 10.12+ x86-64

File details

Details for the file fop2-0.1.0.tar.gz.

File metadata

  • Download URL: fop2-0.1.0.tar.gz
  • Upload date:
  • Size: 347.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: maturin/1.8.7

File hashes

Hashes for fop2-0.1.0.tar.gz
Algorithm Hash digest
SHA256 399691779aabbcab66ae6bd8457925862c7124a36b756fc20d0dbfa22c221fc3
MD5 d0cc4898763af4811b76b8b1aead1e93
BLAKE2b-256 4b4056a6a4e94a4092aaf646ab6d4ef79a971d8573e1b7feedb357645de59fad

See more details on using hashes here.

File details

Details for the file fop2-0.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 c21c9cca30d07946f7ba5f95c8c091017061f63b982ff95cc756169541c46f66
MD5 c00a717e839b555f6e025925e1605573
BLAKE2b-256 f376145a053259429e24f186a5ac990bc54446d96bf6dcaf79e1d879aac37999

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 3bd2968367ffa0193ddd9db75214c85f529f283e512256e4da12ab9711cfcd0e
MD5 678a24b021b5be9391c57a6c008dc9e5
BLAKE2b-256 b372ee7f7ba34376ad68cb5c36309c1bb0f719ef94793feceb8552dacac6b6fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 749f6a8aed6aa7ecfc51e6d9e2b7ecfb964c69dc049a04877ef818f0a89e66cc
MD5 491fed8a5b2995e5677c2eb7d274b5c6
BLAKE2b-256 92cfb71aa6d4cde5f08b6ff02454ff6e8cfdcc56ce8e25ce58d801a01e2f5aff

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-cp38-abi3-win_amd64.whl.

File metadata

  • Download URL: fop2-0.1.0-cp38-abi3-win_amd64.whl
  • Upload date:
  • Size: 1.3 MB
  • Tags: CPython 3.8+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for fop2-0.1.0-cp38-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 09b91ea130050d974b1118090da4b90921b68513db66537ea39ac7fb60de46d4
MD5 3f156674c8b11ba4f8a1d1de4225ec8d
BLAKE2b-256 19ec2dccbcec735a65ac85e2c8c0683923b7d8c527d9486ff128e6e1a12a3a5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-cp38-abi3-win_amd64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-cp38-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-cp38-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 69b51b9cad7585d2edf853b03e59a2bc2fb3d781014d7f2669717a1dc81b9c47
MD5 869e1ae52ca31f4d46c22ce21841616c
BLAKE2b-256 2a879c0e47df89d41a34a672f21739ac0416c504f7ad01f13ca3e7fafaea449a

See more details on using hashes here.

File details

Details for the file fop2-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b3bc00e394bcefc8dfbc63d79d083d40c921f528c118c9b279c682c4fdb42cb2
MD5 b81becd0f27e42f60239c5cdf4288d1c
BLAKE2b-256 bda78c05b93c0f53125cba8ef5b55322d5a7f3b398b242601849cf7d252bb417

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 47afb581a8b80b8ff43e94ae3f5c4814329fdc35328c7822ed23e4f3d1390ad5
MD5 4804eafb3af1691194f365b44e778aa9
BLAKE2b-256 2df717d1e01eeb7aaa99e321bc9c8b059bb5e403e49db081a71a75f5201df85b

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fop2-0.1.0-cp38-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-cp38-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b402b819b807ef56e0b6a2da651a3f22f5510435d51633e22b3a6a5d80007cf5
MD5 357084eb6a21f4e5e43125a8be8f7964
BLAKE2b-256 d0dc1a7fe062813ce4bbd896c12f6003f8c3413227775e19b2a4ec2dc3b75801

See more details on using hashes here.

File details

Details for the file fop2-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for fop2-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 fadcf862f136673eb1cee4585da60be67c6088cf4153d27381ec548bbe1ee82e
MD5 e418fc1e2a77f5beb4cb23c23d662592
BLAKE2b-256 28a8016a24815bcbc4ab958822872f73a8442de830b8f798f25bee84d57aa79e

See more details on using hashes here.

Provenance

The following attestation bundles were made for fop2-0.1.0-cp38-abi3-macosx_10_12_x86_64.whl:

Publisher: pypi-publish.yml on cool-japan/fop

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page