Skip to content

Renderização de Páginas

Renderize páginas de PDF em imagens raster (PNG ou JPEG) usando um engine de renderização em Rust puro, construído sobre o tiny-skia. Não são necessárias dependências externas como Poppler ou MuPDF.

Cadeia de fallback de fontes

A renderização precisa de glifos. Quando um PDF referencia uma fonte não embutida (ArialMT, TimesNewRomanPSMT etc.) que não está instalada no host, o PDF Oxide percorre uma cadeia de fallback de fontes abertas bem conhecidas:

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

Um aviso é registrado no log (com o nome da fonte ausente) quando o fallback é acionado, além de uma dica prática: no Linux, instale liberation-fonts, dejavu-fonts ou noto-fonts; em contêineres minimalistas, adicione um desses pacotes ao seu Dockerfile.

Notas de desempenho

  • O banco de dados de fontes do sistema é armazenado em cache no nível do processo — renderizações subsequentes reutilizam o índice já processado.
  • Clusters de glifos com múltiplos caracteres acumulam larguras corretamente (corrige ligaduras perdidas em fontes CID com subconjuntos latinos/arábicos).
  • Imagens malformadas (sem /ColorSpace, dimensões inválidas) são ignoradas com um aviso em vez de travar a página.

Exemplo Rápido

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();

Habilitando o Recurso

A renderização de páginas requer a flag de feature rendering:

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

Isso inclui tiny-skia (renderização 2D), fontdb (carregamento de fontes) e rustybuzz (formatação de texto).


Opções de Renderização

Configure a renderização via 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();

Campos do RenderOptions

Campo Tipo Padrão Descrição
dpi u32 150 Resolução em pontos por polegada
format ImageFormat Png Formato de saída (Png ou Jpeg)
background Option<[f32; 4]> Branco [1,1,1,1] Cor de fundo RGBA (0.0–1.0 por canal)
render_annotations bool true Se deve renderizar anotações
jpeg_quality u8 85 Qualidade JPEG de 1 a 100 (ignorado para PNG)

Métodos Builder

Método Descrição
RenderOptions::with_dpi(dpi) Criar opções com DPI personalizado
.with_transparent_background() Definir fundo transparente (apenas PNG)
.as_jpeg(quality) Mudar para saída JPEG com a qualidade indicada

ImageFormat

Variante Descrição
Png Compressão sem perdas, suporta transparência
Jpeg Compressão com perdas, arquivo menor, sem transparência

RenderedImage

A função render_page() retorna um 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
}

Métodos

Método Retorna Descrição
save(path) Result<()> Gravar imagem em arquivo
as_bytes() &[u8] Obter bytes brutos da imagem

Variantes Avançadas de Renderização

Além do caminho PNG/JPEG codificado, o PDF Oxide expõe três pontos de entrada de renderização de baixo nível: um buffer de pixels brutos (sem codificação PNG/JPEG), uma variante de opções estendidas que adiciona filtragem de grupos de conteúdo opcional (OCG), e uma estimativa de tempo de renderização de baixo custo.

Como obter um buffer de pixels RGBA bruto em vez de PNG/JPEG?

Use o caminho de renderização bruta quando quiser passar pixels diretamente para uma textura GPU, uma biblioteca de imagens ou um compositor sem pagar o custo de codificação PNG/JPEG. O buffer é RGBA8888 pré-multiplicado, em ordem de linha, com origem no canto superior esquerdo, onde 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")

O C ABI expõe isso como pdf_render_page_raw(doc, page_index, dpi, *out_width, *out_height, *error_code); recupere os bytes de pixel com pdf_get_rendered_image_data e libere o handle com pdf_rendered_image_free.

Como ocultar camadas de conteúdo opcional (OCG) durante a renderização?

render_page_with_options_ex é a superfície completa de opções de renderização mais uma lista de /Names de grupos de conteúdo opcional (OCG) a serem suprimidos. Passe os nomes das camadas que deseja ocultar (por exemplo, uma camada de marca d’água “Confidential” ou uma camada CAD “Construction lines”). Referências OCMD que se resolvem para qualquer OCG nomeado também são respeitadas, conforme 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")

A assinatura do 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). Passe um ponteiro excluded_layers nulo (ou contagem zero) para desativar a filtragem, que equivale ao pdf_render_page_with_options simples.

Como estimar o custo de renderização antes de renderizar?

estimate_render_time retorna uma estimativa de custo barata e definida pela implementação para uma página, permitindo que um trabalho em lote priorize ou planeje o trabalho sem rasterizar de verdade. Atualmente está disponível via C ABI e wrapper Swift; o crate Rust e os bindings Python/Go/Node não o expõem como método idiomático.

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).

Cobertura das variantes avançadas

Método C ABI Rust Python Swift
Buffer RGBA bruto pdf_render_page_raw RenderOptions::as_raw() + render_page render_pixmap(page, dpi) renderPageRaw(_:dpi:)
Opções + filtro OCG pdf_render_page_with_options_ex RenderOptions.excluded_layers + render_page render_page(..., excluded_layers=[...]) renderPageWithOptionsEx(...)
Estimativa de tempo pdf_estimate_render_time estimateRenderTime(_:)

Python API

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

Renderiza uma página em bytes de imagem.

Parâmetro Tipo Padrão Descrição
page int obrigatório Índice de página base zero
dpi int 72 Pontos por polegada
format str "png" Formato de saída: "png" ou "jpeg"

Retorna: bytes — dados de imagem codificados (PNG ou 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?)

Renderiza uma página em bytes PNG.

Parâmetro Tipo Padrão Descrição
pageIndex number obrigatório Índice de página base zero
dpi number 150 Pontos por polegada

Retorna: Uint8Array — dados de imagem PNG

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

Casos de Uso Comuns

Renderizar Todas as Páginas

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

Gerar Miniaturas

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")

Fundo Transparente para Composição

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

Cor de Fundo Personalizada

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)?;

Saída de Alta Qualidade para Impressão

// 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);

Converter PDF em Imagens (Flatten)

Converte um PDF inteiro em um PDF plano baseado em imagens. Cada página é renderizada como imagem raster no DPI especificado e então montada em um novo PDF. Isso grava permanentemente todas as anotações, campos de formulário, sobreposições e fontes.

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)?;

Parâmetros

Parâmetro Python JavaScript Rust Padrão Descrição
DPI dpi dpi dpi 150 Resolução de renderização de cada página

Retorna: bytes do arquivo PDF — um novo PDF onde cada página é uma imagem de página inteira.

Casos de Uso

  • Redação — achatar após redigir para remover permanentemente o conteúdo oculto
  • Arquivamento — criar um instantâneo visual idêntico em qualquer visualizador
  • Renderização consistente — eliminar diferenças de fonte e layout entre visualizadores de PDF
  • Preparação para impressão — achatar sobreposições complexas para impressão confiável
  • Envio de formulários — gravar os valores preenchidos nos campos do formulário

Achatar com Alta Qualidade

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

Pipeline de Renderização

O renderizador do PDF Oxide processa o fluxo de conteúdo da página em ordem:

  1. Dimensões — Calcular tamanho em pixels a partir das dimensões da página e do DPI (72 pontos = 1 polegada)
  2. Fundo — Criar pixmap com a cor de fundo configurada
  3. Transformação — Aplicar transformação de coordenadas (origem PDF no canto inferior esquerdo → origem da imagem no canto superior esquerdo)
  4. Fluxo de conteúdo — Analisar e executar todos os operadores PDF:
    • Caminhos — Linhas, curvas, retângulos com preenchimento/contorno
    • Texto — Texto posicionado com seleção de fonte e espaçamento
    • Imagens — Imagens raster embutidas (DeviceGray, DeviceRGB, DeviceCMYK)
    • Estado gráfico — Transparência, modos de mesclagem, recorte, estilos de linha
  5. Codificação — Saída como PNG ou JPEG

Operadores PDF Suportados

Categoria Operadores
Estado gráfico q Q (salvar/restaurar), cm (matriz de transformação)
Cor rg RG g G k K (RGB, escala de cinza, CMYK)
Construção de caminho m l c v y re h (mover, linha, curva, retângulo, fechar)
Pintura de caminho S s f F f* B B* b b* n (contorno, preenchimento, ambos)
Recorte W W* (regra diferente de zero e par-ímpar)
Texto BT ET Td TD Tm Tf Tj TJ ' "
Imagens Do (XObjects: imagens e XObjects de formulário)
Estado estendido gs (transparência ca/CA, modos de mesclagem BM)

Perguntas Frequentes

Qual é o formato do buffer de renderização bruto? RGBA8888 pré-multiplicado, em ordem de linha, com origem no canto superior esquerdo. O comprimento é exatamente width * height * 4 bytes — sem cabeçalho PNG/JPEG, sem compressão. Use quando precisar passar pixels para uma textura GPU ou um pipeline de imagem externo. Em Rust, chame RenderOptions::with_dpi(dpi).as_raw(); em Python, doc.render_pixmap(page, dpi=...); em Swift, doc.renderPageRaw(page, dpi:).

Posso ocultar uma marca d’água ou outra camada durante a renderização? Sim. Passe os /Names do grupo de conteúdo opcional (OCG) que deseja suprimir: render_page(0, excluded_layers=["Watermark"]) em Python, RenderOptions.excluded_layers em Rust ou renderPageWithOptionsEx(... excludedLayers:) em Swift. O renderizador também resolve referências OCMD para esses grupos.

Por que estimate_render_time não está disponível em Python? Ele está limitado ao C ABI e ao wrapper Swift na v0.3.69 — o crate Rust e os bindings Python/Go/Node não o expõem. Use o método Swift estimateRenderTime(_:) ou a função C pdf_estimate_render_time diretamente.

Qual é a velocidade de renderização? O núcleo de extração de texto do PDF Oxide roda a 0,8 ms de média com taxa de aprovação de 100% no corpus de benchmark; a renderização reutiliza o mesmo parser Rust puro com cache de fontes em nível de processo, então renders repetidos evitam reanalisar o banco de dados de fontes do sistema.


Páginas Relacionadas