Skip to content

Primeros pasos con PDF Oxide (Dart / Flutter)

PDF Oxide es la forma más rápida de leer PDF desde Dart y Flutter: 0,8 ms de media en extracción de texto y 100 % de aciertos en 3830 PDF. El paquete pdf_oxide es un wrapper idiomático sobre dart:ffi del núcleo escrito en Rust: los handles de PDF se liberan automáticamente mediante un NativeFinalizer (y con close() explícito), las cadenas y los búferes de C se copian a Dart por ti, y los códigos de error de la C-ABI se exponen como una excepción PdfOxideError.

Instalación

Añade pdf_oxide a tu pubspec.yaml:

dependencies:
  pdf_oxide: ^0.3.69

Luego resuelve las dependencias:

dart pub get

El binding carga la librería nativa (libpdf_oxide.{so,dylib,dll}) en tiempo de ejecución. El orden de resolución es PDF_OXIDE_LIB_PATH (ruta completa) → PDF_OXIDE_LIB_DIR../target/releasetarget/release → el cargador del sistema. En Flutter, distribuye la librería de la plataforma junto con tu aplicación y apunta PDF_OXIDE_LIB_PATH a ella.

Inicio rápido

Abre un PDF y extrae el texto de la primera página. Llama siempre a close() cuando termines: un bloque try/finally lo mantiene ordenado.

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  final doc = PdfDocument.open('research-paper.pdf');
  try {
    print('Pages: ${doc.pageCount}');
    print('PDF version: ${doc.version}'); // e.g. 1.7
    print(doc.extractText(0));
  } finally {
    doc.close();
  }
}

Para abrir un PDF que ya está en memoria (por ejemplo, descargado por HTTP en una aplicación Flutter), usa openFromBytes:

import 'dart:typed_data';
import 'package:pdf_oxide/pdf_oxide.dart';

void render(Uint8List bytes) {
  final doc = PdfDocument.openFromBytes(bytes);
  try {
    print(doc.extractText(0));
  } finally {
    doc.close();
  }
}

Texto, Markdown y HTML

Cada página puede renderizarse como texto plano, Markdown o HTML. Los métodos por página reciben un índice de página basado en 0; las variantes …All() se ejecutan sobre todo el documento.

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  final doc = PdfDocument.open('report.pdf');
  try {
    // Single page (index 0)
    print(doc.extractText(0));   // raw extracted text
    print(doc.toPlainText(0));   // normalized plain text
    print(doc.toMarkdown(0));    // Markdown with headings, lists, tables
    print(doc.toHtml(0));        // HTML

    // Whole document
    print(doc.toMarkdownAll());
    print(doc.toHtmlAll());
    print(doc.toPlainTextAll());
  } finally {
    doc.close();
  }
}

También existe una vista ligera Page si prefieres trabajar página a página:

final doc = PdfDocument.open('report.pdf');
final page = doc.page(0);
print(page.text());
print(page.markdown());
doc.close();

Palabras y líneas con coordenadas

extractWords devuelve cada palabra con su bounding box, su fuente y su grosor; extractTextLines devuelve líneas completas. Las coordenadas se expresan en puntos del espacio de usuario del PDF.

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  final doc = PdfDocument.open('paper.pdf');
  try {
    for (final word in doc.extractWords(0)) {
      final b = word.bbox; // Bbox(x, y, width, height)
      print("'${word.text}' at (${b.x}, ${b.y}) "
          'font=${word.fontName} size=${word.fontSize} bold=${word.bold}');
    }

    for (final line in doc.extractTextLines(0)) {
      print('${line.wordCount} words: ${line.text}');
    }
  } finally {
    doc.close();
  }
}

Para detalle a nivel de glifo, extractChars devuelve cada Char con su punto de código Unicode, su bounding box, el nombre de la fuente y el tamaño:

final doc = PdfDocument.open('paper.pdf');
for (final ch in doc.extractChars(0)) {
  print('${String.fromCharCode(ch.character)} @ ${ch.bbox} ${ch.fontSize}pt');
}
doc.close();

Búsqueda

search busca dentro de una sola página; searchAll recorre todo el documento. Ambos reciben un término de búsqueda y un flag caseSensitive, y devuelven registros SearchResult que llevan el texto coincidente, su índice de página y su bounding box.

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  final doc = PdfDocument.open('manual.pdf');
  try {
    // Single page (page 0), case-insensitive
    for (final hit in doc.search(0, 'configuration', false)) {
      print("page ${hit.page}: '${hit.text}' at ${hit.bbox}");
    }

    // Across the whole document
    final hits = doc.searchAll('configuration', false);
    print('${hits.length} matches');
  } finally {
    doc.close();
  }
}

Crear un PDF

El builder Pdf convierte Markdown, HTML o texto plano en un PDF. Llama a toBytes() para obtener los bytes o a save() para escribir un archivo, y a close() cuando termines.

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  final pdf = Pdf.fromMarkdown('# Hello World\n\nThis is a **PDF**.\n');
  try {
    pdf.save('output.pdf');
    final bytes = pdf.toBytes();
    print('Wrote ${bytes.length} bytes');
  } finally {
    pdf.close();
  }
}

Pdf.fromHtml('<h1>Invoice</h1><p>Amount: \$42</p>') y Pdf.fromText('Plain text content.') funcionan de la misma manera. Como un Pdf construido no es más que bytes, puedes pasarlo directamente a PdfDocument.openFromBytes(pdf.toBytes()) para volver a extraerlo sin tocar el disco.

Manejo de errores

Toda llamada que pueda fallar lanza PdfOxideError (que implements Exception), que lleva el código de error subyacente de la C-ABI:

import 'package:pdf_oxide/pdf_oxide.dart';

void main() {
  try {
    final doc = PdfDocument.open('/nonexistent/nope.pdf');
    doc.close();
  } on PdfOxideError catch (e) {
    print('Failed to open PDF: $e');
  }
}

Próximos pasos