Skip to content

Renderizado de páginas

Renderiza páginas de PDF a imágenes raster (PNG o JPEG) usando un motor de renderizado en Rust puro construido sobre tiny-skia. No se requieren dependencias externas como Poppler o MuPDF.

Cadena de reserva de fuentes

El renderizado necesita glifos. Cuando un PDF hace referencia a una fuente no incrustada (ArialMT, TimesNewRomanPSMT, etc.) que no está instalada en el host, PDF Oxide recorre una cadena de reserva con fuentes de código abierto reconocidas:

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

Se registra una advertencia en el log (con el nombre de la fuente ausente) cuando se activa la reserva, junto con una sugerencia práctica: en Linux instala liberation-fonts, dejavu-fonts o noto-fonts; en contenedores minimalistas agrega uno de esos paquetes a tu Dockerfile.

Notas de rendimiento

  • La base de datos de fuentes del sistema se almacena en caché a nivel de proceso — los renderizados posteriores reutilizan el índice ya analizado.
  • Los clústeres de glifos de varios caracteres acumulan anchuras correctamente (corrige la pérdida de ligaduras en fuentes CID con subconjuntos latinos/árabes).
  • Las imágenes malformadas (sin /ColorSpace, dimensiones inválidas) se omiten con una advertencia en lugar de provocar un fallo en la página.

Ejemplo 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 la funcionalidad

El renderizado de páginas requiere el flag de feature rendering:

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

Esto incluye tiny-skia (renderizado 2D), fontdb (carga de fuentes) y rustybuzz (formación de texto).


Opciones de renderizado

Configura el renderizado mediante 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 de RenderOptions

Campo Tipo Valor por defecto Descripción
dpi u32 150 Resolución en puntos por pulgada
format ImageFormat Png Formato de salida (Png o Jpeg)
background Option<[f32; 4]> Blanco [1,1,1,1] Color de fondo RGBA (0.0–1.0 por canal)
render_annotations bool true Si se deben renderizar las anotaciones
jpeg_quality u8 85 Calidad JPEG de 1 a 100 (ignorado para PNG)

Métodos builder

Método Descripción
RenderOptions::with_dpi(dpi) Crear opciones con DPI personalizado
.with_transparent_background() Establecer fondo transparente (solo PNG)
.as_jpeg(quality) Cambiar a salida JPEG con la calidad indicada

ImageFormat

Variante Descripción
Png Compresión sin pérdida, soporta transparencia
Jpeg Compresión con pérdida, archivo más pequeño, sin transparencia

RenderedImage

La función render_page() devuelve un 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 Devuelve Descripción
save(path) Result<()> Escribir la imagen en un archivo
as_bytes() &[u8] Obtener los bytes brutos de la imagen

Variantes avanzadas de renderizado

Además de la ruta con PNG/JPEG codificados, PDF Oxide expone tres puntos de entrada de renderizado de bajo nivel: un búfer de píxeles bruto (sin codificación PNG/JPEG), una variante de opciones extendidas que añade filtrado por grupos de contenido opcional (OCG), y una estimación del tiempo de renderizado de bajo coste.

¿Cómo obtengo un búfer de píxeles RGBA bruto en lugar de PNG/JPEG?

Usa la ruta de renderizado bruto cuando quieras pasar píxeles directamente a una textura GPU, una biblioteca de imágenes o un compositor sin pagar el coste de la codificación PNG/JPEG. El búfer es RGBA8888 premultiplicado, en orden de filas, con origen en la esquina superior izquierda: 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")

El C ABI lo expone como pdf_render_page_raw(doc, page_index, dpi, *out_width, *out_height, *error_code); recupera los bytes de píxeles con pdf_get_rendered_image_data y libera el handle con pdf_rendered_image_free.

¿Cómo oculto capas de contenido opcional (OCG) al renderizar?

render_page_with_options_ex es la superficie completa de opciones de renderizado más una lista de /Names de grupos de contenido opcional (OCG) que se deben suprimir. Pasa los nombres de las capas que quieres ocultar (por ejemplo, una capa de marca de agua “Confidential” o una capa CAD “Construction lines”). Las referencias OCMD que se resuelven en cualquier OCG nombrado también son respetadas, según 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")

La firma del C ABI es 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). Pasa un puntero excluded_layers nulo (o un conteo de cero) para deshabilitar el filtrado, lo cual equivale al pdf_render_page_with_options simple.

¿Cómo estimo el coste de renderizado antes de renderizar?

estimate_render_time devuelve un valor de coste barato y definido por la implementación para una página, de modo que un trabajo por lotes pueda priorizar o presupuestar trabajo sin rasterizar realmente. Actualmente está disponible a través del C ABI y el wrapper de Swift; el crate de Rust y los bindings de Python/Go/Node no lo exponen 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 de variantes avanzadas

Método C ABI Rust Python Swift
Búfer RGBA bruto pdf_render_page_raw RenderOptions::as_raw() + render_page render_pixmap(page, dpi) renderPageRaw(_:dpi:)
Opciones + filtro OCG pdf_render_page_with_options_ex RenderOptions.excluded_layers + render_page render_page(..., excluded_layers=[...]) renderPageWithOptionsEx(...)
Estimación de tiempo pdf_estimate_render_time estimateRenderTime(_:)

API de Python

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

Renderiza una página a bytes de imagen.

Parámetro Tipo Valor por defecto Descripción
page int obligatorio Índice de página basado en cero
dpi int 72 Puntos por pulgada
format str "png" Formato de salida: "png" o "jpeg"

Devuelve: bytes — datos de imagen codificados (PNG o 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")

API de JavaScript

doc.renderPage(pageIndex, dpi?)

Renderiza una página a bytes PNG.

Parámetro Tipo Valor por defecto Descripción
pageIndex number obligatorio Índice de página basado en cero
dpi number 150 Puntos por pulgada

Devuelve: Uint8Array — datos de imagen PNG

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

Casos de uso comunes

Renderizar todas las 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

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

Fondo transparente para composición

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

Color de fondo personalizado

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

Salida de alta calidad para impresión

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

Aplanar PDF a imágenes

Convierte un PDF completo en un PDF plano basado en imágenes. Cada página se renderiza como imagen raster con el DPI especificado y luego se ensambla en un nuevo PDF. Esto graba permanentemente todas las anotaciones, campos de formulario, capas superpuestas y fuentes.

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 Valor por defecto Descripción
DPI dpi dpi dpi 150 Resolución de renderizado para cada página

Devuelve: bytes del archivo PDF — un nuevo PDF donde cada página es una imagen de página completa.

Casos de uso

  • Redacción — aplanar tras redactar para eliminar permanentemente el contenido oculto
  • Archivado — crear una instantánea visual idéntica en cualquier visor
  • Renderizado consistente — eliminar diferencias de fuentes y maquetación entre visores de PDF
  • Preparación para impresión — aplanar capas superpuestas complejas para una impresión fiable
  • Envío de formularios — grabar los valores de los campos de formulario rellenos

Aplanar con alta calidad

# 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 renderizado

El renderizador de PDF Oxide procesa el flujo de contenido de la página en orden:

  1. Dimensiones — Calcular el tamaño en píxeles a partir de las dimensiones de la página y el DPI (72 puntos = 1 pulgada)
  2. Fondo — Crear el mapa de píxeles con el color de fondo configurado
  3. Transformación — Aplicar la transformación de coordenadas (origen PDF abajo a la izquierda → origen de imagen arriba a la izquierda)
  4. Flujo de contenido — Analizar y ejecutar todos los operadores PDF:
    • Trazados — Líneas, curvas, rectángulos con relleno/trazo
    • Texto — Texto posicionado con selección de fuente y espaciado
    • Imágenes — Imágenes raster incrustadas (DeviceGray, DeviceRGB, DeviceCMYK)
    • Estado gráfico — Transparencia, modos de fusión, recorte, estilos de línea
  5. Codificación — Salida como PNG o JPEG

Operadores PDF soportados

Categoría Operadores
Estado gráfico q Q (guardar/restaurar), cm (matriz de transformación)
Color rg RG g G k K (RGB, escala de grises, CMYK)
Construcción de trazado m l c v y re h (mover, línea, curva, rectángulo, cerrar)
Pintura de trazado S s f F f* B B* b b* n (trazo, relleno, ambos)
Recorte W W* (regla distinta de cero y par-impar)
Texto BT ET Td TD Tm Tf Tj TJ ' "
Imágenes Do (XObjects: imágenes y XObjects de formulario)
Estado extendido gs (transparencia ca/CA, modos de fusión BM)

Preguntas frecuentes

¿En qué formato está el búfer de renderizado bruto? RGBA8888 premultiplicado, en orden de filas, con origen en la esquina superior izquierda. La longitud es exactamente width * height * 4 bytes — sin cabecera PNG/JPEG, sin compresión. Úsalo cuando necesites pasar píxeles a una textura GPU o a un pipeline de imagen externo. En Rust llama a RenderOptions::with_dpi(dpi).as_raw(), en Python a doc.render_pixmap(page, dpi=...) y en Swift a doc.renderPageRaw(page, dpi:).

¿Puedo ocultar una marca de agua u otra capa al renderizar? Sí. Pasa los /Names del grupo de contenido opcional (OCG) que quieres suprimir: render_page(0, excluded_layers=["Watermark"]) en Python, RenderOptions.excluded_layers en Rust o renderPageWithOptionsEx(... excludedLayers:) en Swift. El renderizador también resuelve las referencias OCMD a esos grupos.

¿Por qué estimate_render_time no está disponible en Python? Está limitado al C ABI y al wrapper de Swift en v0.3.69 — el crate de Rust y los bindings de Python/Go/Node no lo exponen. Usa el método de Swift estimateRenderTime(_:) o la función C pdf_estimate_render_time directamente.

¿Qué tan rápido es el renderizado? El núcleo de extracción de texto de PDF Oxide corre a una media de 0,8 ms con una tasa de éxito del 100 % en el corpus de referencia; el renderizado reutiliza el mismo analizador Rust puro con una caché de fuentes a nivel de proceso, de modo que los renderizados repetidos evitan volver a analizar la base de datos de fuentes del sistema.


Páginas relacionadas