Сообщений 3 Оценка 0 Оценить |
Как известно, для получения пользователем изображения в среде LaTeX предусмотрены команды, которые после компиляции текста пользователя выглядят в виде композиции примитивов, составляющих вместе готовый рисунок. О том, как создавать динамические рисунки, писал Francesc Sunol [1].
Проблема рисования в LaTeX, а также мотивация рисовать в нём, а не вставлять готовое изображение, хорошо описаны в диссертации Jie Xiao [2]. Поскольку процесс создания рисунков в редакторе LaTeX реализован не в концепции WYSIWYG («What You See Is what You Get» – «что Вы видите, то и получите»), а сводится к ручному набору команд вывода графики на языке TeX, пользователю приходится лишь представлять, как будет выглядеть готовый рисунок, а также приблизительно подбирать координаты опорных точек.
В данной работе описывается разработанное автором программное обеспечение PaintTeX, созданное для решения этой проблемы. Оно было разработано на языке C++ и WinAPI, с применением методов многопоточной обработки данных, что гарантирует высокую производительность работы программы.
ПРИМЕЧАНИЕ Для упрощения создания рисунков другими авторами также разрабатывались программные обеспечения Graphviz [2] для рисования графов, Drawlets [2] для рисования произвольных рисунков, а также FeynEdit [3] и JaxoDraw [4] для рисования диаграмм Фейнмана. |
Для вывода прямолинейного отрезка или вектора в тексте пользователя, помимо координаты опорной точки, приходится указывать ещё и угол наклона с помощью отношения ширины к высоте. В языке TeX команда вывода отрезка выглядит следующим образом:
\put(60,50){\line(1,-2){20}} |
где (60,50) – координаты начальной точки отрезка, (1,-2) – угол наклона в виде соотношения длины к высоте, 20 – длина проекции на ось ОХ. Значения в отношении, задающем наклон, не должны превосходить 6 по абсолютной величине у отрезков, и 4 у векторов, а также не должны иметь общих делителей, кроме 1. Подробности можно найти в книгах С. М. Львовского [5] и Д. Е. Кнута [6].
Созданное автором программное обеспечение PaintTeX предоставляет WYSIWYG-интерфейс для рисования изображения с помощью примитивов, а затем преобразует каждый примитив в соответствующую команду вывода графики на языке TeX. Например, чтобы нарисовать изображение, показанное на рисунке 1, нужно долгое время рассчитывать координаты опорных точек и другие параметры команд вывода графики для каждого примитива, или подгонять их приблизительно. Данный рисунок был нарисован в программе PaintTeX, код вывода следующий:
\begin{picture}(215,283) \qbezier(99,172)(105,172)(112,172) \qbezier(112,172)(108,174)(105,175) \qbezier(112,172)(108,171)(105,169) \qbezier(63,193)(82,170)(102,147) \qbezier(102,147)(100,151)(99,155) \qbezier(102,147)(98,149)(95,151) \qbezier(0,14)(111,14)(209,14) \qbezier(209,14)(205,16)(202,17) \qbezier(209,14)(205,13)(202,11) \qbezier(168,22)(89,129)(8,234) \qbezier(8,22)(93,22)(168,22) \qbezier(8,234)(8,128)(8,22) \qbezier(0,13)(0,145)(0,276) \qbezier(0,276)(-1,273)(-3,269) \qbezier(0,276)(1,273)(3,270) \put(64,192){\circle{38}} \put(101,160){V} \put(8,2){O} \put(10,283){Y} \put(215,0){Х} \end{picture} |
Рисунок 1 - Пример рисунка в LaTeX
Рассмотрим PaintTeX в действии. Пользователь выбирает желаемый примитив и рисует его, указывая координаты опорных точек, по которым программа рисует примитив и сохраняет их в памяти. При сохранении рисунка программа вставляет в файл текст команды вывода примитива с сохранёнными координатами опорных точек. Сложные примитивы выводятся в виде композиции более простых примитивов, например вектор – это 3 прямых отрезка, соединённых концами в одной точке, что образует стрелку, а прямоугольник – 4 прямых отрезка. Таким методом можно выводить огромное множество фигур, включая и трёхмерные.
Принцип работы разрабатываемой программы состоит в следующем. При запуске программы появляется окно с меню, панелью инструментов и областью для рисования. Пользователь выбирает примитив на панели инструментов, а затем курсором мыши задаёт координаты опорных точек. Координаты опорных точек сохраняются в экземпляре класса выбранной фигуры и по ним рисуется сам примитив. При выборе пункта меню «Сохранить», программа сохраняет команды вывода примитивов в файл результатов, вставляя необходимые параметры (координаты, радиус) из координат опорных точек.
Под каждый примитив в программе выделяется объект класса для данного примитива. В ходе текущей стадии разработки существует 7 классов: VETREX, LINE, LABEL, BIZE, SQVR, CIRKLE, FISH. Каждый из этих классов является дочерним от базового класса FIGURE. Содержание класса FIGURE следующее:
class FIGURE { public: POINT *pt; FIGURE *nextFig; virtualvoid print() = 0; }; |
Благодаря механизму наследования, каждый дочерний класс наследует от базового указатели типов POINT, FIGURE и виртуальную функцию print(). При создании примитива запускается функция инициализации, которая преобразовывает указатель *pt в массив точек необходимой размерности для заданного примитива. То есть, если создаётся отрезок, в конструкторе LINE срабатывает команда pt = new POINT[2], а если прямоугольник – pt = new POINT[4] в конструкторе SQVR. Указатель *nextFig служит для формирования стека примитивов. Благодаря механизму наследования он может указывать на любой класс примитива.
В каждом описании классов примитивов по-своему переопределена функция вывода print(). Эта функция отвечает за вывод команды рисования примитива в текстовый файл, из которого набор этих команд можно копировать в статью и скомпилировать средствами LaTeX. В каждом классе данная функция выводит в файл собственную команду и параметры, содержащиеся в выбранном объекте. Ниже для примера приведено содержание класса примитивов «метка»:
class LABEL : public FIGURE { public: char *lab; void ini (int x, int y, char *str, int len, HDC hdc) { pt = new POINT; pt[0].x = x; pt[0].y = y; lab = newchar[len+1]; strcpy(lab, str); TextOutA(hdc, pt->x, pt->y, lab, strlen(lab)); } LABEL() void print() { ofile << "\\put(" << pt[0].x - Canv_left << "," << Canv_top - pt[0].y << "){" << lab << "}" << endl; } }*label; |
Этот класс содержит указатель *lab, который преобразуется в строку для хранения текста метки, функцию инициализации, которая сохраняет данные в структуре и рисует текст, функцию print(), преобразующую рисунок в команды вывода графики с учётом кадрирования, и указатель *label, отвечающего за работу стека. Функция создания примитива «метка» выглядит следующим образом:
LABEL *new_label(int x, int y, char *str, int len, HDC hdc) { LABEL *label_new = new LABEL; label_new->ini(x, y, str, len, hdc); if (!labelcount++) label_new->nextFig = 0; else label_new->nextFig = label; return label_new; } |
Когда пользователь рисует примитив, в данном случае метку, функции создания передаются координаты опорной точки, строка текста, длина строки и дескриптор устройства, куда будет рисоваться текст. Поскольку для каждого нового примитива память выделяется динамически, то для инициализации пришлось использовать функцию инициализации, а не конструктор.
При сохранении команд, программа на каждый класс примитива создаёт отдельный поток. Каждый поток запускает функцию, которая при помощи мютекса синхронизирует вывод каждой команды. Объявление данной функции следующее.
void save(FIGURE *curfig, int counter) |
Как видим, аргумент *curfig – указатель на стек примитивов, а counter – их общее количество. Благодаря механизму наследования, каждый класс примитива является классом FIGURE, а значит, для синхронного вывода примитивов любого класса достаточно использовать одну функцию. Так же, благодаря виртуальной функции print(), при помощи curfig->print(); можно обращаться к функции вывода каждого примитива, а программа уже будет знать, какой именно примитив надо вывести в файл.
Математические модели были взяты из книги «Математические основы машинной графики» [7]. Например, Кривая Безье — параметрическая кривая, задаваемая выражением
где Pi — функция компонент векторов опорных вершин, а bi,n(t) — базисные функции кривой Безье, называемые также полиномами Бернштейна.
, где
число сочетаний из n по i, где n – степень полинома, i – порядковый номер опорной вершины. Поскольку синтаксис языка TeX позволяет выводить кривые только по трём точкам, формула для их вывода была упрощена.
X = (1 - t)*(1 - t) * pt[0].x + 2*t*(1-t)*pt[1].x + t*t*pt[2].x; Y = (1 - t)*(1 - t) * pt[0].y + 2*t*(1-t)*pt[1].y + t*t*pt[2].y; |
Где pt[0].x, pt[1].x, pt[2].x – координаты опорных точек по оси OX, а pt[0].y, pt[1].y, pt[2].y – координаты опорных точек по оси OY. При построении кривой, программа с шагом t = t + 0.01 находит точки, расположенные на кривой, и затем соединяет их маленькими отрезками.
В ходе работы над программным обеспечением появились следующие проблемы. Поскольку значения, отвечающие за наклон в примитивах «отрезок» и «вектор» должны быть целыми, и их количество сильно ограничено, то для наклона примитива существует ограниченное количество углов. В разрабатываемом программном обеспечении пользователь рисует отрезки и векторы, задавая координаты начальной и конечной точки. Конвертировать их координаты в команду вывода на языке TeX не удалось, поэтому для вывода прямых линий было решено использовать кривые Безье, определяя начало линии, середину и конец. Поскольку для вывода кривых Безье не нужно указывать соотношение наклона и длину проекции, то через кривые можно выводить прямые отрезки и векторы под любым углом.
Так же была проблема с определением области рисунка. В LaTeX область рисования указывается вручную, и пользователю, как и примитивы, тоже приходится подбирать приблизительно, определяя, каких размеров будет рисунок. Благодаря автоматической обрезке PaintTeX определяет границы прямоугольника (холста), в котором было нарисовано изображение и обрезает рисунок до нужных размеров, вставляя соответствующие параметры в команду begin{picture}().
Другая проблема – работа с координатами Windows и LaTeX. Поскольку начальной точкой координат в Windows является верхний левый край окна, а в LaTeX – нижний левый, при преобразовании рисунка в файл сохранялись координаты Windows, а после компиляции изображение выглядело в зеркальном отображении по вертикали. Теперь PaintTeX при сохранении рисунка учитывает этот нюанс.
Сообщений 3 Оценка 0 Оценить |