add a first concept of a cursor to the BoxView

The cursor can only be moved forward and backward, it's just a
demonstration how the cursor, and other metainformation (e.g. the
selection or spelling errors) can be visualized without dependencies
from the box model to visualization. 

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java
new file mode 100644
index 0000000..2be7dcc
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/ContentMap.java
@@ -0,0 +1,80 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.boxes;
+
+/**
+ * @author Florian Thienel
+ */
+public class ContentMap {
+
+	private RootBox rootBox;
+
+	public void setRootBox(final RootBox rootBox) {
+		this.rootBox = rootBox;
+	}
+
+	public IContentBox findBoxForPosition(final int offset) {
+		final IBoxVisitorWithResult<IContentBox> search = new BaseBoxVisitorWithResult<IContentBox>() {
+
+			@Override
+			public IContentBox visit(final RootBox box) {
+				return traverseChildren(box);
+			}
+
+			@Override
+			public IContentBox visit(final VerticalBlock box) {
+				return traverseChildren(box);
+			}
+
+			@Override
+			public IContentBox visit(final Frame box) {
+				return box.getComponent().accept(this);
+			}
+
+			@Override
+			public IContentBox visit(final NodeReference box) {
+				if (box.getStartOffset() == offset || box.getEndOffset() == offset) {
+					return box;
+				}
+				if (box.getStartOffset() < offset && box.getEndOffset() > offset) {
+					return box.getComponent().accept(this);
+				}
+				return null;
+			}
+
+			@Override
+			public IContentBox visit(final Paragraph box) {
+				return traverseChildren(box);
+			}
+
+			@Override
+			public IContentBox visit(final TextContent box) {
+				if (box.getStartOffset() <= offset && box.getEndOffset() >= offset) {
+					return box;
+				}
+				return null;
+			}
+
+			private <T extends IBox> IContentBox traverseChildren(final IParentBox<T> box) {
+				for (final T child : box.getChildren()) {
+					final IContentBox childResult = child.accept(this);
+
+					if (childResult != null) {
+						return childResult;
+					}
+				}
+				return null;
+			}
+		};
+
+		return rootBox.accept(search);
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Cursor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Cursor.java
new file mode 100644
index 0000000..d90eb2f
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/Cursor.java
@@ -0,0 +1,363 @@
+/*******************************************************************************
+ * Copyright (c) 2014 Florian Thienel and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * 		Florian Thienel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.vex.core.internal.boxes;
+
+import org.eclipse.vex.core.internal.core.Color;
+import org.eclipse.vex.core.internal.core.ColorResource;
+import org.eclipse.vex.core.internal.core.FontSpec;
+import org.eclipse.vex.core.internal.core.Graphics;
+import org.eclipse.vex.core.internal.core.Rectangle;
+import org.eclipse.vex.core.provisional.dom.BaseNodeVisitorWithResult;
+import org.eclipse.vex.core.provisional.dom.IComment;
+import org.eclipse.vex.core.provisional.dom.IDocument;
+import org.eclipse.vex.core.provisional.dom.IElement;
+import org.eclipse.vex.core.provisional.dom.IIncludeNode;
+import org.eclipse.vex.core.provisional.dom.INode;
+import org.eclipse.vex.core.provisional.dom.IProcessingInstruction;
+
+/**
+ * @author Florian Thienel
+ */
+public class Cursor {
+
+	private static final Color FOREGROUND_COLOR = new Color(255, 255, 255);
+	private static final Color BACKGROUND_COLOR = new Color(0, 0, 0);
+	private static final FontSpec FONT = new FontSpec("Arial", FontSpec.BOLD, 10.0f);
+
+	private int offset;
+	private final ContentMap contentMap;
+
+	public Cursor(final ContentMap contentMap) {
+		this.contentMap = contentMap;
+	}
+
+	private Rectangle getAbsolutePositionArea(final Graphics graphics, final IContentBox box, final int offset) {
+		if (box == null) {
+			return Rectangle.NULL;
+		}
+		return box.accept(new BaseBoxVisitorWithResult<Rectangle>() {
+			@Override
+			public Rectangle visit(final NodeReference box) {
+				if (box.isAtStart(offset)) {
+					return makeAbsolute(box.getPositionArea(graphics, offset), box);
+				} else if (box.isAtEnd(offset) && box.canContainText() && !box.isEmpty()) {
+					final int lastOffset = offset - 1;
+					final IContentBox lastBox = contentMap.findBoxForPosition(lastOffset);
+					return getAbsolutePositionArea(graphics, lastBox, lastOffset);
+				} else if (box.isAtEnd(offset)) {
+					return makeAbsolute(box.getPositionArea(graphics, offset), box);
+				} else {
+					return Rectangle.NULL;
+				}
+			}
+
+			@Override
+			public Rectangle visit(final TextContent box) {
+				return makeAbsolute(box.getPositionArea(graphics, offset), box);
+			}
+		});
+	}
+
+	private Rectangle makeAbsolute(final Rectangle rectangle, final IBox box) {
+		return new Rectangle(rectangle.getX() + box.getAbsoluteLeft(), rectangle.getY() + box.getAbsoluteTop(), rectangle.getWidth(), rectangle.getHeight());
+	}
+
+	private Caret getCaretForNode(final Graphics graphics, final NodeReference box) {
+		final Rectangle area = getAbsolutePositionArea(graphics, box, offset);
+		if (box.isAtStart(offset)) {
+			return new InsertBeforeNodeCaret(area, box.getNode());
+		} else if (box.isAtEnd(offset) && box.canContainText()) {
+			return new AppendNodeWithTextCaret(area, box.getNode(), box.isEmpty());
+		} else if (box.isAtEnd(offset) && !box.canContainText()) {
+			return new AppendStructuralNodeCaret(area, box.getNode());
+		} else {
+			return null;
+		}
+	}
+
+	private Caret getCaretForText(final Graphics graphics, final TextContent box) {
+		if (box.getStartOffset() > offset || box.getEndOffset() < offset) {
+			return null;
+		}
+		final Rectangle relativeArea = box.getPositionArea(graphics, offset);
+		final Rectangle area = new Rectangle(relativeArea.getX() + box.getAbsoluteLeft(), relativeArea.getY() + box.getAbsoluteTop(), relativeArea.getWidth(), relativeArea.getHeight());
+		final FontSpec font = box.getFont();
+		final String character = box.getText().substring(offset - box.getStartOffset(), offset - box.getStartOffset() + 1);
+		return new TextCaret(area, font, character, false);
+	}
+
+	public void paint(final Graphics graphics) {
+		final IContentBox box = contentMap.findBoxForPosition(offset);
+		if (box == null) {
+			return;
+		}
+		final Caret caret = box.accept(new BaseBoxVisitorWithResult<Caret>() {
+			@Override
+			public Caret visit(final NodeReference box) {
+				return getCaretForNode(graphics, box);
+			}
+
+			@Override
+			public Caret visit(final TextContent box) {
+				return getCaretForText(graphics, box);
+			}
+		});
+
+		if (caret == null) {
+			return;
+		}
+		caret.paint(graphics);
+	}
+
+	public void setPosition(final int offset) {
+		this.offset = offset;
+	}
+
+	public int getPosition() {
+		return offset;
+	}
+
+	private static interface Caret {
+		void paint(Graphics graphics);
+	}
+
+	private static class InsertBeforeNodeCaret implements Caret {
+		private final Rectangle area;
+		private final INode node;
+
+		public InsertBeforeNodeCaret(final Rectangle area, final INode node) {
+			this.area = area;
+			this.node = node;
+		}
+
+		@Override
+		public void paint(final Graphics graphics) {
+			if (area == Rectangle.NULL) {
+				return;
+			}
+
+			final ColorResource foregroundColor = graphics.getColor(FOREGROUND_COLOR);
+			final ColorResource backgroundColor = graphics.getColor(BACKGROUND_COLOR);
+			graphics.setForeground(foregroundColor);
+			graphics.setBackground(backgroundColor);
+
+			final int x = area.getX();
+			final int y = area.getY();
+
+			graphics.fillRect(x, y, area.getWidth(), 2);
+			graphics.fillRect(x, y, 2, area.getHeight());
+
+			graphics.setCurrentFont(graphics.getFont(FONT));
+			final String nodeName = getNodeName(node);
+			final int textWidth = graphics.stringWidth(nodeName);
+			final int textHeight = graphics.getFontMetrics().getHeight();
+			final int textPadding = 3;
+			final int textOffset = 5;
+			graphics.fillRect(x + textOffset, y + textOffset, textWidth + textPadding * 2, textHeight + textPadding * 2);
+			graphics.drawString(nodeName, x + textOffset + textPadding, y + textOffset + textPadding);
+		}
+
+		private static String getNodeName(final INode node) {
+			return node.accept(new BaseNodeVisitorWithResult<String>() {
+				@Override
+				public String visit(final IDocument document) {
+					return "DOCUMENT";
+				}
+
+				@Override
+				public String visit(final IElement element) {
+					return "<" + element.getPrefixedName() + "...";
+				}
+
+				@Override
+				public String visit(final IComment comment) {
+					return "<!--";
+				}
+
+				@Override
+				public String visit(final IProcessingInstruction pi) {
+					return "<?" + pi.getTarget() + "...";
+				}
+
+				@Override
+				public String visit(final IIncludeNode include) {
+					return getNodeName(include.getReference());
+				}
+			});
+		}
+	}
+
+	private static class AppendNodeWithTextCaret implements Caret {
+		private final Rectangle area;
+		private final INode node;
+		private final boolean nodeIsEmpty;
+
+		public AppendNodeWithTextCaret(final Rectangle area, final INode node, final boolean nodeIsEmpty) {
+			this.area = area;
+			this.node = node;
+			this.nodeIsEmpty = nodeIsEmpty;
+		}
+
+		@Override
+		public void paint(final Graphics graphics) {
+			if (area == Rectangle.NULL) {
+				return;
+			}
+
+			final ColorResource foregroundColor = graphics.getColor(FOREGROUND_COLOR);
+			final ColorResource backgroundColor = graphics.getColor(BACKGROUND_COLOR);
+			graphics.setForeground(foregroundColor);
+			graphics.setBackground(backgroundColor);
+
+			final int x = nodeIsEmpty ? area.getX() : area.getX() + area.getWidth();
+			final int y = area.getY();
+
+			graphics.fillRect(x, y, 2, area.getHeight());
+
+			graphics.setCurrentFont(graphics.getFont(FONT));
+			final String nodeName = getNodeName(node);
+			final int textWidth = graphics.stringWidth(nodeName);
+			final int textHeight = graphics.getFontMetrics().getHeight();
+			final int textPadding = 3;
+			final int textOffsetX = 5;
+			final int textOffsetY = (area.getHeight() - textHeight - textPadding * 2) / 2;
+			graphics.fillRect(x + textOffsetX, y + textOffsetY, textWidth + textPadding * 2, textHeight + textPadding * 2);
+			graphics.drawString(nodeName, x + textOffsetX + textPadding, y + textOffsetY + textPadding);
+		}
+
+		private static String getNodeName(final INode node) {
+			return node.accept(new BaseNodeVisitorWithResult<String>() {
+				@Override
+				public String visit(final IDocument document) {
+					return "DOCUMENT";
+				}
+
+				@Override
+				public String visit(final IElement element) {
+					return "</" + element.getPrefixedName() + ">";
+				}
+
+				@Override
+				public String visit(final IComment comment) {
+					return "--!>";
+				}
+
+				@Override
+				public String visit(final IProcessingInstruction pi) {
+					return "?>";
+				}
+
+				@Override
+				public String visit(final IIncludeNode include) {
+					return getNodeName(include.getReference());
+				}
+			});
+		}
+	}
+
+	private static class AppendStructuralNodeCaret implements Caret {
+		private final Rectangle area;
+		private final INode node;
+
+		public AppendStructuralNodeCaret(final Rectangle area, final INode node) {
+			this.area = area;
+			this.node = node;
+		}
+
+		@Override
+		public void paint(final Graphics graphics) {
+			if (area == Rectangle.NULL) {
+				return;
+			}
+
+			final ColorResource foregroundColor = graphics.getColor(FOREGROUND_COLOR);
+			final ColorResource backgroundColor = graphics.getColor(BACKGROUND_COLOR);
+			graphics.setForeground(foregroundColor);
+			graphics.setBackground(backgroundColor);
+
+			final int x = area.getX();
+			final int y = area.getY() + area.getHeight();
+
+			graphics.fillRect(x, y, area.getWidth(), 2);
+			graphics.fillRect(x + area.getWidth() - 2, y, 2, -area.getHeight());
+
+			graphics.setCurrentFont(graphics.getFont(FONT));
+			final String nodeName = getNodeName(node);
+			final int textWidth = graphics.stringWidth(nodeName);
+			final int textHeight = graphics.getFontMetrics().getHeight();
+			final int textPadding = 3;
+			final int textOffset = 5;
+			graphics.fillRect(x, y + textOffset, textWidth + textPadding * 2, textHeight + textPadding * 2);
+			graphics.drawString(nodeName, x + textPadding, y + textOffset + textPadding);
+		}
+
+		private static String getNodeName(final INode node) {
+			return node.accept(new BaseNodeVisitorWithResult<String>() {
+				@Override
+				public String visit(final IDocument document) {
+					return "DOCUMENT";
+				}
+
+				@Override
+				public String visit(final IElement element) {
+					return "</" + element.getPrefixedName() + ">";
+				}
+
+				@Override
+				public String visit(final IComment comment) {
+					return "--!>";
+				}
+
+				@Override
+				public String visit(final IProcessingInstruction pi) {
+					return "?>";
+				}
+
+				@Override
+				public String visit(final IIncludeNode include) {
+					return getNodeName(include.getReference());
+				}
+			});
+		}
+	}
+
+	private static class TextCaret implements Caret {
+		private final Rectangle area;
+		private final FontSpec font;
+		private final String character;
+		private final boolean overwrite;
+
+		public TextCaret(final Rectangle area, final FontSpec font, final String character, final boolean overwrite) {
+			this.area = area;
+			this.font = font;
+			this.character = character;
+			this.overwrite = overwrite;
+		}
+
+		@Override
+		public void paint(final Graphics graphics) {
+			if (area == Rectangle.NULL) {
+				return;
+			}
+
+			graphics.setForeground(graphics.getColor(FOREGROUND_COLOR));
+			graphics.setBackground(graphics.getColor(BACKGROUND_COLOR));
+
+			if (overwrite) {
+				graphics.fillRect(area.getX(), area.getY(), area.getWidth(), area.getHeight());
+				graphics.setCurrentFont(graphics.getFont(font));
+				graphics.drawString(character, area.getX(), area.getY());
+			} else {
+				graphics.fillRect(area.getX() - 1, area.getY(), 2, area.getHeight());
+			}
+		}
+	}
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
index 2eac69b..f676b6c 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/boxes/NodeReference.java
@@ -28,6 +28,7 @@
 	private IChildBox component;
 
 	private INode node;
+	private boolean canContainText;
 
 	@Override
 	public void setParent(final IBox parent) {
@@ -116,6 +117,18 @@
 		this.node = node;
 	}
 
+	public INode getNode() {
+		return node;
+	}
+
+	public void setCanContainText(final boolean canContainText) {
+		this.canContainText = canContainText;
+	}
+
+	public boolean canContainText() {
+		return canContainText;
+	}
+
 	@Override
 	public void layout(final Graphics graphics) {
 		if (component == null) {
@@ -150,7 +163,19 @@
 
 	@Override
 	public Rectangle getPositionArea(final Graphics graphics, final int offset) {
-		return new Rectangle(left, top, width, height);
+		return new Rectangle(0, 0, width, height);
+	}
+
+	public boolean isEmpty() {
+		return getEndOffset() - getStartOffset() <= 1;
+	}
+
+	public boolean isAtEnd(final int offset) {
+		return getEndOffset() == offset;
+	}
+
+	public boolean isAtStart(final int offset) {
+		return getStartOffset() == offset;
 	}
 
 }
diff --git a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
index e875f18..7839847 100644
--- a/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
+++ b/org.eclipse.vex.ui/src/org/eclipse/vex/ui/boxview/BoxView.java
@@ -13,26 +13,34 @@
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.frame;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.paragraph;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.rootBox;
+import static org.eclipse.vex.core.internal.boxes.BoxFactory.staticText;
 import static org.eclipse.vex.core.internal.boxes.BoxFactory.verticalBlock;
 
 import java.util.TreeSet;
 
 import org.eclipse.core.runtime.QualifiedName;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.part.ViewPart;
+import org.eclipse.vex.core.internal.boxes.Border;
+import org.eclipse.vex.core.internal.boxes.ContentMap;
+import org.eclipse.vex.core.internal.boxes.Cursor;
 import org.eclipse.vex.core.internal.boxes.IBox;
 import org.eclipse.vex.core.internal.boxes.IChildBox;
 import org.eclipse.vex.core.internal.boxes.IInlineBox;
 import org.eclipse.vex.core.internal.boxes.IParentBox;
+import org.eclipse.vex.core.internal.boxes.Margin;
 import org.eclipse.vex.core.internal.boxes.NodeReference;
+import org.eclipse.vex.core.internal.boxes.Padding;
 import org.eclipse.vex.core.internal.boxes.Paragraph;
 import org.eclipse.vex.core.internal.boxes.RootBox;
 import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.boxes.VerticalBlock;
 import org.eclipse.vex.core.internal.core.FontSpec;
+import org.eclipse.vex.core.internal.core.Graphics;
 import org.eclipse.vex.core.internal.dom.Document;
-import org.eclipse.vex.core.internal.dom.Element;
 import org.eclipse.vex.core.internal.widget.swt.BoxWidget;
 import org.eclipse.vex.core.internal.widget.swt.BoxWidget.ILayoutListener;
 import org.eclipse.vex.core.internal.widget.swt.BoxWidget.IPaintingListener;
@@ -40,6 +48,7 @@
 import org.eclipse.vex.core.provisional.dom.IDocument;
 import org.eclipse.vex.core.provisional.dom.IElement;
 import org.eclipse.vex.core.provisional.dom.INode;
+import org.eclipse.vex.core.provisional.dom.IParent;
 import org.eclipse.vex.core.provisional.dom.IText;
 
 /**
@@ -49,16 +58,20 @@
  */
 public class BoxView extends ViewPart {
 
-	private static final FontSpec TIMES_NEW_ROMAN = new FontSpec(new String[] { "Times New Roman" }, 0, 20.0f);
+	private static final FontSpec TIMES_NEW_ROMAN = new FontSpec("Times New Roman", FontSpec.PLAIN, 20.0f);
 
 	private static final String LOREM_IPSUM_LONG = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur.";
 
 	private Composite parent;
 	private BoxWidget boxWidget;
+	private ContentMap contentMap;
+	private Cursor cursor;
 
 	@Override
 	public void createPartControl(final Composite parent) {
 		this.parent = parent;
+		contentMap = new ContentMap();
+		cursor = new Cursor(contentMap);
 		refresh();
 	}
 
@@ -109,10 +122,38 @@
 			public void paintingFinished(final Graphics graphics) {
 				final long duration = System.currentTimeMillis() - startTime;
 				System.out.println("took " + duration + "ms");
+
+				cursor.paint(graphics);
 			}
 
 		});
-		boxWidget.setContent(createTestModel());
+		boxWidget.addKeyListener(new KeyListener() {
+			@Override
+			public void keyReleased(final KeyEvent e) {
+				// ignore
+			}
+
+			@Override
+			public void keyPressed(final KeyEvent e) {
+				switch (e.keyCode) {
+				case SWT.ARROW_LEFT:
+					cursor.setPosition(Math.max(0, cursor.getPosition() - 1));
+					boxWidget.invalidate();
+					break;
+				case SWT.ARROW_RIGHT:
+					cursor.setPosition(cursor.getPosition() + 1);
+					boxWidget.invalidate();
+					break;
+				default:
+					break;
+				}
+			}
+		});
+
+		final RootBox testModel = createTestModel();
+		boxWidget.setContent(testModel);
+		contentMap.setRootBox(testModel);
+		// cursor.setPosition(0);
 		parent.layout();
 	}
 
@@ -134,17 +175,30 @@
 
 	private static Document createTestDocument() {
 		final Document document = new Document(new QualifiedName(null, "doc"));
-		for (int i = 0; i < 25000; i += 1) {
-			insertParagraph(document);
+		for (int i = 0; i < 25; i += 1) {
+			insertSection(document.getRootElement());
 		}
 		return document;
 	}
 
-	private static void insertParagraph(final Document document) {
-		final Element textElement = document.insertElement(document.getRootElement().getEndOffset(), new QualifiedName(null, "para"));
+	private static void insertSection(final IParent parent) {
+		final IDocument document = parent.getDocument();
+		final IElement section = document.insertElement(parent.getEndOffset(), new QualifiedName(null, "section"));
+		insertParagraph(section);
+		insertEmptyParagraph(section);
+	}
+
+	private static void insertParagraph(final IParent parent) {
+		final IDocument document = parent.getDocument();
+		final IElement textElement = document.insertElement(parent.getEndOffset(), new QualifiedName(null, "para"));
 		document.insertText(textElement.getEndOffset(), LOREM_IPSUM_LONG);
 	}
 
+	private static void insertEmptyParagraph(final IParent parent) {
+		final IDocument document = parent.getDocument();
+		document.insertElement(parent.getEndOffset(), new QualifiedName(null, "para"));
+	}
+
 	private static VisualizationChain buildVisualizationChain() {
 		final VisualizationChain visualizationChain = new VisualizationChain();
 		visualizationChain.addForRoot(new DocumentRootVisualization());
@@ -157,18 +211,30 @@
 	private static final class DocumentRootVisualization extends NodeVisualization<RootBox> {
 		@Override
 		public RootBox visit(final IDocument document) {
-			final RootBox result = rootBox();
-			visualizeChildrenStructure(document.children(), result);
-			return result;
+			final RootBox rootBox = rootBox();
+			final NodeReference rootReference = new NodeReference();
+			rootReference.setNode(document);
+			rootBox.appendChild(rootReference);
+
+			final VerticalBlock rootChildren = verticalBlock();
+			visualizeChildrenStructure(document.children(), rootChildren);
+
+			rootReference.setComponent(rootChildren);
+			return rootBox;
 		}
 	}
 
 	private static final class StructureElementVisualization extends NodeVisualization<IChildBox> {
 		@Override
 		public IChildBox visit(final IElement element) {
+			final NodeReference elementReference = new NodeReference();
+			elementReference.setNode(element);
+
 			final VerticalBlock component = verticalBlock();
 			visualizeChildrenStructure(element.children(), component);
-			return frame(component);
+
+			elementReference.setComponent(frame(component, Margin.NULL, Border.NULL, new Padding(3, 3)));
+			return elementReference;
 		}
 	}
 
@@ -184,12 +250,21 @@
 			}
 
 			final Paragraph paragraph = paragraph();
-			visualizeChildrenInline(element.children(), paragraph);
+			if (element.hasChildren()) {
+				visualizeChildrenInline(element.children(), paragraph);
+			} else {
+				visualizeEmptyParagraph(element, paragraph);
+			}
 			final NodeReference nodeReference = new NodeReference();
-			nodeReference.setComponent(frame(paragraph));
+			nodeReference.setComponent(frame(paragraph, Margin.NULL, Border.NULL, new Padding(5, 4)));
 			nodeReference.setNode(element);
+			nodeReference.setCanContainText(true);
 			return nodeReference;
 		}
+
+		private void visualizeEmptyParagraph(final IElement element, final Paragraph paragraph) {
+			paragraph.appendChild(staticText(" ", TIMES_NEW_ROMAN));
+		}
 	}
 
 	private static final class TextVisualization extends NodeVisualization<IInlineBox> {