Пишем расширение для Inkscape на любом языке программирования
Запись от diadiavova размещена 01.06.2020 в 09:00
Начнем с того, что Inkscape – это редактор векторной графики с открытым исходным кодом. Я сам не занимаюсь графикой и мои потребности в этом направлении минимальны, просто иногда бывает надо что-то нарисовать, да еще чтобы этим рисунком можно было управлять. Естественно последнее обстоятельство (которое «управлять») заставляет делать выбор именно в пользу векторной графики, ну, а Inkscape, помимо того, что это редактор векторный и бесплатный, еще и ориентирован на SVG. То есть SVG является его основным форматом. Данный формат поддерживается современными браузерами, графика в этом формате может быть анимированной, интерактивной, управляемой скриптами и при этом имеет огромное количество возможностей. Редактор же при этом является полноценным профессиональным инструментом, позволяющим создавать векторную графику, ну наверно любой сложности. Тем не менее, несмотря на мощный инструментарий программы, зачастую возникают задачи, требующие сделать что-то очень специфичное. Например, это может быть создание каких-то фигур, требующих математических расчетов, какого-то точного взаимного позиционирования элементов и тому подобных вещей. Естественно, для таких задач хотелось бы иметь возможность добавлять в программу собственный функционал. И тут надо сказать, что программа имеет достаточно мощную систему расширений, которая подразумевает возможность использования нескольких языков, но как быть, когда среди этих «нескольких» нет того, который нужен? Хотелось бы, наверно, иметь возможность писать расширения на том языке, который знаешь лучше или который подходит больше для решения конкретной задачи. Список поддерживаемых языков обычно бывает ограниченным, и вот вопросом о том, как в данном конкретном случае расширить этот список мы здесь и будем заниматься. Система расширений Inkscape Система расширений Inkscape описана здесь и далее по ссылкам. Вкратце все выглядит примерно следующим образом. Существуют внутренние и внешние расширения. Внутренние встраиваются в программу непосредственно и могут манипулировать объектами самой программы. К сожалению, пишутся они только на языке C++ и поэтому в контексте нашей темы они не интересны, хотя подозреваю, возможностей они имеют больше. Внешние расширения могут писаться на языках: Python, Ruby, Perl, Bash и XSLT. Они бывают четырех типов: input, output, print и effect. Первые два типа – это импорт и экспорт соответственно, пока они нам не нужны, третий тип позволяет выводить данные на принтеры и прочие внешние устройства (тоже можно сказать разновидность экспорта). А вот effect-расширения – это как раз то, что нам и нужно. Этот тип расширений позволяет выполнять манипуляции над объектами прямо во время работы в программе, то есть создание, изменение объектов и тому подобные вещи. Вот этим мы сейчас и будем заниматься. Здесь следует сказать, что среди перечисленных языков в нашем случае два отпадают сразу, а именно Bash и XSLT. Первый из-за того, что работает в Linux, а нам надо что-то более кроссплатформенное, а второй – может использоваться в input и output расширениях, а для effect расширений не поддерживается (это мы исправим). Из оставшихся трех языков лично я имел дело только с Python, но мое знакомство с ним не очень близкое. То есть написать что-то небольшое и несложное на нем я могу, а вот что-то посложнее и побольше – уже нежелательно. Но, как мы увидим дальше, этого мне вполне хватит для того, чтобы реализовать задумку. Как работают внешние расширения Именно механизм работы внешних расширений и натолкнул меня на мысль, так сказать, расширить этот самый механизм. Общая схема создания расширения следующая:
Теперь собственно о том, что происходит, когда параметры назначены и все эти данные отправляются на выполнение. Я уже упоминал выше, что для Inkscape SVG является «родным» форматом. Так вот перед запуском скрипта программа сохраняет текущее состояние документа в расширенном SVG формате во временный файл, а запуская скрипт она передает ему через аргументы командной строки следующие данные: идентификаторы выделенных объектов; параметры, заданные пользователем в диалоге, о котором выше писал; и последним аргументом – ни что иное как адрес того самого временного файла, в который программа сохранила текущий документ полностью. Задача расширения сводится к тому, чтобы прочитать этот самый документ, внести изменения в соответствии с полученными аргументами командной строки и вернуть программе документ со всеми изменениями через стандартный output (stdout) приложения. Это все. И вот собственно из этого и возникла идея: «А что, если скрипт на Python будет просто получать данные, переадресовывать их другому приложению и пересылать полученный от него ответ программе?». Вот это ровно то, чем мы сейчас и займемся. Создаем промежуточный скрипт на Python В принципе задача, которую будет выполнять этот скрипт решается в несколько строк, так что можно было бы эти строки писать всякий раз при создании расширения, но все-таки упростим немного задачу и создадим отдельный модуль, чтобы в дальнейшем можно было просто импортировать этот модуль, вызвать функцию, передать ей адрес реального приложения и дальше работать уже с этим приложением.
Далее процесс создания расширения будет сводиться к тому, чтобы создать описание расширения .inx, как это описано в документации, указать в нем в качестве скрипта файл с примерно таким содержимым
Теперь, когда все предварительные приготовления закончены, можно перейти к созданию расширения для теста. XSLT движок на C# Итак, создадим собственное расширение. Выше я уже упоминал, что effect расширения не поддерживают XSLT, а между тем это достаточно удобный язык для преобразований XML документов, а SVG, я напомню, является XML. Таким образом я подумал, что можно написать на C# программу, которая будет выполнять XSLT преобразование и в результате мы и протестируем, что у нас получилось и добавим поддержку XSLT, тем более, что сделать это на C# совсем несложно. Для начала создадим файл описания расширения. Чтобы долго не мудрить, я взял за основу код из документации, и внес в него несколько изменений. Так что id расширения не стал менять, но вполне понятно, что идентификатор должен быть уникальным для каждого расширения. Вот что у меня получилось
Приложение на C# Создаем консольное приложение. Задача приложения состоит в том, чтобы прочитать аргументы командной строки, найти файл преобразования, файл SVG вычислить аргументы, которые нужно передать в преобразование, выполнить преобразование и отправить результат в выходной поток приложения. Кроме того, было бы совсем не лишним добавить в XSLT несколько дополнительных функций, которые позволили бы решать некоторые специфические задачи. Например: мы передадим преобразованию в качестве параметров идентификаторы выделенных элементов. Это будет один параметр, нам надо будет идентификаторы как-то разделить и неплохо было бы иметь функцию, которая сможет определить, присутствует ли конкретный идентификатор в этом списке. Другой пример – работа с CSS. В XSLT нет таких функций, так что тоже было бы неплохо что-то такое иметь. Для нашей задачи этого достаточно, но вообще, было бы неплохо иметь функции для работы с матрицами, путями, тригонометрией и т.п. Все это тоже можно и желательно добавить. Собственно, класс, реализующий описанный выше набор выглядит так
CssGet получает значение атрибута style и имя CSS свойства и возвращает значение. CssSet получает текущее значение атрибута style, имя свойства, которое нужно изменить и новое значение, а возвращает строку нового значения всего атрибута style. Все это мы будем использовать в преобразовании. Теперь в Program.cs создаем код преобразования
Ну и метод Main
Об XSLT К XSLT есть пара требований. Поскольку данный формат выполняет преобразование всего документа, нам нужно позаботиться о том, чтобы документ в основном был скопирован в точности, а изменениям подверглись только те части, которые должны быть изменены. В XSLT должны быть объявлены все параметры, которые будут ему переданы. Кроме того, если мы хотим использовать функционал класса Helper, то надо в документе объявить пространство имен, с которым мы его связали. Также не следует забывать и о том, что Inkscape использует расширенный SVG и расширения связаны с определенными пространствами имен, так что, если мы хотим работать с этими расширениями, то их пространства имен тоже надо будет объявить в документе. Собственно, вот что у меня получилось
Несколько слов об Inkscape SVG Уже упоминал выше, что в Inkscape довольно специфический SVG. То есть SVG там вроде как нормальный, но при этом он имеет несколько дополнительных пространств имен, добавляющих нестандартные атрибуты и элементы в документ. В общем и целом, на отображение документа в браузере это никак не влияет, поскольку движки SVG все лишнее и непонятное для них просто игнорируют (в пределах разумного, разумеется), однако для нашей задачи некоторое понимание этих «приращений» может оказаться не только полезным, но и необходимым. Все, что я нашел по поводу этих расширений формата в документации – это вот эта страница. Там есть вначале ссылка на страницу, где, по всей видимости, раньше была еще какая-то информация, но сейчас такой страницы не существует, так что и говорить не о чем. Но и из этой страницы (той, которая пока еще есть) мы узнаем важные вещи. Например то, что если данные в этих дополнительных атрибутах будут расходиться с данными основных атрибутов SVG, то Inkscape отдаст предпочтение своим атрибутам, а атрибуты SVG будут приведены в соответствие с ними. Что такого важного содержится в этих атрибутах? Возьмем для примера нестандартные фигуры, то есть те, которых нет в SVG, но которые поддерживаются Inkscape. Например, многоугольники и звезды. Понятно, что эти объекты будут порождать элементы path, в которых будут задаваться просто координаты точек, по которым будут строиться фигуры. Но это в конечном итоге, а в процессе редактирования программе нужно как-то с этими объектами работать, то есть программа должна воспринимать их не как просто пути, а как звезду, у которой есть координаты центра, большой и малый радиусы, количество лучей и тому подобные вещи. Просто для того, чтобы этим можно было как-то управлять. Когда в интерфейсе программы меняются какие-то из этих характеристик, параметры пути пересчитываются. Таким образом, если мы в своем расширении хотим звезде добавить лучей, то ну нужно вычислять где будут находиться точки в новой звезде, а вместо этого надо изменить значение соответствующего атрибута. Поэтому тут возникает вопрос о том, где брать информацию о назначении атрибутов. Назначение некоторых можно понять по названию, другие надо изучать. В программе есть редактор XML, с помощью которого можно кое-что понять. Если выделен тот или иной объект, можно подвигать различные манипуляторы и посмотреть, как при этом меняются атрибуты. Кое что все равно остается непонятным, например у тех же звезд есть пара манипуляторов, которые соответствуют атрибутам sodipodi:arg1 и sodipodi:arg2 , что они делают визуально можно наблюдать, но смысл их значений лично я не уловил, так что воспользоваться этим будет сложно. Но в общем и целом разобраться в основных моментах, как мне кажется, большого труда не составит.>> |
Всего комментариев 0
Комментарии