Идея
Нам необходимо давать пользователям возможность быстрого (беглого) просмотра связанных с клиентом документов, которые хранятся в PDF. Сами по себе они являются отсканированными реальными договорами, заявлениями, доп. соглашениями и т.п. Просмотр должен осуществляться на странице "Карточки клиента" или в плавающем фрейме. Так же первое ограничение — использование в качестве СУБД — PostgreSQL.
Это ограничение оговаривается в силу того, что в последующем необходимо сделать переносимый код, который не будет зависеть от используемой СУБД.
Итак, наш компонент должен:
Обрабатывать загруженные файлы формата pdf (потом возможно и другие).
Сохранять их во внутреннем формате, который подойдёт для быстрого отображения клиенту. Сохранять с порядком следования страниц, потому что изначально формат PDF применялся как контейнер для отсканированных TIFF-файлов.
Отображать запрошенные документы на странице, либо в плавающем фрейме.
Должна быть встроена навигация по документу и возможность вывода на принтер.
Внешний вид
Проектирование было в лучших олд-скульных традициях — почти на салфетке (на листе черновой бумаги ;) ).

Рис 1. Черновой скетч (олд-скул).

Рис 2. После этого «в руки был взят» Фотошоп и получился такой прототип, к которому и будем стремится.
Может быть кнопки маловаты, но это решит время и мнение пользователей. (А к тому же ещё масштаб изображений маловат)
Внутреннее устройство
Изначально ставится задача использовать PostgreSQL для хранения этих документов. Хранение файлов на ФС я отмёл сразу, потому что при создании бэкапов очень желательно не «размазывать» данные по разным местам, а делать всё сразу одной командой.
База данных
Структура базы данных должна удовлетворять следующим условиям:
хранить бинарные данные с использованием OID (в случае с PostgreSQL), или в полях типа BLOB (в случае с MySQL);
Хранение в полях типа Bytea (для PostgreSQL) создает дополнительные накладные расходы, а также отличается в 3-4 раза меньшим быстродействием по сравнению с использованием OID.
необходимо хранить оригинал загруженного файла и его преобразованные страницы;
для страниц нужна нумерация.
Для PostgreSQL нам нужна такая структура:
CREATE TABLE documents ( id integer NOT NULL, filename text NOT NULL, filetype character varying(100) NOT NULL, additional text, creation_date timestamp without time zone, last_change timestamp without time zone, ); COMMENT ON TABLE documents IS 'Таблица различных электронных документов (договоры, факсы и т.п.)'; CREATE SEQUENCE documents_id_seq INCREMENT BY 1 NO MAXVALUE NO MINVALUE CACHE 1; ALTER SEQUENCE documents_id_seq OWNED BY documents.id; CREATE TABLE documents_blobs ( document_id integer NOT NULL, oid oid ); COMMENT ON TABLE documents_blobs IS 'Оригиналы документов'; CREATE TABLE documents_pages ( document_id integer, page_number integer, page_oid oid ); COMMENT ON TABLE documents_pages IS 'Страницы документов';
Изменение формата и разбивка на отдельные страницы
После загрузки на сервер PDF-документ должен быть подвергнут обработке, разбивке на странице и конвертации в иной формат (например, JPEG или PNG).
Полученные изображения должны быть отсортированы по порядку следования страниц и загружены в соответствующую таблицу. А исходный файл должен быть загружен в таблицу оригиналов.
Важно
Всё это сделать во время одной транзакции, которую можно откатить, если добавление в БД пошло не так, как планировалось.
Для преобразования форматов я принял решение использовать проверенные никсовые утилиты: ghostscript, convert, библиотеку imagemagick.
В качестве конечно формата, после теста, мной был выбран PNG, как более качественный, занимающий не намного больше места, чем JPEG, являющий открытой заменой GIF'у, да и просто мне PNG больше импонирует.
Программный интерфейс
Самое основное в этом компоненте - это, конечно же, отображение документов.
Для этого необходимо рендерить саму форму виджета (которую я так бережно рисовал «от руки», а потом в Фотошопе), а также изображения внутри неё.
Нам необходимо реализовать два метода:
public function render() { //... } public function renderImage($documentId, $pageNumber) { //... }
Первый будет отображать виджет с элементами управления и окном отображения документами. А второй будет вызываться первым, забирать данные из БД и передавать их клиенту.
Вроде бы всё
Программная реализация, а также скриншоты черновых (но уже рабочих!) вариантов я опубликую в следующем посте.

