rename ContentMap to ContentTopology; move related methods to this class

All methods that are related to the topology of content boxes, and that
were duplicated several times were moved to the ContentTopology class.

Signed-off-by: Florian Thienel <florian@thienel.org>
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentMap.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentMap.java
deleted file mode 100644
index b0c2a79..0000000
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentMap.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*******************************************************************************
- * 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.cursor;
-
-import org.eclipse.vex.core.internal.boxes.DepthFirstTraversal;
-import org.eclipse.vex.core.internal.boxes.IContentBox;
-import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
-import org.eclipse.vex.core.internal.boxes.RootBox;
-import org.eclipse.vex.core.internal.boxes.TextContent;
-import org.eclipse.vex.core.provisional.dom.ContentRange;
-
-/**
- * @author Florian Thienel
- */
-public class ContentMap {
-
-	private RootBox rootBox;
-	private IContentBox outmostContentBox;
-
-	public void setRootBox(final RootBox rootBox) {
-		this.rootBox = rootBox;
-		outmostContentBox = findOutmostContentBox();
-	}
-
-	private IContentBox findOutmostContentBox() {
-		return rootBox.accept(new DepthFirstTraversal<IContentBox>(null) {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				return box;
-			}
-
-			@Override
-			public IContentBox visit(final TextContent box) {
-				return box;
-			}
-		});
-	}
-
-	public int getLastOffset() {
-		if (outmostContentBox == null) {
-			return 0;
-		}
-		return outmostContentBox.getEndOffset();
-	}
-
-	public IContentBox getOutmostContentBox() {
-		return outmostContentBox;
-	}
-
-	public IContentBox findBoxForPosition(final int offset) {
-		return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference 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 TextContent box) {
-				if (box.getStartOffset() <= offset && box.getEndOffset() >= offset) {
-					return box;
-				}
-				return null;
-			}
-		});
-	}
-
-	public IContentBox findBoxForRange(final ContentRange range) {
-		return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (box.getRange().contains(range)) {
-					final IContentBox childBox = box.getComponent().accept(this);
-					if (childBox == null) {
-						return box;
-					} else {
-						return childBox;
-					}
-				}
-
-				return null;
-			}
-
-			@Override
-			public IContentBox visit(final TextContent box) {
-				if (box.getRange().contains(range)) {
-					return box;
-				}
-				return null;
-			}
-		});
-	}
-
-	public Environment findEnvironmentForCoordinates(final int x, final int y, final boolean preferClosest) {
-		final IContentBox[] deepestContainer = new IContentBox[1];
-		final Neighbourhood neighbours = new Neighbourhood();
-		rootBox.accept(new DepthFirstTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				super.visit(box);
-				if (box.isAbove(y)) {
-					neighbours.setAbove(box, y - box.getAbsoluteTop() - box.getHeight(), preferClosest);
-					return null;
-				} else if (box.isBelow(y)) {
-					neighbours.setBelow(box, box.getAbsoluteTop() - y, preferClosest);
-					return null;
-				} else if (box.isRightOf(x)) {
-					neighbours.setRight(box, box.getAbsoluteLeft() - x, preferClosest);
-					return null;
-				} else if (box.isLeftOf(x)) {
-					neighbours.setLeft(box, x - box.getAbsoluteLeft() - box.getWidth(), preferClosest);
-					return null;
-				} else {
-					if (deepestContainer[0] == null) {
-						deepestContainer[0] = box;
-					}
-					if (neighbours.getBelow().box != null) {
-						return deepestContainer[0];
-					} else {
-						return null;
-					}
-				}
-			}
-
-			@Override
-			public IContentBox visit(final TextContent box) {
-				if (box.isAbove(y)) {
-					neighbours.setAbove(box, y - box.getAbsoluteTop() - box.getHeight(), true);
-					return null;
-				} else if (box.isBelow(y)) {
-					neighbours.setBelow(box, box.getAbsoluteTop() - y, true);
-					return null;
-				} else if (box.isRightOf(x)) {
-					neighbours.setRight(box, box.getAbsoluteLeft() - x, true);
-					return null;
-				} else if (box.isLeftOf(x)) {
-					neighbours.setLeft(box, x - box.getAbsoluteLeft() - box.getWidth(), true);
-					return null;
-				} else {
-					deepestContainer[0] = box;
-					return null;
-				}
-			}
-		});
-		return new Environment(neighbours, deepestContainer[0]);
-	}
-
-	public static class Neighbour {
-		public static final Neighbour NULL = new Neighbour(null, Integer.MAX_VALUE);
-
-		public final IContentBox box;
-		public final int distance;
-
-		public Neighbour(final IContentBox box, final int distance) {
-			this.box = box;
-			this.distance = distance;
-		}
-	}
-
-	public static class Neighbourhood {
-		private Neighbour above = Neighbour.NULL;
-		private Neighbour below = Neighbour.NULL;
-		private Neighbour left = Neighbour.NULL;
-		private Neighbour right = Neighbour.NULL;
-
-		public void setAbove(final IContentBox box, final int distance, final boolean overwrite) {
-			if (above == Neighbour.NULL || overwrite && above.distance >= distance) {
-				above = new Neighbour(box, distance);
-			}
-		}
-
-		public Neighbour getAbove() {
-			return above;
-		}
-
-		public void setBelow(final IContentBox box, final int distance, final boolean overwrite) {
-			if (below == Neighbour.NULL || overwrite && below.distance >= distance) {
-				below = new Neighbour(box, distance);
-			}
-		}
-
-		public Neighbour getBelow() {
-			return below;
-		}
-
-		public void setLeft(final IContentBox box, final int distance, final boolean overwrite) {
-			if (left == Neighbour.NULL || overwrite && left.distance >= distance) {
-				left = new Neighbour(box, distance);
-			}
-		}
-
-		public void setRight(final IContentBox box, final int distance, final boolean overwrite) {
-			if (right == Neighbour.NULL || overwrite && right.distance >= distance) {
-				right = new Neighbour(box, distance);
-			}
-		}
-
-		public Neighbour getClosestOnLine() {
-			return closest(left, right);
-		}
-
-		private Neighbour closest(final Neighbour neighbour1, final Neighbour neighbour2) {
-			if (neighbour1.distance < neighbour2.distance) {
-				return neighbour1;
-			} else {
-				return neighbour2;
-			}
-		}
-	}
-
-	public static class Environment {
-		public final Neighbourhood neighbours;
-		public final IContentBox deepestContainer;
-
-		public Environment(final Neighbourhood neighbours, final IContentBox deepestContainer) {
-			this.neighbours = neighbours;
-			this.deepestContainer = deepestContainer;
-		}
-	}
-}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java
new file mode 100644
index 0000000..cb4994e
--- /dev/null
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ContentTopology.java
@@ -0,0 +1,187 @@
+/*******************************************************************************
+ * 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.cursor;
+
+import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
+import org.eclipse.vex.core.internal.boxes.DepthFirstTraversal;
+import org.eclipse.vex.core.internal.boxes.IBox;
+import org.eclipse.vex.core.internal.boxes.IContentBox;
+import org.eclipse.vex.core.internal.boxes.ParentTraversal;
+import org.eclipse.vex.core.internal.boxes.RootBox;
+import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
+import org.eclipse.vex.core.internal.boxes.TextContent;
+import org.eclipse.vex.core.provisional.dom.ContentRange;
+
+/**
+ * @author Florian Thienel
+ */
+public class ContentTopology {
+
+	private RootBox rootBox;
+	private IContentBox outmostContentBox;
+
+	public void setRootBox(final RootBox rootBox) {
+		this.rootBox = rootBox;
+		outmostContentBox = findOutmostContentBox(rootBox);
+	}
+
+	private static IContentBox findOutmostContentBox(final RootBox rootBox) {
+		return rootBox.accept(new DepthFirstTraversal<IContentBox>(null) {
+			@Override
+			public IContentBox visit(final StructuralNodeReference box) {
+				return box;
+			}
+
+			@Override
+			public IContentBox visit(final TextContent box) {
+				return box;
+			}
+		});
+	}
+
+	public int getLastOffset() {
+		if (outmostContentBox == null) {
+			return 0;
+		}
+		return outmostContentBox.getEndOffset();
+	}
+
+	public IContentBox getOutmostContentBox() {
+		return outmostContentBox;
+	}
+
+	public IContentBox findBoxForPosition(final int offset) {
+		return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
+			@Override
+			public IContentBox visit(final StructuralNodeReference 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 TextContent box) {
+				if (box.getStartOffset() <= offset && box.getEndOffset() >= offset) {
+					return box;
+				}
+				return null;
+			}
+		});
+	}
+
+	public IContentBox findBoxForRange(final ContentRange range) {
+		return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
+			@Override
+			public IContentBox visit(final StructuralNodeReference box) {
+				if (box.getRange().contains(range)) {
+					final IContentBox childBox = box.getComponent().accept(this);
+					if (childBox == null) {
+						return box;
+					} else {
+						return childBox;
+					}
+				}
+
+				return null;
+			}
+
+			@Override
+			public IContentBox visit(final TextContent box) {
+				if (box.getRange().contains(range)) {
+					return box;
+				}
+				return null;
+			}
+		});
+	}
+
+	public IContentBox findBoxForCoordinates(final int x, final int y) {
+		if (outmostContentBox == null) {
+			return null;
+		}
+
+		return outmostContentBox.accept(new DepthFirstTraversal<IContentBox>() {
+			@Override
+			public IContentBox visit(final StructuralNodeReference box) {
+				if (!box.containsCoordinates(x, y)) {
+					return null;
+				}
+				final IContentBox deeperContainer = super.visit(box);
+				if (deeperContainer != null) {
+					return deeperContainer;
+				}
+				return box;
+			}
+
+			@Override
+			public IContentBox visit(final TextContent box) {
+				if (!box.containsCoordinates(x, y)) {
+					return null;
+				}
+				return box;
+			}
+		});
+	}
+
+	public static IContentBox getParentContentBox(final IContentBox childBox) {
+		return childBox.accept(new ParentTraversal<IContentBox>() {
+			@Override
+			public IContentBox visit(final StructuralNodeReference box) {
+				if (box == childBox) {
+					return super.visit(box);
+				}
+				return box;
+			}
+		});
+	}
+
+	public static int verticalDistance(final IContentBox box, final int y) {
+		return box.accept(new BaseBoxVisitorWithResult<Integer>(0) {
+			@Override
+			public Integer visit(final StructuralNodeReference box) {
+				return Math.abs(y - box.getAbsoluteTop() - box.getHeight());
+			}
+
+			@Override
+			public Integer visit(final TextContent box) {
+				return Math.abs(y - box.getAbsoluteTop() - box.getBaseline());
+			}
+		});
+	}
+
+	public static IContentBox findHorizontallyClosestContentBox(final Iterable<IContentBox> candidates, final int x) {
+		IContentBox finalCandidate = null;
+		int minHorizontalDistance = Integer.MAX_VALUE;
+		for (final IContentBox candidate : candidates) {
+			final int distance = horizontalDistance(candidate, x);
+			if (distance < minHorizontalDistance) {
+				finalCandidate = candidate;
+				minHorizontalDistance = distance;
+			}
+		}
+		return finalCandidate;
+	}
+
+	public static int horizontalDistance(final IBox box, final int x) {
+		if (box.getAbsoluteLeft() > x) {
+			return box.getAbsoluteLeft() - x;
+		}
+		if (box.getAbsoluteLeft() + box.getWidth() < x) {
+			return x - box.getAbsoluteLeft() - box.getWidth();
+		}
+		return 0;
+	}
+
+}
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
index 53ec2cc..ea7bbd1 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/Cursor.java
@@ -39,7 +39,7 @@
 	private static final Color SELECTION_FOREGROUND_COLOR = new Color(255, 255, 255);
 	private static final Color SELECTION_BACKGROUND_COLOR = new Color(0, 0, 255);
 
-	private final ContentMap contentMap = new ContentMap();
+	private final ContentTopology contentTopology = new ContentTopology();
 	private final IContentSelector selector;
 	private final LinkedList<MoveWithSelection> moves = new LinkedList<MoveWithSelection>();
 
@@ -54,7 +54,7 @@
 	}
 
 	public void setRootBox(final RootBox rootBox) {
-		contentMap.setRootBox(rootBox);
+		contentTopology.setRootBox(rootBox);
 	}
 
 	public int getOffset() {
@@ -94,7 +94,7 @@
 
 	public void applyMoves(final Graphics graphics) {
 		for (MoveWithSelection move = moves.poll(); move != null; move = moves.poll()) {
-			offset = move.move.calculateNewOffset(graphics, contentMap, offset, box, getHotArea(), preferredX);
+			offset = move.move.calculateNewOffset(graphics, contentTopology, offset, box, getHotArea(), preferredX);
 			if (move.select) {
 				if (move.move.isAbsolute()) {
 					selector.setEndAbsoluteTo(offset);
@@ -124,7 +124,7 @@
 	}
 
 	private void applyCaretForPosition(final Graphics graphics, final int offset) {
-		box = contentMap.findBoxForPosition(offset);
+		box = contentTopology.findBoxForPosition(offset);
 		if (box == null) {
 			return;
 		}
@@ -139,7 +139,7 @@
 			return;
 		}
 		final ContentRange selectedRange = selector.getRange();
-		final IBox selectionRootBox = contentMap.findBoxForRange(selectedRange);
+		final IBox selectionRootBox = contentTopology.findBoxForRange(selectedRange);
 		selectionRootBox.accept(new DepthFirstTraversal<Object>() {
 			@Override
 			public Object visit(final StructuralNodeReference box) {
@@ -198,7 +198,7 @@
 					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);
+					final IContentBox lastBox = contentTopology.findBoxForPosition(lastOffset);
 					return getAbsolutePositionArea(graphics, lastBox, lastOffset);
 				} else if (box.isAtEnd(offset)) {
 					return makeAbsolute(box.getPositionArea(graphics, offset), box);
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
index 0128868..ea77783 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/ICursorMove.java
@@ -19,7 +19,7 @@
  */
 public interface ICursorMove {
 
-	int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX);
+	int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, int preferredX);
 
 	boolean preferX();
 
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
index b2920f9..808891e 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveDown.java
@@ -10,14 +10,16 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findHorizontallyClosestContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.verticalDistance;
+
 import java.util.Iterator;
 import java.util.LinkedList;
 
 import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.DepthFirstTraversal;
-import org.eclipse.vex.core.internal.boxes.IBox;
 import org.eclipse.vex.core.internal.boxes.IContentBox;
-import org.eclipse.vex.core.internal.boxes.ParentTraversal;
 import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
 import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.core.Graphics;
@@ -39,7 +41,7 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		if (isAtStartOfEmptyBox(currentOffset, currentBox)) {
 			return currentBox.getEndOffset();
 		}
@@ -89,18 +91,6 @@
 		});
 	}
 
-	private static IContentBox getParentContentBox(final IContentBox childBox) {
-		return childBox.accept(new ParentTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (box == childBox) {
-					return super.visit(box);
-				}
-				return box;
-			}
-		});
-	}
-
 	private static IContentBox getFirstContentBoxChild(final IContentBox parent) {
 		return parent.accept(new DepthFirstTraversal<IContentBox>() {
 			private IContentBox firstChild;
@@ -188,18 +178,13 @@
 		final int[] minVerticalDistance = new int[1];
 		minVerticalDistance[0] = Integer.MAX_VALUE;
 		parent.accept(new DepthFirstTraversal<Object>() {
-
-			private boolean isBelow(final int distance) {
-				return distance > 0;
-			}
-
 			@Override
 			public Object visit(final StructuralNodeReference box) {
 				if (box == parent) {
 					super.visit(box);
 				} else {
-					final int distance = verticalDistanceFromBelow(box, y);
-					if (isBelow(distance)) {
+					final int distance = verticalDistance(box, y);
+					if (box.isBelow(y)) {
 						candidates.add(box);
 						minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
 					}
@@ -209,8 +194,8 @@
 
 			@Override
 			public Object visit(final TextContent box) {
-				final int distance = verticalDistanceFromBelow(box, y);
-				if (isBelow(distance)) {
+				final int distance = verticalDistance(box, y);
+				if (box.isBelow(y)) {
 					candidates.add(box);
 					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
 				}
@@ -220,50 +205,13 @@
 
 		for (final Iterator<IContentBox> iter = candidates.iterator(); iter.hasNext();) {
 			final IContentBox candidate = iter.next();
-			if (verticalDistanceFromBelow(candidate, y) > minVerticalDistance[0]) {
+			if (verticalDistance(candidate, y) > minVerticalDistance[0]) {
 				iter.remove();
 			}
 		}
 		return candidates;
 	}
 
-	private static int verticalDistanceFromBelow(final IContentBox box, final int y) {
-		return box.accept(new BaseBoxVisitorWithResult<Integer>(0) {
-			@Override
-			public Integer visit(final StructuralNodeReference box) {
-				return box.getAbsoluteTop() - y;
-			}
-
-			@Override
-			public Integer visit(final TextContent box) {
-				return box.getAbsoluteTop() + box.getBaseline() - y;
-			}
-		});
-	}
-
-	private static IContentBox findHorizontallyClosestContentBox(final Iterable<IContentBox> candidates, final int x) {
-		IContentBox finalCandidate = null;
-		int minHorizontalDistance = Integer.MAX_VALUE;
-		for (final IContentBox candidate : candidates) {
-			final int distance = horizontalDistance(candidate, x);
-			if (distance < minHorizontalDistance) {
-				finalCandidate = candidate;
-				minHorizontalDistance = distance;
-			}
-		}
-		return finalCandidate;
-	}
-
-	private static int horizontalDistance(final IBox box, final int x) {
-		if (box.getAbsoluteLeft() > x) {
-			return box.getAbsoluteLeft() - x;
-		}
-		if (box.getAbsoluteLeft() + box.getWidth() < x) {
-			return x - box.getAbsoluteLeft() - box.getWidth();
-		}
-		return 0;
-	}
-
 	private static IContentBox handleSpecialCaseMovingIntoLastLineOfParagraph(final IContentBox candidate, final int x, final int y) {
 		if (candidate == null) {
 			return null;
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
index 2e534be..6c212b0 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveLeft.java
@@ -20,7 +20,7 @@
 public class MoveLeft implements ICursorMove {
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		return Math.max(0, currentOffset - 1);
 	}
 
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
index b0f8d9c..698178d 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveRight.java
@@ -11,8 +11,6 @@
 package org.eclipse.vex.core.internal.cursor;
 
 import org.eclipse.vex.core.internal.boxes.IContentBox;
-import org.eclipse.vex.core.internal.boxes.ParentTraversal;
-import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
 import org.eclipse.vex.core.internal.core.Graphics;
 import org.eclipse.vex.core.internal.core.Rectangle;
 
@@ -32,26 +30,7 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
-		final int nextOffset = currentOffset + 1;
-		if (nextOffset > currentBox.getEndOffset()) {
-			final IContentBox parent = getParentContentBox(currentBox);
-			if (parent == null) {
-				return currentOffset;
-			}
-		}
-		return nextOffset;
-	}
-
-	private static IContentBox getParentContentBox(final IContentBox childBox) {
-		return childBox.accept(new ParentTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (box == childBox) {
-					return super.visit(box);
-				}
-				return box;
-			}
-		});
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+		return Math.min(currentOffset + 1, contentTopology.getLastOffset());
 	}
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
index 2093af2..2da8170 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToAbsoluteCoordinates.java
@@ -10,13 +10,13 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.horizontalDistance;
+
 import java.util.LinkedList;
 
 import org.eclipse.vex.core.internal.boxes.DepthFirstTraversal;
-import org.eclipse.vex.core.internal.boxes.IBox;
 import org.eclipse.vex.core.internal.boxes.IContentBox;
-import org.eclipse.vex.core.internal.boxes.ParentTraversal;
-import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
 import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.core.Graphics;
 import org.eclipse.vex.core.internal.core.Rectangle;
@@ -45,9 +45,8 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
-		final IContentBox outmostContentBox = contentMap.getOutmostContentBox();
-		final IContentBox box = findClosestBoxByCoordinates(outmostContentBox, x, y);
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+		final IContentBox box = findClosestBoxByCoordinates(contentTopology, x, y);
 		if (box.containsCoordinates(x, y)) {
 			return box.getOffsetForCoordinates(graphics, x - box.getAbsoluteLeft(), y - box.getAbsoluteTop());
 		} else if (box.isLeftOf(x)) {
@@ -63,38 +62,11 @@
 		}
 	}
 
-	private static IContentBox findClosestBoxByCoordinates(final IContentBox outmostContentBox, final int x, final int y) {
-		final IContentBox deepestContainer = findDeepestContainerOfCoordinates(outmostContentBox, x, y);
-		if (deepestContainer == null) {
-			return outmostContentBox;
-		}
+	private static IContentBox findClosestBoxByCoordinates(final ContentTopology contentTopology, final int x, final int y) {
+		final IContentBox deepestContainer = contentTopology.findBoxForCoordinates(x, y);
 		return findClosestBoxInContainer(deepestContainer, x, y);
 	}
 
-	private static IContentBox findDeepestContainerOfCoordinates(final IContentBox rootBox, final int x, final int y) {
-		return rootBox.accept(new DepthFirstTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (!box.containsCoordinates(x, y)) {
-					return null;
-				}
-				final IContentBox deeperContainer = super.visit(box);
-				if (deeperContainer != null) {
-					return deeperContainer;
-				}
-				return box;
-			}
-
-			@Override
-			public IContentBox visit(final TextContent box) {
-				if (!box.containsCoordinates(x, y)) {
-					return null;
-				}
-				return box;
-			}
-		});
-	}
-
 	private static IContentBox findClosestBoxInContainer(final IContentBox container, final int x, final int y) {
 		final LinkedList<IContentBox> candidates = new LinkedList<IContentBox>();
 		container.accept(new DepthFirstTraversal<Object>() {
@@ -120,16 +92,6 @@
 		return closestBox;
 	}
 
-	private static int horizontalDistance(final IBox box, final int x) {
-		if (box.getAbsoluteLeft() > x) {
-			return box.getAbsoluteLeft() - x;
-		}
-		if (box.getAbsoluteLeft() + box.getWidth() < x) {
-			return x - box.getAbsoluteLeft() - box.getWidth();
-		}
-		return 0;
-	}
-
 	private static boolean isLastEnclosedBox(final IContentBox enclosedBox) {
 		final IContentBox parent = getParentContentBox(enclosedBox);
 		if (parent == null) {
@@ -138,16 +100,4 @@
 		return enclosedBox.getEndOffset() == parent.getEndOffset() - 1;
 	}
 
-	private static IContentBox getParentContentBox(final IContentBox childBox) {
-		return childBox.accept(new ParentTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (box == childBox) {
-					return super.visit(box);
-				}
-				return box;
-			}
-		});
-	}
-
 }
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
index 61fd9a3..6b93d0d 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveToOffset.java
@@ -26,7 +26,7 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		return offset;
 	}
 
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
index c395a51..f7925aa 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/cursor/MoveUp.java
@@ -10,14 +10,17 @@
  *******************************************************************************/
 package org.eclipse.vex.core.internal.cursor;
 
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.findHorizontallyClosestContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.getParentContentBox;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.horizontalDistance;
+import static org.eclipse.vex.core.internal.cursor.ContentTopology.verticalDistance;
+
 import java.util.Iterator;
 import java.util.LinkedList;
 
 import org.eclipse.vex.core.internal.boxes.BaseBoxVisitorWithResult;
 import org.eclipse.vex.core.internal.boxes.DepthFirstTraversal;
-import org.eclipse.vex.core.internal.boxes.IBox;
 import org.eclipse.vex.core.internal.boxes.IContentBox;
-import org.eclipse.vex.core.internal.boxes.ParentTraversal;
 import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
 import org.eclipse.vex.core.internal.boxes.TextContent;
 import org.eclipse.vex.core.internal.core.Graphics;
@@ -39,7 +42,7 @@
 	}
 
 	@Override
-	public int calculateNewOffset(final Graphics graphics, final ContentMap contentMap, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
+	public int calculateNewOffset(final Graphics graphics, final ContentTopology contentTopology, final int currentOffset, final IContentBox currentBox, final Rectangle hotArea, final int preferredX) {
 		if (isAtEndOfEmptyBox(currentOffset, currentBox)) {
 			return currentBox.getStartOffset();
 		}
@@ -88,18 +91,6 @@
 		});
 	}
 
-	private static IContentBox getParentContentBox(final IContentBox childBox) {
-		return childBox.accept(new ParentTraversal<IContentBox>() {
-			@Override
-			public IContentBox visit(final StructuralNodeReference box) {
-				if (box == childBox) {
-					return super.visit(box);
-				}
-				return box;
-			}
-		});
-	}
-
 	private static IContentBox getLastContentBoxChild(final IContentBox parent) {
 		return parent.accept(new DepthFirstTraversal<IContentBox>() {
 			private IContentBox lastChild;
@@ -154,15 +145,10 @@
 		final int[] minVerticalDistance = new int[1];
 		minVerticalDistance[0] = Integer.MAX_VALUE;
 		parent.accept(new DepthFirstTraversal<Object>() {
-
-			private boolean isAbove(final int distance) {
-				return distance >= 0;
-			}
-
 			@Override
 			public Object visit(final StructuralNodeReference box) {
-				final int distance = verticalDistanceFromAbove(box, y);
-				if (box != parent && !isAbove(distance)) {
+				final int distance = verticalDistance(box, y);
+				if (box != parent && !box.isAbove(y)) {
 					return box;
 				}
 
@@ -180,8 +166,8 @@
 
 			@Override
 			public Object visit(final TextContent box) {
-				final int distance = verticalDistanceFromAbove(box, y);
-				if (isAbove(distance)) {
+				final int distance = verticalDistance(box, y);
+				if (box.isAbove(y)) {
 					candidates.add(box);
 					minVerticalDistance[0] = Math.min(distance, minVerticalDistance[0]);
 				}
@@ -191,55 +177,18 @@
 
 		for (final Iterator<IContentBox> iter = candidates.iterator(); iter.hasNext();) {
 			final IContentBox candidate = iter.next();
-			if (verticalDistanceFromAbove(candidate, y) > minVerticalDistance[0]) {
+			if (verticalDistance(candidate, y) > minVerticalDistance[0]) {
 				iter.remove();
 			}
 		}
 		return candidates;
 	}
 
-	private static int verticalDistanceFromAbove(final IContentBox box, final int y) {
-		return box.accept(new BaseBoxVisitorWithResult<Integer>(0) {
-			@Override
-			public Integer visit(final StructuralNodeReference box) {
-				return y - box.getAbsoluteTop() - box.getHeight();
-			}
-
-			@Override
-			public Integer visit(final TextContent box) {
-				return y - box.getAbsoluteTop() - box.getBaseline();
-			}
-		});
-	}
-
-	private static IContentBox findHorizontallyClosestContentBox(final Iterable<IContentBox> candidates, final int x) {
-		IContentBox finalCandidate = null;
-		int minHorizontalDistance = Integer.MAX_VALUE;
-		for (final IContentBox candidate : candidates) {
-			final int distance = horizontalDistance(candidate, x);
-			if (distance < minHorizontalDistance) {
-				finalCandidate = candidate;
-				minHorizontalDistance = distance;
-			}
-		}
-		return finalCandidate;
-	}
-
-	private static int horizontalDistance(final IBox box, final int x) {
-		if (box.getAbsoluteLeft() > x) {
-			return box.getAbsoluteLeft() - x;
-		}
-		if (box.getAbsoluteLeft() + box.getWidth() < x) {
-			return x - box.getAbsoluteLeft() - box.getWidth();
-		}
-		return 0;
-	}
-
 	private static IContentBox handleSpecialCaseMovingIntoLastLineOfParagraph(final IContentBox candidate, final int x, final int y) {
 		if (candidate == null) {
 			return null;
 		}
-		if (!(verticalDistanceFromAbove(candidate, y) >= 0 && horizontalDistance(candidate, x) == 0)) {
+		if (!(verticalDistance(candidate, y) >= 0 && horizontalDistance(candidate, x) == 0)) {
 			return candidate;
 		}
 
diff --git a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DOMVisualization.java b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DOMVisualization.java
index bdd36f3..07ae002 100644
--- a/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DOMVisualization.java
+++ b/org.eclipse.vex.core/src/org/eclipse/vex/core/internal/widget/DOMVisualization.java
@@ -19,7 +19,7 @@
 import org.eclipse.vex.core.internal.boxes.StructuralNodeReference;
 import org.eclipse.vex.core.internal.boxes.RootBox;
 import org.eclipse.vex.core.internal.boxes.TextContent;
-import org.eclipse.vex.core.internal.cursor.ContentMap;
+import org.eclipse.vex.core.internal.cursor.ContentTopology;
 import org.eclipse.vex.core.internal.cursor.Cursor;
 import org.eclipse.vex.core.internal.visualization.VisualizationChain;
 import org.eclipse.vex.core.provisional.dom.ContentRange;
@@ -31,7 +31,7 @@
  */
 public class DOMVisualization {
 
-	private final ContentMap contentMap = new ContentMap();
+	private final ContentTopology contentTopology = new ContentTopology();
 	private final Cursor cursor;
 	private final BoxView view;
 
@@ -64,13 +64,13 @@
 			rootBox = new RootBox();
 		}
 
-		contentMap.setRootBox(rootBox);
+		contentTopology.setRootBox(rootBox);
 		cursor.setRootBox(rootBox);
 		view.setRootBox(rootBox);
 	}
 
 	public void rebuildStructure(final INode node) {
-		final IContentBox modifiedBox = contentMap.findBoxForRange(node.getRange());
+		final IContentBox modifiedBox = contentTopology.findBoxForRange(node.getRange());
 		final IStructuralBox newBox = visualizationChain.visualizeStructure(node);
 		final IStructuralBox newChildBox = newBox.accept(new BaseBoxVisitorWithResult<IStructuralBox>(newBox) {
 			@Override
@@ -89,7 +89,7 @@
 	}
 
 	public void rebuildContentRange(final ContentRange range) {
-		final IContentBox modifiedBox = contentMap.findBoxForRange(range);
+		final IContentBox modifiedBox = contentTopology.findBoxForRange(range);
 		if (modifiedBox == null) {
 			return;
 		}