텍스트 추출
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_threshold,line_gap_threshold,profile,page_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 |
0부터 시작하는 페이지 인덱스 |
반환값: 페이지의 전체 텍스트 콘텐츠.
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은 동일한 폰트와 스타일을 가진 연속된 텍스트 단위입니다. 각 span에는 텍스트 콘텐츠, 경계 박스, 폰트 이름, 폰트 크기, 굵기, 이탤릭 플래그, 색상이 포함됩니다. 레이아웃 또는 폰트 정보가 필요한 대부분의 추출 작업에 권장되는 방식입니다.
| 파라미터 | 타입 | 설명 |
|---|---|---|
page_index |
int / usize |
0부터 시작하는 페이지 인덱스 |
반환값: 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 |
0부터 시작하는 페이지 인덱스 |
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 |
0부터 시작하는 페이지 인덱스 |
반환값: 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 |
0부터 시작하는 페이지 인덱스 |
반환값: 필드 spans, chars, page_width, page_height, text를 가진 PageText 객체 (Python dict / JS object).
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 |
– | 0부터 시작하는 페이지 인덱스 |
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 |
0부터 시작하는 페이지 인덱스 |
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
관련 페이지
- Markdown 변환 – 텍스트를 구조화된 Markdown으로 변환
- HTML 변환 – 서식이 포함된 HTML로 텍스트 변환
- 텍스트 검색 – 정규식으로 추출된 텍스트 검색
- 메타데이터 및 XMP – 문서 수준 메타데이터 읽기