npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cooljapan/fop

v0.1.0

Published

WebAssembly XSL-FO to PDF/SVG converter - Pure Rust implementation

Downloads

47

Readme

fop-wasm

Crates.io docs.rs License WebAssembly

WebAssembly bindings for FOP — a high-performance, pure-Rust reimplementation of the Apache FOP XSL-FO processor. This crate exposes XSL-FO to PDF, SVG, and plain-text conversion directly in the browser or Node.js via wasm-bindgen.


Features

  • XSL-FO to PDF — converts XSL-FO documents to valid PDF bytes (Uint8Array) consumable directly by the browser or Node.js
  • XSL-FO to SVG — renders XSL-FO to scalable vector graphics (SVG string)
  • XSL-FO to plain text — extracts text content from XSL-FO documents
  • Document validation — parses and validates XSL-FO without rendering; returns a JSON result with node count or error details
  • Class-based API (FopConverter) — stateful object with optional verbose logging
  • One-shot function API (convertFoToPdf, convertFoToSvg) — convenience functions for ad-hoc conversions without instantiating a class
  • Supported format query (supportedFormats) — returns the list of supported output format identifiers at runtime
  • Pure Rust — no native C/Fortran dependencies; compiles cleanly to wasm32-unknown-unknown
  • 10–1200x faster than Java FOP for typical documents

Installation

Via npm / yarn (pre-built WASM package)

npm install fop-wasm
# or
yarn add fop-wasm

Rust [dependencies]

[dependencies]
fop-wasm = "0.1"

To use as a WASM library from another Rust crate that compiles to WASM, add:

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
fop-wasm = { version = "0.1", default-features = false }
wasm-bindgen = "0.2"

JavaScript / TypeScript Usage

Initialization

The WASM module must be initialized before any conversion function is called. The default export returned by wasm-pack is the init async function.

import init, { FopConverter, convertFoToPdf, convertFoToSvg, supportedFormats } from 'fop-wasm';

await init(); // loads and compiles the .wasm binary

In Node.js (CommonJS) environments built with wasm-pack --target nodejs:

const { FopConverter, convertFoToPdf, supportedFormats } = require('fop-wasm');
// Node.js target does not require an explicit init() call

Class-based API (FopConverter)

FopConverter is the primary API surface. It accepts XSL-FO XML strings and returns the rendered output.

import init, { FopConverter } from 'fop-wasm';

await init();

const foXml = `<?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 in the browser!</fo:block>
      <fo:block font-size="12pt" space-before="6pt">
        Rendered entirely in WebAssembly — no server required.
      </fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>`;

const fop = new FopConverter();

// --- PDF ---
// Returns Uint8Array
const pdfBytes = fop.convertToPdf(foXml);
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);

// --- SVG ---
// Returns a string containing the <svg>...</svg> markup
const svgString = fop.convertToSvg(foXml);
document.getElementById('preview').innerHTML = svgString;

// --- Plain text ---
// Returns a string with extracted text content
const textContent = fop.convertToText(foXml);
console.log(textContent);

// --- Validation (no render) ---
// Returns JSON: {"valid": true, "nodes": 7} or {"valid": false, "error": "..."}
const validationJson = fop.validate(foXml);
const result = JSON.parse(validationJson);
if (result.valid) {
  console.log(`Document is valid (${result.nodes} nodes)`);
} else {
  console.error(`Validation failed: ${result.error}`);
}

// --- Version ---
console.log(fop.version()); // e.g. "fop-wasm 0.1.0"

// --- Verbose logging ---
fop.setVerbose(true);

One-shot Function API

For simple one-off conversions without managing a FopConverter instance:

import init, { convertFoToPdf, convertFoToSvg } from 'fop-wasm';

await init();

// Convert directly to PDF bytes (Uint8Array)
const pdfBytes = convertFoToPdf(foXml);

// Convert directly to SVG string
const svgString = convertFoToSvg(foXml);

Querying Supported Formats

import init, { supportedFormats } from 'fop-wasm';

await init();

const formats = supportedFormats(); // ["pdf", "svg", "text"]
console.log('Supported formats:', formats.join(', '));

TypeScript Type Signatures

When using TypeScript, the wasm-bindgen-generated .d.ts declarations provide:

export class FopConverter {
  constructor();
  setVerbose(verbose: boolean): void;
  convertToPdf(fo_xml: string): Uint8Array;
  convertToSvg(fo_xml: string): string;
  convertToText(fo_xml: string): string;
  validate(fo_xml: string): string;
  version(): string;
  free(): void;
}

export function convertFoToPdf(fo_xml: string): Uint8Array;
export function convertFoToSvg(fo_xml: string): string;
export function supportedFormats(): string[];

export default function init(input?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module): Promise<InitOutput>;

Error Handling

All conversion methods return JavaScript Error objects when conversion fails (via wasm-bindgen's Result<T, JsValue> mapping). Use try/catch:

try {
  const pdf = fop.convertToPdf(invalidFoXml);
} catch (err) {
  console.error('Conversion failed:', err.message);
}

Building from Source

Prerequisites

# Install wasm-pack
cargo install wasm-pack

# Install the wasm32-unknown-unknown target
rustup target add wasm32-unknown-unknown

Build commands

# Browser (ES module, recommended for bundlers like Webpack/Vite)
wasm-pack build crates/fop-wasm --target web --out-dir pkg

# Node.js (CommonJS)
wasm-pack build crates/fop-wasm --target nodejs --out-dir pkg-node

# Bundler (for use with bundlers that handle WASM directly)
wasm-pack build crates/fop-wasm --target bundler --out-dir pkg-bundler

# Release build (optimized, smaller WASM binary)
wasm-pack build crates/fop-wasm --target web --release --out-dir pkg

After building, the pkg/ directory contains:

  • fop_wasm_bg.wasm — the compiled WebAssembly binary
  • fop_wasm.js — JavaScript glue code
  • fop_wasm.d.ts — TypeScript declarations
  • package.json — npm package metadata

Running WASM tests

# Run tests in a headless browser (requires wasm-pack)
wasm-pack test crates/fop-wasm --headless --chrome

# Run native (non-WASM) unit tests
cargo test -p fop-wasm

Browser vs Node.js

| Aspect | Browser (--target web) | Node.js (--target nodejs) | |---|---|---| | Initialization | await init() required | No explicit init() needed | | Module format | ES module (import) | CommonJS (require) | | PDF output | Uint8ArrayBlob → object URL | Buffer.from(pdfBytes)fs.writeFileSync | | SVG output | Set innerHTML directly | Write to file or respond via HTTP |

Node.js Example

const { FopConverter } = require('./pkg-node/fop_wasm');
const fs = require('fs');

const fop = new FopConverter();
const pdfBytes = fop.convertToPdf(foXmlString);

// pdfBytes is a Uint8Array; convert to Buffer for Node.js file I/O
fs.writeFileSync('output.pdf', Buffer.from(pdfBytes));
console.log('PDF written to output.pdf');

Webpack / Vite Integration

For projects using Webpack 5 or Vite, use the bundler target and configure the bundler to handle .wasm files as assets:

// vite.config.js (Vite)
export default {
  optimizeDeps: {
    exclude: ['fop-wasm'],
  },
};
// Dynamic import pattern for lazy loading
const { default: init, FopConverter } = await import('fop-wasm');
await init();
const fop = new FopConverter();

Feature Flags

| Feature | Default | Description | |---|---|---| | (none) | — | The crate has no optional Cargo features. All output formats (PDF, SVG, text) are always available. The fop-render dependency is included with default-features = false to minimize WASM binary size. |


Crate Architecture

fop-wasm is a thin wasm-bindgen adapter layer over the core FOP pipeline:

JavaScript / TypeScript
        |
   wasm-bindgen
        |
   fop-wasm (this crate)
        |
   +-----------+----------+----------+
   |           |          |          |
fop-core   fop-layout  fop-render  fop-types
(parse)    (layout)    (PDF/SVG)   (types/errors)

The conversion pipeline in each method follows three deterministic steps:

  1. ParseFoTreeBuilder::new().parse(cursor) produces an arena-allocated FO tree
  2. LayoutLayoutEngine::new().layout(&arena) produces an area tree
  3. RenderPdfRenderer / SvgRenderer / TextRenderer serializes the area tree to the target format

Because FoArena carries a lifetime parameter for property inheritance, the entire parse-layout-render pipeline executes within a single function call, keeping lifetimes on the stack.


Related Crates

| Crate | Description | |---|---| | fop-types | Shared types: Length, Color, Rect, FopError | | fop-core | XSL-FO document parsing and property system | | fop-layout | Layout engine: block, inline, table, list | | fop-render | Rendering backends: PDF, SVG, text | | fop-cli | Command-line interface | | fop-python | Python bindings via PyO3 |


License

Copyright 2024 COOLJAPAN OU (Team Kitasan)

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

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-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.