LibreOffice Module starmath (master) 1
Visual Formula Editing

A visual formula editor allows users to easily edit formulas without having to learn and use complicated commands.

A visual formula editor is a WYSIWYG editor. For OpenOffice Math this essentially means that you can click on the formula image, to get a caret, which you can move with arrow keys, and use to modify the formula by entering text, clicking buttons or using shortcuts.

Formula Trees

A formula in OpenOffice Math is a tree of nodes, take for instance the formula "A + {B cdot C} over D", it looks like this $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $. The tree for this formula looks like this:

The vertices are nodes, their label says what kind of node and the number in parentheses is the identifier of the node (In practices a pointer is used instead of the id). The direction of the edges tells which node is parent and which is child. The label of the edges are the child node index number, given to SmNode::GetSubNode() of the parent to get the child node.

Visual Lines

Inorder to do caret movement in visual lines, we need a definition of caret position and visual line. In a tree such as the above there are three visual lines. There's the outer most line, with entries such as $\mbox{A}$, $ + $ and $ \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $. Then there's the numerator line of the fraction it has entries $ \mbox{B} $, $ \cdot $ and $ \mbox{C} $. And last by not least there's the denominator line of the fraction it's only entry is $ \mbox{D} $.

For visual editing it should be possible to place a caret on both sides of any line entry, consider a line entry a character or construction that in a line is treated as a character. Imagine the caret is placed to the right of the plus sign (id: 6), now if user presses backspace this should delete the plus sign (id: 6), and if the user presses delete this should delete the entire fraction (id: 7). This is because the caret is in the outer most line where the fraction is considered a line entry.

However, inorder to prevent users from accidentally deleting large subtrees, just because they logically placed there caret a in the wrong line, require that complex constructions such as a fraction is selected before it is deleted. Thus in this case it wouldn't be deleted, but only selected and then deleted if the user hit delete again. Anyway, this is slightly off topic for now.

Important about visual lines is that they don't always have an SmExpressionNode as root and the entries in a visual line is all the nodes of a subtree ordered left to right that isn't either an SmExpressionNode, SmBinHorNode or SmUnHorNode.

Caret Positions

A caret position in OpenOffice Math is represented by an instance of SmCaretPos. That is a caret position is a node and an index related to this node. For most nodes the index 0, means caret is in front of this node, the index 1 means caret is after this node. For SmTextNode the index is the caret position after the specified number of characters, imagine an SmTextNode with the number 1337. The index 3 in such SmTextNode would mean a caret placed right before 7, e.g. "133|7".

For SmExpressionNode, SmBinHorNode and SmUnHorNode the only legal index is 0, which means in front of the node. Actually the index 0 may only because for the first caret position in a visual line. From the example above, consider the following subtree that constitutes a visual line:

Here the caret positions are:

Caret position:Example:
{id: 8, index: 0} $ \mid \mbox{C} \cdot \mbox{C} $
{id: 10, index: 1} $ \mbox{C} \mid \cdot \mbox{C} $
{id: 11, index: 1} $ \mbox{C} \cdot \mid \mbox{C} $
{id: 12, index: 1} $ \mbox{C} \cdot \mbox{C} \mid $

$ \mid $ is used to denote caret position.

With these exceptions included in the definition the id and index: {id: 11, index: 0} does not constitute a caret position in the given context. Note the method SmCaretPos::IsValid() does not check if this invariant holds true, but code in SmCaret, SmSetSelectionVisitor and other places depends on this invariant to hold.

Caret Movement

As the placement of caret positions depends very much on the context within which a node appears it is not trivial to find all caret positions and determine which follows which. In OpenOffice Math this is done by the SmCaretPosGraphBuildingVisitor. This visitor builds graph (an instance of SmCaretPosGraph) over the caret positions. For details on how this graph is build, and how new methods should be implemented see SmCaretPosGraphBuildingVisitor.

The result of the SmCaretPosGraphBuildingVisitor is a graph over the caret positions in a formula, represented by an instance of SmCaretPosGraph. Each entry (instances of SmCaretPosGraphEntry) has a pointer to the entry to the left and right of itself. This way we can easily find the caret position to a right or left of a given caret position. Note each caret position only appears once in this graph.

When searching for a caret position after a left click on the formula this map is also used. We simply iterate over all entries, uses the SmCaretPos2LineVisitor to find a line for each caret position. Then the distance from the click to the line is computed and we choose the caret position closest to the click.

For up and down movement, we also iterator over all caret positions and use SmCaretPos2LineVisitor to find a line for each caret position. Then we compute the distance from the current caret position to every other caret position and chooses the one closest that is either above or below the current caret position, depending on whether we're doing up or down movement.

This result of this approach to caret movement is that we have logically predictable movement for left and right, whilst leftclick, up and down movement depends on the sizes and placement of all node and may be less logically predictable. This solution also means that we only have one complex visitor generating the graph, imagine the nightmare if we had a visitor for movement in each direction.

Making up and down movement independent of node sizes and placement wouldn't necessarily be a good thing either. Consider the formula $ \frac{1+2+3+4+5}{6} $, if the caret is placed as displayed here: $ \frac{1+2+3+4+5}{6 \mid} $, up movement should move to right after "3": $ \frac{1+2+3|+4+5}{6} $. However, such a move depends on the sizes and placement of all nodes in the fraction.

Example of Caret Position Graph

If we consider the formula $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $ from Formula Trees. It has the following caret positions:

Caret position: Example:
{id: 3, index: 0} $ \mid\mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $
{id: 5, index: 1} $ \mbox{A}\mid + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $
{id: 6, index: 1} $ \mbox{A} + \mid \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} $
{id: 8, index: 0} $ \mbox{A} + \frac{ \mid \mbox{B} \cdot \mbox{C}}{\mbox{D}} $
{id: 10, index: 1} $ \mbox{A} + \frac{\mbox{B} \mid \cdot \mbox{C}}{\mbox{D}} $
{id: 11, index: 1} $ \mbox{A} + \frac{\mbox{B} \cdot \mid \mbox{C}}{\mbox{D}} $
{id: 12, index: 1} $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C} \mid}{\mbox{D}} $
{id: 14, index: 0} $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mid \mbox{D}} $
{id: 14, index: 1} $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D} \mid} $
{id: 7, index: 1} $ \mbox{A} + \frac{\mbox{B} \cdot \mbox{C}}{\mbox{D}} \mid $

Below is a directed graph over the caret positions and how you can move between them.