Перезагрузка проекта sK1

В проекте sK1, который, казалось бы, подозрительно притих, на самом деле происходят масштабные изменения. Игорь Новиков поделился подробностями, ему и слово.

Эта статья служит своего рода обобщением серии докладов, сделанных на конференциях FOSS Lviv 2011 (Львов, 1-6 февраля), FOSS Sea 2011 (Одесса, 15-16 сентября) и OSDN 2011 (Киев, 1 октября). В окончательном виде доклад запланирован на конференцию Libre Graphics Meeting 2012 (Вена, начало мая).

Год назад в развитии проекта было принято кардинальное решение выполнить полный рефакторинг исходного кода — как редактора sK1, так и универсального транслятора графических форматов UniConvertor.

Причины «перезагрузки»

Речь идет не о тривиальном «перелопачивании» исходного кода, а о полном переписывании проекта. Такое решение не было случайным. Как известно, проект является форком редактора Sketch/Skencil. Соответственно, части исходного кода, а также архитектуре проекта уже много лет. Несмотря на интересные подходы, заложенные в проект в конце 90-х, многие решения в нем морально устарели и не соответствуют текущим потребностям и целям. Ввиду особенностей проекта переработка его по частям могла бы занять гораздо больше времени, чем разработка с нуля.

Это решение далось нелегко, поскольку крайне тяжело выбрасывать труд многих лет. Тем не менее, другого пути не было, так как поддержка базы кода занимала крайне много времени, в то время как большая часть ресурсов начала уходить на решение различных проблем, связанных с библиотекой графических элементов интерфейса.

Редактор sK1 использовал достаточно старый виджетсет Tk, который сильно затормозился в своем развитии и потерял актуальность. Еще 2-3 года назад была надежда на принципиальные изменения в версиях Tcl/Tk 8.5-8.6. Увы, попытка реанимации тулкита не удалась. Поэтому назрела необходимость портирования редактора на современный тулкит.

Выбор пал между GTK+ и Qt. Параллельно возникла масса вопросов по архитектуре приложения. Поддерживать два независимых проекта, sK1 и UniConvertor, основанных примерно на одной и той же кодовой базе, достаточно затратно и чревато самыми разными сложностями. Также накопилась масса претензий по внутреннему устройству приложений и архитектуре модели.

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

Всё сначала — новая архитектура

Архитектура редактора sK1/Skencil формально декларировалась как MVC-паттерн, т.е. Model-View-Controller:

MVC

В таком шаблоне проектирования View — это UI приложения, Controller — та часть кода, которая реагирует на действия пользователя, а Model — древовидная структура объектов документа, хранящая всю информацию об изображении на холсте.

В реальном же коде ситуация обстояла гораздо хуже. Масса хаков привела к тому, что все три компонента были перепутаны. Например, в модели можно было найти фрагменты UI, а объекты модели были завязаны на структуры Xlib, ну и т.д. Редактор, по сути, проектировался как простое однодокументное приложение, так что загрузка нового документа сопровождалась утечкой памяти.

При проектировании новой архитектуры выбор пал на шаблон проектирования MVP, т.е. Model-View-Presenter:

MVP

В нём модель документа изолирована от пользовательского интерфейса. Это решало сразу несколько проблем:

  • такой подход позволял создать гибридное приложение, ядром которого стал бы UniConvertor (Model + Presenter), а векторный редактор выполнял бы роль только фронтенда (на самом деле, Presenter редактора — это расширенный вариант аналогичного класса UniConvertor);
  • тщательное проектирование взаимодействия компонентов помогло избежать утечки памяти;
  • на основе UniConvertor как ядра приложения можно было бы создавать производные приложения — прежде всего, приложение для разработки фильтров импорта и экспорта графических форматов;
  • переработка модели позволила резко сократить потребляемую память.

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

Формат хранения данных — чем проще, тем лучше

Формат sk1 был унаследован от формата sk (Sketch/Skencil) и представлял собой расширение последнего (поддержка CMYK, многостраничность, Unicode в тексте и т.п.), оставаясь при этом простым текстовым файлом, оптимизированным для чтения интерпретатором Python.

Но формат sK1 не решал несколько серьезных проблем:

  • встраивание сторонних файлов, таких как шрифты, цветовые профили, палитры и т.п.;
  • простое для пользователя извлечение встроенных изображений;
  • наглядная, удобочитаемая структура документа.

Чтобы комплексно разобраться с этими проблемами, было решено создать свой новый формат хранения данных на основе спецификации OpenDocument. Формат получил название PDXF (PrintDesign XML Format). По сути, файл PDXF представляет собой архив ZIP, внутри которого по соответствующим папкам разложены внедренные файлы, а в корне находится content.xml, описывающий структуру документа. Всё очень похоже на документы OpenRaster или на файлы OpenOffice. Но в отличии от OpenOffice, XML-файл формата PDXF специально форматируется програмой для удобного восприятия структуры документа человеком и в своей основе он не содержит дополнительных файлов CSS и т.п., являясь well-formed документом XML. Каждый объект полностью описан внутри своего тега, поскольку такой подход повышает устойчивость и позволяет легко манипулировать вручную содержимым документа.

Выбор виджетсета

Как уже упоминалось ранее, перед нами стояла задача определиться, какой из ведущих виджетсетов начать использовать — GTK+ или Qt. Чтобы решить эту задачу, мы поступили крайне просто: написали первичные функционирующие макеты на обоих тулкитах. Анализ дал следующее:

  • Qt — более качественная библиотека с большими возможностями в сравнении с GTK+
  • Рендерер QPainter в несколько раз быстрее Cairo и позволяет использовать OpenGL;
  • вместе с тем, GTK+ — более лёгкий тулкит, потребляющий в два раза меньше памяти в сравнении с Qt;
  • Cairo не встроен в GTK+ так, как QPainter в Qt (QPainter является частью модуля QtGui и зависит от QtCore), и это позволяет делать серверную сборку без привязки к Xlib/WinAPI; это самый важный момент, т.к. рендерер должен использоваться в UniConvertor как базовая часть для конвертации векторной графики в растровые изображения.
  • GTK+ является на 100% свободной библиотекой, тогда как права на Qt перепродаются от одной компании к другой, а печальный пример судьбы Java в руках Oracle мы имели сомнительное счастье наблюдать буквально несколько месяцев назад.

Исходя из перечисленного выше, выбор пал на GTK+. При этом архитектура UniConvertor создавалась таким образом, чтобы осталась возможность создания варианта векторного редактора на Qt.

PrintDesign

Итак, за основу будущего редактора был взят GTK+. Первоначально приложение планировалось как продолжение sK1. Но поскольку весь код был переписан с нуля, мы решили заодно переименовать проект в PrintDesign — более понятное и само за себя говорящее название.

PrintDesign

Был изменен и логотип приложения. Значок PrintDesign по-прежнему символизирует четыре базовых цвета CMYK в четырех тубах с краской, которые размещены по периметру квадрата. Такая форма не случайна — она отлично воспроизводится во всех разрешениях начиная с 10х10px, в том числе, прозрачная часть посередине логотипа.

PrintDesign icon PrintDesign icon PrintDesign icon PrintDesign icon

Поскольку GTK+ не предоставляет готового решения MDI, интерфейс пользователя для управления документами был разработан на вкладках (a-la Firefox). В отличие от sK1, в приложении PrintDesign каждый загруженный документ имеет собственный холст (canvas) и свою внутреннюю очередь событий (message loop). Это позволило надёжно изолировать каждый экземпляр документа и избежать утечки памяти на ранней стадии проектирования приложения.

PrintDesign

Первые же тесты показали эффективность заложенной архитектуры редактора:

  • быстрый, практически моментальный старт приложения;
  • устойчивая работа в многодокументном режиме;
  • надёжная очистка памяти при закрытии документа;
  • быстрая отрисовка сложных изображений за счет оптимизации рендеринга.

Последний пункт имеет смысл прокомментировать подробнее. Проект sK1 использует библиотеку Cairo достаточно давно: еще в 2007 году на первой публичной презентации проекта был продемонстрирован рендеринг редактора на базе Cairo. С тех пор было потрачено много усилий на оптимизацию отрисовки, чтобы ускорить этот процесс, т.к. для редактора это жизненно важная опция. Но как бы там ни было, использование Cairo выглядело «нашлепкой» поверх уже существовавшей реализации, которая была основана на Xlib (кстати, именно этот этап сейчас проходит проект Inkscape).

В разработке PrintDesign вопросу интеграции с Cairo было уделено достаточно пристальное внимание и, как нам кажется, удалось добиться наиболее эффективной схемы оптимизации отрисовки, без серьёзных перекосов в сторону потребления большего количества памяти.

Новый маркер выделения

Хоть PrintDesign пока в самом начале разработки, тем не менее, уже готов базовый функционал холста (выделение, трансформация объектов), разработаны новые маркеры для объектов взамен XOR из sK1, а также внедрены множественные активные поля для ручной трансформации. Всё это — универсальная функциональность, применимая ко всем объектам. На следующем этапе развития будет выполнена реализация базовых примитивов: эллипс, многоугольник, кривая, растровое изображение, текстовые объекты.

UniConvertor 2.0

Поддержка большого числа векторных форматов — самая сильная сторона проекта sK1. Но архитектура, унаследованная от проекта Skencil, была несовершенна и накладывала массу ограничений на развитие проекта. Импорт форматов был основан на потоковом чтении данных и преобразовании этих данных в объекты модели «на лету». Такой подход оправдан для несложных форматов вроде PLT, в которых все данные про объект записаны «по месту». В сложных форматах, таких как CDR или SVG, свойства объекта могут определятся совершенно в другом месте, отличным от его расположения в иерархии объектов. На практике это приводит к тому, что код фильтров превращается в жуткое процедурное «спагетти», внутри которого перепутаны разбор данных, их анализ и создание объектов модели sK1/Skencil.

Для исправления ситуации за основу новой архитектуры импорта/экспорта был взят стандартный подход построения форматоспецифических моделей для каждого из поддерживаемых форматов. Это значит, что при импорте файла стороннего формата сначала создаётся модель этого формата, которая может загружать/сохранять содержимое файла в исходном неизменённом виде. На второй стадии импорта специализированный транслятор производит «перевод» данных модели этого формата в объекты модели PrintDesign, создавая новый документ PrintDesign. Наконец, на третьем (необязательном) этапе выполняется слияние полученного документа и документа, в который выполняется импорт данных. Таким образом решается вопрос разделения кода по функциональности, сам код становится не процедурным, а объектно-ориентированным, легко читаемым, с чёткой внутренней структурой.

Этот же подход позволяет решить вопрос «вложенности» форматов. В сложных форматах внутри одного файла порой попадаются внедрённые другие файлы. Например, в файлах CDR наблюдаются вставки формата CMX, шрифтов TTF и т.п. В процедурном варианте разбора это все сильно затрудняет работу, приводя к дублированию кода и избыточной сложности в реализации. В случае с объектно-ориентированной архитектурой построения модели внедрение в базовую модель моделей других типов не представляет принципиальных проблем.

Другой немаловажный момент — сам процесс разработки фильтров импорта и экспорта. Выполнять реализацию по существующим спецификациям — достаточно тяжёлая и монотонная работа, а в случае с закрытыми форматами — и вовсе невозможная. В процессе реверс-инжиниринга форматов Corel DRAW мы написали вспомогательное приложение CDR Explorer, которое визуализировало иерархию объектов. Это сильно облегчило разработку импортера файлов формата CDR.

Однако этот подход страдает серьёзным недостатком: код приходится дважды переписывать. С введением объектно-ориентированных промежуточных моделей появилась возможность создания приложения Format Explorer для визуализации и анализа структуры импортируемых файлов, которое использовало бы код UniConvertor напрямую. Это сразу же облегчает работу над фильтрами импорта, а само приложение служит своего рода «строительными лесами» для фильтров: созданный код сразу же поступает в UniConvertor и автоматически становится доступным как для консольной трансляции, так и для векторного редактора PrintDesign.

Format Explorer разрабатывается исключительно для внутренних нужд проекта. Его код доступен на Google Code. А поскольку в данном случае у нас отсутствуют ограничения в плане выбора виджетсета, поддержки кодовой базы и т.п., интерфейс Format Explorer написан на Qt, т.к. для подобных технических приложений Qt гораздо удобнее из-за богатства предоставляемого функционала.

Репозитории

Ожидать анонсы релизов в ближайшие недели вряд ли стоит. Но раз проекты открытые, имеет смысл дать ссылки на репозитории:

UniConvertor 2.0: http://uniconvertor.googlecode.com/svn/trunk/
PrintDesign 1.0: http://print-design.googlecode.com/svn/trunk/
FormatExplorer: http://formats-explorer.googlecode.com/svn/trunk/

Как можно видеть из ссылок, инфраструктура проектов перекочевала с SourceForge на Google Code. Причины миграции тривиальны: сервисы Google Code работают гораздо быстрее и надёжнее.

Для тех, кто желает попробовать PrintDesign, напоминаем, что он зависит от UniConvertor 2.0.

Заключение

Начатая в проекте перестройка масштабна. Не стоит ожидать быстрых результатов: даже для коммерческих компаний такие изменения оборачиваются годами разработки. В наших планах прежде всего подготовить релиз UniConvertor 2.0 с обновлённым функционалом. Прежде всего, это расширенные и улучшенные импортировщики форматов Corel DRAW с поддержкой X5 и возможностью записи в формат CDR. Мы надеемся выпустить UniConvertor 2.0 в ближайшие месяцы, а на конференции Libre Graphics Meetings 2012 представить уже работающий вариант PrintDesign 1.0.

4 Comments

  1. Вы Новикову передайте, что он успешно творит очередного коня в вакууме. У него похоже скрипт инсталляции uniconvertor косячит. Обновление из svn-репа нормально происходит. Но при установке он чего-то не подхватывает и после запуска prime-print выдает следующее.

    Traceback (most recent call last):
    File «/home/slawa/tmp/print-design-read-only/src/pdesign-launcher.py», line 21, in
    import pdesign
    File «/home/slawa/tmp/print-design-read-only/src/pdesign/__init__.py», line 20, in
    from pdesign.app_conf import get_app_config
    File «/home/slawa/tmp/print-design-read-only/src/pdesign/app_conf.py», line 24, in
    from uc2.formats.pdxf.const import DOC_STRUCTURE
    File «/usr/local/lib/python2.7/site-packages/uc2/formats/__init__.py», line 25, in
    from uc2.formats import data
    File «/usr/local/lib/python2.7/site-packages/uc2/formats/data.py», line 40, in
    from uc2.formats.wmf import wmf_loader, wmf_saver, check_wmf
    ImportError: No module named wmf

    Я который день пытаюсь достучаться до него через google-code все безуспешно.

  2. setup.py не косячит :) Его просто не дописали на тот момент. Он делался ровно настолько, насколько нужно было для разработки — т.е. для сборки нативных экстеншинов. Во время разработки приложения запускаются напрямую из Эклипса, а в проекте прописываются все зависимости (файлы эклипсовских проектов есть в репозиториях).

    Тем не менее, «по многочисленным просьбам трудящихся», инсталляция UC2 переписана полностью и на данный момент она готова для инсталляции через «setup.py install» и для сборки deb-пакетов через «setup.py dbist_deb» (последняя опция реализована внутри модуля libutils.py, который идет с сорцами). Дистроспецифические спеки для сборки rpm пока не созданы, но это скоро реализуем.

    Рекомендуемый вариант для тестинга ессно через установку пакетов, т.к. в установке идут фишки, связанные с настройкой MIME и т.п., поэтому использовать «setup.py install» не рекомендуется.

    Сборка PrintDesign должна быть готова сегодня. Правда, приложение и так можно запустить прямо из сорцов, поскольку все нативные экстеншины в UC2.

1 Trackback / Pingback

  1. Выпущена глубокая альфа PrintDesign для самых нетерпеливых

Оставить комментарий

Ваш электронный адрес не будет опубликован.


*