Skip to content

文本提取

PDF Oxide 提供多个层级的文本提取:整页文本、带字体元数据的样式化文本段(span),以及带精确定位信息的单个字符。快速获取内容请使用 extract_text(),需要字体和位置数据时使用 extract_spans(),用于自定义布局引擎或 OCR 后处理等逐字符分析时使用 extract_chars()

对于带标签的 PDF,文本提取会自动遵循文档结构树以获得正确的阅读顺序。对于不带标签的 PDF,提取使用页面内容顺序并进行智能换行检测——包括单列保护机制,防止 RFC 风格和论文风格文档中正文文本出现碎片化。

阅读顺序支持

阅读顺序流水线在各种文字体系和版式下均能产生正确输出:

  • Latin — 默认从左到右、从上到下,支持分栏检测。
  • Arabic — 预整形 span 反转(Pass 0)将字符排列为逻辑阅读顺序而非视觉顺序。
  • CJK — 通过空间表格检测器保留 rowspan 标签列;3pt Y 轴量化防止表格内容与正文内容混排。
  • 旋转 / dvips 生成的 PDF — 分栏检测中基于中位数的异常值剔除可处理退化的 CTM 坐标。
  • 多栏学术论文 — XYCut 单列保护修复碎片化;行感知 span 排序处理正文中的表格内容。

单词与行分割

extract_words()extract_text_lines() 接受可选的关键字参数,用于调整单词和换行阈值:

参数 默认值 说明
word_gap_threshold 自适应 相邻字符间判定为单词边界的最小水平间距(单位:点)
line_gap_threshold 自适应 基线间判定为换行的最小垂直间距
profile "auto" "auto""dense""standard""sparse" 之一 — 针对不同版式选择预设参数

自适应参数由页面字体度量推导得出;使用 page_layout_params() 查看计算值,使用 ExtractionProfile 构建自定义配置文件。

仅限 Python 的调整参数: word_gap_thresholdline_gap_thresholdprofilepage_layout_params() 在 Python 绑定中公开。Node.js、JavaScript、Go、C# 和 WASM 绑定提供 extractWords(pageIndex) / extractTextLines(pageIndex),使用自适应默认值且不接受关键字参数。如需在这些语言中进行调整,请使用下方的 Rust API。

Python

from pdf_oxide import PdfDocument, ExtractionProfile

doc = PdfDocument("receipt.pdf")

params = doc.page_layout_params(0)
print(params.word_gap_threshold, params.line_gap_threshold)

words = doc.extract_words(0, word_gap_threshold=2.5, profile="dense")
lines = doc.extract_text_lines(0, profile=ExtractionProfile.DENSE)

Node.js

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

const doc = new PdfDocument("receipt.pdf");
const words = doc.extractWords(0);      // adaptive defaults
const lines = doc.extractTextLines(0);
doc.close();

JavaScript

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

const doc = new PdfDocument("receipt.pdf");
const words = doc.extractWords(0);
const lines = doc.extractTextLines(0);
doc.close();

TypeScript

import { PdfDocument } from "pdf-oxide";

const doc: PdfDocument = new PdfDocument("receipt.pdf");
const words = doc.extractWords(0);
const lines = doc.extractTextLines(0);
doc.close();

Rust

use pdf_oxide::{PdfDocument, ExtractionProfile};

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

let params = doc.page_layout_params(0)?;
println!("{} {}", params.word_gap_threshold, params.line_gap_threshold);

let words = doc.extract_words_with_config(0, /* word_gap_threshold */ Some(2.5), ExtractionProfile::Dense)?;
let lines = doc.extract_text_lines_with_profile(0, ExtractionProfile::Dense)?;

Go

words, _ := doc.ExtractWords(0)     // adaptive defaults
lines, _ := doc.ExtractTextLines(0)

C#

var words = doc.ExtractWords(0);     // adaptive defaults
var lines = doc.ExtractTextLines(0);

WASM

const doc = new WasmPdfDocument(bytes);
const words = doc.extractWords(0);
const lines = doc.extractTextLines(0);

Java

try (PdfDocument doc = PdfDocument.open(Path.of("receipt.pdf"))) {
    List<TextWord> words = doc.page(0).words();      // adaptive defaults
    List<TextLine> lines = doc.page(0).lines();
}

C++

auto doc = pdf_oxide::Document::open("receipt.pdf");
auto words = doc.extract_words(0);   // adaptive defaults
auto lines = doc.extract_text_lines(0);

Swift

let doc = try Document.open("receipt.pdf")
let words = try doc.extractWords(0)   // adaptive defaults
let lines = try doc.extractTextLines(0)

Kotlin

PdfDocument.open(Path.of("receipt.pdf")).use { doc ->
    val words = doc.page(0).words()   // adaptive defaults
    val lines = doc.page(0).lines()
}

Dart

final doc = PdfDocument.open('receipt.pdf');
final words = doc.extractWords(0);   // adaptive defaults
final lines = doc.extractTextLines(0);

R

doc <- pdf_open("receipt.pdf")
words <- pdf_extract_words(doc, 0)        # adaptive defaults
lines <- pdf_extract_text_lines(doc, 0)

Julia

doc = open_document("receipt.pdf")
words = extract_words(doc, 0)    # adaptive defaults
lines = extract_text_lines(doc, 0)

Zig

var doc = try pdf_oxide.Document.open("receipt.pdf");
const words = try doc.extractWords(a, 0);    // adaptive defaults
const lines = try doc.extractTextLines(a, 0);

Scala

Using.resource(PdfDocument.open("receipt.pdf")) { doc =>
  val words = doc.page(0).wordsSeq   // adaptive defaults
  val lines = doc.page(0).linesSeq
}

Clojure

(with-open [doc (pdf/open "receipt.pdf")]
  (pdf/words (pdf/page doc 0))   ; adaptive defaults
  (pdf/lines (pdf/page doc 0)))

Objective-C

POXDocument *doc = [POXDocument openPath:@"receipt.pdf" error:&err];
NSArray<POXWord*> *words = [doc extractWords:0 error:&err];        // adaptive defaults
NSArray<POXTextLine*> *lines = [doc extractTextLines:0 error:&err];

Elixir

{:ok, doc} = PdfOxide.open("receipt.pdf")
{:ok, words} = PdfOxide.extract_words(doc, 0)    # adaptive defaults
{:ok, lines} = PdfOxide.extract_text_lines(doc, 0)

快速示例

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
print(text)

Node.js

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

const doc = new PdfDocument("report.pdf");
const text = doc.extractText(0);
console.log(text);

Go

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

doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
text, _ := doc.ExtractText(0)
fmt.Println(text)

C#

using PdfOxide.Core;

using var doc = PdfDocument.Open("report.pdf");
string text = doc.ExtractText(0);
Console.WriteLine(text);

WASM

const doc = new WasmPdfDocument(bytes);
const text = doc.extractText(0);
console.log(text);

Rust

use pdf_oxide::PdfDocument;

let mut doc = PdfDocument::open("report.pdf")?;
let text = doc.extract_text(0)?;
println!("{}", text);

Java

import fyi.oxide.pdf.*;
import java.nio.file.Path;

try (PdfDocument doc = PdfDocument.open(Path.of("report.pdf"))) {
    String text = doc.extractText(0);
    System.out.println(text);
}

PHP

use PdfOxide\PdfDocument;

$doc = PdfDocument::open('report.pdf');
$text = $doc->extractText(0);
echo $text;
$doc->close();

Ruby

require 'pdf_oxide'

PdfOxide::PdfDocument.open('report.pdf') do |doc|
  text = doc.extract_text(0)
  puts text
end

C++

#include <pdf_oxide/pdf_oxide.hpp>

auto doc = pdf_oxide::Document::open("report.pdf");
auto text = doc.extract_text(0);
std::cout << text << "\n";

Swift

import PdfOxide

let doc = try Document.open("report.pdf")
let text = try doc.extractText(0)
print(text)

Kotlin

import fyi.oxide.pdf.*

PdfDocument.open(java.nio.file.Path.of("report.pdf")).use { doc ->
    val text = doc.extractText(0)
    println(text)
}

Dart

import 'package:pdf_oxide/pdf_oxide.dart';

final doc = PdfDocument.open('report.pdf');
final text = doc.extractText(0);
print(text);

R

library(pdfoxide)

doc <- pdf_open("report.pdf")
text <- pdf_extract_text(doc, 0)
cat(text)

Julia

using PdfOxide

doc = open_document("report.pdf")
text = extract_text(doc, 0)
println(text)

Zig

const pdf_oxide = @import("pdf_oxide");

var doc = try pdf_oxide.Document.open("report.pdf");
const text = try doc.extractText(a, 0);
std.debug.print("{s}\n", .{text});

Scala

import fyi.oxide.pdf.PdfDocument
import scala.util.Using

Using.resource(PdfDocument.open("report.pdf")) { doc =>
  val text = doc.extractText(0)
  println(text)
}

Clojure

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

(with-open [doc (pdf/open "report.pdf")]
  (println (pdf/extract-text doc 0)))

Objective-C

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

POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSString *text = [doc extractText:0 error:&err];
NSLog(@"%@", text);

Elixir

{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)

API 参考

extract_text(page_index) -> str

将页面的全部文本提取为单个字符串。自动检测带标签的 PDF,并在可用时使用结构树确定阅读顺序。根据 span 之间的垂直和水平间距插入换行符和空格。

参数 类型 说明
page_index int / usize 从零开始的页面索引

返回值: 页面的完整文本内容。

Python

doc = PdfDocument("report.pdf")
for i in range(doc.page_count()):
    text = doc.extract_text(i)
    print(f"--- Page {i + 1} ---")
    print(text)

Node.js

const doc = new PdfDocument("report.pdf");
for (let i = 0; i < doc.getPageCount(); i++) {
    const text = doc.extractText(i);
    console.log(`--- Page ${i + 1} ---`);
    console.log(text);
}

Go

doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
count, _ := doc.PageCount()
for i := 0; i < count; i++ {
    text, _ := doc.ExtractText(i)
    fmt.Printf("--- Page %d ---\n", i+1)
    fmt.Println(text)
}

C#

using var doc = PdfDocument.Open("report.pdf");
for (int i = 0; i < doc.PageCount; i++)
{
    string text = doc.ExtractText(i);
    Console.WriteLine($"--- Page {i + 1} ---");
    Console.WriteLine(text);
}

WASM

const doc = new WasmPdfDocument(bytes);
for (let i = 0; i < doc.pageCount(); i++) {
    const text = doc.extractText(i);
    console.log(`--- Page ${i + 1} ---`);
    console.log(text);
}

Rust

let mut doc = PdfDocument::open("report.pdf")?;
let page_count = doc.page_count()?;
for i in 0..page_count {
    let text = doc.extract_text(i)?;
    println!("--- Page {} ---", i + 1);
    println!("{}", text);
}

Java

try (PdfDocument doc = PdfDocument.open(Path.of("report.pdf"))) {
    int n = doc.pageCount();
    for (int i = 0; i < n; i++) {
        System.out.println("--- Page " + (i + 1) + " ---");
        System.out.println(doc.extractText(i));
    }
}

PHP

$doc = PdfDocument::open('report.pdf');
$n = $doc->pageCount();
for ($i = 0; $i < $n; $i++) {
    echo "--- Page " . ($i + 1) . " ---\n";
    echo $doc->extractText($i);
}
$doc->close();

Ruby

PdfOxide::PdfDocument.open('report.pdf') do |doc|
  (0...doc.page_count).each do |i|
    puts "--- Page #{i + 1} ---"
    puts doc.extract_text(i)
  end
end

C++

auto doc = pdf_oxide::Document::open("report.pdf");
int n = doc.page_count();
for (int i = 0; i < n; i++) {
    std::cout << "--- Page " << (i + 1) << " ---\n";
    std::cout << doc.extract_text(i) << "\n";
}

Swift

let doc = try Document.open("report.pdf")
let n = try doc.pageCount()
for i in 0..<n {
    print("--- Page \(i + 1) ---")
    print(try doc.extractText(i))
}

Kotlin

PdfDocument.open(java.nio.file.Path.of("report.pdf")).use { doc ->
    for (i in 0 until doc.pageCount()) {
        println("--- Page ${i + 1} ---")
        println(doc.extractText(i))
    }
}

Dart

final doc = PdfDocument.open('report.pdf');
for (var i = 0; i < doc.pageCount; i++) {
    print('--- Page ${i + 1} ---');
    print(doc.extractText(i));
}

R

doc <- pdf_open("report.pdf")
for (i in seq_len(pdf_page_count(doc)) - 1) {
    cat(sprintf("--- Page %d ---\n", i + 1))
    cat(pdf_extract_text(doc, i))
}

Julia

doc = open_document("report.pdf")
for i in 0:(page_count(doc) - 1)
    println("--- Page $(i + 1) ---")
    println(extract_text(doc, i))
end

Zig

var doc = try pdf_oxide.Document.open("report.pdf");
const n = try doc.pageCount();
var i: usize = 0;
while (i < n) : (i += 1) {
    std.debug.print("--- Page {d} ---\n", .{i + 1});
    const text = try doc.extractText(a, i);
    std.debug.print("{s}\n", .{text});
}

Scala

Using.resource(PdfDocument.open("report.pdf")) { doc =>
  for (i <- 0 until doc.pageCount()) {
    println(s"--- Page ${i + 1} ---")
    println(doc.extractText(i))
  }
}

Clojure

(with-open [doc (pdf/open "report.pdf")]
  (doseq [i (range (pdf/page-count doc))]
    (println (str "--- Page " (inc i) " ---"))
    (println (pdf/extract-text doc i))))

Objective-C

POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSInteger n = [doc pageCountError:&err];
for (NSInteger i = 0; i < n; i++) {
    NSLog(@"--- Page %ld ---", (long)(i + 1));
    NSLog(@"%@", [doc extractText:i error:&err]);
}

Elixir

{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, n} = PdfOxide.page_count(doc)
Enum.each(0..(n - 1), fn i ->
  IO.puts("--- Page #{i + 1} ---")
  {:ok, text} = PdfOxide.extract_text(doc, i)
  IO.puts(text)
end)

extract_spans(page_index) -> list[TextSpan]

将文本提取为 span——具有相同字体和样式的连续文本段。每个 span 包含文本内容、边界框、字体名称、字体大小、字重、斜体标志和颜色。这是大多数需要布局或字体信息的提取任务的推荐方式。

参数 类型 说明
page_index int / usize 从零开始的页面索引

返回值: TextSpan 对象的列表/向量。

TextSpan 字段

字段 类型 说明
text str span 的文本内容
bbox Rect 边界框(x, y, width, height)
font_name str 字体名称/族(如 “Helvetica”、“TimesNewRoman”)
font_size f32 字体大小(单位:点)
font_weight FontWeight 字重:Normal、Bold、Light、SemiBold 等
is_italic bool span 是否为斜体
color Color RGB 颜色(r, g, b),值范围 0.0–1.0
mcid Option<u32> 带标签 PDF 的标记内容 ID
sequence usize 提取顺序(Y 坐标排序的决胜因子)
is_monospace bool 字体是否为等宽字体(Courier、Consolas 等)
char_widths list[float] 每个字形的推进宽度,用于精确边界框
char_spacing f32 字符间距(Tc 参数)
word_spacing f32 单词间距(Tw 参数)
horizontal_scaling f32 水平缩放百分比(Tz,默认 100.0)

Rust

let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;

for span in &spans {
    println!(
        "'{}' at ({:.1}, {:.1}) font={} size={:.1}pt bold={} italic={}",
        span.text,
        span.bbox.x, span.bbox.y,
        span.font_name,
        span.font_size,
        span.font_weight == FontWeight::Bold,
        span.is_italic,
    );
}

extract_spans_with_config(page_index, config) -> Vec<TextSpan>

使用自定义 span 合并配置提取 span。当默认合并行为对您的文档产生错误的单词边界时使用此方法。

参数 类型 说明
page_index usize 从零开始的页面索引
config SpanMergingConfig 控制提取参数的配置

Rust

use pdf_oxide::extractors::SpanMergingConfig;

let mut doc = PdfDocument::open("report.pdf")?;
let config = SpanMergingConfig::adaptive();
let spans = doc.extract_spans_with_config(0, config)?;

extract_chars(page_index) -> list[TextChar]

提取带精确边界框、字体元数据和变换属性的单个字符。这是低级 API——大多数场景请优先使用 extract_text()extract_spans()。字符提取比 span 提取快 30–50%,因为跳过了文本分组和合并步骤。

参数 类型 说明
page_index int / usize 从零开始的页面索引

返回值: TextChar 对象的列表/向量。

TextChar 字段

字段 类型 说明
char char 字符
bbox Rect 边界框(x, y, width, height)
font_name str 字体名称/族
font_size f32 字体大小(单位:点)
font_weight FontWeight 字重(Normal、Bold 等)
is_italic bool 斜体标志
color Color RGB 颜色(每通道 0.0–1.0)
mcid Option<u32> 标记内容 ID
origin_x f32 基线原点 X 坐标
origin_y f32 基线原点 Y 坐标
rotation_degrees f32 文本旋转角度(0–360,顺时针)
advance_width f32 到下一字符位置的水平距离
matrix [f32; 6] 完整变换矩阵 [a, b, c, d, e, f]

Python

doc = PdfDocument("report.pdf")
chars = doc.extract_chars(0)

for ch in chars:
    print(f"'{ch.char}' at ({ch.bbox[0]:.1f}, {ch.bbox[1]:.1f}) "
          f"font={ch.font_name} size={ch.font_size:.1f}")

<!-- Node.js: extractChars not yet in binding (js/src/index.ts) -->

Go

doc, _ := pdfoxide.Open("report.pdf")
defer doc.Close()
chars, _ := doc.ExtractChars(0)

for _, ch := range chars {
    fmt.Printf("'%c' at (%.1f, %.1f) font=%s size=%.1f\n",
        ch.Char, ch.X, ch.Y, ch.FontName, ch.FontSize)
}

C#

using var doc = PdfDocument.Open("report.pdf");
var chars = doc.ExtractChars(0);

foreach (var ch in chars)
{
    Console.WriteLine($"'{ch.Char}' at ({ch.X:F1}, {ch.Y:F1}) {ch.W:F1}x{ch.H:F1}");
}

WASM

const doc = new WasmPdfDocument(bytes);
const chars = doc.extractChars(0);

for (const ch of chars) {
    console.log(`'${ch.char}' at (${ch.bbox[0].toFixed(1)}, ${ch.bbox[1].toFixed(1)}) font=${ch.fontName} size=${ch.fontSize.toFixed(1)}`);
}

Rust

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

for ch in &chars {
    println!(
        "'{}' origin=({:.1}, {:.1}) rotation={:.0} advance={:.1}",
        ch.char, ch.origin_x, ch.origin_y,
        ch.rotation_degrees, ch.advance_width,
    );
}

C++

auto doc = pdf_oxide::Document::open("report.pdf");
auto chars = doc.extract_chars(0);

for (const auto& ch : chars) {
    std::printf("U+%04X at (%.1f, %.1f) font=%s size=%.1f\n",
        ch.character, ch.bbox.x, ch.bbox.y,
        ch.font_name.c_str(), ch.font_size);
}

Swift

let doc = try Document.open("report.pdf")
let chars = try doc.extractChars(0)

for ch in chars {
    let scalar = String(UnicodeScalar(ch.character)!)
    print("'\(scalar)' at (\(ch.bbox.x), \(ch.bbox.y)) font=\(ch.fontName) size=\(ch.fontSize)")
}

Dart

final doc = PdfDocument.open('report.pdf');
final chars = doc.extractChars(0);

for (final ch in chars) {
    final glyph = String.fromCharCode(ch.character);
    print("'$glyph' at (${ch.bbox.x}, ${ch.bbox.y}) font=${ch.fontName} size=${ch.fontSize}");
}

R

doc <- pdf_open("report.pdf")
chars <- pdf_extract_chars(doc, 0)

for (ch in chars) {
    cat(sprintf("U+%04X at (%.1f, %.1f) font=%s size=%.1f\n",
        ch$character, ch$bbox$x, ch$bbox$y, ch$font_name, ch$font_size))
}

Julia

doc = open_document("report.pdf")
chars = extract_chars(doc, 0)

for ch in chars
    glyph = Char(ch.character)
    println("'$glyph' at ($(ch.bbox.x), $(ch.bbox.y)) font=$(ch.font_name) size=$(ch.font_size)")
end

Zig

var doc = try pdf_oxide.Document.open("report.pdf");
const chars = try doc.extractChars(a, 0);

for (chars) |ch| {
    std.debug.print("U+{X:0>4} at ({d:.1}, {d:.1}) font={s} size={d:.1}\n",
        .{ ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize });
}

Objective-C

POXDocument *doc = [POXDocument openPath:@"report.pdf" error:&err];
NSArray<POXChar*> *chars = [doc extractChars:0 error:&err];

for (POXChar *ch in chars) {
    NSLog(@"U+%04X at (%.1f, %.1f) font=%@ size=%.1f",
        ch.character, ch.bbox.x, ch.bbox.y, ch.fontName, ch.fontSize);
}

Elixir

{:ok, doc} = PdfOxide.open("report.pdf")
{:ok, chars} = PdfOxide.extract_chars(doc, 0)

Enum.each(chars, fn ch ->
  glyph = <<ch.character::utf8>>
  IO.puts("'#{glyph}' at (#{ch.bbox.x}, #{ch.bbox.y}) font=#{ch.font_name} size=#{ch.font_size}")
end)

extract_page_text(page_index) -> PageText

通过单次提取获取 span、字符和页面尺寸。由于只解析一次页面内容流,比分别调用 extract_spans() + extract_chars() 更高效。

参数 类型 说明
page_index int / usize 从零开始的页面索引

返回值: PageText 对象(Python 中为 dict,JS 中为 object),包含字段:spanscharspage_widthpage_heighttext

Python

doc = PdfDocument("report.pdf")
result = doc.extract_page_text(0)
# result is a dict with: spans, chars, page_width, page_height, text

for span in result["spans"]:
    print(f"'{span.text}' font={span.font_name} size={span.font_size}")

<!-- Node.js: extractPageText not yet in binding (js/src/index.ts) --> <!-- Go: ExtractPageText not yet in binding (go/pdf_oxide.go) --> <!-- C#: ExtractPageText not yet in binding (csharp/PdfOxide/Core/PdfDocument.cs) -->

WASM

const result = doc.extractPageText(0);
// result has: spans, chars, pageWidth, pageHeight, text

for (const span of result.spans) {
    console.log(`'${span.text}' font=${span.fontName} size=${span.fontSize}`);
}

Rust

let mut doc = PdfDocument::open("report.pdf")?;
let result = doc.extract_page_text(0)?;
println!("Page is {}x{} pt", result.page_width, result.page_height);
for span in &result.spans {
    println!("'{}' font={} size={:.1}", span.text, span.font_name, span.font_size);
}

分栏感知阅读顺序

对于多栏 PDF(研究论文、报纸等),使用分栏感知阅读顺序可以逐栏读取,而不是跨栏读取:

Python

# Default: top-to-bottom (reads across columns)
spans = doc.extract_spans(0)

# Column-aware: reads each column separately
spans = doc.extract_spans(0, reading_order="column_aware")

<!-- Node.js: extractSpans not yet in binding (js/src/index.ts) --> <!-- Go: ExtractSpans not yet in binding (go/pdf_oxide.go) --> <!-- C#: ExtractSpans not yet in binding (csharp/PdfOxide/Core/PdfDocument.cs) -->

WASM

const spans = doc.extractSpans(0, undefined, "column_aware");

Rust

use pdf_oxide::extractors::ReadingOrder;

let spans = doc.extract_spans_with_reading_order(0, ReadingOrder::ColumnAware)?;

to_plain_text(page_index, options) -> str

将单页转换为纯文本。接受转换选项以保持 API 一致性,但大多数选项主要适用于 Markdown/HTML 输出。

参数 类型 默认值 说明
page_index int / usize 从零开始的页面索引
preserve_layout bool false 保留视觉布局
detect_headings bool true 检测标题
include_images bool true 包含图片
image_output_dir str / None None 图片输出目录

Python

doc = PdfDocument("paper.pdf")
text = doc.to_plain_text(0)

Node.js

const doc = new PdfDocument("paper.pdf");
const text = doc.toPlainText(0);

Go

doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
text, _ := doc.ToPlainText(0)

C#

using var doc = PdfDocument.Open("paper.pdf");
string text = doc.ToPlainText(0);

WASM

const doc = new WasmPdfDocument(bytes);
const text = doc.extractText(0);

Rust

use pdf_oxide::converters::ConversionOptions;

let mut doc = PdfDocument::open("paper.pdf")?;
let options = ConversionOptions::default();
let text = doc.to_plain_text(0, &options)?;

C++

auto doc = pdf_oxide::Document::open("paper.pdf");
auto text = doc.to_plain_text(0);

Swift

let doc = try Document.open("paper.pdf")
let text = try doc.toPlainText(0)

Dart

final doc = PdfDocument.open('paper.pdf');
final text = doc.toPlainText(0);

R

doc <- pdf_open("paper.pdf")
text <- pdf_to_plain_text(doc, 0)

Julia

doc = open_document("paper.pdf")
text = to_plain_text(doc, 0)

Zig

var doc = try pdf_oxide.Document.open("paper.pdf");
const text = try doc.toPlainText(a, 0);

Objective-C

POXDocument *doc = [POXDocument openPath:@"paper.pdf" error:&err];
NSString *text = [doc toPlainText:0 error:&err];

Elixir

{:ok, doc} = PdfOxide.open("paper.pdf")
{:ok, text} = PdfOxide.to_plain_text(doc, 0)

extract_hierarchical_content(page_index) -> Option<StructureElement>

将页面内容提取为层次化结构树。对于不带标签的 PDF 返回 None。对于带标签的 PDF,返回表示文档逻辑结构(标题、段落、表格、图形)的 StructureElement 树。

参数 类型 说明
page_index int / usize 从零开始的页面索引

Rust

let mut doc = PdfDocument::open("tagged-report.pdf")?;
if let Some(root) = doc.extract_hierarchical_content(0)? {
    println!("Structure type: {:?}", root.structure_type);
    for child in &root.children {
        println!("  Child: {:?}", child.structure_type);
    }
}

进阶示例

从 span 构建词频表

from collections import Counter
from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
words = Counter()

for page in range(doc.page_count()):
    text = doc.extract_text(page)
    for word in text.split():
        words[word.lower().strip(".,;:!?\"'()[]")] += 1

for word, count in words.most_common(20):
    print(f"{word:20s} {count}")

使用 span 元数据检测粗体标题

use pdf_oxide::PdfDocument;
use pdf_oxide::layout::FontWeight;

let mut doc = PdfDocument::open("paper.pdf")?;
let spans = doc.extract_spans(0)?;

let headings: Vec<_> = spans.iter()
    .filter(|s| s.font_weight == FontWeight::Bold && s.font_size > 14.0)
    .collect();

for h in headings {
    println!("Heading: '{}' ({}pt)", h.text, h.font_size);
}

将逐字符数据导出为 CSV

import csv
from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
chars = doc.extract_chars(0)

with open("characters.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["char", "x", "y", "width", "height", "font", "size"])
    for ch in chars:
        writer.writerow([
            ch.char, ch.bbox[0], ch.bbox[1],
            ch.bbox[2], ch.bbox[3],
            ch.font_name, ch.font_size,
        ])

提取矢量路径

extract_paths() 返回页面的矢量路径数据(直线、曲线、矩形),可用于检测表格边框、分隔线和图形元素。

doc = PdfDocument("report.pdf")
paths = doc.extract_paths(0)
for path in paths:
    for op in path["operations"]:
        print(f"{op['type']}: {op.get('x', '')}, {op.get('y', '')}")
        # types: move_to, line_to, curve_to, rectangle, close_path

相关页面