Рендеринг сторінок
Рендеринг сторінок 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