Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

A WPF TikZ Editor (TikzEdt)

0.00/5 (No votes)
6 Feb 2011 1  
A combined text/WYSIWYG editor for the TikZ vector graphics language.

Introduction

TikZ/PGF is a pair of widely used languages for creating vector graphics, in particular in Latex documents. There are a couple of editors assisting in the creation of TikZ code. However, mostly one has the choice between a text editor with preview, with no WYSIWYG capabilities, and a true WYSIWYG editor with a TikZ export function, without direct code access. Combining both direct code editing and WYSIWYG features is quite involved, since it requires a parser and interpreter that "understands" the TikZ code so that it can be rendered and edited in a WYSIWYG manner. We recently wrote such an editor, TikzEdt. This article describes the main programming challenges we faced, and design decisions we made to overcome them. Furthermore, there are some components of our program that can be reused for similar applications.

The latest version of the code and some further documentation can be found here.

For space reasons, we do not document TikzEdt's source code in full here. Instead, we will mostly restrict ourselves to describing the design decisions we made in human language and give only occasional code samples.

Prerequisites

This article should be accessible to an audience acquainted with C#. However, the reader will benefit by knowing the essentials of LaTeX and TikZ/PGF programming, as well as of the Antlr parser generator to understand examples.

What TikzEdt does

Before we begin, let us briefly se what the editor does by looking at the above screenshot. On the left, there is a text box in which the user can freely type and edit LaTeX/TikZ code. On the right, there is a preview of the compiled TikZ picture, i.e., of the PDF produced by the LaTeX compiler when fed with the code on the left. On top of the preview, there is an overlay displayed (red X's). The user can edit this overlay in a WYSIWYG manner, for example, drag and drop a red X with the mouse. When (s)he does so, the TikZ-code in the textbox on the left is updated accordingly, and again compiled, so that the preview displayed on the right changes.

The text editor

The text editor (left half of screenshot) is based on the AvalonEdit component. It features syntax highlighting and basic code completion. AvalonEdit is described in detail in this CodeProject article. We added the following features:

A Find/Replace dialog

It incorporates the standard functions of Find/Replace dialogs.

Customizable code completions

The code completions are stored in an external XML file (CodeCompletions.xml). Each code completion belongs to a certain Environment, characterized by a start and an end tag, which can be specified as a Regular Expression in CodeCompletions.xml. The class CodeCompleter is used to read the XML file and fill the AvalonEdit CompletionWindow with the appropriate completions. The code for displaying the completions at the end looks as follows:

private void ShowCodeCompletionsCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
    // Open code completion window
    completionWindow = new CompletionWindow(txtCode.TextArea);
    IList<ICompletionData> data = completionWindow.CompletionList.CompletionData;
    codeCompleter.GetCompletions(txtCode.Document, txtCode.CaretOffset, data);

    ...

    completionWindow.Show();
    ...
}

Remark: We would like to make the Find/Replace dialog and CodeCompleter available as reusable libraries. However, for time reasons, we will need some help with more rigorous testing and maintenance. If you are interested, please contact one of the authors.

The snippet library

TikzEdt comes with a configurable snippet library. Its functionality is more or less standard, and not worth describing, except for one point: each snippet comes with a thumbnail image, which is compiled directly from LaTeX/TikZ code. Hence the user inserting a custom snippet does not have to produce a separate image file to display, but just has to insert some sample code, which is compiled to produce the thumbnail image.

The TikZ Parser

The technical heart of TikzEdt is the TikZ parser. The following issues had to be taken into account:

  • LaTeX/TikZ is not a very "clean" language, in particular it is ambiguous and not LL*.
  • We do not want to re-implement the full TikZ language, but only parts that are relevant for WYSIWYG editing. Hence the parser should be fault tolerant, i.e., also accept language constructions that it cannot "understand".
  • We do not have first hand specifications of TikZ. So part of the specification had to be reverse engineered.

To build the parser, we used the Antlr parser generator. The resulting parser produces from the TikZ code an abstract syntax tree (AST), representing only that portion of the TikZ file TikzEdt can understand. The AST is, in a second step, translated into a Tikz_ParseTree, which contains custom nodes for each type of TikZ object. For example, here is the C# class representing a TikZ node object:

public class Tikz_Node : Tikz_XYItem
{
    public static Tikz_Node FromCommonTree(CommonTree t, 
                            CommonTokenStream tokens)
    {
        ...
    }
    public void SetName(string tname)
    {
        ...
    }
    public override bool GetAbsPos(out Point ret, bool OnlyOffset = false) 
    {
        ...
    }
    public override void SetAbsPos(Point p)
    {
        ...
    }
    ...
}

Here, the static method FromCommonTree() is used to generate a Tikz_Node instance from an abstract AST node, which is passed as the argument t. The method GetAbsPos() returns the coordinates of the node.

The interpreter and coordinate computations

TikZ/PGF features an extensive system of coordinate transformations, and ways to specify relative coordinates or polar coordinates. For example, the following TikZ code produces a rotated and stretched rectangle:

\draw[rotate=30, xscale=3] (1,1) rectangle (3,3);

In order to accurately understand the position of nodes, those coordinate transformations have to be parsed and represented in TikzEdt. If the position of some object is queried, the correct coordinate transformations have to be applied to the raw coordinates parsed from the TikZ code (e.g., (1,1) in the example above). This is essentially implemented in the method GetCurrentTransformAt() in the nodes of the Tikz_ParseTree.

public bool GetCurrentTransformAt(TikzParseItem childtpi, out TikzMatrix M)
{
    if (parent != null)
    {
        if (!parent.GetCurrentTransformAt(this, out M))
            return false;
    }
    else
        M = new TikzMatrix(); // identity matrix

    foreach (TikzParseItem tpi in Children)
    {
        if (tpi == childtpi && childtpi != null)
        {
            break;
        }
        else if (tpi is Tikz_Options)
        {
            TikzMatrix MM;
            if ((tpi as Tikz_Options).GetTransform(out MM))
                M = M * MM;
            else
                return false;
        }
    }
    return true;
}

The method accepts some item in the parse tree and computes the current transform at this item's position. Afterwards, the raw coordinate, e.g., (1,1), can be transformed using TikzMatrix.Transform().

Real time PDF preview

TikzEdt does not have a TikZ rendering engine. The preview displayed in the WYSIWYG editor is the compiled PDFLaTeX output. Such an approach is only feasible if the compilation and display can happen very fast so as to not introduce significant delays between the user WYSIWYG-editing the overlay, and the preview following the change. We speed up PDFLaTeX compilation by using precompiled headers. Furthermore, to display the PDF, we use PDFLibNet wrapping the Xpdf/muPDF libraries, which is very fast. In effect, for small TikZ files, there is very little "lag" in the WYSIWYG editor.

Determining the bounding box

There is, as we know, no direct way to read off the bounding box of a TikZ picture from the compiled .pdf or LaTeX .aux files. However, we need this bounding box in order to correctly align the displayed overlay for WYSIWYG editing with the underlying PDF preview. What we do to determine the bounding box is that we write the following piece of code into the TeX file, to read out the bounding box and write it to a separate text file during LaTeX compilation.

\pgftransformreset
...
\newwrite\metadatafile
\immediate\openout\metadatafile=\jobname_BB.txt
\path
  let
    \p1=(current bounding box.south west),
    \p2=(current bounding box.north east)
  in
  node[inner sep=0pt,outer sep=0pt,minimum size=0pt,line width=0pt,
       text width=0pt,text height=0pt,draw=white] at (current bounding box) {
\immediate\write\metadatafile{\p1,\p2}
};
\immediate\closeout\metadatafile

The overlay and WYSIWYG functionality

To allow for WYSIWYG editing, an overlay is displayed on top of the PDF preview. For example, for the following piece of TikZ code:

\begin{tikzpicture}
\draw(-4,3) .. controls (-3,5) and (-1,6) .. (-1,3);
\draw (1,5) rectangle (4,3);
\begin{scope}[]
\draw(6,3) -- (9,5);
\end{scope}
\end{tikzpicture}

the raw output and overlay look like this:

One sees that overlay items are displayed for editing of the Bezier control points, the various coordinates present, and for the TikZ scope. The user can drag and drop the overlay items with the mouse, as in any WYSIWYG editor, and the TikZ source code is updated appropriately.

PdfOverlay class and display tree

All WYSIWYG functionality is implemented in the class PdfOverlay. Internally, this class translates the Tikz_Parsetree into another tree structure, a display tree (thus following the standard Model View pattern). For example, here is part of the definition of the class representing a coordinate in the display tree:

public class OverlayNode : OverlayShape
{
    public Tikz_XYItem tikzitem;
    public override TikzParseItem item { get { return tikzitem; } }

    public delegate void PositionChangedHandler(OverlayNode sender);
    public event PositionChangedHandler PositionChanged;

    public override bool AdjustPosition(double Resolution)
    {
        ...
    }
    ...
}

The field tikzitem holds the item in the Tikz_ParseTree this node of the display tree represents. The method AdjustPosition() is called to align the position of the OverlayNode on the screen with the position encoded in the underlying tikzitem.

Coordinate systems and rasterizer

The user usually likes to align multiple objects. For this reason, the coordinates of objects moved with the mouse should be rasterized. This is done in TikzEdt by the rasterControl class, which also displays a visual raster on screen. This class is a little more complicated than one might think as it has to support coordinate transformations and polar coordinates. For example, here is an image of a rotated, anisotropically scaled polar coordinate raster:

The underlying TikZ code in this case is this:

\begin{tikzpicture}
  \draw[rotate=30, xscale=1, yscale=4] (0,0)--(90:2)--+(180:5);
\end{tikzpicture}

Extensible tool system

The picture can be edited in a WYSIWYG manner using multiple tools. We wanted the tool system to be as modular as possible, allowing for later additions of tools, or improvements on existing tools, without worrying about the implementation details of the rest of the program. Each tool is contained in a separate class, deriving from the OverlayTool base class. The access to the TikZ picture and user interface is granted through the OverlayInterface interface.

class OverlayTool
{
    /// <summary>
    /// Access to the PdfOverlay. It will be set
    /// before the first call to OnActivate().
    /// </summary>
    public OverlayInterface overlay;

    /// <summary>
    /// This method is called when the tool is selected by the user.
    /// For example, the cursor shape should be set here.
    /// </summary>
    public virtual void OnActivate() { }
    public virtual void OnDeactivate() { }

    public virtual void OnLeftMouseButtonDown(OverlayShape item, 
                        Point p, MouseButtonEventArgs e) { }
    ...
}

Final remarks, and to-dos

We sketched here a combined text/WYSIWYG editor for vector graphics. We are not aware of any similar project within the Open Source community. At present, TikzEdt is still in development, and numerous features are left on the authors' wish list. At present, all help is welcome, be it in testing or coding, or just comments on our work. (Just contact the authors if you are interested to become a contributor.)

Thanks

Our work would not have been possible without building on previous efforts by the community. In particular, we use the following components:

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here