Skip to content

Конвертація у Markdown

PDF Oxide конвертує сторінки PDF у чистий, читабельний Markdown. Конвеєр витягає текстові span’и, кластеризує їх у рядки, звіряє /StructTreeRoot для заголовків і ролей списків у тегованих PDF, виявляє міжколонкові проміжки та «зворотні» переноси порядку читання по x, групує абзаци і видає Markdown-синтаксис.

Починаючи з v0.3.36, для тегованих PDF конвертер зчитує StructRole(Heading(1..6) | ListItem | ListItemLabel | ListItemBody) прямо зі /StructTreeRoot, а не обчислює рівні заголовків за розміром шрифта. Інформація про роль поширюється через вкладені MCR (H1 → Span → MCR, LI → LBody → Span → MCR). Для нетегованих документів досі працює геометричний відкат: жирність + 5 % збільшення розміру просуває до H4, а is_ordered_list_marker розпізнає 1. / 12. / a) / iv. / A. і відкидає підписи рисунків та роки.

Багатоколонковий режим: span’и з однаковою базовою лінією, розділені > max(3 × font_size, 30 pt), вважаються міжколонковими. Зворотні по x переноси порядку читання (останній span col 1 → перший span col 2) розривають абзаци, а не зшивають їх у безглузді токени.

RTL: bidi-переупорядковування за замовчуванням вимкнене — попереднє безумовне перетворення visual→logical ламало PDF, що вже були в логічному порядку (івритське בנימін інвертувалося). Хибні маркери **bold** навколо контекстних арабських гліфів прибираються. Якщо ваш вхід у візуальному порядку — викликайте text::bidi::reorder_visual_to_logical вручну (Rust).

Вбудовані зображення обмежені payload у 200 КБ base64 (додано в v0.3.36). Для зображень понад ліміт виводиться HTML-коментар із зазначенням оригінального розміру; зберігайте їх на диск через image_output_dir.

Швидкий приклад

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("paper.pdf")
md = doc.to_markdown(0, detect_headings=True)
print(md)

Node.js

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

const doc = new PdfDocument("paper.pdf");
const md = doc.toMarkdown(0, { detectHeadings: true });
console.log(md);
doc.close();

Go

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

doc, _ := pdfoxide.Open("paper.pdf")
defer doc.Close()
md, _ := doc.ToMarkdown(0)
fmt.Println(md)

C#

using PdfOxide.Core;

using var doc = PdfDocument.Open("paper.pdf");
var md = doc.ToMarkdown(0);
Console.WriteLine(md);

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdown(0, true);
console.log(md);

Rust

use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;

let mut doc = PdfDocument::open("paper.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown(0, &options)?;
println!("{}", md);

Довідник API

to_markdown(page_index, ...) -> str

Конвертувати одну сторінку у Markdown.

Сигнатура Python

doc.to_markdown(
    page: int,
    preserve_layout: bool = False,
    detect_headings: bool = True,
    include_images: bool = True,
    image_output_dir: str | None = None,
    embed_images: bool = True,
) -> str

Сигнатура JavaScript

doc.toMarkdown(pageIndex, detectHeadings?, includeImages?, includeFormFields?) -> string

Сигнатура Rust

pub fn to_markdown(
    &mut self,
    page_index: usize,
    options: &ConversionOptions,
) -> Result<String>
Параметр Тип За замовчуванням Опис
page_index int / usize / number Індекс сторінки з нуля
preserve_layout bool false Зберігати позиціонування візуального макета
detect_headings bool true Виявляти заголовки на основі розміру та насиченості шрифту
include_images bool true Включати зображення у вивід
image_output_dir str / None None Каталог для збереження видобутих зображень (лише Python/Rust). Не залежить від обмеження 200 КБ на inline-вкладення.
embed_images bool true Вбудовувати зображення як base64 data URI (лише Python/Rust). Payload понад 200 КБ замінюється HTML-коментарем із зазначенням оригінального розміру (v0.3.36).
include_form_fields bool true Включати значення полів форм (Python/JS)

Повертає: рядок Markdown для сторінки.


to_markdown_all(...) -> str

Конвертувати всі сторінки у Markdown, розділені горизонтальними лініями (---).

Сигнатура Python

doc.to_markdown_all(
    preserve_layout: bool = False,
    detect_headings: bool = True,
    include_images: bool = True,
    image_output_dir: str | None = None,
    embed_images: bool = True,
) -> str

Сигнатура JavaScript

doc.toMarkdownAll(detectHeadings?, includeImages?, includeFormFields?) -> string

Сигнатура Rust

pub fn to_markdown_all(
    &mut self,
    options: &ConversionOptions,
) -> Result<String>
Параметр Тип За замовчуванням Опис
preserve_layout bool false Зберігати візуальний макет
detect_headings bool true Виявляти заголовки
include_images bool true Включати зображення
image_output_dir str / None None Каталог виводу зображень
embed_images bool true Вбудовувати зображення як base64

Повертає: рядок Markdown для всіх сторінок, об’єднаних роздільниками ---.


to_markdown_with_ocr(page_index, model_path, options) -> str

Конвертувати сторінку у Markdown із резервним OCR для сканованих сторінок. Коли на сторінці мало або зовсім немає тексту, придатного до видобування, OCR використовується для розпізнавання тексту з відмальованого зображення сторінки. Потребує функції ocr.

Параметр Тип Опис
page_index usize Індекс сторінки з нуля
model_path &str Шлях до файлів моделі OCR
options &ConversionOptions Параметри конвертації

Rust

let mut doc = PdfDocument::open("scanned.pdf")?;
let options = ConversionOptions { detect_headings: true, ..Default::default() };
let md = doc.to_markdown_with_ocr(0, "/path/to/models", &options)?;
println!("{}", md);

ConversionOptions

Структура ConversionOptions керує всією поведінкою конвертації.

Поле Тип За замовчуванням Опис
preserve_layout bool false Зберігати візуальний макет із позиціонуванням
detect_headings bool true Автоматично виявляти заголовки за кластерами розмірів шрифту
extract_tables bool false Видобувати таблиці (експериментально)
include_images bool true Включати зображення у вивід
image_output_dir Option<String> None Зберігати зображення в цей каталог
embed_images bool true Вбудовувати зображення як base64 data URI
reading_order_mode ReadingOrderMode Auto Як визначати порядок читання
bold_marker_behavior BoldMarkerBehavior Conservative Стратегія застосування маркерів жирності

Як це працює

Конвеєр конвертації у Markdown працює в кілька етапів:

  1. Видобування тексту – Видобуває об’єкти TextSpan із потоку вмісту сторінки, фіксуючи текст, позицію, шрифт, розмір, насиченість і колір.

  2. Кластеризація символів – Групує символи у слова на основі проміжків між символами, а потім слова у рядки на основі вертикальної близькості.

  3. Порядок читання – Визначає порядок читання, використовуючи або дерево структури тегованого PDF (бажано), або графовий просторовий аналіз позицій текстових блоків.

  4. Виявлення заголовків – Коли увімкнено detect_headings, кластеризує розміри шрифтів по сторінці для визначення рівнів заголовків. Більший і жирніший текст відображається на заголовки #, ##, ###.

  5. Форматування – Застосовує маркери жирності (**text**) та курсиву (*text*) на основі метаданих насиченості та стилю шрифту.

  6. Виявлення таблиць – Визначає табличні макети за допомогою просторового аналізу вирівняних текстових блоків і видає Markdown-таблиці у стилі GFM.

  7. Очищення пробілів – Нормалізує інтервали, видаляє зайві порожні рядки та забезпечує узгоджені розриви абзаців.


Розширені приклади

Конвертація всього PDF у файл Markdown

Python

from pdf_oxide import PdfDocument

doc = PdfDocument("book.pdf")
md = doc.to_markdown_all(detect_headings=True)

with open("book.md", "w", encoding="utf-8") as f:
    f.write(md)

Node.js

const fs = require("node:fs");

const doc = new PdfDocument("book.pdf");
const md = doc.toMarkdownAll();
fs.writeFileSync("book.md", md);
doc.close();

Go

doc, _ := pdfoxide.Open("book.pdf")
defer doc.Close()
md, _ := doc.ToMarkdownAll()
os.WriteFile("book.md", []byte(md), 0644)

C#

using var doc = PdfDocument.Open("book.pdf");
var md = doc.ToMarkdownAll();
File.WriteAllText("book.md", md);

WASM

const doc = new WasmPdfDocument(bytes);
const md = doc.toMarkdownAll(true);
writeFileSync("book.md", md);
doc.free();

Конвертація зі збереженням зображень у каталог

use pdf_oxide::PdfDocument;
use pdf_oxide::converters::ConversionOptions;

let mut doc = PdfDocument::open("report.pdf")?;
let options = ConversionOptions {
    detect_headings: true,
    include_images: true,
    embed_images: false,
    image_output_dir: Some("output/images".to_string()),
    ..Default::default()
};

let md = doc.to_markdown_all(&options)?;
std::fs::write("output/report.md", &md)?;

Посторінкова конвертація з відображенням прогресу

from pdf_oxide import PdfDocument

doc = PdfDocument("report.pdf")
pages = doc.page_count()

parts = []
for i in range(pages):
    md = doc.to_markdown(i, detect_headings=True)
    parts.append(md)
    print(f"Converted page {i + 1}/{pages}")

full_md = "\n\n---\n\n".join(parts)
with open("report.md", "w") as f:
    f.write(full_md)

Вимкнення виявлення заголовків для плаского тексту

doc = PdfDocument("form.pdf")
md = doc.to_markdown(0, detect_headings=False)
# All text rendered as paragraphs, no # headings

Пов’язані сторінки