Building a Server-Side PDF Processing Pipeline with RadaeePDF

Server-side PDF SDK pipeline processing documents in batch

This guide shows how to build a PDF processing pipeline with a server-side PDF SDK: the RadaeePDF Server engine driven from shell scripts, Java and C# on Windows and Linux.

Applies to: RadaeePDF Server SDK (Java bindings, C# bindings and CLI tools) on Windows and Linux.

Key takeaways:

  • RadaeePDF Server is a server-side PDF SDK: a native C++ engine for Windows and Linux that runs headless.
  • You can drive it from shell scripts with the CLI tools, from Java through JNI, or from C#.
  • The same engine renders pages to images, extracts text, encrypts with AES-256 and merges documents.
  • One server license covers any number of concurrent processes on that machine.

Backend workflows often need PDF processing without any user interface: turning uploaded documents into page images, extracting text for search, protecting files before delivery, or assembling several documents into one. The RadaeePDF Server SDK is built for that job. It is compiled from native C++ code, runs on Windows and Linux, and exposes the same engine through Java, C# and a set of command-line tools.

This guide walks through a pipeline in three layers: shell scripts that call the CLI tools, Java code built on the classes in the PDFJniDemo project, and C# code built on the wrappers shipped in the C# folder. Every call shown here comes from the public RadaeePDF-Server repository.

What the server-side PDF SDK package contains

The repository is organized by integration type:

  • Binaries/: the native engine, RDDJni.dll for Windows (x64 and x86) and libRDDJni.so for Ubuntu 22.04 (x64 and ARM64) and Ubuntu 24.04 (x64).
  • CLI/: ready-to-run executables rdinfo, rdsec, rdpage and rdcvt, plus their C++ sources.
  • java/: the PDFJniDemo Eclipse project (packages com.radaee.comm, com.radaee.pdf, com.radaee.util) and a JSPDemo web sample.
  • C#/: wrapper sources for Windows and Linux (RDDCom.cs, RDDPDF.cs, RDDDOCX.cs) and two demo projects.
  • res/: resource files, including cmaps.dat and umaps.dat, which are required for correct rendering and text extraction.

Choosing the right layer

The three layers share one engine, so the choice is about how the pipeline is operated, not about features:

  • CLI tools fit scheduled jobs, shell-based workers and quick prototypes. Nothing has to be compiled, and every operation is one process call with a clear exit point.
  • Java bindings fit services that already run on the JVM, including servlet containers: the JSPDemo folder shows page rendering, splitting and DOCX conversion called from JSP pages.
  • C# bindings fit .NET services on Windows or Linux. The wrappers are plain source files that you add to your project, and they call the native library through DllImport.

A common pattern is to start with the CLI to validate the output on real documents, then move the same steps into Java or C# when you need finer control, such as custom file naming, per-page error handling or streaming results back to a web request.

Deploying the native engine

The rule repeated across the READMEs is simple: the native library and cmaps.dat/umaps.dat must be deployed together. On Linux, the Binaries README installs them like this:

sudo cp "Binaries/Ubuntu22.04.1 LTS x64/libRDDJni.so" /usr/local/lib/
sudo cp res/cmaps.dat res/umaps.dat /usr/local/lib/
sudo ldconfig

For Java, start the JVM with -Djava.library.path pointing to the folder that holds the library, for example java -Djava.library.path=/usr/local/lib -jar YourApp.jar.

There is one Linux detail worth knowing before your first run. Both Global.Init in the Java demo and RDGlobal.Init in the C# wrapper load the CMaps, the CMYK profile and the fallback fonts from hard-coded paths under /root/res/. On Windows they call a different loader. Edit those paths so they point to your real resource directory, or the engine starts without fonts and character maps.

Layer 1: batch jobs with the CLI tools

The CLI tools are the fastest way to add PDF processing to cron jobs, queues or CI scripts. Copy the whole content of CLI/Ubuntu-22.04-LTS (or CLI/Windows-x64) to the target folder, keep libRDDJni.so, cmaps.dat and umaps.dat next to the executables, and on Linux run chmod +x rdinfo rdsec rdpage rdcvt.

The tools need an activated license. If they report “license not activated”, run ./rdinfo -r to print the request code, obtain a key for it from RadaeePDF, and save the key in a file named key.txt in the program directory.

Each tool takes the file, then its password ("" when there is none):

  • rdinfo -info <file> <pswd> prints document information; rdinfo -set <file> <pswd> Author=... Title=... writes metadata.
  • rdpage -txt <file> <pswd> <list> <out.txt> extracts text; an empty list means all pages.
  • rdpage -exp <file> <pswd> <png|jpg> <dpi> <directory> exports every page as 0000.png, 0001.png and so on. A dpi of 72 means 100%, 144 means 200%.
  • rdpage -del, -rot and -imp delete, rotate (multiples of 90) and import pages.
  • rdsec -enc <file> <pswd> <out> <owner-pswd> <user-pswd> encrypts; rdsec -dec removes the password; rdsec -wm stamps a PNG (RGBA) watermark on every page.

A nightly job that processes a folder of incoming files can be written with nothing but these options:

#!/bin/bash
# libRDDJni.so, cmaps.dat, umaps.dat and key.txt
IN=/data/incoming
OUT=/data/processed

for f in "$IN"/*.pdf; do
  name=$(basename "$f" .pdf)
  mkdir -p "$OUT/$name/pages"

  ./rdinfo -info "$f" "" > "$OUT/$name/info.txt"
  ./rdpage -txt "$f" "" "" "$OUT/$name/text.txt"
  ./rdpage -exp "$f" "" png 144 "$OUT/$name/pages"
  ./rdsec -enc "$f" "" "$OUT/$name/$name-protected.pdf" "owner-secret" ""
done

To merge documents, ./rdpage -imp test.pdf "" src.pdf "" appends all pages of src.pdf to test.pdf, creating test.pdf if it does not exist. Options such as -del, -rot and -imp take no output path, so run them on a copy if you need to keep the original.

Layer 2: Java integration

When the pipeline lives inside a Java service, use the classes from java/PDFJniDemo/src. Initialization is a single call: Global.Init(String serial) loads the RDDJni library, activates the license and returns true on success. Later calls return immediately, so call it once at startup. Without an active license the demo shows that GetPage returns null after the first pages, so always check for it.

The following class renders every page to PNG. It follows the render_pages method of JSPDemo/images.jsp, including its 4096-pixel cap that keeps very large pages from exhausting memory.

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

import com.radaee.comm.Global;
import com.radaee.comm.Matrix;
import com.radaee.pdf.Document;
import com.radaee.pdf.Page;

public class PdfPipeline {

    public static void main(String[] args) throws IOException {
        if (!Global.Init("YOUR-LICENSE-KEY")) {
            System.err.println("License not activated: output will be limited.");
        }
        int err = renderPages(args[0], "", new File(args[1]), 2.0f);
        System.out.println("Result: " + err);
    }

    // Returns 0 on success or the Document.Open error code:
    // -1 password needed, -2 unknown encryption, -3 damaged file, -10 access denied
    static int renderPages(String pdfPath, String password, File outDir, float scale)
            throws IOException {
        Document doc = new Document();
        int err = doc.Open(pdfPath, password);
        if (err != 0) return err;
        outDir.mkdirs();
        try {
            int count = doc.GetPageCount();
            for (int pno = 0; pno < count; pno++) {
                Page page = doc.GetPage(pno);
                if (page == null) break; // license not activated
                float pw = doc.GetPageWidth(pno);
                float ph = doc.GetPageHeight(pno);
                float s = scale;
                if (pw * s > 4096.0f) s = 4096.0f / pw;
                if (ph * s > 4096.0f) s = 4096.0f / ph;
                int iw = (int) (pw * s + 0.5f);
                int ih = (int) (ph * s + 0.5f);

                BufferedImage bimg = new BufferedImage(iw, ih, BufferedImage.TYPE_INT_ARGB);
                Matrix mat = new Matrix(s, -s, 0, ph * s); // flip the PDF y axis
                boolean ok = page.RenderToBuf(bimg, -1, mat); // -1 = white background
                mat.Destroy();
                page.Close();
                if (ok) {
                    ImageIO.write(bimg, "png", new File(outDir, String.format("p%06d.png", pno)));
                }
            }
        } finally {
            doc.Close();
        }
        return 0;
    }
}

Encrypting and merging in Java

Document.EncryptAs writes an encrypted copy. The bundled guide (java/doc/genc.html) explains the arguments: user password, owner password, permission bits (-1 grants all), method 3 for AES-256 (other values use AES-128), and a 32-byte file ID. The method requires a premium license.

import java.security.SecureRandom;
import com.radaee.pdf.Document;

public class PdfSecurity {
    static boolean encrypt(String src, String dst, String ownerPassword) {
        Document doc = new Document();
        if (doc.Open(src, null) != 0) return false;
        byte[] id = new byte[32];
        new SecureRandom().nextBytes(id);
        boolean ok = doc.EncryptAs(dst, "", ownerPassword, -1, 3, id);
        doc.Close();
        return ok;
    }
}

To decrypt, open the file with its password and call doc.SaveAs(path, true), which saves the document with its security removed.

Merging uses Document.ImportStart, which returns a Document.ImportContext. Keep the source document open until you call Destroy() on the context. Importing also requires a premium license.

import com.radaee.pdf.Document;

public class PdfMerge {
    // Appends all pages of srcPath to the end of dstPath
    static boolean append(String dstPath, String srcPath) {
        Document dst = new Document();
        Document src = new Document();
        if (dst.Open(dstPath, null) != 0 || src.Open(srcPath, null) != 0) {
            src.Close();
            dst.Close();
            return false;
        }
        Document.ImportContext ctx = dst.ImportStart(src);
        boolean ok = (ctx != null);
        if (ok) {
            int base = dst.GetPageCount();
            int count = src.GetPageCount();
            for (int i = 0; i < count && ok; i++) {
                ok = ctx.ImportPage(i, base + i);
            }
            ctx.Destroy();
        }
        if (ok) ok = dst.Save();
        src.Close();
        dst.Close();
        return ok;
    }
}

To start from an empty file instead, call Document.Create(path), which returns 0 on success. This is how split.jsp writes each page to its own PDF.

Layer 3: C# integration

The C# wrappers live in the RDDLib.comm and RDDLib.pdf namespaces. The Windows demo calls RDGlobal.Init() and then RDGlobal.Active(key), which returns 3 on success. Rendering uses an RDDIB pixel buffer, and RDDIB can save itself straight to PNG or JPEG, so no imaging library is needed.

using System;
using RDDLib.comm;
using RDDLib.pdf;

public class Program
{
    static int Main(string[] args)
    {
        RDGlobal.Init();
        if (RDGlobal.Active("YOUR-LICENSE-KEY") != 3)
        {
            Console.WriteLine("License activation failed");
            return 1;
        }

        PDFDoc doc = new PDFDoc();
        int err = doc.Open(args[0], "");
        if (err != 0)
        {
            Console.WriteLine("Open failed: " + err);
            return 2;
        }

        const float scale = 2.0f; // 144 dpi
        int count = doc.GetPageCount();
        for (int pno = 0; pno < count; pno++)
        {
            PDFPage page = doc.GetPage(pno);
            if (page == null) break;
            float pw = doc.GetPageWidth(pno);
            float ph = doc.GetPageHeight(pno);
            RDDIB dib = new RDDIB((int)(pw * scale), (int)(ph * scale));
            RDMatrix mat = new RDMatrix(scale, -scale, 0, ph * scale);
            page.RenderPrepare(dib);
            if (page.Render(dib, mat, 2))
                dib.SavePNG(string.Format("p{0:D6}.png", pno));
            page.Close();
        }
        doc.Close();
        return 0;
    }
}

The same PDFDoc class has EncryptAs, SaveAs, ImportStart (returning a PDFImporter with ImportPage and ImportEnd), and GetMeta/SetMeta. The Windows demo also converts Word files: DOCXDoc.ExportPDF(PDFDoc) writes a DOCX document into a PDF created with PDFDoc.Create.

Common pitfalls

  • Missing resource files: cmaps.dat and umaps.dat must sit next to the native library. On Linux, also check the /root/res/ paths in Global.Init or RDGlobal.Init.
  • Library not found on Linux: put libRDDJni.so in a directory listed in LD_LIBRARY_PATH or /etc/ld.so.conf, run ldconfig, and inspect dependencies with ldd libRDDJni.so.
  • Architecture or glibc mismatch: compare file libRDDJni.so with uname -m (x86_64 or aarch64). The Ubuntu 22.04 build needs glibc 2.35+, the 24.04 build glibc 2.39+.
  • Windows runtime: the Visual C++ Redistributable must be installed, and the DLL must match the process bitness (x64 or x86).
  • License-bound features: the source marks EncryptAs, ImportStart and NewPage as premium and SaveAs as professional or premium. The license is tied to a request code derived from hardware and network settings, so changing the network configuration can invalidate it.

Licensing for server deployment

Section 12 of the RadaeePDF licensing fact sheet covers server installation. Installing applications, command-line executables or services on a workstation or server requires a proper license, meaning a valid commercial license with a legitimate activation key. The server license does not restrict the number of concurrent processes or applications that invoke the SDK runtime, so one license covers a worker pool or several services on the same machine. Each workstation or server is identified by a unique ID generated by the operating system on its first run, which is why a new or rebuilt server needs its own request code and key.

Frequently asked questions

Can RadaeePDF run on a server without a user interface?

Yes. The RadaeePDF Server SDK is a native C++ engine for Windows and Linux that runs headless. It is meant for conversions, editing and content manipulation in backend services, batch jobs and command-line tools.

Which languages can I use with the RadaeePDF Server SDK?

Java through the JNI bindings in the PDFJniDemo project, C# through the wrappers in the C# folder, and any shell or scheduler that can call the CLI tools such as rdinfo, rdpage and rdsec.

Does the server license limit concurrent processes?

No. Section 12 of the RadaeePDF licensing fact sheet states that the server license does not restrict concurrent processes or applications invoking the SDK runtime. Each workstation or server is identified by a unique ID generated on its first run.

Related on radaeepdf.com

Where to go next

Convert PDF Pages to Images with the RadaeePDF SDK

Converting PDF pages to PNG and JPEG images on server and mobile

This guide shows how to convert PDF to image files, PNG or JPEG, with RadaeePDF on a server, on Android and on iOS.

Applies to: RadaeePDF Server SDK (CLI, Java, C#), RadaeePDF Master for Android, and RadaeePDF Master for iOS.

Key takeaways:

  • To convert PDF to image with RadaeePDF, render each page into a pixel buffer with a matrix, then encode it as PNG or JPEG.
  • On a server you can do it without code using the rdpage CLI, or from Java and C#.
  • Use a scale of dpi / 72: about 2.0 for previews and 4.17 for 300 dpi OCR images.
  • Transparent backgrounds need PNG output; JPEG has no alpha channel.

Turning PDF pages into images is one of the most requested tasks in the RadaeePDF knowledge base. Typical uses are document previews in a web portal, thumbnails in a file list, page images for an OCR or indexing step, and archives that must be readable without a PDF viewer.

Many developers know RadaeePDF as a mobile viewer, but the same native engine also ships as a Server SDK for Windows and Linux. It runs headless and is meant for conversions, editing and content manipulation on a server: rendering pages to bitmaps, extracting text, encrypting, merging, splitting and stamping documents. This article starts on the server, then shows the equivalent code for Android and iOS.

How to convert PDF to image: the rendering model

All editions follow the same model:

  1. Open a Document and get a Page object for a 0-based page number.
  2. Allocate a pixel buffer: a BufferedImage or int[] array (Server Java), an RDDIB (Server C#), a Bitmap (Android) or an RDPDFDIB (iOS).
  3. Build a Matrix that maps PDF coordinates to pixels and render into the buffer.
  4. Encode the pixels as PNG or JPEG, then close the page and the document.

The matrix needs a little attention. PDF coordinates start at the bottom-left corner, while bitmaps start at the top-left. As the Matrix source explains, the usual values are scale for x, -scale for y, and pageHeight * scale as the y offset. A scale of 1.0 renders at the page’s native size, 72 dpi, so 2.0 gives 144 dpi.

Choosing a resolution

Because page sizes are expressed in points (1/72 inch), the scale for a target resolution is simply dpi / 72. A few practical reference points:

  • Thumbnails for a file list or page strip: a scale between 0.25 and 0.5, or a fixed longest side such as 200 pixels.
  • On-screen previews in a web page: 1.5 to 2.0 (108 to 144 dpi) is usually sharp enough on high-density displays.
  • OCR or print-quality archives: scale up to 300 dpi (about 4.17), and keep the output in PNG to avoid compression artifacts around text.

Remember that a 32-bit buffer costs 4 bytes per pixel. An A4 page (595 x 842 points) rendered at scale 2.0 is 1190 x 1684 pixels, about 8 MB before encoding. A server that converts several documents in parallel needs that amount for every page being rendered at the same time.

Server side: convert without writing code

The rdpage tool in the Server SDK’s CLI folder renders pages directly. Use a dpi of 72 for 100% and 144 for 200%:

./rdpage -out test.pdf "" png 108 0/test001.png 1/test002.png

# Export every page as JPEG at 200% into the "test" folder (0000.jpg, 0001.jpg, ...)
./rdpage -exp test.pdf "" jpg 144 test

The second argument is the document password ("" when there is none). Keep libRDDJni.so (or RDDJni.dll), cmaps.dat and umaps.dat in the same folder as the executable. If the tool reports “license not activated”, run ./rdinfo -r to get a request code and put the key you receive in key.txt next to the tools.

Server side: Java

The Java bindings in java/PDFJniDemo render into a standard java.awt.image.BufferedImage through Page.RenderToBuf(BufferedImage bimg, int back_clr, Matrix mat), so the result can go straight to javax.imageio.ImageIO. The method below is modeled on the JSP demo images.jsp. Its size parameter, the length of the longest side in pixels, comes from the knowledge base converter. The method also keeps the JSP demo’s 4096-pixel cap.

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

import com.radaee.comm.Global;
import com.radaee.comm.Matrix;
import com.radaee.pdf.Document;
import com.radaee.pdf.Page;

public class PdfToImages {

    /**
     * @param size        longest side in pixels, 0 = native size (72 dpi)
     * @param png         true for PNG, false for JPEG
     * @param transparent PNG only: keep the page background transparent
     * @return 0 on success, or the Document.Open error code
     *         (-1 password, -2 unknown encryption, -3 damaged, -10 access denied)
     */
    public static int convert(String pdfPath, String password, File outDir,
                              int size, boolean png, boolean transparent) throws IOException {
        Document doc = new Document();
        int result = doc.Open(pdfPath, password);
        if (result != 0) return result;
        outDir.mkdirs();
        try {
            int count = doc.GetPageCount();
            for (int i = 0; i < count; i++) {
                Page page = doc.GetPage(i);
                if (page == null) break; // license not activated
                float pw = doc.GetPageWidth(i);
                float ph = doc.GetPageHeight(i);
                float scale = (size > 0) ? size / Math.max(pw, ph) : 1.0f;
                if (pw * scale > 4096.0f) scale = 4096.0f / pw;
                if (ph * scale > 4096.0f) scale = 4096.0f / ph;
                int iw = Math.max(1, (int) (pw * scale + 0.5f));
                int ih = Math.max(1, (int) (ph * scale + 0.5f));

                BufferedImage bimg = new BufferedImage(iw, ih,
                        png ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
                Matrix mat = new Matrix(scale, -scale, 0, ph * scale);
                int background = (png && transparent) ? 0x00000000 : 0xFFFFFFFF;
                boolean ok = page.RenderToBuf(bimg, background, mat);
                mat.Destroy();
                page.Close();
                if (ok) {
                    String name = String.format("page_%04d.%s", i, png ? "png" : "jpg");
                    ImageIO.write(bimg, png ? "png" : "jpg", new File(outDir, name));
                }
            }
        } finally {
            doc.Close();
        }
        return 0;
    }

    public static void main(String[] args) throws IOException {
        if (!Global.Init("YOUR-LICENSE-KEY")) {
            System.err.println("License not activated");
        }
        System.out.println(convert(args[0], "", new File(args[1]), 1200, true, false));
    }
}

A few notes on this code:

  • Global.Init(String serial) loads the native library and activates the license. On Linux it reads the CMaps and fonts from /root/res/ by default, so adjust those paths in Global.java for your deployment.
  • back_clr is the page background color in ARGB format. The SDK demos pass -1 (opaque white). The Server source documents it only as “the background color”, so a zero-alpha value such as 0x00000000 should leave the background transparent: test it with your SDK version, and remember that transparency only survives if you save as PNG.
  • JPEG has no alpha channel, so the JPEG path renders into a TYPE_INT_RGB image.
  • If you prefer raw pixels, RenderToBuf(int[] data, int w, int h, int back_clr, Matrix mat) fills an array of 0xAARRGGBB values.

Server side: C#

The C# wrappers (RDDLib.comm, RDDLib.pdf) render into an RDDIB buffer, which can encode itself with SavePNG(path) or SaveJPG(path, quality):

using System;
using RDDLib.comm;
using RDDLib.pdf;

public static class PdfToImages
{
    public static int Convert(string pdfPath, string outDir, float scale)
    {
        PDFDoc doc = new PDFDoc();
        int err = doc.Open(pdfPath, "");
        if (err != 0) return err;

        int count = doc.GetPageCount();
        for (int i = 0; i < count; i++)
        {
            PDFPage page = doc.GetPage(i);
            if (page == null) break;
            float pw = doc.GetPageWidth(i);
            float ph = doc.GetPageHeight(i);
            RDDIB dib = new RDDIB((int)(pw * scale), (int)(ph * scale));
            RDMatrix mat = new RDMatrix(scale, -scale, 0, ph * scale);
            page.RenderPrepare(dib);
            if (page.Render(dib, mat, 2))
                dib.SaveJPG(System.IO.Path.Combine(outDir, $"page_{i:D4}.jpg"), 90);
            page.Close();
        }
        doc.Close();
        return 0;
    }
}

Call RDGlobal.Init() and RDGlobal.Active(key) (which returns 3 on success) once before converting, as the Windows demo does.

Android (Master)

On Android, the Master library renders into an android.graphics.Bitmap with Page.RenderToBmp(Bitmap, Matrix). The knowledge base article on converting PDFs to images notes that the logic is the same as on the server. Here it is with the current Master API, where Matrix lives in com.radaee.comm:

import android.content.Context;
import android.graphics.Bitmap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import com.radaee.comm.Global;
import com.radaee.comm.Matrix;
import com.radaee.pdf.Document;
import com.radaee.pdf.Page;

public final class PdfToImages {

    public static int convert(Context ctx, String pdfPath, String password,
                              File outDir, int size, boolean transparent) {
        Global.Init(ctx); // license key is read from Global.mKey
        Document doc = new Document();
        int result = doc.Open(pdfPath, password);
        if (result != 0) return result;
        outDir.mkdirs();
        int count = doc.GetPageCount();
        for (int i = 0; i < count; i++) {
            Page page = doc.GetPage(i);
            if (page == null) continue;
            float pw = doc.GetPageWidth(i);
            float ph = doc.GetPageHeight(i);
            float ratio = (size > 0) ? size / Math.max(pw, ph) : 1.0f;
            Bitmap bmp = Bitmap.createBitmap((int) (pw * ratio), (int) (ph * ratio),
                    Bitmap.Config.ARGB_8888);
            if (transparent) {
                bmp.eraseColor(0x00000000);       // fully transparent
                page.RenderPrepare((Bitmap) null); // reset page status, no erase
            } else {
                page.RenderPrepare(bmp);           // erases the bitmap to white
            }
            Matrix mat = new Matrix(ratio, -ratio, 0, ph * ratio);
            boolean ok = page.RenderToBmp(bmp, mat);
            mat.Destroy();
            page.Close();
            if (ok) {
                File out = new File(outDir, "page_" + i + (transparent ? ".png" : ".jpg"));
                try (FileOutputStream fos = new FileOutputStream(out)) {
                    bmp.compress(transparent ? Bitmap.CompressFormat.PNG
                            : Bitmap.CompressFormat.JPEG, 90, fos);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            bmp.recycle();
        }
        doc.Close();
        return 0;
    }
}

Transparent backgrounds on Android

Transparent backgrounds are among the most-read rendering topics in the knowledge base. The approach above follows the Android article: erase the bitmap to 0x00000000 and render without RenderPrepare(bmp), because that overload erases the bitmap to white. Older articles also call BMP.MulAlpha() afterwards. In the current Master source that method is marked deprecated since 3.12beta2, because the improved color blending no longer needs it.

Form fields are drawn with a translucent highlight (default 0x200040FF). The knowledge base converter turns it off before exporting. In Master for Android, call Global.setAnnotTransparency(0) after Global.Init.

iOS (Master)

The iOS Master library exposes Objective-C classes in PDFObjc.h: RDPDFDoc, RDPDFPage, RDPDFDIB and RDPDFMatrix. Initialize once with [RDVGlobal Init], which activates the serial set in RDVGlobal.m. Then render into a DIB and wrap its pixels in a CGImage, using the same bitmap flags as the sample app’s file browser:

#import <UIKit/UIKit.h>
#import "PDFObjc.h"

// Renders every page to PNG. Returns 0 or the open error code.
static int PDFPagesToPNG(NSString *path, NSString *password, NSString *outDir, float scale)
{
    RDPDFDoc *doc = [[RDPDFDoc alloc] init];
    int err = [doc open:path :password];
    if (err != 0) return err;
    [[NSFileManager defaultManager] createDirectoryAtPath:outDir
                              withIntermediateDirectories:YES attributes:nil error:nil];
    int count = [doc pageCount];
    for (int i = 0; i < count; i++) {
        @autoreleasepool {
            float pw = [doc pageWidth:i];
            float ph = [doc pageHeight:i];
            int iw = (int)(pw * scale);
            int ih = (int)(ph * scale);
            RDPDFPage *page = [doc page:i];
            if (page == nil) continue;
            RDPDFDIB *dib = [[RDPDFDIB alloc] init:iw :ih];
            RDPDFMatrix *mat = [[RDPDFMatrix alloc] init:scale :-scale :0 :ph * scale];
            [page renderPrepare:dib]; // erases the DIB to white
            if (![page render:dib :mat :2]) continue;

            CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, [dib data], iw * ih * 4, NULL);
            CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
            CGImageRef img = CGImageCreate(iw, ih, 8, 32, iw << 2, cs,
                kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
                provider, NULL, false, kCGRenderingIntentDefault);
            CGColorSpaceRelease(cs);
            CGDataProviderRelease(provider);
            NSData *png = UIImagePNGRepresentation([UIImage imageWithCGImage:img]);
            CGImageRelease(img);
            NSString *file = [outDir stringByAppendingPathComponent:
                              [NSString stringWithFormat:@"page_%04d.png", i]];
            [png writeToFile:file atomically:YES];
        }
    }
    return 0; // ARC releases the page, DIB and document objects
}

The @autoreleasepool block frees each page’s image before the next page is rendered, which matters for long documents.

Keeping memory under control

  • Cap the image size. Pixel memory grows with the square of the scale. A 4096-pixel limit, as in the JSP demo, or a fixed longest side keeps memory use predictable.
  • Release native objects promptly. Call Matrix.Destroy() and Page.Close() after every page and Document.Close() at the end. On Android, recycle() each bitmap after it has been saved.
  • Use embedded thumbnails when they exist. Page.RenderThumb(Bitmap) on Android returns true only if the page carries a thumbnail image, so fall back to a normal render when it returns false.
  • Only need the first page? The Android sample’s grid view calls Document.SetOpenFlag(3) before opening and GetPage0() to fetch the first page fast. The flag is global and makes the SDK assume every page has the size of the first one, so the sample restores SetOpenFlag(1) right after opening.

Common pitfalls

  • Upside-down output: the y scale must be negative, with pageHeight * scale as the y offset.
  • Black or missing background in JPEG files: JPEG cannot store alpha, so render on white or keep transparent output in PNG.
  • Only a few pages rendered: without an activated license, GetPage returns null after the first pages. Check the result of the init call.
  • Wrong fonts on Linux servers: make sure the resource paths in Global.Init (Java) or RDGlobal.Init (C#) point to the folder that holds cmaps.dat, umaps.dat and the font files.

Frequently asked questions

How do I convert a PDF page to PNG on a server?

Run the rdpage tool with the -exp option and a dpi value, or render the page in Java with Page.RenderToBuf into a BufferedImage and save it with ImageIO.

Which resolution should I use when converting PDF to image?

Page sizes are in points, so the scale is the target dpi divided by 72. A scale between 1.5 and 2.0 suits on-screen previews, while OCR or print-quality archives need about 300 dpi.

Can I render a page with a transparent background?

Yes on Android: erase the bitmap to fully transparent, render without RenderPrepare and save as PNG. On the server, pass a background color with zero alpha and test the result with your SDK version.

Related on radaeepdf.com

Where to go next