Привет, хабражитель! Хочу поделиться с тобой наболевшим опытом, надеюсь, будет полезен. Сегодня расскажу о том, как разрабатывали систему печати документов в корпоративной системе.
С чего все начиналось
А началось все с разработки ERP-платформы в одной торговой компании примерно 2 года назад. Был выбран Linux, стек С++/Qt, PostgreSql и фронт под web. На C++/Qt был реализован сервер приложений и там же, через прослойку JS интерпретатора писалась бизнес логика. Почему так — это отдельная история, здесь рассмотрим, как разрабатывалась система печати.
Первые пробы пера
HTML
Изначально, пока все документы печатались из 1С (используем связку 1С + своя «ERP» для управленческого учета в компании) все было хорошо, а в планах был переход на свою систему. Тут то и понадобилось прикручивать печать к системе.
Первая идея была верстать форму html, программно заполнять данные, пользователю html-ку в браузер и пусть оттуда печатает.
Сразу выяснились некоторые нюансы:
- Под все документы придется с 0 верстать шаблон
- Конечный пользователь не в состоянии подредактировать html
- Менеджеры вели некоторую аналитику в excel
- Проблемы с переносом строк при печати многостраничных файлов
В итоге был сделан только один шаблон для печати заявки в логистику (одностраничная форма) которым до сих пор успешно пользуются
XLSX
Вторая идея была работать с XLSX документами. Гугл быстро подсказал про библиотеку QtXlsxWriter. Были еще варианты, но в итоге остановились на QtXlsxWriter.
Что умела библиотека:
- Открывать/Создавать xlsx и считывать значение ячеек
- Изменять значение ячеек, сохранять файл
- Работа с форматом ячейки (в том числе обрамление)
- Высота/ширина строк/столбцов
- Объединение ячеек
- Группировка строк/столбцов
- Вставка изображений
Это позволило сразу скоммуниздить взять шаблоны документов в xlsx формате из других систем (Привет 1С), заполнить нужными данными и статикой отдать пользователю xlsx файл, с которым он уже может работать как его душе угодно.
Сразу же появились подводные камни, QtXlsxWriter «некачественно» обрабатывала загрузку документа из шаблона, терялись форматы ячеек (wrap/aling и т.д.). После часов копания «xlsx формата (если кто не знает, xlsx представляет собой zip архив с набором xml документов) выяснили что, в разных версиях boolean атрибуты в xml файлах сохраняются по-разному, либо
<t a="0" b="1" />
либо
<t a="true" b="false" />
а QtXlsxWriter местами парсил только 1/0, местами только true/false, а местами и то и то. Но ничего, руки есть, пофиксили.
Еще был неприятный момент, после формирования xlsx файла через QtXlsxWriter, если его открыть в MS Office, тот начинал ругаться.
В книге „test1.xlsx“ было обнаружено содержимое, которое не удалось прочитать. Попробовать восстановить содержимое книги? Если вы доверяете источнику …
При этом, после открытия, визуально, все данные были на месте. При этом многие рядовые пользователи (наши клиенты) могли пугаться такого сообщения при открытии наших прайс листов.
После многих часов втыкания в xml файлы MS Office и QtXlsxWriter и поиска того не знаю чего, что MS не нравилось, был придуман костыль. Если взять файл, сгенерированный QtXlsxWriter, и обработать его с помощью LibreOffice получается валидный xlsx файл, со всеми данными первоначального, но на него не ругается MS Office:
libreoffice --headless --invisible --quickstart --convert-to xlsx test.xlsx --outdir valid_xlsx
И жить стало хорошо, под нужные отчеты писался небольшой код по формированию xlsx документа, выгружался пользователям, они с ним работали, если надо печатали и MS Office их больше не пугал. Даже смогли реализовать выгрузку OLAP отчетов в xlsx с группировками и куртизанками.
Автоматизация
Компания росла, клиентов больше, документов еще больше ( заявки, реализации, накладные и т.д. ), печать стала отнимать очень много рабочего времени. При этом часть документов печаталась из 1С часть из нашей системы. Решили это дело как-то автоматизировать. До этого (лет 5-7 назад) был опыт печати через Windows OLE контейнеры (создавался контейнер с Excel, открывался файл, задавались настройки печати и отправлялся на печать), но с этим не очень хотелось связываться, да и платформа крутится на Linux и тащить сюда виндовый модуль не хотелось (хотя как крайний вариант рассматривали принт-сервер на винде).
Все в PDF
В Linux есть CUPS и с этим вроде как все хорошо, командой lpr можно легко отправить на печать pdf файл. Вот только pdf генерировать мы не умеем. Решение было найдено быстро.
libreoffice --convert-to pdf 1.xlsx --headless
Но не все так просто оказалось. Файлы конвертировались со 100% масштабом и ни как не подгонялись к размеру страниц (А4/А3, книжная/альбомная, отступы), точнее все подгонялось по стандартным параметрам (А4, книжная). Выяснилось, что если задать эти настрой через LibreOffice(руками открыть LibreOffice Calc), сохранить в xlsx и конвертировать через libreoffice —convert-to pdf, все работало почти отлично.
- Отступы и настройки страницы обрабатывались корректно.
- Если надо было подгонять по масштабу, то этот параметр игнорировался и конвертировало с масштабом 100%.
- Если стояли настройки подгонять по размеру/ числу страниц, все работало
По поводу пункта 2 отписался в поддержку LibreOffice, жду от них ответа.
Благо пункт 3 работает правильно, решили отталкиваться от него. Теперь надо научить QtXlsxWriter работать с настройками страницы. Расковыряв xml файлы в xlsx документах нашли места, отвечающие за это дело
xl/worksheets/sheet1.xml
<worksheet> <sheetPr filterMode="false"> <pageSetUpPr fitToPage="false"/> </sheetPr> ... ... <pageMargins left="0.7875" right="0.7875" top="1.05277777777778" bottom="1.05277777777778" header="0.7875" footer="0.7875"/> <pageSetup paperSize="9" scale="50" firstPageNumber="0" fitToWidth="1" fitToHeight="1" pageOrder="downThenOver" orientation="portrait" usePrinterDefaults="false" blackAndWhite="false" draft="false" cellComments="none" useFirstPageNumber="false" horizontalDpi="300" verticalDpi="300" copies="1"/> ... </worksheet>
Что здесь есть интересного:
pageMargins — думаю с этим все понятно
fitToPage — подгонять под размеры/кол-во страниц или использовать масштаб
fitToWidth — кол-во страниц по ширине
fitToHeight — кол-во страниц по высоте
scale — масштаб в %
paperSize — размер листа (9=А4)
orientation — книжная/альбомная
Добавили работу с этими параметрами в QtXlsxWriter. Осталось только сформировать xlsx документ с отступами в нужных местах, чтобы не печатались куски незавершенного контента на разных листах. С этим не совсем все просто оказалось.
Печать
Рассмотрим ситуацию когда печатаем маршрутный лист на листе А4 книжной ориентации, без отступов.
При этом необходимо чтоб по ширине документ вмещался в 1 страницу. Ставим настройки:
fitToPage=false
fitToWidth=1
fitToHeight=100
pageMargins — все по 0
При таких условиях fitToHeight должно быть заведомо больше числа предполагаемых страниц при печати.
Маршрутный лист представляет собой заголовок с указанием маршрута и список клиентов с доп. информацией, по которым будет производиться доставка.
Если оставить все как есть, велика вероятность что часть блока с информацией о клиенте, попадающие на конец листа будут разбиваться, часть будет в конце первого и часть в начале второго, а это неприемлемо для нас.
В итоги родился следующий подход (возможно костыль).
Нам изначально известен размер листа А4:
Ширина 21 см
Высота 29.7 см
И мы знаем, что наш контент будет подогнан под ширину листа, т.о. можно посчитать относительную степень сжатия контента:
scale = ширина листа / ширину контента
Тут нас ждал сюрприз, чтоб посчитать ширину контента, необходимо сложить ширину всех столбцов, это сделать не сложно
double QXlsx::Document::columnWidth(int column);
Вот только было совершенно непонятно, в каких единицах измерения получается результат. Возможно, правильное решение можно найти тут, но не смогли, в итоги эмпирическим путем было найдено магическое число 5.10238
1 см = 5.10238 е.и.ш.к. (единица измерения ширины колонки)
scale = А4_ширина * 5.10238 / sum(columnWidth)
далее посчитаем размер контента, который мы способны вмести по ширине листа
height=А4_высота * 28.3464567 / scale
Появилось еще одно магическое число, как Вы уже догадались, это для перевода высоты строки в из см. в е.и.в.с (единица измерения высоты строки, на просторах интернета нашел такую информацию „r.Height = ht * 28.3464567 // Convert CM to postscript points“ )
Высоту строки можно найти через:
double QXlsx::Document::rowHeight(int column);
Используя параметр height, мы забиваем контент в xlsx файл пока высота контента <=height. Если при добавлении нового блока Б, мы выходим за границы height, то перед Б вставляем пустую строчку необходимой высоты, чтобы блок Б печатался с новой строки. Высоту пустой строчки можно посчитать зная высоту контента( sum (rowHeight) ) вставленного до блока Б.
Не рассматриваю тут расчет разбивки по страницам с использованием отступов (pageMargins), скажу лишь что в xml данных хранятся значения этих переменных в дюймах (1 дюйм = 2.54 см ).
Таким образом, получается xlsx файл с готовыми настройками и разбивкой по строка для печати. Далее с помощью libreoffice —convert-to pdf конвертируем в pdf и наш документ готов к печати.
Осталось напечатать:
lpr -pFS-4300DN test.pdf
Сейчас делаем автоматизацию печати на МФУ с финишером (степлирование). Уже немного поиграли с тестовым аппаратом и под Linux, оказалось все просто для степлирования.
Печать со степлированием c одной скрепкой в левом верхнем углу:
lpr -P printer_name -o StapleLocation="UpperLeft" order.pdf
Конец
На этом все. Буду рад узнать другие подходы к реализации этой задачи.
Спасибо за внимание )
ссылка на оригинал статьи https://habrahabr.ru/post/325586/
Добавить комментарий