How Convert Enables Cross-Medium File Conversion: Image to PDF and Beyond

Convert supports cross-medium file conversions through a plugin-style architecture where format handlers expose supported formats and a global traversal graph discovers conversion routes, enabling direct image-to-PDF transformation via the ImageMagick handler.

The p2r3/convert repository solves the challenge of transforming files across different media types—such as converting raster images to PDF documents—without requiring users to install multiple standalone tools. By abstracting format capabilities behind a unified handler interface and routing conversions through a directed graph, the system can chain operations or execute direct transformations when supported.

The Handler Architecture: FormatHandler Interface and Supported Formats

At the core of cross-medium conversion lies the FormatHandler interface. Each handler implements this contract to advertise which formats it can read and write through a supportedFormats array of FileFormat objects.

How ImageMagick Exposes PDF Writing Capabilities

The ImageMagickHandler in src/handlers/ImageMagick.ts dynamically builds its format list by querying the ImageMagick-WASM library. It iterates over Magick.supportedFormats to create entries for every image type the library can process.

Crucially, the handler sets the to property to true for PDFs, indicating it can write PDF files, while setting from to false to indicate it does not read PDFs:

// src/handlers/ImageMagick.ts – building the format list
Magick.supportedFormats.forEach(format => {
  const mimeType = format.mimeType || mime.getType(formatName);
  this.supportedFormats.push({
    name: format.description,
    format: formatName === "jpg" ? "jpeg" : formatName,
    extension: formatName,
    mime: normalizeMimeType(mimeType),
    from: mimeType === "application/pdf" ? false : format.supportsReading,
    to: format.supportsWriting,               // ← true for PDF
    internal: format.format,
    category: mimeType.split("/")[0],
    lossless: ["png","bmp","tiff"].includes(formatName)
  });
});

This configuration allows any image format that ImageMagick supports—such as PNG, JPEG, or WebP—to be converted directly into a PDF document.

Building the Conversion Graph with TraversionGraph

Cross-medium conversion routing is handled by the TraversionGraph class in src/TraversionGraph.ts. During initialization in src/main.ts, the system loads each handler, executes handler.init(), caches the supported formats, and registers them in the graph.

The graph treats each FileFormat as a node and creates directed edges for every format where the to property is enabled. When a user selects an input format (e.g., PNG) and an output format (e.g., PDF), the tryConvertByTraversing function queries the graph for the shortest path. Because ImageMagick advertises a direct edge from PNG to PDF, the path consists of a single step:

// src/main.ts – building the option list & initializing the graph
window.traversionGraph.init(window.supportedFormatCache, handlers);

Executing Cross-Medium Conversions

Once the graph identifies a valid conversion route, the attemptConvertPath function traverses the path step-by-step. For each segment, it invokes the handler's doConvert method with the current file bytes and the target FileFormat.

Image to PDF Conversion Flow

The ImageMagickHandler.doConvert method in src/handlers/ImageMagick.ts executes the actual transformation. It reads the input image using Magick's read settings, then writes the output collection to the requested format (PDF):

// src/handlers/ImageMagick.ts – conversion core
const inputSettings = new MagickReadSettings();
inputSettings.format = inputMagickFormat;
// …read input, push to collection…
outputCollection.write(outputMagickFormat, bytes => resolve(new Uint8Array(bytes)));

Reverse Direction: PDF to Image

While ImageMagick handles image-to-PDF conversion, the reverse operation—extracting images from PDF files—is handled by a dedicated pdftoimg handler in src/handlers/pdftoimg.ts. This separation of concerns allows each handler to specialize in specific media transformations while the graph coordinates complex multi-step conversions when necessary.

Programmatic Usage Example

You can trigger cross-medium conversions programmatically using the exported tryConvertByTraversing function:

import { tryConvertByTraversing } from "./main.ts";
import CommonFormats from "./CommonFormats.ts";

/* Prepare a single PNG file */
const pngBytes = await fetch("example.png").then(r => r.arrayBuffer());
const inputFile = { name: "example.png", bytes: new Uint8Array(pngBytes) };

/* Define the source and target formats */
const from = {
  handler: null!,                                 // will be filled by graph
  format: CommonFormats.PNG.builder("png")        // PNG source
};
const to = {
  handler: null!,
  format: CommonFormats.PDF.builder("pdf")        // PDF target
};

/* Perform the conversion */
const result = await tryConvertByTraversing([inputFile], from, to);

if (result) {
  const pdf = result.files[0];
  // e.g., create a blob URL to download
  const url = URL.createObjectURL(new Blob([pdf.bytes], { type: pdf.mime }));
  console.log("PDF generated:", url);
}

Behind the scenes, the graph resolves from.handler and to.handler (ImageMagick for both), then runs ImageMagickHandler.doConvert to produce the PDF.

Summary

  • Convert uses a modular handler architecture where each handler implements the FormatHandler interface and advertises capabilities via supportedFormats.
  • ImageMagickHandler in src/handlers/ImageMagick.ts enables image-to-PDF conversion by setting to: true for PDF formats while leveraging the ImageMagick-WASM library for actual byte transformation.
  • TraversionGraph in src/TraversionGraph.ts constructs a directed graph of format nodes and edges, enabling automatic route discovery for cross-medium conversions.
  • The system supports reverse conversions (PDF to image) through specialized handlers like pdftoimg, allowing bidirectional cross-medium transformations.

Frequently Asked Questions

How does Convert handle formats that require intermediate conversion steps?

When no direct edge exists between the input and output formats, the TraversionGraph searches for the shortest path through intermediate nodes. For example, if converting from an obscure image format to a document format required an intermediate step, the graph would route through the necessary handlers automatically, chaining multiple doConvert calls in src/main.ts until the final format is reached.

Can Convert preserve image quality when converting to PDF?

Yes, the ImageMagickHandler preserves the original image data during conversion. In src/handlers/ImageMagick.ts, the handler reads the source image using MagickReadSettings and writes it directly to PDF format without re-encoding or compression unless specified. The lossless property in the FileFormat object also indicates which formats (like PNG, BMP, and TIFF) maintain lossless quality throughout the conversion chain.

What is the difference between the ImageMagick and pdftoimg handlers?

The ImageMagick handler in src/handlers/ImageMagick.ts specializes in raster image processing and can write PDF files, making it responsible for image-to-PDF conversions. Conversely, the pdftoimg handler in src/handlers/pdftoimg.ts specifically extracts raster images from PDF documents, handling the PDF-to-image direction. This separation allows each handler to optimize for its specific media transformation while the TraversionGraph coordinates complex workflows that might require both.

Is it possible to convert multiple images into a single PDF document?

According to the source code in src/handlers/ImageMagick.ts, the doConvert method processes collections of images using ImageMagick's MagickImageCollection. When multiple input files are provided, the handler can write them as a multi-page PDF document, with each image becoming a separate page in the output file.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →