August 30, 2013

CSimplePdf (создание простых .pdf для C++ Builder)

Написал класс для создания простых pdf файлов в Borland C++ Builder.

Это второй случай в моей практике, когда для решения задачи пришлось задействовать всю мощь ООП: инкапсуляцию, наследование и полиморфизм по-настоящему, а не ради понтов :) Больше всего времени потратил на вывод русского текста, для этого пришлось учиться аттачить в пдф-файл русские шрифты, для чего пришлось учиться парсить .ttf файлы.

В этом мне очень помог сайт http://www.fpdf.org! Парсилку .ttf я почти целиком взял оттуда готовую, пришлось только перевести пхп в c++, а потом долго выгребать блох связанных с отличиями в работе низкоуровневых функций.

Для сжатия FlateDecode использую miniz: http://code.google.com/p/miniz/

Сейчас класс умеет рисовать линии и прямоугольники разной толщины, аттачить разные шрифты, и выводить ними текст. При необходимости класс легко расширяется под специфические задачи :)

Вкратце, обрисую важные моменты для себя будущего.
1. Выводить кириллицу можно только с помощью TrueType Embedded, Type1 не годятся.
2. Чтобы приаттачить шрифт к пдф, помимо основных его параметров надо обязательно прочитать из .ttf файла ширину каждого символа (таблица hmtx) и привязку символов к юникоду (таблица cmap). Без этого русских символов мы не увидим!
3. Так как мы будем работать в кодировке cp1251, надо создать таблицу различий между ней и основной кодировкой cp1252. Для этого нам понадобится сравнить карты этих кодировок, их можно взять с сайта fpdf. Полученная таблица инвариантна присоединяемому шрифту, т.е. неизменна для любого шрифта, и зависит только от выбранной пары кодировок! По сути, её можно было бы сгенерировать раз и навсегда, и использовать как магическую строку.
4. Для одновременного вывода латиницы и кириллицы достаточно использовать ANSI строки (т.е. однобайтовые), а таблицу /ToUnicode можно не заполнять! (не понимаю, почему большинство конвертеров её все равно заполняют, это сильно сбило меня с толку во время работы).
5. Таблицу ширин обязательно пересортировать в соответствии с картой соответствий cp1251 к cp1252. Иначе буквы будут наползать друг на друга.
6. Итого на каждый шрифт нам понадобится 4 внутренних pdf объекта: шрифт (в нём таблица ширин, ссылка на кодировку и дескриптор), дескриптор (в нём несколько не суть важных параметров и ссылка на приаттаченный файл), кодировка (в ней таблица различий, см. п.3), и сам файл, сжатый /FlateDecode, т.е. zip-ом.
7. Повторяю, ибо это важно! Если нам самим Юникод не нужен, и мы собирается работать с однобайтовыми строками, таблица /ToUnicode (begincodespacerange...endcodespacerange) нам не нужна! На большинстве форумов посвященных этому вопросу - ересь!
8. При работе с картами кодировок надо учитывать, что длина этих таблиц разная, и обе чуть короче 256 (ненамного короче, обе почти 256. Мне это сильно путало карты, пока не догадался проверить).

Ссылка на zip-архив: https://sites.google.com/site/pavelkrents/progs/csimplepdf.zip

No comments: