Skip to content

Búsqueda de Texto

PDF Oxide ofrece búsqueda de texto completo en documentos PDF con soporte de expresiones regulares, coincidencia sin distinción de mayúsculas, modo de palabra completa y cuadros delimitadores por coincidencia. Los resultados incluyen número de página, texto encontrado y coordenadas precisas de cada ocurrencia, lo que facilita la creación de flujos de trabajo de búsqueda y resaltado.

Use TextSearcher::search() para consultas de múltiples páginas con opciones personalizadas, o los métodos convenientes de Pdf (search(), search_page(), highlight_matches()) para los casos de uso más habituales.

Ejemplo Rápido

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
results = doc.search("conclusion", case_insensitive=True)
for r in results:
    print(f"Page {r['page']}: '{r['text']}' at ({r['x']:.1f}, {r['y']:.1f})")

Node.js

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("report.pdf");
const results = doc.searchAll("conclusion", { caseSensitive: false });
for (const r of results) {
  console.log(`Page ${r.page}: '${r.text}' at (${r.x.toFixed(1)}, ${r.y.toFixed(1)})`);
}
doc.close();

Go

import pdfoxide "github.com/yfedoseev/pdf_oxide/go"

doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
results, _ := doc.SearchAll("conclusion", false)
for _, r := range results {
    fmt.Printf("Page %d: '%s' at (%.1f, %.1f)\n", r.Page, r.Text, r.X, r.Y)
}

C#

using PdfOxide.Core;

using var doc = PdfDocument.Open("report.pdf");
var results = doc.SearchAll("conclusion");
foreach (var r in results)
{
    Console.WriteLine($"Page {r.Page}: '{r.Text}' at ({r.X:F1}, {r.Y:F1})");
}

WASM

const doc = new WasmPdfDocument(bytes);
const results = doc.search("conclusion");
for (const r of results) {
    console.log(`Page ${r.page}: '${r.text}' at (${r.x.toFixed(1)}, ${r.y.toFixed(1)})`);
}

Rust

use pdf_oxide::api::Pdf;

let mut pdf = Pdf::open("report.pdf")?;
let results = pdf.search("conclusion")?;
for r in &results {
    println!("Page {}: '{}' at ({:.1}, {:.1})", r.page, r.text, r.bbox.x, r.bbox.y);
}

Java

import fyi.oxide.pdf.PdfDocument;
import fyi.oxide.pdf.search.SearchMatch;
import java.nio.file.Path;
import java.util.List;

try (PdfDocument doc = PdfDocument.open(Path.of("report.pdf"))) {
    List<SearchMatch> results = doc.search("conclusion", true, false, 0);
    for (SearchMatch m : results) {
        System.out.printf("Page %d: '%s' at (%.1f, %.1f)%n",
            m.pageIndex(), m.text(), m.bbox().x0(), m.bbox().y0());
    }
}

Kotlin

import fyi.oxide.pdf.PdfDocument
import java.nio.file.Path

PdfDocument.open(Path.of("report.pdf")).use { doc ->
    val results = doc.search("conclusion", true, false, 0)
    for (m in results) {
        println("Page ${m.pageIndex()}: '${m.text()}' at (${m.bbox().x0()}, ${m.bbox().y0()})")
    }
}

Scala

import fyi.oxide.pdf.{PdfDocument, searchSeq}
import scala.util.Using

Using.resource(PdfDocument.open("report.pdf")) { doc =>
  val results = doc.searchSeq("conclusion")
  for (m <- results)
    println(f"Page ${m.pageIndex}: '${m.text}' at (${m.bbox.x0}%.1f, ${m.bbox.y0}%.1f)")
}

Clojure

(require '[pdf-oxide.core :as pdf])

(with-open [doc (pdf/open "report.pdf")]
  (doseq [m (pdf/search doc "conclusion")]
    (printf "Page %d: '%s' at (%.1f, %.1f)%n"
            (.pageIndex m) (.text m) (.x0 (.bbox m)) (.y0 (.bbox m)))))

Ruby

require 'pdf_oxide'

PdfOxide::PdfDocument.open('report.pdf') do |doc|
  doc.search('conclusion', case_sensitive: false).each do |r|
    bbox = r[:bbox]
    printf("Page %d: '%s' at (%.1f, %.1f)\n", r[:page], r[:text], bbox[:x], bbox[:y])
  end
end

C++

#include <pdf_oxide/pdf_oxide.hpp>
#include <cstdio>

auto doc = pdf_oxide::Document::open("report.pdf");
auto results = doc.search_all("conclusion", /*case_sensitive=*/false);
for (const auto& r : results) {
    std::printf("Page %d: '%s' at (%.1f, %.1f)\n",
                r.page, r.text.c_str(), r.bbox.x, r.bbox.y);
}

Swift

import PdfOxide

let doc = try Document.open("report.pdf")
let results = try doc.searchAll("conclusion", false)
for r in results {
    print("Page \(r.page): '\(r.text)' at (\(r.bbox.x), \(r.bbox.y))")
}

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('report.pdf');
final results = doc.searchAll('conclusion', false);
for (final r in results) {
  print("Page ${r.page}: '${r.text}' at (${r.bbox.x}, ${r.bbox.y})");
}
doc.close();

R

library(pdfoxide)

doc <- pdf_open("report.pdf")
results <- pdf_search_all(doc, "conclusion", case_sensitive = FALSE)
for (r in results) {
  cat(sprintf("Page %d: '%s' at (%.1f, %.1f)\n",
              r$page, r$text, r$bbox$x, r$bbox$y))
}

Julia

using PdfOxide

doc = open_document("report.pdf")
results = search_all(doc, "conclusion", false)
for r in results
    println("Page $(r.page): '$(r.text)' at ($(r.bbox.x), $(r.bbox.y))")
end

Zig

const pdf_oxide = @import("pdf_oxide");
const a = std.heap.page_allocator;

var doc = try pdf_oxide.Document.open("report.pdf");
const results = try doc.searchAll(a, "conclusion", false);
defer doc.freeSearchResults(a, results);
for (results) |r| {
    std.debug.print("Page {d}: '{s}' at ({d:.1}, {d:.1})\n", .{ r.page, r.text, r.bbox.x, r.bbox.y });
}

Objective-C

#import "POXPdfOxide.h"
NSError *err = nil;

POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSArray<POXSearchResult*> *results = [doc searchAll:@"conclusion" caseSensitive:NO error:&err];
for (POXSearchResult *r in results) {
    NSLog(@"Page %ld: '%@' at (%.1f, %.1f)", (long)r.page, r.text, r.bbox.x, r.bbox.y);
}

Elixir

{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, results} = PdfOxide.search_all(doc, "conclusion", false)

for r <- results do
  IO.puts("Page #{r.page}: '#{r.text}' at (#{r.bbox.x}, #{r.bbox.y})")
end

Referencia de API

TextSearcher::search(doc, pattern, options) -> Vec<SearchResult>

Busca texto en múltiples páginas de un documento PDF. El patrón se compila como expresión regular a menos que el modo literal esté activado.

Parámetro Tipo Descripción
doc &mut PdfDocument El documento PDF a buscar
pattern &str Patrón de expresión regular (o texto literal si literal está definido)
options &SearchOptions Configuración de búsqueda

Devuelve: Un vector de objetos SearchResult, ordenados por página y posición.

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::search::{TextSearcher, SearchOptions};

let mut doc = PdfDocument::open("report.pdf")?;

let options = SearchOptions::new()
    .with_case_insensitive(true)
    .with_max_results(50);

let results = TextSearcher::search(&mut doc, "error|warning", &options)?;
for r in &results {
    println!("Page {}: '{}'", r.page, r.text);
}

TextSearcher::search_page(doc, page, regex, options) -> Vec<SearchResult>

Busca texto en una página específica usando una expresión regular precompilada.

Parámetro Tipo Descripción
doc &mut PdfDocument El documento PDF
page usize Índice de página con base cero
regex &Regex Patrón de expresión regular precompilado
options &SearchOptions Configuración de búsqueda

Devuelve: Un vector de objetos SearchResult para la página especificada.

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::search::{TextSearcher, SearchOptions};
use regex::Regex;

let mut doc = PdfDocument::open("report.pdf")?;
let regex = Regex::new(r"\d{4}-\d{2}-\d{2}")?; // Date pattern
let options = SearchOptions::default();

let results = TextSearcher::search_page(&mut doc, 0, &regex, &options)?;
for r in &results {
    println!("Date found: '{}' at ({:.1}, {:.1})", r.text, r.bbox.x, r.bbox.y);
}

SearchOptions

Configuración del comportamiento de búsqueda de texto. Utiliza el patrón constructor para una construcción ergonómica.

Campo Tipo Predeterminado Descripción
case_insensitive bool false Ignorar mayúsculas/minúsculas en la coincidencia
literal bool false Tratar el patrón como texto literal (escapar metacaracteres de regex)
whole_word bool false Coincidir solo palabras completas (envuelve el patrón con \b...\b)
max_results usize 0 Número máximo de resultados a devolver (0 = ilimitado)
page_range Option<(usize, usize)> None Rango de páginas a buscar (inicio y fin inclusivos)

Métodos del Constructor

let options = SearchOptions::new()
    .with_case_insensitive(true)
    .with_literal(true)
    .with_whole_word(true)
    .with_max_results(100)
    .with_page_range(0, 9);

Constructor de Conveniencia

// Quick case-insensitive search
let options = SearchOptions::case_insensitive();

SearchResult

Una única coincidencia de búsqueda con información de posición.

Campo Tipo Descripción
page usize Número de página (índice 0)
text String El texto coincidente
bbox Rect Cuadro delimitador combinado de la coincidencia
start_index usize Índice inicial en el texto extraído de la página
end_index usize Índice final en el texto extraído de la página
span_boxes Vec<Rect> Cuadros delimitadores individuales para cada tramo de la coincidencia (útil para coincidencias de varias líneas)

Python: En la API Python, los resultados de búsqueda se devuelven como diccionarios:

{
    "page": 0,
    "text": "conclusion",
    "x": 72.0,
    "y": 650.5,
    "width": 85.3,
    "height": 12.0,
}

Métodos de Conveniencia de Pdf

La API de alto nivel Pdf proporciona métodos abreviados para operaciones de búsqueda comunes.

search(pattern) -> Vec<SearchResult>

Busca en todo el documento con las opciones predeterminadas.

let mut pdf = Pdf::open("report.pdf")?;
let results = pdf.search("important")?;

search_with_options(pattern, options) -> Vec<SearchResult>

Búsqueda con opciones personalizadas.

let options = SearchOptions::case_insensitive()
    .with_whole_word(true)
    .with_page_range(0, 5);
let results = pdf.search_with_options("abstract", options)?;

search_page(page, pattern) -> Vec<SearchResult>

Busca en una sola página con las opciones predeterminadas.

let results = pdf.search_page(0, r"\d+\.\d+")?; // Find decimal numbers

highlight_matches(results, color) -> Result<()>

Crea anotaciones de resaltado para los resultados de búsqueda. Cada resultado recibe una anotación de resaltado amarilla (o de color personalizado) en su página.

Parámetro Tipo Descripción
results &[SearchResult] Resultados de búsqueda a resaltar
color [f32; 3] Color RGB (0.0–1.0 por componente)
let mut pdf = Pdf::open("report.pdf")?;
let results = pdf.search("important")?;
pdf.highlight_matches(&results, [1.0, 1.0, 0.0])?; // Yellow
pdf.save("highlighted.pdf")?;

API de Búsqueda Python

La clase Python PdfDocument expone la búsqueda directamente.

doc.search(pattern, ...) -> list[dict]

doc.search(
    pattern: str,
    case_insensitive: bool = False,
    literal: bool = False,
    whole_word: bool = False,
    max_results: int = 0,
) -> list[dict]

doc.search_page(page, pattern, ...) -> list[dict]

doc.search_page(
    page: int,
    pattern: str,
    case_insensitive: bool = False,
    literal: bool = False,
    whole_word: bool = False,
    max_results: int = 0,
) -> list[dict]

API de Búsqueda JavaScript

La clase WasmPdfDocument expone la misma funcionalidad de búsqueda.

doc.search(pattern, ...) -> Array

doc.search(pattern, caseInsensitive?, literal?, wholeWord?, maxResults?) -> Array

doc.searchPage(pageIndex, pattern, ...) -> Array

doc.searchPage(pageIndex, pattern, caseInsensitive?, literal?, wholeWord?, maxResults?) -> Array

Ejemplo:

const doc = new WasmPdfDocument(bytes);

// Search all pages, case-insensitive
const results = doc.search("error|warning", true);
for (const r of results) {
  console.log(`Page ${r.page}: '${r.text}'`);
}

// Search a single page with whole-word matching
const pageResults = doc.searchPage(0, "abstract", true, false, true);
doc.free();

¿Cómo serializo los resultados de búsqueda a JSON?

Varios bindings exponen un serializador de un solo paso que convierte la lista de resultados de búsqueda de una página en un array JSON en un único cruce de frontera FFI — Rust serializa la lista completa y el binding la decodifica, en lugar de pasar cada campo de cada coincidencia individualmente. Este es el mismo mecanismo que usan internamente los métodos SearchPage de Go y C# para decodificar sus resultados.

La firma C ABI es la definición de referencia:

char *pdf_oxide_search_results_to_json(
    const FfiSearchResults *results,
    int32_t *error_code);

Recibe el manejador de resultados opaco devuelto por pdf_document_search_page(...) y devuelve una cadena JSON UTF-8 asignada con malloc (libérela con pdf_free_string). Cada elemento contiene el page, el text y el cuadro delimitador (x, y, width, height) de la coincidencia.

Swift — el wrapper agrupa la búsqueda y la serialización en una sola llamada, searchResultsToJson(_:_:caseSensitive:):

import PdfOxide

let doc = try PdfDocument(path: "report.pdf")

// Search page 0 for "conclusion" and get the matches as a JSON string
let json = try doc.searchResultsToJson(0, "conclusion", caseSensitive: false)
print(json)
// [{"page":0,"text":"conclusion","x":72.0,"y":650.5,"width":85.3,"height":12.0}, ...]

Go / C#. Estos bindings llaman a pdf_oxide_search_results_to_json internamente y te entregan registros nativos ya decodificados, por lo que no necesitas invocar el serializador directamente. Usa doc.SearchPage(...) (Go: doc.SearchPage(page, text, caseSensitive); C#: doc.SearchPage(pageIndex, text, caseSensitive)) y obtendrás resultados fuertemente tipados. Para obtener JSON en esos lenguajes, serializa los registros devueltos con la biblioteca JSON estándar (encoding/json / System.Text.Json).

Python / Rust. Los métodos Python doc.search(...) / doc.search_page(...) ya devuelven registros nativos list[dict] (serialízalos directamente con json.dumps), y Rust devuelve Vec<SearchResult> tipado que puedes serializar con serde_json. Ninguno de los dos necesita el serializador C-ABI.


Ejemplos Avanzados

Buscar y resaltar con color personalizado

use pdf_oxide::api::Pdf;
use pdf_oxide::search::SearchOptions;

let mut pdf = Pdf::open("contract.pdf")?;

// Find all dollar amounts
let options = SearchOptions::new()
    .with_literal(false); // regex mode
let results = pdf.search_with_options(r"\$[\d,]+\.?\d*", options)?;

println!("Found {} dollar amounts", results.len());
for r in &results {
    println!("  Page {}: {}", r.page + 1, r.text);
}

// Highlight them in green
pdf.highlight_matches(&results, [0.6, 1.0, 0.6])?;
pdf.save("highlighted_amounts.pdf")?;

Búsqueda con restricción de rango de páginas

from pdf_oxide import PdfDocument

doc = PdfDocument("book.pdf")

# Search only the first 10 pages
results = doc.search(
    "introduction",
    case_insensitive=True,
    whole_word=True,
    max_results=5,
)

for r in results:
    print(f"Found on page {r['page'] + 1}")

Construir un índice de búsqueda en múltiples PDFs

use pdf_oxide::PdfDocument;
use pdf_oxide::search::{TextSearcher, SearchOptions};
use std::collections::HashMap;

let files = vec!["paper_a.pdf", "paper_b.pdf", "paper_c.pdf"];
let query = "machine learning";
let options = SearchOptions::case_insensitive();

let mut index: HashMap<String, Vec<(usize, String)>> = HashMap::new();

for file in &files {
    let mut doc = PdfDocument::open(file)?;
    let results = TextSearcher::search(&mut doc, query, &options)?;

    for r in results {
        index.entry(file.to_string())
            .or_default()
            .push((r.page, r.text));
    }
}

for (file, matches) in &index {
    println!("{}: {} matches", file, matches.len());
    for (page, text) in matches {
        println!("  Page {}: '{}'", page + 1, text);
    }
}

Extraer contexto alrededor de las coincidencias

use pdf_oxide::PdfDocument;
use pdf_oxide::search::{TextSearcher, SearchOptions};

let mut doc = PdfDocument::open("report.pdf")?;
let options = SearchOptions::new().with_case_insensitive(true);
let results = TextSearcher::search(&mut doc, "error", &options)?;

for r in &results {
    // Extract full page text for context
    let page_text = doc.extract_text(r.page)?;

    // Show 50 chars before and after the match
    let start = r.start_index.saturating_sub(50);
    let end = (r.end_index + 50).min(page_text.len());
    let context = &page_text[start..end];

    println!("Page {} match: ...{}...", r.page + 1, context.trim());
}

Preguntas Frecuentes

¿Cómo obtengo los resultados de búsqueda como JSON? En Swift, llama a doc.searchResultsToJson(page, term, caseSensitive:), que ejecuta la búsqueda en la página y devuelve un array JSON de coincidencias en una sola llamada. En Python y Rust, la búsqueda devuelve registros nativos (list[dict] / Vec<SearchResult>) que se serializan con json.dumps / serde_json. Go y C# devuelven registros tipados que se serializan con encoding/json / System.Text.Json.

¿Qué contiene cada coincidencia JSON? El page de la coincidencia (índice 0), el text coincidente y el cuadro delimitador combinado: x, y, width, height (puntos PDF, origen en la esquina inferior izquierda).

¿La búsqueda usa regex o texto literal por defecto? Los patrones se compilan como expresiones regulares a menos que actives el modo literal (with_literal(true) / literal=True), que escapa los metacaracteres de regex y hace coincidir el texto tal cual.

¿La búsqueda admite coincidencia sin distinción de mayúsculas y búsqueda de palabras completas? Sí — establece case_insensitive y whole_word en SearchOptions (Rust) o pásalos como argumentos de palabra clave (Python) / opciones (otros bindings).


Páginas Relacionadas