Красивая печать в PDF из Django

в 13:26, , рубрики: django, PDF, python, метки: , ,

Т.к. эта статья — результат нескольких лет экспериментов, то букв будет много. Но — возможно — она сэкономит кому-то многие месяцы езды велосипедом по граблям, которые и описаны.
В общем случае речь идет даже не о Django, а о печати регламентированных документов из python с использованием шаблонизаторов.
Кому дальше читать лень — сразу скажу — проблема до конца не решена. Но более-менее рабочий вариант вырисовался.

1. Задача

  • Пользователь вводит данные в веб-форму
  • Сервер вставляет эти данные в шаблон печатной формы
  • И отдает пользователю в виде, пригодном для печати

2. Ограничения

  • Формы бывают “мягкие” (где точность не очень важна — например Договор или Счет) и “жесткие” (точность — максимальная, под сканер — например уведомление мигранта или заявление на УСН (форма 26.2-1)).
  • При этом даже “мягкие” формы должны печататься максимально близко к задуманному создателем (если я сказал, что границы — 1см, то пользователь должен получить документ с границами ровно 1см) и — особенно — учитывать разрывы страниц (см. формы 11001, 21001 и т.д.).
  • Обязательно — минимальные телодвижения по преобразованию исходного материала (как правило — .xls или .doc, цельнотянутые из «Консультаната» или «Гаранта»).
  • Т.к. речь идет о веб-приложении — крайне желательны отзывчивость и надежность решения => крайне желательна работа с нативными библиотеками python.
  • Желательна возможность размещения всего этого хозяйства на арендованном хостинге (в идеале — GAE).
  • Желательна возможность визуального редактирования шаблонов.
  • Желателен быстрый предварительный просмотр шаблона (а еще лучше — и результата).

Первый этап — выбор конечного формата. После недолгих размышлений с различных т.з. (кросс-платформенность, гарантированность результата, конвертируемость в) выбор пал на PDF.
Теперь — входные форматы и как их преобразовать.

3. Мягкие формы

ODF

Речь идет об Open Document Format — ODS, ODT и иже.
Здесь всё очень просто:

  • Редактируем шаблон в LibreOffice (оставляя место для данных).
  • Каким-то образом заполняем поля в Django.
  • Каким-то образом получаем PDF

Место для данных: или же добавляем user-defined поля в документ — или же прямо в текст вставляем {{ теги_django }}. В первом случае заполнение этих полей потом из python — скорее всего возможно, но я даже не представляю как (точнее — всё представляемое выглядит крайне заморочливо). Поэтому просто расставляем теги в виде текста.
В этом случае заполнение полей — элементарно — просто скармливаем шаблон шаблонизатору Django (ковыряние внутри шаблона библиотеками питона оставим гентушникам :-). А дабы не раззиповывать/зазиповывать документы при каждом пинке — документы сохраняются в *.fodX (Flat X) — один-единственный неупакованный xml. Шаблонеру скармливается как xml же.
Получение PDF — без вариантов — с помощью LibreOffice: скармливаем демону LibreOffice (libreofficed (где-то у убунтоводов нашел)) или unoconv или ручной работы запускалку LO в режиме демона. Все эти варианты — примерно одно и то же.

Достоинства

  • Можно сразу использовать нарытые в Интернетах документы (как правило — из “Консультанта”, в форматах Microsoft Office).
  • С редактированием шаблонов — никаких проблем.
  • Как и с предварительным просмотром.
  • Возможно — получение PDF об Google Docs — пока не пробовал. Но в том, что это будет реактивно — сомневаюсь уже сейчас (а в том, что некорректно — не сомневаюсь; попробуйте загрузить в гугледокс ту же форму 21001 от Консультанта (лежит на сайте налоговой)).

Недостатки

  • Иногда при записи шаблонов LibreOffice спонтанно гробит теги, вставляя внутрь {{..}} всякие span lang=”en-GB” и иже. Приходится потом вручную возвращать всё назад.
  • Просто фантастическая ресурсоемкость для сервера — CPU 100% (только одного, сколько бы их не было), сотни метров RAM, получение PDF — до минуты или после (форма 21001 — 50 секунд на P4-3.0). Java же.
  • Тянет за собой немеряно пакетов (Fedora, CentOS).
  • Наличие хоть какого-то X-сервера (Xvfb например).
  • Наверное, на каком-то хостинге и позволят развернуть LibreOffice — но я сильно сомневаюсь в nic.ru например. О GAE речь даже не идет.
  • Предварительного просмотра результата — нет.

Резюме

Как крайний запасной вариант — подходит. Но именно как крайний.

HTML

Здесь с редактированием шаблонов (руками) и шаблонизатором (искаропки) всё понятно. Остался один маленький, но главный вопрос — как получить PDF? Быстро, качественно, с разрывами страниц там, где надо. И вот здесь было больше всего экспериментов.
Многочисленные эксперименты с pure python html render (типа PISA и предков/наследников/форков) привели к одному важному (IMHO) выводу: чтобы получить гарантированный результат — надо использовать готовый html движок. Коих, как мы все знаем, аж 4 (из нормальных). Из них использовать в linux можно аж 2 — gecko и webkit. Вызвать gecko из python, скорее всего, возможно — но а) для этого нужен запущенный X (как в случае LibreOffice) и б) [полу]готового рецепта я не нашел.
Остался webkit:

  • PyQt4>Qt>WebKit>QPrinter (типа такого). Нативно (хоть и тащит с собой очень много), шустро — но pagebreak не ловит. Кроме того — нужны специальные танцы с DPI и ZoomFactor.
  • GTK>WebKit>GTK printer (типа такого такого). Нативно, шустро — но тоже page break не ловит.
  • Использовать специально доработанный webkit — wkhtmltopdf — в виде внешнего бинарника (сейчас именно этот вариант и используется) или через нативный python binding (в процессе, но есть небольшие проблемы). Нативно (если binding), шустро, ловит page-break, результат гарантирован.

Достоинства

  • Теоретически возможно визуальное редактирование.
  • Мгновенный предварительный просмотр (в виде html же) — как шаблона — так и результата.
  • Реактивная конвертация в PDF.
  • Pure python API конвертирования в PDF (это которое “в процессе”).

Недостатки

  • Всё-таки качественный HTML — ручной работы.
  • Сложные формы (типа 21001) придется самостоятельно писать или рисовать — ибо в Интернетах это — страшный .xls.
  • Т.к. используется либа/бинарник, собранные для Linux — на том же nic.ru (FreeBSD) ничего не получится (без костылей). О GAE речь по-прежнему не идет.

Резюме

Основной вариант для “мягких” документов. Но все-таки нужно искать качественный pure python html render — без флешей, JS и других мультиков — но с качественной обработкой CSS.

Возможно

На будущее рассматриваются форматы TeX, LaTeX, Lyx, docbook — но пока преимуществ не видать (особенно для “почти мягких” форм — типа той же 21001).

4. Жесткие формы

Здесь всё намного печальнее. Особенно в свете того, что здесь уже визуальный редактор крайне желателен.
Кроме того — в подавляющем большинстве (если не во всех) “жестких” форм РФ используются “квадратики” — когда текст разбивается на буквы — и каждая вписывается в свой квадратик (пример).
Отбросим первые попавшиеся (типа “натянуть текст на tiff”) и сразу перейдем к финалистам.

RML

Разработка компании Reportlab (да-да, python-reportlab — это их) — обыкновенный XML, позволяющий выделывать чудеса с PDF. Т.к. всем известный python-trml2pdf уже RIP (как мне честно отписал его разработчик) — пришлось взять этот trml2pdf и немного допилить, т.к. он не поддерживает многие интересные фичи RML, а покупать (а тем более — ломать) коммерческий rml2pdf мне запрещает религия.

Достоинства

  • Нативно
  • Шустро
  • Гибко
  • С хостингом (теоретически) проблем быть не должно — даже в GAE (не пробовал).

Недостатки
  • Строго ручная работа
  • Очень заморочливый синтаксис — когда надо смешать точное позиционирование (“graphics”) с “мягким” текстом (“flowables”) (отсюда, видимо, отсутствие визуального редактора).
  • Никакого предварительного просмотра — ни шаблона, ни результата.

Резюме

Запасной вариант для точных форм (особенно простых).

PDF-формы

Здесь всё очень просто: исходник в PDF — и конечный результат в PDF.

  1. Берем исходную PDF-форму в левую руку
  2. XFDF (простенький xml), обработанный встроенным шаблонером Django — в правую
  3. сливаем их (populate) в новый PDF (“раскатанный” — flatten)
  4. и отдаем пользователю

Проблема всего лишь одна — п.3.
На сегодняшний день нативного и корректно работающего python API для работы с PDF-формами не обнаружено (хотя poppler кое-что уже умеет — но пилить там, судя по всему, еще много), поэтому единственный приемлемый вариант — iText. Через pdftk или свой велосипед — это уже по вкусу.

Достоинства

  • В PDF-форму можно превратить что угодно (как — отдельный вопрос).
  • Можно даже редактировать (аналогично).
  • Абсолютно гарантированный результат.
  • Встроенные в формат PDF “квадратики” (combo).
  • Скорее всего — нет проблем с хостингом (возможно — и с GAE) — не пробовал.

Недостатки

  • Вызов внешнего приложения вместо python API.
  • Java же.

Резюме

Основной вариант для точных печатных форм.

5. Общее резюме

Итого на сегодня сформировалось:

  • “Мягкие” формы — html|webkit — но через довольно тяжелую, избыточную и не слишком портабельную библиотеку webkittox (и продолжать искать).
  • “Жесткие” формы — PDF-формы, но через костыль внешнюю JAVA-библиотеку (и продолжать насиловать poppler).
  • ODF и RML — как запасные варианты соответственно.

PS. Как всё это работает — можно посмотреть здесь — без ODF и RML, но последние предусмотрены.

Автор: TIEugene

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js