Комментарии 31
Как текст извлекается из pdf?
Учитывается ли взаимное положение элементов в документе или текст склеивается 'в том порядке что их разместил автор' даже если он редактировал его и верхние части документа добавил последними? Правильно ли распознается многоколоночные документы? Наличие формул и графиков и т.п.
Ну и в догонку, а как быть с pdf без текстовой информации? как картинка или векторный ps? А поддержка русского и не только языка?
Признаюсь, пока с этим сложно. Сделал MVP, чтобы обкатать технологию и модель. Но у меня впереди несколько лет, чтобы это всё закодить. Я настроен оптимистично. Прикручу нейросеть, чтобы картинки в векторе или битмапе распознавать как текст. Есть опыт работы с библиотекой Candle на Rust от Hugging Face — в общем, распознавание текста — это банальная задача. Короче, все решаемо. Не вижу здесь особого R&D. Я бы сказал, что все понятно, нужно только сидеть и терпеливо кодить.
И по поводу всего остального, касаемо формул и так далее, — я знаю, думаю и планирую. Просто нужно с чего-то начать. Сделать минимально жизнеспособный продукт (MVP). А потом, рефакторинг никто не отменял. Учитывая, что это молодая и быстро растущая библиотека, у меня нет миллионов пользователей, чтобы поддерживать старые API, так что пока мои руки развязаны. Вороти и твори, что хочешь :) Это потом, когда достигнешь популярности, становится сложнее — нужно поддерживать старые версии. Нельзя же бросать людей, которые доверились тебе и использовали твою библиотеку. Но это пока всё лишь мечты. Впереди несколько лет кодирования. )
Нельзя же бросать людей, которые доверились тебе и использовали твою библиотеку.
И мона, и нуна (с) из старого детского анекдота
Люди пусть продолжают использовать старую версию, а новая вполне может быть с несовместимым API. Ну или донаты никто не отменял (в размере полной оплаты поддержки старой версии).
Может будет полезно: https://pandoc.org/
Сразу вижу проблему: зачем в один трейт сложено чтение из документа и генерация? Есть много форматов, например DjVu, которые можно прочитать без зависимостей (вся метадата лежит по известному отступу), но хрен сгенерируешь.
И да, pandoc.
Дело хорошее, удачи проекту!
По вашему ощущению, разработка на расте вашей библиотеки дает какое-то преимущество: скорость написания кода, проектирования, и т.п.
Просто про безопасность пишут везде, но вот в части повешения эффективности разработки, например на расте тоже самое напишу за месяц, что на плюсах - за три, а на питоне за неделю. Интересен исключительно лично ваша субъективная оценка.
Тут в соседнем топике обсуждают:
https://habr.com/en/articles/804915/
Удачи автору в переписывании pandoc на Rust.
Вместо своего поисковика посмотрите на SoLR. «Все уже украдено»
Поддержка разных типов документов это не тот проект который один человек может потянуть, не говоря о том, что вся проблема имеющихся - invented not here синдром.
Недавно стать читал, про то что «big data” больше не является проблемой.
Поскольку при распаллеливании задачи ваша производительность растёт линейно, а по закону Мура производительность компьютеров - степенная функция.
Пока автор будет парсер pdf писать на Rust-e в смартфоне столика памяти будет, что туда все Java библиотеки можно будет засунуть :)
Закон Мура ограничен сверху законами квантовой физики, а "распаллеливания" законом Амдала.
Вот только мы в него пока не уперлись.
Рекомендую к прочтению стать от автора оригинальной идеи: https://motherduck.com/blog/big-data-is-dead/
fn element_type(&self) -> ElementType;}
pub enum ElementType { Text, Paragraph,
fn paragraph_as_ref(&self) -> anyhow::Result<&ParagraphElement>
В таком подходе тип элементов описывается в трех местах, что во-первых требует больше времени для добавления нового типа, а во-вторых отсуствует статическая проверка типизации - при match element.element_type() нет никакой гарантии компилятора что там будет действительно лежать нужный тип при даункасте. Плюс Vec<Box<dyn Element>>
не эффективная по перфомансу, будет постоянный кэш мисс и эвристики подгрузки не работают.
Может быть лучше попробовать, как первое решение, просто алгебраческий тип:
pub enum Element<'a> {
Header { level: u8, /* todo */ },
Paragraph { child: &'a Element<'a> },
...
}
и Vec<Element>
Еще все элементы создаются в куче с динамик трейтами вместо статического полиморфизма, а конструктуры объявлены через Result, хотя ошибку они никогда не кидают. Например
pub fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> {
Ok(ListItemElement {
element: element.clone(),
})
}
Надеюсь это не будет звучать грубо, но такое решение сильно похоже на попытку использовать ссылочный объектно-ориентированый подход как в java/.net , что смотрится несколько неорганично в расте.
Спасибо. Согласен. Опыт Java давит. Буду делать рефакторинг. А то не комильфо.
Вообще-то у автора уже проблема в реализации типа Document.
Тип назван документ, но поскольку у него есть pagewidth, … page header и page footer,
То это скорее всего должен быть Page а не Документ.
Элементы которые могут быть в Page/Document. Это параграф, … table of context
Table of Context не имеет смысла в отрыве от документа, который состоит из набора страниц.
способ объединения Page/Document в «нормальный документ» состоящий из набора страниц не виден.
Далее, есть table header, но отсутствует table footer, если уж работать по аналогии с html table,
Зачем-то появился елемент PageBreak, который в рамках поиска, для которого автор делает парсер, вообще не имеет смысла.
Как уже говорилось в у автора не Document а Page, для которой тип PageBreak тоже не имеет смысла.
В общем похоже ещё один фанат Rust, готовый переписать весь мир на новом языке.
Советую обратиться к первоисточникам - перечитать Маркса, т.е. Бруста «Мифический человекомесяц» и его главное утверждение «серебряной пули не существует»
:)
Спасибо за замечание. Долго думал над архитектурой. Дело в том, что у меня много идей, куда и как применить мою библиотеку. В частности, одна из многих идей — это создание генератора отчетов, на входе которого данные и шаблон отчета. Я всегда иду по пути наименьшего сопротивления и решил принять за аксиому, что документ имеет все страницы одинакового формата. Просто я не видел отчетов, у которых каждая страница имеет разные отступы, ориентацию и т.д.
У меня, к сожалению, всего две руки и 24 часа в сутки. Я иду на жертвы с точки зрения универсальности, иду на упрощение. В целом, я с вами не спорю. Надо, возможно, для каждой страницы делать и отступы, и т. д. Надо подумать. Может, пока не поздно, дойти до такой универсальности, как вы сказали.
Вы оказались правы! Спасибо! Поигрался в песочнеце:
#[derive(Debug)]
pub enum Element<'a> {
Header { level: u8, text: &'a str },
Paragraph { elements: Vec<Element<'a>> },
List { ordered: bool, text: &'a crate::Element<'a> },
Text { text: &'a str, size: u8 },
}
fn main() {
let header = Element::Header { level: 1, text: "Hello, World!" };
let paragraph = Element::Paragraph { elements: vec![header] };
let list = Element::List { ordered: false, text: ¶graph };
println!("{:?}", list);
}
про fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> тут вообще все очевидно - излишний Rusult конечно
Буду все рефакторить, пока кода мало!
Вы оказались правы! Спасибо! Поигрался в песочнеце:
#[derive(Debug)]
pub enum Element<'a> {
Header { level: u8, text: &'a str },
Paragraph { elements: Vec<Element<'a>> },
List { ordered: bool, text: &'a crate::Element<'a> },
Text { text: &'a str, size: u8 },
}
fn main() {
let header = Element::Header { level: 1, text: "Hello, World!" };
let paragraph = Element::Paragraph { elements: vec![header] };
let list = Element::List { ordered: false, text: ¶graph };
println!("{:?}", list);
}
про fn new(element: &Box<dyn Element>) -> anyhow::Result<ListItemElement> тут вообще все очевидно - излишний Rusult конечно
Буду все рефакторить, пока кода мало!
Плюс
Vec<Box<dyn Element>>
не эффективная по перфомансу
Где-то у matklad в блоге было про что-то похожее. Запихивание всего в Box<dyn Element>
позволяет этому лежать в памяти выровнено по размеру Box
, а не Element
, что наоборот ускоряет обработку отдельных токенов и уменьшает количество дыр из-за выравнивания, экономя память. Даже не смотря на пару уровней индирекции это всё равно может быть быстрее голых Element
в векторе. Так что спешу поставить под сомнение это утверждение и посоветую автору прогонять подобные штуки через профайлер (flamegraph, hyperfine) до и после рефакторинга.
Похожим образом Zig оптимизирует свои Енамы, но уже на уровне компилятора.
Даже не смотря на пару уровней индирекции это всё равно может быть быстрее голых Element в векторе.
Индирекции в общем случае будут медленее, так как соседние элементы банально могут не обладать пространственной когерентностью и даже могут быть на разных страницах. А вы приводите зачем-то узкий случай оптимизации под конкретный алгоритм, "If your access pattern does not require blind iteration (which can be the case for flattened, index-based tree structures)", при том что в исходном коде буквально делается итерация по всему вектору: document.elements.iter()
Так что спешу поставить под сомнение(с) ваши советы запихивать все в Вox<dyn Element> , хотя использовать профайл дело вполне полезное.
Так что спешу поставить под сомнение(с) ваши советы запихивать все в Вox<dyn Element>
именно поэтому и сказал, что нужно использовать профайлер. Ну и в случае с matklad - он писал парсер для языка программирования, так что количество и разнообразие токенов там заметно обширнее в сравнениии с документами и манипуляции над токенами несколько иные.
Большое спасибо за Вашу мотивацию. Мне определённо нравится Ваш проект. У меня и самого вертелась схожая идея в голове.
Совершенно соглашусь. Задача по созданию такой библиотеки вполне интересна
Я такую задачу решил на С# в проекте Pullenti Unitext с конвертацией в Java, Javascript и Python. Пытался конвертировать на Rust, но это не получилось. Понимаю всю сложность этой задачи и желаю автору удачи!
Shiva — Open Source проект на Rust для парсинга и генерации документов любого типа