Skip to content

Рендеринг сторінок

Рендеринг сторінок PDF у растрові зображення (PNG або JPEG) за допомогою чистого Rust-рушія на основі tiny-skia. Зовнішні залежності на кшталт Poppler чи MuPDF не потрібні.

Ланцюг запасних шрифтів

Для рендерингу потрібні гліфи. Якщо PDF посилається на невбудований шрифт (ArialMT, TimesNewRomanPSMT тощо), якого немає на хості, PDF Oxide перебирає ланцюг відомих відкритих шрифтів:

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

Коли запасний шрифт активується, у лог записується попередження (з іменем відсутнього шрифту) та практична підказка: на Linux встановіть liberation-fonts, dejavu-fonts або noto-fonts; у мінімальних контейнерах додайте один із цих пакетів до Dockerfile.

Нотатки про продуктивність

  • База шрифтів системи кешується на рівні процесу — наступні рендери повторно використовують проаналізований індекс.
  • Багатосимвольні кластери гліфів коректно накопичують ширини (виправляє втрату лігатур у 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 Роздільна здатність у точках на дюйм
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 надає три низькорівневі точки входу: сирий піксельний буфер (без кодування PNG/JPEG), варіант розширених параметрів із фільтрацією за групами необов’язкового вмісту (OCG), та дешеву оцінку часу рендерингу.

Як отримати сирий RGBA-буфер пікселів замість PNG/JPEG?

Використовуйте шлях сирого рендерингу, коли потрібно передати пікселі безпосередньо в текстуру GPU, бібліотеку зображень або композитор без витрат на кодування PNG/JPEG. Буфер — попередньо помножений 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-ів груп необов’язкового вмісту (OCG), які потрібно приховати. Передайте імена шарів, які хочете вимкнути (наприклад, шар водяного знаку «Confidential» або шар САПР «Construction lines»). Посилання OCMD, що розв’язуються в будь-яку з названих OCG, також враховуються відповідно до ISO 32000-1 §8.11.2.

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 (або лічильник нуль), щоб вимкнути фільтрацію — це відповідає звичайному pdf_render_page_with_options.

Як оцінити вартість рендерингу до його виконання?

estimate_render_time повертає дешеву, визначену реалізацією вартість сторінки, щоб пакетне завдання могло розставити пріоритети або спланувати роботу без фактичної растеризації. Наразі доступно через C ABI та обгортку Swift; крейт 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 Точок на дюйм
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 Точок на дюйм

Повертає: 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 на зображення (flatten)

Перетворює весь 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, де кожна сторінка є повносторінковим зображенням.

Сценарії використання

  • Редагування (Redaction) — зглаживання після вилучення даних для остаточного видалення прихованого вмісту
  • Архівування — створення візуального знімку, однакового в будь-якому переглядачі
  • Послідовний рендеринг — усунення відмінностей у шрифтах і верстці між переглядачами 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:).

Чи можна приховати водяний знак або інший шар під час рендерингу? Так. Передайте /Name-и груп необов’язкового вмісту (OCG), які потрібно придушити: у 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,8 мс і рівнем проходження 100 % на еталонному корпусі; рендеринг повторно використовує той самий чистий Rust-парсер із кешем шрифтів на рівні процесу, тому повторні рендери не потребують повторного аналізу системної бази шрифтів.


Пов’язані сторінки