Рендеринг страниц
Рендеринг страниц 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 обрабатывает поток содержимого страницы по порядку:
- Размеры — вычислить размер в пикселях по размерам страницы и DPI (72 пункта = 1 дюйм)
- Фон — создать пиксмап с заданным цветом фона
- Трансформация — применить преобразование координат (начало координат PDF снизу слева → начало координат изображения сверху слева)
- Поток содержимого — разобрать и выполнить все операторы PDF:
- Пути — линии, кривые, прямоугольники с заливкой/обводкой
- Текст — позиционированный текст с выбором шрифта и межсимвольным интервалом
- Изображения — встроенные растровые изображения (DeviceGray, DeviceRGB, DeviceCMYK)
- Графическое состояние — прозрачность, режимы смешивания, отсечение, стили линий
- Кодирование — вывести в 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-парсер с кешем шрифтов на уровне процесса, поэтому повторные рендеры не требуют повторного разбора базы системных шрифтов.
Связанные страницы
- Извлечение текста — извлечь текстовое содержимое из страниц
- Извлечение изображений — извлечь встроенные изображения из PDF
- OCR отсканированных PDF — OCR использует рендеринг внутри
- Создание из изображений — конвертировать изображения обратно в PDF