Сообщений 3    Оценка 0        Оценить  
Система Orphus

Программное обеспечение для создания рисунков в редакторе LaTeX

Автор: Беженцев Роман Вадимович
Опубликовано: 18.11.2015
Исправлено: 10.12.2016
Версия текста: 1.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 при сохранении рисунка учитывает этот нюанс.

Список использованных источников

  1. Sunol F. Tools for creating LaTeX-integrated graphics and animations under GNU/Linux // The PracTeX Journal, №1 (2010), P. 1-12.
  2. Jie X. Extending Two Drawing Frameworks to Create : Presented in partial Fulfillment of the requirements for the degree of master of computer science, Concordia University Monreal, Quebec, Canada, 2005.
  3. Hahn T., Lang P. “FeynEdit – a tool for drawing Feynman diagrams”, Munich, 2007. 9p. Preprint, arXiv:0711.1345v1 [hep-ph], Cornell Univ. http://arxiv.org/abs/0711.1345v1
  4. Binosi D., Colins J., Kaufhold C., Theussl L. “JaxoDraw: A graphical user interface for drawing Feynman diagrams. Version 2.0 release notes.”, Comput. Phys. Commun. 2008. 17p. Preprint, arXiv:0811.4113v1 [hep-ph], Cornell Univ. http://arxiv.org/abs/0811.4113v1
  5. Львовский С. М. Набор и вёрстка в системе LaTeX, СПб.: Питер, 2003.
  6. Knuth, D. E. The TeXbook, part А series Computers and Typesetting. - Addison-Wesley, 1994.
  7. Роджерс Д., Адамс Дж. Математические основы машинной графики, М.: Мир, 2001.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 3    Оценка 0        Оценить