Skip to content

ページレンダリング

tiny-skia をベースとした純粋Rustレンダリングエンジンを使用して、PDFページをラスター画像(PNG または JPEG)にレンダリングします。Poppler や MuPDF などの外部依存は不要です。

フォントフォールバックチェーン

レンダリングにはグリフが必要です。PDFが非埋め込みフォント(ArialMT、TimesNewRomanPSMT など)を参照しており、そのフォントがホストにインストールされていない場合、PDF Oxide は既知のオープンソースフォントのフォールバックチェーンを順に試みます。

  • DejaVu Sans / DejaVu Serif / DejaVu Sans Mono
  • Noto Sans / Noto Serif
  • FreeSans / FreeSerif

フォールバックが発動した際は、不足しているフォント名とともに警告がログに記録され、対処方法のヒントも表示されます。Linuxでは liberation-fontsdejavu-fontsnoto-fonts をインストールしてください。最小限のコンテナ環境では、Dockerfile に該当パッケージを追加してください。

パフォーマンスについて

  • システムのフォントDBはプロセスレベルでキャッシュされるため、以降のレンダリングでは解析済みインデックスが再利用されます。
  • 複数文字のグリフクラスターは幅を正しく累積するため、ラテン/アラビック系サブセットCIDフォントのリガチャー欠落が修正されています。
  • 不正な画像(/ColorSpace 欠落、無効な寸法)はパニックせず警告を出してスキップします。

クイックサンプル

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::rendering::{render_page, RenderOptions};

let mut doc = PdfDocument::open("document.pdf")?;

// Render first page as PNG at 150 DPI (default)
let image = render_page(&mut doc, 0, &RenderOptions::default())?;
image.save("page1.png")?;

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")

# Render first page as PNG at 150 DPI
png_bytes = doc.render_page(0, dpi=150)
with open("page1.png", "wb") as f:
    f.write(png_bytes)

# Render as JPEG
jpeg_bytes = doc.render_page(0, dpi=150, format="jpeg")
with open("page1.jpg", "wb") as f:
    f.write(jpeg_bytes)

Node.js

const { PdfDocument } = require("pdf-oxide");
const fs = require("node:fs");

const doc = new PdfDocument("document.pdf");

// Render first page as PNG
const pngBytes = doc.renderPage(0, "png");
fs.writeFileSync("page1.png", Buffer.from(pngBytes));

// Render as JPEG
const jpegBytes = doc.renderPage(0, "jpeg");
fs.writeFileSync("page1.jpg", Buffer.from(jpegBytes));

doc.close();

Go

import pdfoxide "github.com/yfedoseev/pdf_oxide/go"

doc, _ := pdfoxide.Open("document.pdf")
defer doc.Close()

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
png, _ := doc.RenderPage(0, 0)
os.WriteFile("page1.png", png.Data, 0644)

// Render as JPEG
jpeg, _ := doc.RenderPage(0, 1)
os.WriteFile("page1.jpg", jpeg.Data, 0644)

C#

using PdfOxide.Core;

using var doc = PdfDocument.Open("document.pdf");

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
var pngBytes = doc.RenderPage(0, 0);
File.WriteAllBytes("page1.png", pngBytes);

// Render as JPEG
var jpegBytes = doc.RenderPage(0, 1);
File.WriteAllBytes("page1.jpg", jpegBytes);

Java

import fyi.oxide.pdf.PdfDocument;
import java.nio.file.*;

try (PdfDocument doc = PdfDocument.open(Path.of("document.pdf"))) {
    // Render first page as PNG at 150 DPI
    byte[] png = doc.render(0, 150);
    Files.write(Path.of("page1.png"), png);
}

Kotlin

import fyi.oxide.pdf.PdfDocument
import java.nio.file.*

PdfDocument.open(Path.of("document.pdf")).use { doc ->
    // Render first page as PNG at 150 DPI
    val png = doc.render(0, 150)
    Files.write(Path.of("page1.png"), png)
}

Scala

import fyi.oxide.pdf.PdfDocument
import scala.util.Using
import java.nio.file.{Files, Paths}

Using.resource(PdfDocument.open("document.pdf")) { doc =>
  // Render first page as PNG at 150 DPI
  val png = doc.render(0, 150)
  Files.write(Paths.get("page1.png"), png)
}

Clojure

(require '[pdf-oxide.core :as pdf])
(require '[clojure.java.io :as io])

(with-open [doc (pdf/open "document.pdf")]
  ;; Render first page as PNG at 150 DPI
  (with-open [out (io/output-stream "page1.png")]
    (.write out (pdf/render doc 0 150))))

Ruby

require 'pdf_oxide'

PdfOxide::PdfDocument.open('document.pdf') do |doc|
  # Render first page as PNG at 150 DPI
  File.binwrite('page1.png', doc.render(0, dpi: 150))

  # Render as JPEG (format 1)
  jpeg = doc.render_with_layers(0, dpi: 150, format: 1)
  File.binwrite('page1.jpg', jpeg)
end

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("document.pdf");

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
doc.render_page(0, 0).save("page1.png");

// Render as JPEG
doc.render_page(0, 1).save("page1.jpg");

Swift

import PdfOxide

let doc = try Document.open("document.pdf")

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
try doc.renderPage(0, format: 0).save("page1.png")

// Render as JPEG
try doc.renderPage(0, format: 1).save("page1.jpg")

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('document.pdf');

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
doc.renderPage(0, 0).save('page1.png');

// Render as JPEG
doc.renderPage(0, 1).save('page1.jpg');

R

library(pdfoxide)

doc <- pdf_open("document.pdf")

# Render first page as PNG (format 0 = PNG, 1 = JPEG)
img <- pdf_render_page(doc, 0, format = 0L)
pdf_rendered_image_save(img, "page1.png")

# Render as JPEG
jpg <- pdf_render_page(doc, 0, format = 1L)
pdf_rendered_image_save(jpg, "page1.jpg")

Julia

using PdfOxide

doc = open_document("document.pdf")

# Render first page as PNG (format 0 = PNG, 1 = JPEG)
img = render_page(doc, 0, 0)
save(img, "page1.png")

# Render as JPEG
jpg = render_page(doc, 0, 1)
save(jpg, "page1.jpg")

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("document.pdf");

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
var png = try doc.renderPage(a, 0, 0);
defer png.deinit();
try png.save("page1.png");

// Render as JPEG
var jpg = try doc.renderPage(a, 0, 1);
defer jpg.deinit();
try jpg.save("page1.jpg");

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];

// Render first page as PNG (format 0 = PNG, 1 = JPEG)
POXRenderedImage *png = [doc renderPage:0 format:0 error:&err];
[png saveToPath:@"page1.png" error:&err];

// Render as JPEG
POXRenderedImage *jpg = [doc renderPage:0 format:1 error:&err];
[jpg saveToPath:@"page1.jpg" error:&err];

Elixir

{:ok, doc} = PdfOxide.open("document.pdf")

# Render first page as PNG (format 0 = PNG, 1 = JPEG)
{:ok, png} = PdfOxide.render_page(doc, 0, 0)
PdfOxide.save(png, "page1.png")

# Render as JPEG
{:ok, jpg} = PdfOxide.render_page(doc, 0, 1)
PdfOxide.save(jpg, "page1.jpg")

WASM

import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);

// Render first page as PNG at 150 DPI
const pngBytes = doc.renderPage(0, 150);

// Save in Node.js
import { writeFileSync } from "fs";
writeFileSync("page1.png", Buffer.from(pngBytes));

doc.free();

機能の有効化

ページレンダリングには rendering フィーチャーフラグが必要です。

[dependencies]
pdf_oxide = { version = "0.3", features = ["rendering"] }

これにより tiny-skia(2Dレンダリング)、fontdb(フォント読み込み)、rustybuzz(テキストシェーピング)が追加されます。


レンダリングオプション

RenderOptions でレンダリングを設定できます。

use pdf_oxide::rendering::{RenderOptions, ImageFormat};

// Default: 150 DPI, PNG, white background, render annotations
let opts = RenderOptions::default();

// High-quality rendering at 300 DPI
let opts = RenderOptions::with_dpi(300);

// JPEG output with 90% quality
let opts = RenderOptions::with_dpi(300).as_jpeg(90);

// Transparent background (PNG only)
let opts = RenderOptions::default().with_transparent_background();

RenderOptions フィールド

フィールド デフォルト 説明
dpi u32 150 1インチあたりのドット数(解像度)
format ImageFormat Png 出力フォーマット(Png または Jpeg
background Option<[f32; 4]> [1,1,1,1] RGBA背景色(チャンネルごとに 0.0〜1.0)
render_annotations bool true 注釈をレンダリングするかどうか
jpeg_quality u8 85 JPEGクオリティ 1〜100(PNGでは無視)

ビルダーメソッド

メソッド 説明
RenderOptions::with_dpi(dpi) カスタムDPIでオプションを生成
.with_transparent_background() 背景を透明に設定(PNGのみ)
.as_jpeg(quality) 指定クオリティのJPEG出力に切り替え

ImageFormat

バリアント 説明
Png ロスレス圧縮、透明度をサポート
Jpeg 非可逆圧縮、ファイルサイズが小さい、透明度非対応

RenderedImage

render_page() 関数は RenderedImage を返します。

pub struct RenderedImage {
    pub data: Vec<u8>,       // Encoded image bytes
    pub width: u32,          // Width in pixels
    pub height: u32,         // Height in pixels
    pub format: ImageFormat, // PNG or JPEG
}

メソッド

メソッド 戻り値 説明
save(path) Result<()> 画像をファイルに書き込む
as_bytes() &[u8] 生の画像バイトを取得

高度なレンダリングバリアント

PNG/JPEGエンコード済みパスに加えて、PDF Oxide は3つの低レベルレンダリングエントリポイントを提供しています。生ピクセルバッファ(PNG/JPEGエンコードをスキップ)、オプショナルコンテンツグループ(OCG)レイヤーフィルタリングを追加した拡張オプションバリアント、そして低コストなレンダリング時間の推定です。

生のRGBAピクセルバッファを取得するには?

PNG/JPEGエンコードを省略して、ピクセルをGPUテクスチャ・画像ライブラリ・コンポジターに直接渡したい場合に使用します。バッファはプリマルチプライドRGBA8888、行優先、左上原点で、len == width * height * 4 です。

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::rendering::{render_page, RenderOptions};

let mut doc = PdfDocument::open("document.pdf")?;

// `.as_raw()` switches the encoder off — `image.data` is the raw RGBA buffer.
let opts = RenderOptions::with_dpi(150).as_raw();
let image = render_page(&mut doc, 0, &opts)?;

assert_eq!(image.data.len(), (image.width * image.height * 4) as usize);
println!("Raw RGBA buffer: {}×{}, {} bytes", image.width, image.height, image.data.len());

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")

# render_pixmap returns a RenderedPixmap (raw RGBA8888, no encoding)
pixmap = doc.render_pixmap(0, dpi=150)
assert len(pixmap.data) == pixmap.width * pixmap.height * 4
print(f"Raw RGBA buffer: {pixmap.width}x{pixmap.height}, {len(pixmap.data)} bytes")

Swift

import PdfOxide

let doc = try PdfDocument(path: "document.pdf")

// renderPageRaw returns the RenderedImage plus the pixel dimensions
let (image, width, height) = try doc.renderPageRaw(0, dpi: 150)
print("Raw RGBA buffer: \(width)x\(height), \(image.data.count) bytes")

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("document.pdf");

// render_page_raw writes the pixel dimensions into out_width/out_height;
// the raw RGBA buffer is the returned image's data().
int width = 0, height = 0;
auto image = doc.render_page_raw(0, /*dpi=*/150, width, height);
// data().size() == width * height * 4
printf("Raw RGBA buffer: %dx%d, %zu bytes\n", width, height, image.data().size());

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('document.pdf');

// renderPageRaw returns a RenderedImage holding the raw RGBA8888 buffer
final image = doc.renderPageRaw(0, 150);
assert(image.data.length == image.width * image.height * 4);
print('Raw RGBA buffer: ${image.width}x${image.height}, ${image.data.length} bytes');

R

library(pdfoxide)

doc <- pdf_open("document.pdf")

# pdf_render_page_raw returns a rendered-image with the raw RGBA8888 buffer
img <- pdf_render_page_raw(doc, 0, dpi = 150L)
stopifnot(length(img$data) == img$width * img$height * 4)
cat(sprintf("Raw RGBA buffer: %dx%d, %d bytes\n", img$width, img$height, length(img$data)))

Julia

using PdfOxide

doc = open_document("document.pdf")

# render_page_raw returns a RenderedImage holding the raw RGBA8888 buffer
img = render_page_raw(doc, 0, 150)
@assert length(img.data) == img.width * img.height * 4
println("Raw RGBA buffer: $(img.width)x$(img.height), $(length(img.data)) bytes")

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("document.pdf");

// renderPageRaw returns a RenderedImage whose `data` is the raw RGBA8888 buffer
var image = try doc.renderPageRaw(a, 0, 150);
defer image.deinit();
std.debug.assert(image.data.len == @as(usize, @intCast(image.width * image.height * 4)));
std.debug.print("Raw RGBA buffer: {d}x{d}, {d} bytes\n", .{ image.width, image.height, image.data.len });

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];

// renderPageRaw writes the pixel dimensions into outWidth/outHeight
int32_t width = 0, height = 0;
POXRenderedImage *image = [doc renderPageRaw:0 dpi:150 outWidth:&width outHeight:&height error:&err];
// image.data.length == width * height * 4
NSLog(@"Raw RGBA buffer: %dx%d, %lu bytes", width, height, (unsigned long)image.data.length);

Elixir

{:ok, doc} = PdfOxide.open("document.pdf")

# render_page_raw returns a RenderedImage whose data holds the raw RGBA8888 buffer
{:ok, image} = PdfOxide.render_page_raw(doc, 0, 150)
true = byte_size(image.data) == image.width * image.height * 4
IO.puts("Raw RGBA buffer: #{image.width}x#{image.height}, #{byte_size(image.data)} bytes")

C ABIでは pdf_render_page_raw(doc, page_index, dpi, *out_width, *out_height, *error_code) として公開されています。ピクセルバイトは pdf_get_rendered_image_data で取得し、ハンドルは pdf_rendered_image_free で解放してください。

オプショナルコンテンツ(OCG)レイヤーをレンダリング時に非表示にするには?

render_page_with_options_ex は完全なレンダリングオプション面に加え、非表示にするオプショナルコンテンツグループの /Name リストを受け取ります。「Confidential」透かしレイヤーや「Construction lines」CADレイヤーなど、隠したいレイヤー名を渡してください。ISO 32000-1 §8.11.2 に従い、名前付きOCGを参照するOCMDも正しく解決されます。

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::rendering::{render_page, RenderOptions};

let mut doc = PdfDocument::open("layered.pdf")?;

let mut opts = RenderOptions::with_dpi(200);
// Suppress these optional-content groups by /Name
opts.excluded_layers = ["Watermark", "Draft Notes"].into_iter().map(String::from).collect();

let image = render_page(&mut doc, 0, &opts)?;
image.save("page_no_watermark.png")?;

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("layered.pdf")

# render_page takes excluded_layers — the OCG /Name strings to hide
png = doc.render_page(0, dpi=200, excluded_layers=["Watermark", "Draft Notes"])
with open("page_no_watermark.png", "wb") as f:
    f.write(png)

Swift

import PdfOxide

let doc = try PdfDocument(path: "layered.pdf")

// dpi 200, PNG (format 0), excluded OCG names
let image = try doc.renderPageWithOptionsEx(
    0, dpi: 200, format: 0,
    excludedLayers: ["Watermark", "Draft Notes"]
)
try image.save("page_no_watermark.png")

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("layered.pdf");

// dpi 200, PNG (format 0), white bg, render annotations, then OCG /Names to suppress
auto image = doc.render_page_with_options_ex(
    0, /*dpi=*/200, /*format=*/0,
    1.0f, 1.0f, 1.0f, 1.0f, /*transparent=*/false, /*render_annotations=*/true,
    /*jpeg_quality=*/85, {"Watermark", "Draft Notes"});
image.save("page_no_watermark.png");

Ruby

require 'pdf_oxide'

PdfOxide::PdfDocument.open('layered.pdf') do |doc|
  # render_with_layers takes excluded_layers — the OCG /Name strings to hide
  png = doc.render_with_layers(0, dpi: 200, excluded_layers: ["Watermark", "Draft Notes"])
  File.binwrite('page_no_watermark.png', png)
end

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('layered.pdf');

// renderPageWithOptionsEx takes excludedLayers — the OCG /Name strings to hide
final image = doc.renderPageWithOptionsEx(
  0,
  dpi: 200,
  format: 0,
  excludedLayers: ["Watermark", "Draft Notes"],
);
image.save('page_no_watermark.png');

R

library(pdfoxide)

doc <- pdf_open("layered.pdf")

# excluded_layers is a character vector of OCG /Names to suppress
img <- pdf_render_page_with_options_ex(
  doc, 0, dpi = 200L, format = 0L,
  excluded_layers = c("Watermark", "Draft Notes")
)
pdf_rendered_image_save(img, "page_no_watermark.png")

Julia

using PdfOxide

doc = open_document("layered.pdf")

# excluded_layers is a vector of OCG /Name strings to suppress
img = render_page_with_options_ex(
    doc, 0, 200, 0, 1.0, 1.0, 1.0, 1.0, 0, 1, 85,
    ["Watermark", "Draft Notes"],
)
save(img, "page_no_watermark.png")

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("layered.pdf");

// excluded_layers are the OCG /Names to suppress
const layers = [_][*:0]const u8{ "Watermark", "Draft Notes" };
var image = try doc.renderPageWithOptionsEx(
    a, 0, 200, 0, 1.0, 1.0, 1.0, 1.0, false, true, 85, &layers,
);
defer image.deinit();
try image.save("page_no_watermark.png");

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"layered.pdf" error:&err];

// excludedLayers are the OCG /Names to suppress
POXRenderedImage *image = [doc renderPageWithOptionsEx:0
                                                   dpi:200
                                                format:0
                                                   bgR:1.0 bgG:1.0 bgB:1.0 bgA:1.0
                                 transparentBackground:0
                                     renderAnnotations:1
                                           jpegQuality:85
                                        excludedLayers:@[@"Watermark", @"Draft Notes"]
                                                 error:&err];
[image saveToPath:@"page_no_watermark.png" error:&err];

Elixir

{:ok, doc} = PdfOxide.open("layered.pdf")

# render_page_with_options_ex takes the OCG /Name strings to hide
{:ok, image} =
  PdfOxide.render_page_with_options_ex(doc, 0, ["Watermark", "Draft Notes"], dpi: 200)
PdfOxide.save(image, "page_no_watermark.png")

C ABIのシグネチャは pdf_render_page_with_options_ex(doc, page_index, dpi, format, bg_r, bg_g, bg_b, bg_a, transparent_background, render_annotations, jpeg_quality, excluded_layers, excluded_layers_count, *error_code) です。フィルタリングを無効にするには、excluded_layers にnullポインタ(またはカウント0)を渡してください。これは通常の pdf_render_page_with_options と同じ動作になります。

レンダリング前にコストを見積もるには?

estimate_render_time はページのレンダリングコストを安価な実装定義の数値で返します。バッチジョブで実際にラスタライズせずに優先順位を決めたりワーク量を見積もったりするのに役立ちます。現在は C ABISwift ラッパーで公開されており、RustクレートとPython/Go/Nodeバインディングにはイディオマティックなメソッドとして公開されていません。

Swift

import PdfOxide

let doc = try PdfDocument(path: "document.pdf")

// Implementation-defined cost units — useful for relative comparisons
let cost = try doc.estimateRenderTime(0)
print("Estimated render cost for page 0: \(cost)")

C ABI: int32_t pdf_estimate_render_time(const void *doc, int32_t page_index, int32_t *error_code)

高度なバリアントの対応状況

メソッド C ABI Rust Python Swift
生RGBAバッファ pdf_render_page_raw RenderOptions::as_raw() + render_page render_pixmap(page, dpi) renderPageRaw(_:dpi:)
オプション + OCGフィルター pdf_render_page_with_options_ex RenderOptions.excluded_layers + render_page render_page(..., excluded_layers=[...]) renderPageWithOptionsEx(...)
レンダリング時間推定 pdf_estimate_render_time estimateRenderTime(_:)

Python API

doc.render_page(page, dpi=None, format=None)

ページを画像バイトにレンダリングします。

パラメータ デフォルト 説明
page int 必須 ゼロベースのページインデックス
dpi int 72 1インチあたりのドット数
format str "png" 出力フォーマット:"png" または "jpeg"

戻り値: bytes — エンコード済み画像データ(PNG または JPEG)

# PNG at default DPI
png = doc.render_page(0)

# High-quality PNG
png = doc.render_page(0, dpi=300)

# JPEG with default quality
jpeg = doc.render_page(0, format="jpeg")

# High-DPI JPEG
jpeg = doc.render_page(0, dpi=300, format="jpeg")

JavaScript API

doc.renderPage(pageIndex, dpi?)

ページをPNGバイトにレンダリングします。

パラメータ デフォルト 説明
pageIndex number 必須 ゼロベースのページインデックス
dpi number 150 1インチあたりのドット数

戻り値: Uint8Array — PNG画像データ

const pngBytes = doc.renderPage(0);       // 150 DPI default
const hiRes = doc.renderPage(0, 300);     // 300 DPI

よくある使用例

全ページのレンダリング

use pdf_oxide::PdfDocument;
use pdf_oxide::rendering::{render_page, RenderOptions};

let mut doc = PdfDocument::open("document.pdf")?;
let opts = RenderOptions::with_dpi(200);

for page in 0..doc.page_count()? {
    let image = render_page(&mut doc, page, &opts)?;
    image.save(format!("page_{}.png", page + 1))?;
}

Python

from pdf_oxide import PdfDocument
from pathlib import Path

doc = PdfDocument("document.pdf")
for i in range(doc.page_count()):
    png_bytes = doc.render_page(i, dpi=200)
    Path(f"page_{i + 1}.png").write_bytes(png_bytes)

Node.js

const doc = new PdfDocument("document.pdf");
for (let i = 0; i < doc.pageCount(); i++) {
  const pngBytes = doc.renderPage(i, "png");
  fs.writeFileSync(`page_${i + 1}.png`, Buffer.from(pngBytes));
}
doc.close();

Go

doc, _ := pdfoxide.Open("document.pdf")
defer doc.Close()
pages, _ := doc.PageCount()
for i := 0; i < pages; i++ {
    img, _ := doc.RenderPage(i, 0)
    os.WriteFile(fmt.Sprintf("page_%d.png", i+1), img.Data, 0644)
}

C#

using var doc = PdfDocument.Open("document.pdf");
for (int i = 0; i < doc.PageCount; i++)
{
    var pngBytes = doc.RenderPage(i, 0);
    File.WriteAllBytes($"page_{i + 1}.png", pngBytes);
}

Java

try (PdfDocument doc = PdfDocument.open(Path.of("document.pdf"))) {
    for (int i = 0; i < doc.pageCount(); i++) {
        byte[] png = doc.render(i, 200);
        Files.write(Path.of("page_" + (i + 1) + ".png"), png);
    }
}

Kotlin

PdfDocument.open(Path.of("document.pdf")).use { doc ->
    for (i in 0 until doc.pageCount()) {
        val png = doc.render(i, 200)
        Files.write(Path.of("page_${i + 1}.png"), png)
    }
}

Scala

Using.resource(PdfDocument.open("document.pdf")) { doc =>
  for (i <- 0 until doc.pageCount()) {
    val png = doc.render(i, 200)
    Files.write(Paths.get(s"page_${i + 1}.png"), png)
  }
}

Clojure

(with-open [doc (pdf/open "document.pdf")]
  (doseq [i (range (pdf/page-count doc))]
    (with-open [out (io/output-stream (str "page_" (inc i) ".png"))]
      (.write out (pdf/render doc i 200)))))

Ruby

PdfOxide::PdfDocument.open('document.pdf') do |doc|
  (0...doc.page_count).each do |i|
    File.binwrite("page_#{i + 1}.png", doc.render(i, dpi: 200))
  end
end

C++

auto doc = pdf_oxide::Document::open("document.pdf");
for (int i = 0; i < doc.page_count(); i++) {
    doc.render_page_with_options(i, /*dpi=*/200, /*format=*/0,
                                 1.0f, 1.0f, 1.0f, 1.0f, false, true, 85)
       .save("page_" + std::to_string(i + 1) + ".png");
}

Swift

let doc = try Document.open("document.pdf")
for i in 0..<(try doc.pageCount()) {
    let image = try doc.renderPageWithOptions(i, dpi: 200)
    try image.save("page_\(i + 1).png")
}

Dart

final doc = PdfDocument.open('document.pdf');
for (var i = 0; i < doc.pageCount; i++) {
  doc.renderPageWithOptions(i, dpi: 200).save('page_${i + 1}.png');
}

R

doc <- pdf_open("document.pdf")
for (i in seq_len(pdf_page_count(doc)) - 1L) {
  img <- pdf_render_page_with_options(doc, i, dpi = 200L)
  pdf_rendered_image_save(img, sprintf("page_%d.png", i + 1))
}

Julia

doc = open_document("document.pdf")
for i in 0:(page_count(doc) - 1)
    img = render_page_with_options(doc, i, 200, 0, 1.0, 1.0, 1.0, 1.0, 0, 1, 85)
    save(img, "page_$(i + 1).png")
end

Zig

var doc = try pdf_oxide.Document.open("document.pdf");
var i: i32 = 0;
const pages = try doc.pageCount();
while (i < pages) : (i += 1) {
    var image = try doc.renderPageWithOptions(a, i, 200, 0, 1.0, 1.0, 1.0, 1.0, false, true, 85);
    defer image.deinit();
    var buf: [64]u8 = undefined;
    const name = try std.fmt.bufPrintZ(&buf, "page_{d}.png", .{i + 1});
    try image.save(name);
}

Objective-C

POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];
for (NSInteger i = 0; i < [doc pageCountError:&err]; i++) {
    POXRenderedImage *image = [doc renderPageWithOptions:i dpi:200 format:0
                                                     bgR:1.0 bgG:1.0 bgB:1.0 bgA:1.0
                                   transparentBackground:0 renderAnnotations:1
                                             jpegQuality:85 error:&err];
    [image saveToPath:[NSString stringWithFormat:@"page_%ld.png", (long)(i + 1)] error:&err];
}

Elixir

{:ok, doc} = PdfOxide.open("document.pdf")
{:ok, n} = PdfOxide.page_count(doc)
for i <- 0..(n - 1) do
  {:ok, image} = PdfOxide.render_page_with_options(doc, i, dpi: 200)
  PdfOxide.save(image, "page_#{i + 1}.png")
end

サムネイル生成

use pdf_oxide::rendering::{render_page, RenderOptions};

// Low DPI for fast thumbnail generation
let opts = RenderOptions::with_dpi(72).as_jpeg(75);
let thumb = render_page(&mut doc, 0, &opts)?;
thumb.save("thumbnail.jpg")?;
println!("Thumbnail: {}×{} ({} bytes)", thumb.width, thumb.height, thumb.data.len());

Python

doc = PdfDocument("document.pdf")
thumb = doc.render_page(0, dpi=72, format="jpeg")
Path("thumbnail.jpg").write_bytes(thumb)

Node.js

const doc = new PdfDocument("document.pdf");
const thumb = doc.renderPage(0, "jpeg");
fs.writeFileSync("thumbnail.jpg", Buffer.from(thumb));
doc.close();

Go

doc, _ := pdfoxide.Open("document.pdf")
defer doc.Close()
// RenderThumbnail returns a 72-DPI thumbnail (format 1 = JPEG)
thumb, _ := doc.RenderThumbnail(0, 72, 1)
os.WriteFile("thumbnail.jpg", thumb.Data, 0644)

C#

using var doc = PdfDocument.Open("document.pdf");
// RenderThumbnail returns a 72-DPI thumbnail (format 1 = JPEG)
var thumb = doc.RenderThumbnail(0, 1);
File.WriteAllBytes("thumbnail.jpg", thumb);

Java

try (PdfDocument doc = PdfDocument.open(Path.of("document.pdf"))) {
    // Low DPI for fast thumbnail generation (PNG)
    byte[] thumb = doc.render(0, 72);
    Files.write(Path.of("thumbnail.png"), thumb);
}

Kotlin

PdfDocument.open(Path.of("document.pdf")).use { doc ->
    // Low DPI for fast thumbnail generation (PNG)
    Files.write(Path.of("thumbnail.png"), doc.render(0, 72))
}

Scala

Using.resource(PdfDocument.open("document.pdf")) { doc =>
  // Low DPI for fast thumbnail generation (PNG)
  Files.write(Paths.get("thumbnail.png"), doc.render(0, 72))
}

Clojure

(with-open [doc (pdf/open "document.pdf")]
  ;; Low DPI for fast thumbnail generation (PNG)
  (with-open [out (io/output-stream "thumbnail.png")]
    (.write out (pdf/render doc 0 72))))

Ruby

PdfOxide::PdfDocument.open('document.pdf') do |doc|
  # Low DPI + JPEG (format 1) for fast thumbnail generation
  thumb = doc.render_with_layers(0, dpi: 72, format: 1)
  File.binwrite('thumbnail.jpg', thumb)
end

C++

auto doc = pdf_oxide::Document::open("document.pdf");
// render_page_thumbnail fits the page within `size` px (format 1 = JPEG)
doc.render_page_thumbnail(0, /*size=*/256, /*format=*/1).save("thumbnail.jpg");

Swift

let doc = try Document.open("document.pdf")
// renderPageThumbnail fits the page within `size` px (format 1 = JPEG)
try doc.renderPageThumbnail(0, size: 256, format: 1).save("thumbnail.jpg")

Dart

final doc = PdfDocument.open('document.pdf');
// renderPageThumbnail fits the page within `size` px (format 1 = JPEG)
doc.renderPageThumbnail(0, 256, 1).save('thumbnail.jpg');

R

doc <- pdf_open("document.pdf")
# pdf_render_page_thumbnail fits the page within `size` px (format 1 = JPEG)
thumb <- pdf_render_page_thumbnail(doc, 0, size = 256L, format = 1L)
pdf_rendered_image_save(thumb, "thumbnail.jpg")

Julia

doc = open_document("document.pdf")
# render_page_thumbnail fits the page within `size` px (format 1 = JPEG)
thumb = render_page_thumbnail(doc, 0, 256, 1)
save(thumb, "thumbnail.jpg")

Zig

var doc = try pdf_oxide.Document.open("document.pdf");
// renderPageThumbnail fits the page within `size` px (format 1 = JPEG)
var thumb = try doc.renderPageThumbnail(a, 0, 256, 1);
defer thumb.deinit();
try thumb.save("thumbnail.jpg");

Objective-C

POXDocument *doc = [POXDocument openPath:@"document.pdf" error:&err];
// renderPageThumbnail fits the page within `size` px (format 1 = JPEG)
POXRenderedImage *thumb = [doc renderPageThumbnail:0 size:256 format:1 error:&err];
[thumb saveToPath:@"thumbnail.jpg" error:&err];

Elixir

{:ok, doc} = PdfOxide.open("document.pdf")
# render_page_thumbnail fits the page within `size` px (format 1 = JPEG)
{:ok, thumb} = PdfOxide.render_page_thumbnail(doc, 0, 256, 1)
PdfOxide.save(thumb, "thumbnail.jpg")

合成用の透明背景

let opts = RenderOptions::with_dpi(150).with_transparent_background();
let image = render_page(&mut doc, 0, &opts)?;
image.save("page_transparent.png")?;

カスタム背景色

let opts = RenderOptions {
    dpi: 150,
    background: Some([0.95, 0.95, 0.95, 1.0]), // Light gray
    ..RenderOptions::default()
};
let image = render_page(&mut doc, 0, &opts)?;

高品質な印刷出力

// 300 DPI for print-quality output
let opts = RenderOptions::with_dpi(300);
let image = render_page(&mut doc, 0, &opts)?;
image.save("print_quality.png")?;
println!("Image size: {}×{}", image.width, image.height);

PDFを画像にフラット化

PDF全体をフラットな画像ベースのPDFに変換します。各ページを指定されたDPIでラスター画像としてレンダリングし、新しいPDFに組み立てます。この処理により、すべての注釈・フォームフィールド・オーバーレイ・フォントが恒久的に焼き込まれます。

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")
flattened = doc.flatten_to_images(dpi=150)
with open("flattened.pdf", "wb") as f:
    f.write(flattened)

WASM

import { WasmPdfDocument } from "pdf-oxide-wasm";
import { writeFileSync } from "fs";

const doc = new WasmPdfDocument(bytes);
const flattened = doc.flattenToImages(150);
writeFileSync("flattened.pdf", Buffer.from(flattened));
doc.free();

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::rendering::flatten_to_images;

let mut doc = PdfDocument::open("document.pdf")?;
let flattened = flatten_to_images(&mut doc, 150)?;
std::fs::write("flattened.pdf", flattened)?;

パラメータ

パラメータ Python JavaScript Rust デフォルト 説明
DPI dpi dpi dpi 150 各ページのレンダリング解像度

戻り値: PDFファイルバイト — 各ページが全ページ画像になった新しいPDF。

ユースケース

  • 墨消し(リダクション) — 墨消し後にフラット化して隠しコンテンツを完全に除去
  • アーカイブ — あらゆるビューアで同一に表示される視覚的スナップショットを作成
  • 一貫したレンダリング — PDFビューア間のフォントやレイアウトの違いを排除
  • 印刷準備 — 複雑なオーバーレイをフラット化して確実な印刷を実現
  • フォーム提出 — 記入されたフォームフィールドの値を焼き込む

高品質でフラット化

# 300 DPI for print-quality flattening
flattened = doc.flatten_to_images(dpi=300)
with open("print_ready.pdf", "wb") as f:
    f.write(flattened)

レンダリングパイプライン

PDF Oxideのレンダラーはページコンテンツストリームを順番に処理します。

  1. 寸法 — ページ寸法とDPIからピクセルサイズを計算(72ポイント = 1インチ)
  2. 背景 — 設定された背景色でピックスマップを生成
  3. 変換 — 座標変換を適用(PDF左下原点 → 画像左上原点)
  4. コンテンツストリーム — すべてのPDFオペレータを解析・実行:
    • パス — 塗りつぶし・ストローク付きの直線・曲線・矩形
    • テキスト — フォント選択とスペーシングを伴う配置済みテキスト
    • 画像 — 埋め込みラスター画像(DeviceGray、DeviceRGB、DeviceCMYK)
    • グラフィクスステート — 透明度・ブレンドモード・クリッピング・線スタイル
  5. エンコード — PNG または JPEG として出力

サポートされるPDFオペレータ

カテゴリ オペレータ
グラフィクスステート q Q(保存/復元)、cm(変換行列)
カラー rg RG g G k K(RGB、グレー、CMYK)
パス構築 m l c v y re h(移動、直線、曲線、矩形、クローズ)
パス描画 S s f F f* B B* b b* n(ストローク、塗りつぶし、両方)
クリッピング W W*(非ゼロおよび偶奇ワインディング)
テキスト BT ET Td TD Tm Tf Tj TJ ' "
画像 Do(XObject:画像およびフォームXObject)
拡張ステート gs(透明度 ca/CA、ブレンドモード BM

よくある質問

生レンダーバッファのフォーマットは? プリマルチプライドRGBA8888、行優先、左上原点です。長さは正確に width * height * 4 バイトで、PNG/JPEGヘッダーや圧縮はありません。ピクセルをGPUテクスチャや外部画像パイプラインに渡す際に使用してください。Rustでは RenderOptions::with_dpi(dpi).as_raw()、Pythonでは doc.render_pixmap(page, dpi=...)、Swiftでは doc.renderPageRaw(page, dpi:) を呼び出してください。

レンダリング時に透かしやレイヤーを非表示にできますか? はい。非表示にしたいオプショナルコンテンツグループ(OCG)の /Name を渡してください。Pythonでは render_page(0, excluded_layers=["Watermark"])、Rustでは RenderOptions.excluded_layers、Swiftでは renderPageWithOptionsEx(... excludedLayers:) を使用します。レンダラーはそれらのグループへのOCMD参照も解決します。

estimate_render_time がPythonで利用できないのはなぜですか? v0.3.69 では C ABI と Swift ラッパーに限定されています。RustクレートとPython/Go/Nodeバインディングには公開されていません。Swiftの estimateRenderTime(_:) メソッドか、C関数の pdf_estimate_render_time を直接使用してください。

レンダリングの速度は? PDF Oxideのテキスト抽出コアはベンチマークコーパスで平均0.8ms・パス率100%で動作します。レンダリングは同じ純粋Rustパーサーとプロセスレベルのフォントキャッシュを再利用するため、繰り返しのレンダリングではシステムフォントデータベースの再解析が不要です。


関連ページ