DEV Community

Jeremy K.
Jeremy K.

Posted on

Pure Frontend RTF-to-PDF Conversion with JavaScript

Introduction

Converting document formats within a web application is a perennial challenge for frontend developers. Traditional approaches lean on server‑side APIs, but they introduce extra infrastructure costs, network latency, and privacy concerns. With WebAssembly (WASM) reaching broad browser support, it is now feasible to perform RTF‑to‑PDF conversion entirely on the client side. This article walks through a WASM‑based implementation that runs in the browser with no external service dependencies.


Setup and Installation

Before writing any code, complete the following preparatory steps.

1. Install the package

Use npm to add the document‑processing library:

npm i spire.office
Enter fullscreen mode Exit fullscreen mode

This package supports multiple file formats; we will use only its RTF‑to‑PDF capability.

2. Deploy WASM assets

After installation, copy the required runtime files into your project's static directory (e.g., public/). The essential files are:

  • spire.doc.js
  • spire.common.js
  • Spire.Doc.Wasm.zip
  • Spire.Common.Wasm.zip
  • _framework/ (entire folder)

After placing them in public/, your directory structure should resemble:

public/
├── spire.doc.js
├── spire.common.js
├── Spire.Doc.Wasm.zip
├── Spire.Common.Wasm.zip
├── _framework/
│   └── ...
└── static/
    ├── font/          # TrueType fonts (e.g., times.ttf)
    └── data/          # Sample RTF input files (optional)
Enter fullscreen mode Exit fullscreen mode

3. Prepare font files

To ensure correct text rendering in the output PDF, place the necessary TrueType font files in the static font directory (e.g., public/static/font/). This example uses Times New Roman; you can substitute any fonts licensed for your use case.

Important: Verify that you have the legal right to distribute and use these fonts within your application.

Once these assets are in place, you are ready to implement the conversion logic.


Architecture Overview

Our solution leverages WebAssembly to compile a robust document‑processing library into a WASM module that executes directly in the browser. The architecture consists of four layers:

  • Loader – dynamically imports the WASM module using import().
  • Virtual File System (VFS) – emulates a file system inside the WASM memory, enabling the module to read/write files without touching the host OS.
  • Conversion Engine – the WASM core that parses RTF and generates PDF data.
  • Output Handler – extracts the PDF from the VFS and triggers a browser download.

Core Implementation

1. Loading the WASM Module

We use a React useEffect hook to manage the module lifecycle, loading it asynchronously on demand:

useEffect(() => {
  (async () => {
    try {
      const publicUrl = process.env.PUBLIC_URL || '';
      // Dynamic import – webpackIgnore prevents bundling interference
      const spireModule = await import(
        /* webpackIgnore: true */ `${publicUrl}/spire.doc.js`
      );
      const rawModule = spireModule.default || spireModule;
      // Initialize the WASM instance; locateFile tells it where to fetch .wasm binaries
      window.wasmModule = typeof rawModule === 'function'
        ? await rawModule({ 
            locateFile: p => p.endsWith('.wasm') 
              ? `${publicUrl}/${p}` 
              : p 
          })
        : rawModule;
      setWasmModule(window.wasmModule);
    } catch (error) {
      console.error('Failed to load WASM module:', error);
    }
  })();
}, []);
Enter fullscreen mode Exit fullscreen mode

The locateFile callback is critical—it guides the runtime to the correct URL for the .wasm files. The /* webpackIgnore: true */ comment prevents Webpack from attempting to resolve the dynamic path at build time.

2. Virtual File System (VFS) Setup

Because the WASM module runs in a sandbox, it cannot access the host file system directly. We populate an in‑memory VFS with all required resources (fonts and input documents) using the provided FetchFileToVFS helper:

// Load fonts into the VFS under the expected path
await window.spire.FetchFileToVFS(
  'times.ttf',
  '/Library/Fonts/',
  `${publicUrl}/static/font/`
);
await window.spire.FetchFileToVFS('timesbd.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
await window.spire.FetchFileToVFS('timesbi.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
await window.spire.FetchFileToVFS('timesi.ttf', '/Library/Fonts/', `${publicUrl}/static/font/`);
Enter fullscreen mode Exit fullscreen mode

Fonts are essential for accurate PDF output—without them, characters may render as missing glyphs.

3. Conversion Workflow

The core conversion function performs these steps:

  • Writes the input RTF file into the VFS.
  • Instantiates a Document object.
  • Loads the RTF from the VFS.
  • Saves it as a PDF (also into the VFS).
  • Reads the generated PDF data from the VFS.
  • Creates a downloadable Blob and triggers the download.
  • Cleans up resources.
const convertRtfToPdf = async () => {
  const wasmModule = window.wasmModule.spiredoc;
  if (!wasmModule) return;

  // Place the input file into the VFS
  await window.spire.FetchFileToVFS(
    'input.rtf',
    '',
    `${process.env.PUBLIC_URL}/static/data/`
  );

  const doc = new wasmModule.Document();
  doc.LoadFromFile('input.rtf');

  const outputFileName = 'RtfToPdf.pdf';
  doc.SaveToFile({
    fileName: outputFileName,
    fileFormat: wasmModule.FileFormat.PDF
  });

  // Read the PDF from the VFS
  const pdfData = window.dotnetRuntime.Module.FS.readFile(outputFileName);

  // Trigger download
  const blob = new Blob([pdfData], { type: 'application/pdf' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = outputFileName;
  a.click();

  // Release object URL and dispose document
  URL.revokeObjectURL(url);
  doc.Dispose();
};
Enter fullscreen mode Exit fullscreen mode

4. Cleanup

WASM memory is not garbage‑collected automatically; you must explicitly release files and objects to avoid leaks:

// Remove temporary files from the VFS
window.dotnetRuntime.Module.FS.unlink('input.rtf');
window.dotnetRuntime.Module.FS.unlink('RtfToPdf.pdf');

// Dispose the Document instance
doc.Dispose();
Enter fullscreen mode Exit fullscreen mode

Browser support: This approach relies on WebAssembly and Blob URLs, which are supported in all modern browsers (Chrome, Firefox, Safari, Edge). No fallback is provided for legacy browsers.


Conclusion

By harnessing WebAssembly, we have built a client‑side RTF‑to‑PDF converter that eliminates server dependencies and reduces both cost and complexity. The key benefits are:

  • Low latency – all processing occurs locally, with zero network round‑trips.
  • Reduced infrastructure – no need to maintain or scale conversion servers.
  • Enhanced privacy – user documents never leave the browser, avoiding third‑party exposure.

That said, this approach has its limits. Very large files or extremely complex layouts may still benefit from a server‑side fallback. Evaluate your specific performance requirements and document complexity to decide whether a pure frontend solution suffices, or whether a hybrid client‑server model is more appropriate.

Top comments (0)