Текстовий пошук
PDF Oxide забезпечує повнотекстовий пошук у PDF-документах з підтримкою регулярних виразів, нечутливого до регістру зіставлення, режиму цілих слів і обмежувальних прямокутників для кожного збігу. Результати пошуку містять номер сторінки, знайдений текст і точні координати кожного збігу, що дозволяє легко будувати робочі процеси пошуку та виділення тексту.
Для багатосторінкових запитів із власними параметрами використовуйте TextSearcher::search(), а для типових завдань — зручні методи Pdf (search(), search_page(), highlight_matches()).
Швидкий приклад
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
Довідник API
TextSearcher::search(doc, pattern, options) -> Vec<SearchResult>
Пошук тексту на кількох сторінках PDF-документа. Паттерн компілюється як регулярний вираз, якщо не ввімкнено режим literal.
| Параметр | Тип | Опис |
|---|---|---|
doc |
&mut PdfDocument |
PDF-документ для пошуку |
pattern |
&str |
Паттерн регулярного виразу (або простий текст при встановленому literal) |
options |
&SearchOptions |
Налаштування пошуку |
Повертає: Вектор об’єктів SearchResult, упорядкованих за сторінкою і позицією.
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>
Пошук тексту на конкретній сторінці за допомогою попередньо скомпільованого регулярного виразу.
| Параметр | Тип | Опис |
|---|---|---|
doc |
&mut PdfDocument |
PDF-документ |
page |
usize |
Індекс сторінки (з нуля) |
regex |
&Regex |
Попередньо скомпільований регулярний вираз |
options |
&SearchOptions |
Налаштування пошуку |
Повертає: Вектор об’єктів SearchResult для вказаної сторінки.
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, ®ex, &options)?;
for r in &results {
println!("Date found: '{}' at ({:.1}, {:.1})", r.text, r.bbox.x, r.bbox.y);
}
SearchOptions
Конфігурація поведінки текстового пошуку. Використовує паттерн будівника для зручності.
| Поле | Тип | За замовчуванням | Опис |
|---|---|---|---|
case_insensitive |
bool |
false |
Ігнорувати регістр під час зіставлення |
literal |
bool |
false |
Розглядати паттерн як звичайний текст (екранувати спецсимволи регулярних виразів) |
whole_word |
bool |
false |
Зіставляти лише цілі слова (огортає паттерн у \b...\b) |
max_results |
usize |
0 |
Максимальна кількість результатів (0 = без обмежень) |
page_range |
Option<(usize, usize)> |
None |
Діапазон сторінок для пошуку (включно з початком і кінцем) |
Методи будівника
let options = SearchOptions::new()
.with_case_insensitive(true)
.with_literal(true)
.with_whole_word(true)
.with_max_results(100)
.with_page_range(0, 9);
Зручний конструктор
// Quick case-insensitive search
let options = SearchOptions::case_insensitive();
SearchResult
Окремий знайдений збіг з інформацією про положення.
| Поле | Тип | Опис |
|---|---|---|
page |
usize |
Номер сторінки (з нуля) |
text |
String |
Знайдений текст |
bbox |
Rect |
Загальний обмежувальний прямокутник збігу |
start_index |
usize |
Початковий індекс у витягнутому тексті сторінки |
end_index |
usize |
Кінцевий індекс у витягнутому тексті сторінки |
span_boxes |
Vec<Rect> |
Окремі обмежувальні прямокутники для кожного фрагмента збігу (корисно для багаторядкових збігів) |
Python: У Python API результати пошуку повертаються у вигляді словників:
{
"page": 0,
"text": "conclusion",
"x": 72.0,
"y": 650.5,
"width": 85.3,
"height": 12.0,
}
Зручні методи Pdf
Високорівневий API Pdf надає скорочені методи для поширених операцій пошуку.
search(pattern) -> Vec<SearchResult>
Пошук по всьому документу з налаштуваннями за замовчуванням.
let mut pdf = Pdf::open("report.pdf")?;
let results = pdf.search("important")?;
search_with_options(pattern, options) -> Vec<SearchResult>
Пошук із власними налаштуваннями.
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>
Пошук на одній сторінці з налаштуваннями за замовчуванням.
let results = pdf.search_page(0, r"\d+\.\d+")?; // Find decimal numbers
highlight_matches(results, color) -> Result<()>
Створення анотацій-виділень для результатів пошуку. Кожен результат отримує жовту (або заданого кольору) анотацію-виділення на своїй сторінці.
| Параметр | Тип | Опис |
|---|---|---|
results |
&[SearchResult] |
Результати пошуку для виділення |
color |
[f32; 3] |
RGB-колір (0.0–1.0 для кожного компонента) |
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")?;
Python API пошуку
Клас Python PdfDocument надає пошук безпосередньо.
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]
JavaScript API пошуку
Клас WasmPdfDocument надає ті самі можливості пошуку.
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
Приклад:
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();
Як серіалізувати результати пошуку в JSON?
Деякі прив’язки надають одноразовий серіалізатор, що перетворює список результатів пошуку сторінки в JSON-масив за один перетин FFI-межі — Rust серіалізує весь список, а прив’язка декодує його, замість того щоб передавати кожне поле окремо. Саме так методи SearchPage у Go та C# декодують результати під капотом.
Авторитетним є підпис C ABI:
char *pdf_oxide_search_results_to_json(
const FfiSearchResults *results,
int32_t *error_code);
Функція приймає непрозорий дескриптор результатів, повернутий pdf_document_search_page(...), і повертає виділений через malloc рядок JSON UTF-8 (звільнити через pdf_free_string). Кожен елемент містить page, text і обмежувальний прямокутник збігу (x, y, width, height).
Swift — обгортка об’єднує пошук і серіалізацію в один виклик 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#. Ці прив’язки викликають
pdf_oxide_search_results_to_jsonвсередині і повертають уже декодовані нативні записи, тому викликати серіалізатор напряму не потрібно. Використовуйтеdoc.SearchPage(...)(Go:doc.SearchPage(page, text, caseSensitive); C#:doc.SearchPage(pageIndex, text, caseSensitive)) і отримуйте строго типізовані результати. Щоб отримати JSON у цих мовах, серіалізуйте повернуті записи стандартною бібліотекою (encoding/json/System.Text.Json).
Python / Rust. Методи Python
doc.search(...)/doc.search_page(...)вже повертають нативні записиlist[dict](серіалізуйте безпосередньо черезjson.dumps), а Rust повертає типізованийVec<SearchResult>, який можна серіалізувати черезserde_json. Жодному з них C-ABI серіалізатор не потрібен.
Розширені приклади
Пошук і виділення з власним кольором
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")?;
Пошук з обмеженням діапазону сторінок
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}")
Побудова пошукового індексу по кількох PDF
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);
}
}
Витягнення контексту навколо збігів
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());
}
Часті запитання
Як отримати результати пошуку у форматі JSON? У Swift викличте doc.searchResultsToJson(page, term, caseSensitive:) — метод виконає пошук на сторінці і одразу поверне JSON-масив збігів. У Python і Rust пошук повертає нативні записи (list[dict] / Vec<SearchResult>), які серіалізуються через json.dumps / serde_json. Go і C# повертають типізовані записи, які серіалізуються через encoding/json / System.Text.Json.
Що містить кожен JSON-збіг? Поле page (з нуля), знайдений text і загальний обмежувальний прямокутник: x, y, width, height (у точках PDF, початок координат — лівий нижній кут).
Пошук за замовчуванням використовує регулярні вирази чи звичайний текст? Паттерни компілюються як регулярні вирази, якщо не ввімкнено режим literal (with_literal(true) / literal=True), який екранує метасимволи і зіставляє текст дослівно.
Чи підтримується нечутливий до регістру пошук і пошук цілих слів? Так — встановіть case_insensitive і whole_word у SearchOptions (Rust) або передайте їх як іменовані аргументи (Python) / параметри (інші прив’язки).
Пов’язані сторінки
- Витягнення тексту – Витягнення тексту, на якому ґрунтується пошук
- Витягнення з областю – Витягнення за регіоном і структурований JSON регіонів
- Витягнення анотацій – Анотації, створені highlight_matches
- Конвертація в Markdown – Перетворення контексту результатів пошуку на Markdown