Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 11748)
@@ -29,5 +29,4 @@
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -41,5 +40,4 @@
 import java.util.function.Consumer;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 import javax.swing.AbstractButton;
@@ -66,4 +64,5 @@
 import org.openstreetmap.josm.gui.NavigatableComponent;
 import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
 import org.openstreetmap.josm.gui.mappaint.ElemStyles;
 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
@@ -76,5 +75,4 @@
 import org.openstreetmap.josm.gui.mappaint.styleelement.MapImage;
 import org.openstreetmap.josm.gui.mappaint.styleelement.NodeElement;
-import org.openstreetmap.josm.gui.mappaint.styleelement.PositionForAreaStrategy;
 import org.openstreetmap.josm.gui.mappaint.styleelement.RepeatImageElement.LineImageAlignment;
 import org.openstreetmap.josm.gui.mappaint.styleelement.StyleElement;
@@ -82,4 +80,5 @@
 import org.openstreetmap.josm.gui.mappaint.styleelement.TextElement;
 import org.openstreetmap.josm.gui.mappaint.styleelement.TextLabel;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
 import org.openstreetmap.josm.tools.CompositeList;
 import org.openstreetmap.josm.tools.Geometry;
@@ -340,50 +339,4 @@
 
     /**
-     * Displays text at specified position including its halo, if applicable.
-     *
-     * @param gv Text's glyphs to display. If {@code null}, use text from {@code s} instead.
-     * @param s text to display if {@code gv} is {@code null}
-     * @param x X position
-     * @param y Y position
-     * @param disabled {@code true} if element is disabled (filtered out)
-     * @param text text style to use
-     */
-    private void displayText(GlyphVector gv, String s, int x, int y, boolean disabled, TextLabel text) {
-        if (gv == null && s.isEmpty()) return;
-        if (isInactiveMode || disabled) {
-            g.setColor(inactiveColor);
-            if (gv != null) {
-                g.drawGlyphVector(gv, x, y);
-            } else {
-                g.setFont(text.font);
-                g.drawString(s, x, y);
-            }
-        } else if (text.haloRadius != null) {
-            g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
-            g.setColor(text.haloColor);
-            Shape textOutline;
-            if (gv == null) {
-                FontRenderContext frc = g.getFontRenderContext();
-                TextLayout tl = new TextLayout(s, text.font, frc);
-                textOutline = tl.getOutline(AffineTransform.getTranslateInstance(x, y));
-            } else {
-                textOutline = gv.getOutline(x, y);
-            }
-            g.draw(textOutline);
-            g.setStroke(new BasicStroke());
-            g.setColor(text.color);
-            g.fill(textOutline);
-        } else {
-            g.setColor(text.color);
-            if (gv != null) {
-                g.drawGlyphVector(gv, x, y);
-            } else {
-                g.setFont(text.font);
-                g.drawString(s, x, y);
-            }
-        }
-    }
-
-    /**
      * Worker function for drawing areas.
      *
@@ -400,10 +353,10 @@
      * polygons)
      * @param disabled If this should be drawn with a special disabled style.
-     * @param text The text to write on the area.
-     */
-    protected void drawArea(OsmPrimitive osm, Path2D.Double path, Color color,
+     * @param text Ignored. Use {@link #drawText(OsmPrimitive, TextLabel)} instead.
+     */
+    protected void drawArea(OsmPrimitive osm, MapViewPath path, Color color,
             MapImage fillImage, Float extent, Path2D.Double pfClip, boolean disabled, TextLabel text) {
         if (!isOutlineOnly && color.getAlpha() != 0) {
-            Shape area = path.createTransformedShape(mapState.getAffineTransform());
+            Shape area = path;
             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
             if (fillImage == null) {
@@ -450,30 +403,4 @@
             g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialiasing);
         }
-
-        drawAreaText(osm, text, path);
-    }
-
-    private void drawAreaText(OsmPrimitive osm, TextLabel text, Path2D.Double path) {
-        if (text != null && isShowNames() && isAreaVisible(path)) {
-            // abort if we can't compose the label to be rendered
-            if (text.labelCompositionStrategy == null) return;
-            String name = text.labelCompositionStrategy.compose(osm);
-            if (name == null || name.isEmpty()) return;
-
-            Shape area = path.createTransformedShape(mapState.getAffineTransform());
-            FontMetrics fontMetrics = g.getFontMetrics(orderFont); // if slow, use cache
-            Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
-
-            Rectangle2D centeredNBounds = text.getLabelPositionStrategy().findLabelPlacement(area, nb);
-            if (centeredNBounds != null) {
-                Font defaultFont = g.getFont();
-                int x = (int) (centeredNBounds.getMinX() - nb.getMinX());
-                int y = (int) (centeredNBounds.getMinY() - nb.getMinY());
-                displayText(null, name, x, y, osm.isDisabled(), text);
-                g.setFont(defaultFont);
-            } else if (Main.isTraceEnabled()) {
-                Main.trace("Couldn't find a correct label placement for "+osm+" / "+name);
-            }
-        }
     }
 
@@ -495,5 +422,7 @@
         if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
             for (PolyData pd : multipolygon.getCombinedPolygons()) {
-                Path2D.Double p = pd.get();
+                MapViewPath p = new MapViewPath(mapState);
+                p.appendFromEastNorth(pd.get());
+                p.setWindingRule(Path2D.WIND_EVEN_ODD);
                 Path2D.Double pfClip = null;
                 if (!isAreaVisible(p)) {
@@ -574,6 +503,9 @@
         g.setFont(text.font);
 
-        int x = (int) (Math.round(p.getInViewX()) + text.xOffset);
-        int y = (int) (Math.round(p.getInViewY()) + text.yOffset);
+        FontRenderContext frc = g.getFontRenderContext();
+        Rectangle2D bounds = text.font.getStringBounds(s, frc);
+
+        double x = Math.round(p.getInViewX()) + text.xOffset + bounds.getCenterX();
+        double y = Math.round(p.getInViewY()) + text.yOffset + bounds.getCenterY();
         /**
          *
@@ -591,6 +523,4 @@
             x += box.x + box.width + 2;
         } else {
-            FontRenderContext frc = g.getFontRenderContext();
-            Rectangle2D bounds = text.font.getStringBounds(s, frc);
             int textWidth = (int) bounds.getWidth();
             if (bs.hAlign == HorizontalTextAlignment.CENTER) {
@@ -604,5 +534,4 @@
             y += box.y + box.height;
         } else {
-            FontRenderContext frc = g.getFontRenderContext();
             LineMetrics metrics = text.font.getLineMetrics(s, frc);
             if (bs.vAlign == VerticalTextAlignment.ABOVE) {
@@ -616,5 +545,6 @@
             } else throw new AssertionError();
         }
-        displayText(null, s, x, y, n.isDisabled(), text);
+
+        displayText(n, text, s, bounds, new MapViewPositionAndRotation(mapState.getForView(x, y), 0));
         g.setFont(defaultFont);
     }
@@ -757,11 +687,10 @@
 
         forEachPolygon(osm, path -> {
-            Shape area = path.createTransformedShape(mapState.getAffineTransform());
-            Rectangle2D placement = iconPosition.findLabelPlacement(area, iconRect);
+            MapViewPositionAndRotation placement = iconPosition.findLabelPlacement(path, iconRect);
             if (placement == null) {
                 return;
             }
-            MapViewPoint p = mapState.getForView(placement.getCenterX(), placement.getCenterY());
-            drawIcon(p, img, disabled, selected, member, theta, (g, r) -> {
+            MapViewPoint p = placement.getPoint();
+            drawIcon(p, img, disabled, selected, member, theta + placement.getRotation(), (g, r) -> {
                 if (useStrokes) {
                     g.setStroke(new BasicStroke(2));
@@ -1071,41 +1000,4 @@
 
     /**
-     * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
-     * @author Michael Zangl
-     */
-    private static class HalfSegment {
-        /**
-         * start point of half segment (as length along the way)
-         */
-        final double start;
-        /**
-         * end point of half segment (as length along the way)
-         */
-        final double end;
-        /**
-         * quality factor (off screen / partly on screen / fully on screen)
-         */
-        final double quality;
-
-        /**
-         * Create a new half segment
-         * @param start The start along the way
-         * @param end The end of the segment
-         * @param quality A quality factor.
-         */
-        HalfSegment(double start, double end, double quality) {
-            super();
-            this.start = start;
-            this.end = end;
-            this.quality = quality;
-        }
-
-        @Override
-        public String toString() {
-            return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
-        }
-    }
-
-    /**
      * Draws a text for the given primitive
      * @param osm The primitive to draw the text for
@@ -1114,12 +1006,80 @@
      */
     public void drawText(OsmPrimitive osm, TextLabel text) {
-        PositionForAreaStrategy position = text.getLabelPositionStrategy();
-        if (position.supportsGlyphVector()) {
-            if (osm instanceof Way) {
-                // we might allow this for the outline of relations as well.
-                drawTextOnPath((Way) osm, text);
-            }
+        if (!isShowNames()) {
+            return;
+        }
+        String name = text.getString(osm);
+        if (name == null || name.isEmpty()) {
+            return;
+        }
+
+        FontMetrics fontMetrics = g.getFontMetrics(text.font); // if slow, use cache
+        Rectangle2D nb = fontMetrics.getStringBounds(name, g); // if slow, approximate by strlen()*maxcharbounds(font)
+
+        Font defaultFont = g.getFont();
+        forEachPolygon(osm, path -> {
+            //TODO: Ignore areas that are out of bounds.
+            PositionForAreaStrategy position = text.getLabelPositionStrategy();
+            MapViewPositionAndRotation center = text.getLabelPositionStrategy().findLabelPlacement(path, nb);
+            if (center != null) {
+                displayText(osm, text, name, nb, center);
+            } else if (position.supportsGlyphVector()) {
+                List<GlyphVector> gvs = Utils.getGlyphVectorsBidi(name, text.font, g.getFontRenderContext());
+
+                List<GlyphVector> translatedGvs = position.generateGlyphVectors(path, nb, gvs, isGlyphVectorDoubleTranslationBug(text.font));
+                displayText(() -> translatedGvs.forEach(gv -> g.drawGlyphVector(gv, 0, 0)),
+                        () -> translatedGvs.stream().collect(
+                        		() -> new Path2D.Double(),
+                        		(p, gv) -> p.append(gv.getOutline(0, 0), false),
+                        		(p1, p2) -> p1.append(p2, false)),
+                        osm.isDisabled(), text);
+            } else if (Main.isTraceEnabled()) {
+                Main.trace("Couldn't find a correct label placement for " + osm + " / " + name);
+            }
+        });
+        g.setFont(defaultFont);
+    }
+
+    private void displayText(OsmPrimitive osm, TextLabel text, String name, Rectangle2D nb,
+            MapViewPositionAndRotation center) {
+        AffineTransform at = AffineTransform.getTranslateInstance(center.getPoint().getInViewX(), center.getPoint().getInViewY());
+        at.rotate(center.getRotation());
+        at.translate(-nb.getCenterX(), -nb.getCenterY());
+        displayText(() -> {
+            AffineTransform defaultTransform = g.getTransform();
+            g.setTransform(at);
+            g.setFont(text.font);
+            g.drawString(name, 0, 0);
+            g.setTransform(defaultTransform);
+        }, () -> {
+            FontRenderContext frc = g.getFontRenderContext();
+            TextLayout tl = new TextLayout(name, text.font, frc);
+            return tl.getOutline(at);
+        }, osm.isDisabled(), text);
+    }
+
+    /**
+     * Displays text at specified position including its halo, if applicable.
+     *
+     * @param fill The function that fills the text
+     * @param outline The function to draw the outline
+     * @param disabled {@code true} if element is disabled (filtered out)
+     * @param text text style to use
+     */
+    private void displayText(Runnable fill, Supplier<Shape> outline, boolean disabled, TextLabel text) {
+        if (isInactiveMode || disabled) {
+            g.setColor(inactiveColor);
+            fill.run();
+        } else if (text.haloRadius != null) {
+            g.setStroke(new BasicStroke(2*text.haloRadius, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND));
+            g.setColor(text.haloColor);
+            Shape textOutline = outline.get();
+            g.draw(textOutline);
+            g.setStroke(new BasicStroke());
+            g.setColor(text.color);
+            g.fill(textOutline);
         } else {
-            forEachPolygon(osm, path -> drawAreaText(osm, text, path));
+            g.setColor(text.color);
+            fill.run();
         }
     }
@@ -1130,5 +1090,5 @@
      * @param consumer The consumer to call.
      */
-    private void forEachPolygon(OsmPrimitive osm, Consumer<Path2D.Double> consumer) {
+    private void forEachPolygon(OsmPrimitive osm, Consumer<MapViewPath> consumer) {
         if (osm instanceof Way) {
             consumer.accept(getPath((Way) osm));
@@ -1137,5 +1097,7 @@
             if (!multipolygon.getOuterWays().isEmpty()) {
                 for (PolyData pd : multipolygon.getCombinedPolygons()) {
-                    consumer.accept(pd.get());
+                    MapViewPath path = new MapViewPath(mapState);
+                    path.appendFromEastNorth(pd.get());
+                    consumer.accept(path);
                 }
             }
@@ -1147,146 +1109,9 @@
      * @param way The way to draw the text on.
      * @param text The text definition (font/.../text content) to draw.
-     */
+     * @deprecated Use {@link #drawText(OsmPrimitive, TextLabel)} instead.
+     */
+    @Deprecated
     public void drawTextOnPath(Way way, TextLabel text) {
-        if (way == null || text == null)
-            return;
-        String name = text.getString(way);
-        if (name == null || name.isEmpty())
-            return;
-
-        FontMetrics fontMetrics = g.getFontMetrics(text.font);
-        Rectangle2D rec = fontMetrics.getStringBounds(name, g);
-
-        Rectangle bounds = g.getClipBounds();
-
-        List<MapViewPoint> points = way.getNodes().stream().map(mapState::getPointFor).collect(Collectors.toList());
-
-        // find half segments that are long enough to draw text on (don't draw text over the cross hair in the center of each segment)
-        List<HalfSegment> longHalfSegment = new ArrayList<>();
-
-        double pathLength = computePath(2 * (rec.getWidth() + 4), bounds, points, longHalfSegment);
-
-        if (rec.getWidth() > pathLength)
-            return;
-
-        double t1, t2;
-
-        if (!longHalfSegment.isEmpty()) {
-            // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
-            Optional<HalfSegment> besto = longHalfSegment.stream().max(
-                    Comparator.comparingDouble(segment ->
-                        segment.quality - 1e-5 * Math.abs(0.5 * (segment.end + segment.start) - 0.5 * pathLength)
-                    ));
-            if (!besto.isPresent())
-                throw new IllegalStateException("Unable to find the segment with the best quality for " + way);
-            HalfSegment best = besto.get();
-            double remaining = best.end - best.start - rec.getWidth(); // total space left and right from the text
-            // The space left and right of the text should be distributed 20% - 80% (towards the center),
-            // but the smaller space should not be less than 7 px.
-            // However, if the total remaining space is less than 14 px, then distribute it evenly.
-            double smallerSpace = Math.min(Math.max(0.2 * remaining, 7), 0.5 * remaining);
-            if ((best.end + best.start)/2 < pathLength/2) {
-                t2 = best.end - smallerSpace;
-                t1 = t2 - rec.getWidth();
-            } else {
-                t1 = best.start + smallerSpace;
-                t2 = t1 + rec.getWidth();
-            }
-        } else {
-            // doesn't fit into one half-segment -> just put it in the center of the way
-            t1 = pathLength/2 - rec.getWidth()/2;
-            t2 = pathLength/2 + rec.getWidth()/2;
-        }
-        t1 /= pathLength;
-        t2 /= pathLength;
-
-        double[] p1 = pointAt(t1, points, pathLength);
-        double[] p2 = pointAt(t2, points, pathLength);
-
-        if (p1 == null || p2 == null)
-            return;
-
-        double angleOffset;
-        double offsetSign;
-        double tStart;
-
-        if (p1[0] < p2[0] &&
-                p1[2] < Math.PI/2 &&
-                p1[2] > -Math.PI/2) {
-            angleOffset = 0;
-            offsetSign = 1;
-            tStart = t1;
-        } else {
-            angleOffset = Math.PI;
-            offsetSign = -1;
-            tStart = t2;
-        }
-
-        List<GlyphVector> gvs = Utils.getGlyphVectorsBidi(name, text.font, g.getFontRenderContext());
-        double gvOffset = 0;
-        for (GlyphVector gv : gvs) {
-            double gvWidth = gv.getLogicalBounds().getBounds2D().getWidth();
-            for (int i = 0; i < gv.getNumGlyphs(); ++i) {
-                Rectangle2D rect = gv.getGlyphLogicalBounds(i).getBounds2D();
-                double t = tStart + offsetSign * (gvOffset + rect.getX() + rect.getWidth()/2) / pathLength;
-                double[] p = pointAt(t, points, pathLength);
-                if (p != null) {
-                    AffineTransform trfm = AffineTransform.getTranslateInstance(p[0] - rect.getX(), p[1]);
-                    trfm.rotate(p[2]+angleOffset);
-                    double off = -rect.getY() - rect.getHeight()/2 + text.yOffset;
-                    trfm.translate(-rect.getWidth()/2, off);
-                    if (isGlyphVectorDoubleTranslationBug(text.font)) {
-                        // scale the translation components by one half
-                        AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(), -0.5 * trfm.getTranslateY());
-                        tmp.concatenate(trfm);
-                        trfm = tmp;
-                    }
-                    gv.setGlyphTransform(i, trfm);
-                }
-            }
-            displayText(gv, null, 0, 0, way.isDisabled(), text);
-            gvOffset += gvWidth;
-        }
-    }
-
-    private static double computePath(double minSegmentLength, Rectangle bounds, List<MapViewPoint> points,
-            List<HalfSegment> longHalfSegment) {
-        MapViewPoint lastPoint = points.get(0);
-        double pathLength = 0;
-        for (MapViewPoint p : points.subList(1, points.size())) {
-            double segmentLength = p.distanceToInView(lastPoint);
-            if (segmentLength > minSegmentLength) {
-                Point2D center = new Point2D.Double((lastPoint.getInViewX() + p.getInViewX())/2, (lastPoint.getInViewY() + p.getInViewY())/2);
-                double q = computeQuality(bounds, lastPoint, center);
-                // prefer the first one for quality equality.
-                longHalfSegment.add(new HalfSegment(pathLength, pathLength + segmentLength / 2, q));
-
-                q = 0;
-                if (bounds != null) {
-                    if (bounds.contains(center) && bounds.contains(p.getInView())) {
-                        q = 2;
-                    } else if (bounds.contains(center) || bounds.contains(p.getInView())) {
-                        q = 1;
-                    }
-                }
-                longHalfSegment.add(new HalfSegment(pathLength + segmentLength / 2, pathLength + segmentLength, q));
-            }
-            pathLength += segmentLength;
-            lastPoint = p;
-        }
-        return pathLength;
-    }
-
-    private static double computeQuality(Rectangle bounds, MapViewPoint p1, Point2D p2) {
-        double q = 0;
-        if (bounds != null) {
-            if (bounds.contains(p1.getInView())) {
-                q += 1;
-            }
-            if (bounds.contains(p2)) {
-                q += 1;
-            }
-        }
-        return q;
+        // NOP.
     }
 
@@ -1484,20 +1309,10 @@
     }
 
-    private static Path2D.Double getPath(Way w) {
-        Path2D.Double path = new Path2D.Double();
-        boolean initial = true;
-        for (Node n : w.getNodes()) {
-            EastNorth p = n.getEastNorth();
-            if (p != null) {
-                if (initial) {
-                    path.moveTo(p.getX(), p.getY());
-                    initial = false;
-                } else {
-                    path.lineTo(p.getX(), p.getY());
-                }
-            }
-        }
+    private MapViewPath getPath(Way w) {
+        MapViewPath path = new MapViewPath(mapState);
         if (w.isClosed()) {
-            path.closePath();
+            path.appendClosed(w.getNodes(), false);
+        } else {
+            path.append(w.getNodes(), false);
         }
         return path;
@@ -1631,28 +1446,4 @@
     public boolean isShowNames() {
         return showNames;
-    }
-
-    private static double[] pointAt(double t, List<MapViewPoint> poly, double pathLength) {
-        double totalLen = t * pathLength;
-        double curLen = 0;
-        double dx, dy;
-        double segLen;
-
-        // Yes, it is inefficient to iterate from the beginning for each glyph.
-        // Can be optimized if it turns out to be slow.
-        for (int i = 1; i < poly.size(); ++i) {
-            dx = poly.get(i).getInViewX() - poly.get(i - 1).getInViewX();
-            dy = poly.get(i).getInViewY() - poly.get(i - 1).getInViewY();
-            segLen = Math.sqrt(dx*dx + dy*dy);
-            if (totalLen > curLen + segLen) {
-                curLen += segLen;
-                continue;
-            }
-            return new double[] {
-                    poly.get(i - 1).getInViewX() + (totalLen - curLen) / segLen * dx,
-                    poly.get(i - 1).getInViewY() + (totalLen - curLen) / segLen * dy,
-                    Math.atan2(dy, dx)};
-        }
-        return null;
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/draw/MapViewPath.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/draw/MapViewPath.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/gui/draw/MapViewPath.java	(revision 11748)
@@ -5,4 +5,5 @@
 import java.awt.Shape;
 import java.awt.Stroke;
+import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
 
@@ -44,4 +45,13 @@
 
     /**
+     * Gets the map view state this path is used for.
+     * @return The state.
+     * @since 11748
+     */
+    public MapViewState getMapViewState() {
+        return state;
+    }
+
+    /**
      * Move the cursor to the given node.
      * @param n The node
@@ -171,4 +181,40 @@
 
     /**
+     * Converts a path in east/north coordinates to view space.
+     * @param path The path
+     * @since 11748
+     */
+    public void appendFromEastNorth(Path2D.Double path) {
+        new AbstractPathVisitor() {
+            @Override
+            void visitMoveTo(double x, double y) {
+                moveTo(new EastNorth(x, y));
+            }
+
+            @Override
+            void visitLineTo(double x, double y) {
+                lineTo(new EastNorth(x, y));
+            }
+
+            @Override
+            void visitClose() {
+                closePath();
+            }
+        }.visit(path);
+    }
+
+    /**
+     * Visits all segments of this path.
+     * @param consumer The consumer to send path segments to
+     * @return the total line length
+     * @since 11748
+     */
+    public double visitLine(PathSegmentConsumer consumer) {
+        LineVisitor visitor = new LineVisitor(consumer);
+        visitor.visit(this);
+        return visitor.inLineOffset;
+    }
+
+    /**
      * Compute a line that is similar to the current path expect for that parts outside the screen are skipped using moveTo commands.
      *
@@ -229,4 +275,12 @@
     }
 
+    /**
+     * Gets the length of the way in visual space.
+     * @return The length.
+     * @since 11748
+     */
+    public double getLength() {
+        return visitLine((inLineOffset, start, end, startIsOldEnd) -> { });
+    }
 
     /**
@@ -246,39 +300,15 @@
          */
         void addLineBetween(double inLineOffset, MapViewPoint start, MapViewPoint end, boolean startIsOldEnd);
-
-    }
-
-    private class ClampingPathVisitor {
-        private final MapViewRectangle clip;
-        private final PathSegmentConsumer consumer;
-        protected double strokeProgress;
-        private final double strokeLength;
-        private MapViewPoint lastMoveTo;
-
-        private MapViewPoint cursor;
-        private boolean cursorIsActive;
-
-        /**
-         * Create a new {@link ClampingPathVisitor}
-         * @param clip View clip rectangle
-         * @param strokeOffset Initial stroke offset
-         * @param strokeLength Total length of a stroke sequence
-         * @param consumer The consumer to notify of the path segments.
-         */
-        ClampingPathVisitor(MapViewRectangle clip, double strokeOffset, double strokeLength, PathSegmentConsumer consumer) {
-            this.clip = clip;
-            this.strokeProgress = Math.min(strokeLength - strokeOffset, 0);
-            this.strokeLength = strokeLength;
-            this.consumer = consumer;
-        }
-
+    }
+
+    private abstract static class AbstractPathVisitor {
         /**
          * Append a path to this one. The path is clipped to the current view.
-         * @param mapViewPath The iterator
+         * @param path The iterator
          * @return true if adding the path was successful.
          */
-        public boolean visit(MapViewPath mapViewPath) {
+        public boolean visit(Path2D.Double path) {
             double[] coords = new double[8];
-            PathIterator it = mapViewPath.getPathIterator(null);
+            PathIterator it = path.getPathIterator(null);
             while (!it.isDone()) {
                 int type = it.currentSegment(coords);
@@ -294,5 +324,5 @@
                     break;
                 default:
-                    // cannot handle this shape - this should be very rare. We let Java2D do the clipping.
+                    // cannot handle this shape - this should be very rare and not happening in OSM draw code.
                     return false;
                 }
@@ -302,20 +332,92 @@
         }
 
+        abstract void visitClose();
+
+        abstract void visitMoveTo(double x, double y);
+
+        abstract void visitLineTo(double x, double y);
+    }
+
+    private abstract class AbstractMapPathVisitor extends AbstractPathVisitor {
+        private MapViewPoint lastMoveTo;
+
+        @Override
+        void visitMoveTo(double x, double y) {
+            MapViewPoint move = state.getForView(x, y);
+            lastMoveTo = move;
+            visitMoveTo(move);
+        }
+
+        abstract void visitMoveTo(MapViewPoint p);
+
+        @Override
+        void visitLineTo(double x, double y) {
+            visitLineTo(state.getForView(x, y));
+        }
+
+        abstract void visitLineTo(MapViewPoint p);
+
+        @Override
         void visitClose() {
-            drawLineTo(lastMoveTo);
-        }
-
-        void visitMoveTo(double x, double y) {
-            MapViewPoint point = state.getForView(x, y);
-            lastMoveTo = point;
+            visitLineTo(lastMoveTo);
+        }
+    }
+
+    private final class LineVisitor extends AbstractMapPathVisitor {
+        private final PathSegmentConsumer consumer;
+        private MapViewPoint last;
+        private double inLineOffset = 0;
+        private boolean startIsOldEnd = false;
+
+        LineVisitor(PathSegmentConsumer consumer) {
+            this.consumer = consumer;
+        }
+
+        @Override
+        void visitMoveTo(MapViewPoint p) {
+            last = p;
+            startIsOldEnd = false;
+        }
+
+        @Override
+        void visitLineTo(MapViewPoint p) {
+            consumer.addLineBetween(inLineOffset, last, p, startIsOldEnd);
+            inLineOffset += last.distanceToInView(p);
+            last = p;
+            startIsOldEnd = true;
+        }
+    }
+
+    private class ClampingPathVisitor extends AbstractMapPathVisitor {
+        private final MapViewRectangle clip;
+        private final PathSegmentConsumer consumer;
+        protected double strokeProgress;
+        private final double strokeLength;
+
+        private MapViewPoint cursor;
+        private boolean cursorIsActive;
+
+        /**
+         * Create a new {@link ClampingPathVisitor}
+         * @param clip View clip rectangle
+         * @param strokeOffset Initial stroke offset
+         * @param strokeLength Total length of a stroke sequence
+         * @param consumer The consumer to notify of the path segments.
+         */
+        ClampingPathVisitor(MapViewRectangle clip, double strokeOffset, double strokeLength, PathSegmentConsumer consumer) {
+            this.clip = clip;
+            this.strokeProgress = Math.min(strokeLength - strokeOffset, 0);
+            this.strokeLength = strokeLength;
+            this.consumer = consumer;
+        }
+
+        @Override
+        void visitMoveTo(MapViewPoint point) {
             cursor = point;
             cursorIsActive = false;
         }
 
-        void visitLineTo(double x, double y) {
-            drawLineTo(state.getForView(x, y));
-        }
-
-        private void drawLineTo(MapViewPoint next) {
+        @Override
+        void visitLineTo(MapViewPoint next) {
             MapViewPoint entry = clip.getLineEntry(cursor, next);
             if (entry != null) {
@@ -348,3 +450,4 @@
         }
     }
+
 }
Index: trunk/src/org/openstreetmap/josm/gui/draw/MapViewPositionAndRotation.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/draw/MapViewPositionAndRotation.java	(revision 11748)
+++ trunk/src/org/openstreetmap/josm/gui/draw/MapViewPositionAndRotation.java	(revision 11748)
@@ -0,0 +1,49 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.draw;
+
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+
+/**
+ * A map view point combined with a rotation angle.
+ *
+ * @author Michael Zangl
+ * @since 11748
+ */
+public class MapViewPositionAndRotation {
+
+    private final MapViewPoint point;
+
+    private final double theta;
+
+    /**
+     * Create a new {@link MapViewPositionAndRotation}
+     * @param point the point
+     * @param theta the rotation
+     */
+    public MapViewPositionAndRotation(MapViewPoint point, double theta) {
+        super();
+        this.point = point;
+        this.theta = theta;
+    }
+
+    /**
+     * Gets the point.
+     * @return The point
+     */
+    public MapViewPoint getPoint() {
+        return point;
+    }
+
+    /**
+     * Gets the rotation
+     * @return the rotation
+     */
+    public double getRotation() {
+        return theta;
+    }
+
+    @Override
+    public String toString() {
+        return "MapViewPositionAndRotation [" + point + ", theta=" + theta + "]";
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaIconElement.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaIconElement.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/AreaIconElement.java	(revision 11748)
@@ -9,4 +9,6 @@
 import org.openstreetmap.josm.gui.mappaint.Cascade;
 import org.openstreetmap.josm.gui.mappaint.Environment;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PartiallyInsideAreaStrategy;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
 import org.openstreetmap.josm.gui.util.RotationAngle;
 
@@ -30,5 +32,5 @@
      * The position of the icon inside the area.
      */
-    private final PositionForAreaStrategy iconPosition = PositionForAreaStrategy.PARTIALY_INSIDE;
+    private final PositionForAreaStrategy iconPosition = PartiallyInsideAreaStrategy.INSTANCE;
 
     protected AreaIconElement(Cascade c, MapImage iconImage, RotationAngle iconImageAngle) {
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineTextElement.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineTextElement.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineTextElement.java	(revision 11748)
@@ -5,5 +5,4 @@
 
 import org.openstreetmap.josm.data.osm.OsmPrimitive;
-import org.openstreetmap.josm.data.osm.Way;
 import org.openstreetmap.josm.data.osm.visitor.paint.MapPaintSettings;
 import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
@@ -43,6 +42,5 @@
     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
             boolean selected, boolean outermember, boolean member) {
-        Way w = (Way) primitive;
-        painter.drawTextOnPath(w, text);
+        // nop
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextElement.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextElement.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextElement.java	(revision 11748)
@@ -11,4 +11,5 @@
 import org.openstreetmap.josm.gui.mappaint.Environment;
 import org.openstreetmap.josm.gui.mappaint.Keyword;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy;
 
 /**
@@ -58,5 +59,5 @@
             return null;
         }
-        return new TextElement(c, text.withPosition(PositionForAreaStrategy.INSIDE));
+        return new TextElement(c, text.withPosition(CompletelyInsideAreaStrategy.INSTANCE));
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java	(revision 11747)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java	(revision 11748)
@@ -15,5 +15,6 @@
 import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.StaticLabelCompositionStrategy;
 import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.TagLookupCompositionStrategy;
-import org.openstreetmap.josm.gui.mappaint.styleelement.PositionForAreaStrategy.CompletelyInsideAreaStrategy;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.CompletelyInsideAreaStrategy;
+import org.openstreetmap.josm.gui.mappaint.styleelement.placement.PositionForAreaStrategy;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
 import org.openstreetmap.josm.tools.Utils;
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/CompletelyInsideAreaStrategy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/CompletelyInsideAreaStrategy.java	(revision 11748)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/CompletelyInsideAreaStrategy.java	(revision 11748)
@@ -0,0 +1,97 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.styleelement.placement;
+
+import java.awt.Rectangle;
+import java.awt.geom.Rectangle2D;
+
+import org.openstreetmap.josm.gui.MapViewState;
+import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
+
+/**
+ * Places the label / icon so that it is completely inside the area.
+ *
+ * @author Michael Zangl
+ * @since 11722
+ * @since 11748 moved to own file
+ */
+public class CompletelyInsideAreaStrategy implements PositionForAreaStrategy {
+    /**
+     * An instance of this class.
+     */
+    public static final CompletelyInsideAreaStrategy INSTANCE = new CompletelyInsideAreaStrategy();
+
+    @Override
+    public MapViewPositionAndRotation findLabelPlacement(MapViewPath path, Rectangle2D nb) {
+        // Using the Centroid is Nicer for buildings like: +--------+
+        // but this needs to be fast.  As most houses are  |   42   |
+        // boxes anyway, the center of the bounding box    +---++---+
+        // will have to do.                                    ++
+        // Centroids are not optimal either, just imagine a U-shaped house.
+
+        Rectangle pb = path.getBounds();
+
+        // quick check to see if label box is smaller than primitive box
+        if (pb.width < nb.getWidth() || pb.height < nb.getHeight()) {
+            return null;
+        }
+
+        final double w = pb.width - nb.getWidth();
+        final double h = pb.height - nb.getHeight();
+
+        final int x2 = pb.x + (int) (w / 2.0);
+        final int y2 = pb.y + (int) (h / 2.0);
+
+        final int nbw = (int) nb.getWidth();
+        final int nbh = (int) nb.getHeight();
+
+        Rectangle centeredNBounds = new Rectangle(x2, y2, nbw, nbh);
+
+        // slower check to see if label is displayed inside primitive shape
+        if (path.contains(centeredNBounds)) {
+            return centerOf(path.getMapViewState(), centeredNBounds);
+        }
+
+        // if center position (C) is not inside osm shape, try naively some other positions as follows:
+        final int x1 = pb.x + (int) (.25 * w);
+        final int x3 = pb.x + (int) (.75 * w);
+        final int y1 = pb.y + (int) (.25 * h);
+        final int y3 = pb.y + (int) (.75 * h);
+        // +-----------+
+        // |  5  1  6  |
+        // |  4  C  2  |
+        // |  8  3  7  |
+        // +-----------+
+        Rectangle[] candidates = new Rectangle[] {
+                new Rectangle(x2, y1, nbw, nbh),
+                new Rectangle(x3, y2, nbw, nbh),
+                new Rectangle(x2, y3, nbw, nbh),
+                new Rectangle(x1, y2, nbw, nbh),
+                new Rectangle(x1, y1, nbw, nbh),
+                new Rectangle(x3, y1, nbw, nbh),
+                new Rectangle(x3, y3, nbw, nbh),
+                new Rectangle(x1, y3, nbw, nbh)
+        };
+        // Dumb algorithm to find a better placement. We could surely find a smarter one but it should
+        // solve most of building issues with only few calculations (8 at most)
+        for (int i = 0; i < candidates.length; i++) {
+            centeredNBounds = candidates[i];
+            if (path.contains(centeredNBounds)) {
+                return centerOf(path.getMapViewState(), centeredNBounds);
+            }
+        }
+
+        // none found
+        return null;
+    }
+
+    private MapViewPositionAndRotation centerOf(MapViewState mapViewState, Rectangle centeredNBounds) {
+        return new MapViewPositionAndRotation(
+                mapViewState.getForView(centeredNBounds.getCenterX(), centeredNBounds.getCenterY()), 0);
+    }
+
+    @Override
+    public boolean supportsGlyphVector() {
+        return false;
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/OnLineStrategy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/OnLineStrategy.java	(revision 11748)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/OnLineStrategy.java	(revision 11748)
@@ -0,0 +1,314 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.styleelement.placement;
+
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.IntStream;
+
+import org.openstreetmap.josm.gui.MapViewState.MapViewPoint;
+import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.MapViewPath.PathSegmentConsumer;
+import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
+
+/**
+ * Places the label onto the line.
+ *
+ * @author Michael Zangl
+ * @since 11722
+ * @since 11748 moved to own file
+ */
+public class OnLineStrategy implements PositionForAreaStrategy {
+    /**
+     * An instance of this class.
+     */
+    public static final OnLineStrategy INSTANCE = new OnLineStrategy(0);
+
+    private final double yOffset;
+
+    /**
+     * Create a new strategy that places the text on the line.
+     * @param yOffset The offset sidewards to the line.
+     */
+    public OnLineStrategy(double yOffset) {
+        this.yOffset = yOffset;
+    }
+
+    @Override
+    public MapViewPositionAndRotation findLabelPlacement(MapViewPath path, Rectangle2D nb) {
+        return findOptimalWayPosition(nb, path).map(best -> {
+            MapViewPoint center = best.start.interpolate(best.end, .5);
+            return new MapViewPositionAndRotation(center, upsideTheta(best));
+        }).orElse(null);
+    }
+
+    private double upsideTheta(HalfSegment best) {
+        double theta = theta(best.start, best.end);
+        if (theta < -Math.PI / 2) {
+            return theta + Math.PI;
+        } else if (theta > Math.PI / 2) {
+            return theta - Math.PI;
+        } else {
+            return theta;
+        }
+    }
+
+    @Override
+    public boolean supportsGlyphVector() {
+        return true;
+    }
+
+    @Override
+    public List<GlyphVector> generateGlyphVectors(MapViewPath path, Rectangle2D nb, List<GlyphVector> gvs,
+            boolean isDoubleTranslationBug) {
+        double middleOffset = findOptimalWayPosition(nb, path).map(segment -> segment.offset)
+                .orElse(path.getLength() / 2);
+
+        UpsideComputingVisitor upside = new UpsideComputingVisitor(middleOffset - nb.getWidth() / 2,
+                middleOffset + nb.getWidth() / 2);
+        path.visitLine(upside);
+
+        boolean doRotateText = upside.shouldMirrorText();
+        List<OffsetGlyph> offsetGlyphs = computeOffsetGlyphs(gvs,
+                middleOffset + (doRotateText ? 1 : -1) * nb.getWidth() / 2, doRotateText);
+
+        path.visitLine(new GlyphRotatingVisitor(offsetGlyphs, isDoubleTranslationBug));
+        return gvs;
+    }
+
+    /**
+     * Create a list of glyphs with an offset along the way
+     * @param gvs The list of glyphs
+     * @param startOffset The offset in the line
+     * @param rotateText Rotate the text by 180°
+     * @return The list of glyphs.
+     */
+    private List<OffsetGlyph> computeOffsetGlyphs(List<GlyphVector> gvs, double startOffset, boolean rotateText) {
+        double offset = startOffset;
+        ArrayList<OffsetGlyph> offsetGlyphs = new ArrayList<>();
+        for (GlyphVector gv : gvs) {
+            double gvOffset = offset;
+            IntStream.range(0, gv.getNumGlyphs())
+                    .mapToObj(i -> new OffsetGlyph(gvOffset, rotateText, gv, i))
+                    .forEach(offsetGlyphs::add);
+            offset += (rotateText ? -1 : 1) + gv.getLogicalBounds().getBounds2D().getWidth();
+        }
+        Collections.sort(offsetGlyphs, Comparator.comparing(e -> e.offset));
+        return offsetGlyphs;
+    }
+
+    private Optional<HalfSegment> findOptimalWayPosition(Rectangle2D rect, MapViewPath path) {
+        // find half segments that are long enough to draw text on (don't draw text over the cross hair in the center of each segment)
+        List<HalfSegment> longHalfSegment = new ArrayList<>();
+        double minSegmentLength = 2 * (rect.getWidth() + 4);
+        double length = path.visitLine((inLineOffset, start, end, startIsOldEnd) -> {
+            double segmentLength = start.distanceToInView(end);
+            if (segmentLength > minSegmentLength) {
+                MapViewPoint center = start.interpolate(end, .5);
+                double q = computeQuality(start, center);
+                // prefer the first one for quality equality.
+                longHalfSegment.add(new HalfSegment(start, center, q + .1, inLineOffset + .25 * segmentLength));
+
+                q = computeQuality(center, end);
+                longHalfSegment.add(new HalfSegment(center, end, q, inLineOffset + .75 * segmentLength));
+            }
+        });
+
+        // find the segment with the best quality. If there are several with best quality, the one close to the center is prefered.
+        return longHalfSegment.stream().max(
+                Comparator.comparingDouble(segment -> segment.quality - 1e-5 * Math.abs(segment.offset - length / 2)));
+    }
+
+    private static double computeQuality(MapViewPoint p1, MapViewPoint p2) {
+        double q = 0;
+        if (p1.isInView()) {
+            q += 1;
+        }
+        if (p2.isInView()) {
+            q += 1;
+        }
+        return q;
+    }
+
+    /**
+     * A half segment that can be used to place text on it. Used in the drawTextOnPath algorithm.
+     * @author Michael Zangl
+     */
+    private static class HalfSegment {
+        /**
+         * start point of half segment
+         */
+        private final MapViewPoint start;
+
+        /**
+         * end point of half segment
+         */
+        private final MapViewPoint end;
+
+        /**
+         * quality factor (off screen / partly on screen / fully on screen)
+         */
+        private final double quality;
+
+        /**
+         * The offset in the path.
+         */
+        private final double offset;
+
+        /**
+         * Create a new half segment
+         * @param start The start along the way
+         * @param end The end of the segment
+         * @param quality A quality factor.
+         * @param offset The offset in the path.
+         */
+        HalfSegment(MapViewPoint start, MapViewPoint end, double quality, double offset) {
+            super();
+            this.start = start;
+            this.end = end;
+            this.quality = quality;
+            this.offset = offset;
+        }
+
+        @Override
+        public String toString() {
+            return "HalfSegment [start=" + start + ", end=" + end + ", quality=" + quality + "]";
+        }
+    }
+
+    /**
+     * A visitor that computes the side of the way that is the upper one for each segment and computes the dominant upper side of the way.
+     * This is used to always place at least 50% of the text correctly.
+     */
+    private class UpsideComputingVisitor implements PathSegmentConsumer {
+
+        private final double startOffset;
+        private final double endOffset;
+
+        private double upsideUpLines = 0;
+        private double upsideDownLines = 0;
+
+        UpsideComputingVisitor(double startOffset, double endOffset) {
+            super();
+            this.startOffset = startOffset;
+            this.endOffset = endOffset;
+        }
+
+        @Override
+        public void addLineBetween(double inLineOffset, MapViewPoint start, MapViewPoint end, boolean startIsOldEnd) {
+            if (inLineOffset > endOffset) {
+                return;
+            }
+            double length = start.distanceToInView(end);
+            if (inLineOffset + length < startOffset) {
+                return;
+            }
+
+            double segmentStart = Math.max(inLineOffset, startOffset);
+            double segmentEnd = Math.min(inLineOffset + length, endOffset);
+
+            double segmentLength = segmentEnd - segmentStart;
+
+            if (start.getInViewX() < end.getInViewX()) {
+                upsideUpLines += segmentLength;
+            } else {
+                upsideDownLines += segmentLength;
+            }
+        }
+
+        public boolean shouldMirrorText() {
+            return upsideUpLines < upsideDownLines;
+        }
+    }
+
+    /**
+     * Rotate the glyphs along a path.
+     */
+    private class GlyphRotatingVisitor implements PathSegmentConsumer {
+        private final Iterator<OffsetGlyph> gvs;
+        private final boolean isDoubleTranslationBug;
+        private OffsetGlyph next;
+
+        GlyphRotatingVisitor(List<OffsetGlyph> gvs, boolean isDoubleTranslationBug) {
+            this.isDoubleTranslationBug = isDoubleTranslationBug;
+            this.gvs = gvs.iterator();
+            takeNext();
+            while (next != null && next.offset < 0) {
+                // skip them
+                takeNext();
+            }
+        }
+
+        private void takeNext() {
+            if (gvs.hasNext()) {
+                next = gvs.next();
+            } else {
+                next = null;
+            }
+        }
+
+        @Override
+        public void addLineBetween(double inLineOffset, MapViewPoint start, MapViewPoint end, boolean startIsOldEnd) {
+            double segLength = start.distanceToInView(end);
+            double segEnd = inLineOffset + segLength;
+            double theta = theta(start, end);
+            while (next != null && next.offset < segEnd) {
+                Rectangle2D rect = next.getBounds();
+                double centerY = 0;
+                MapViewPoint p = start.interpolate(end, (next.offset - inLineOffset) / segLength);
+
+                AffineTransform trfm = new AffineTransform();
+                trfm.translate(-rect.getCenterX(), -centerY);
+                trfm.translate(p.getInViewX(), p.getInViewY());
+                trfm.rotate(theta + next.preRotate, rect.getWidth() / 2, centerY);
+                trfm.translate(0, next.glyph.getFont().getSize2D() * .25);
+                trfm.translate(0, yOffset);
+                if (isDoubleTranslationBug) {
+                    // scale the translation components by one half
+                    AffineTransform tmp = AffineTransform.getTranslateInstance(-0.5 * trfm.getTranslateX(),
+                            -0.5 * trfm.getTranslateY());
+                    tmp.concatenate(trfm);
+                    trfm = tmp;
+                }
+                next.glyph.setGlyphTransform(next.glyphIndex, trfm);
+                takeNext();
+            }
+        }
+    }
+
+    private static class OffsetGlyph {
+        private final double offset;
+        private final double preRotate;
+        private final GlyphVector glyph;
+        private final int glyphIndex;
+
+        OffsetGlyph(double offset, boolean rotateText, GlyphVector glyph, int glyphIndex) {
+            super();
+            this.preRotate = rotateText ? Math.PI : 0;
+            this.glyph = glyph;
+            this.glyphIndex = glyphIndex;
+            Rectangle2D rect = getBounds();
+            this.offset = offset + (rotateText ? -1 : 1) * (rect.getX() + rect.getWidth() / 2);
+        }
+
+        private Rectangle2D getBounds() {
+            return glyph.getGlyphLogicalBounds(glyphIndex).getBounds2D();
+        }
+
+        @Override
+        public String toString() {
+            return "OffsetGlyph [offset=" + offset + ", preRotate=" + preRotate + ", glyphIndex=" + glyphIndex + "]";
+        }
+
+    }
+
+    private static double theta(MapViewPoint start, MapViewPoint end) {
+        return Math.atan2(end.getInViewY() - start.getInViewY(), end.getInViewX() - start.getInViewX());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PartiallyInsideAreaStrategy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PartiallyInsideAreaStrategy.java	(revision 11748)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PartiallyInsideAreaStrategy.java	(revision 11748)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.styleelement.placement;
+
+import java.awt.geom.Rectangle2D;
+
+import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
+
+/**
+ * A strategy that places the label / icon so that is is on the area.
+ *
+ * The center of that place should be in the area, but the icon / label may overlap on the edges.
+ *
+ * @author Michael Zangl
+ * @since 11722
+ * @since 11748 moved to own file
+ */
+public class PartiallyInsideAreaStrategy extends CompletelyInsideAreaStrategy {
+    /**
+     * An instance of this class.
+     */
+    public static final PartiallyInsideAreaStrategy INSTANCE = new PartiallyInsideAreaStrategy();
+
+    @Override
+    public MapViewPositionAndRotation findLabelPlacement(MapViewPath path, Rectangle2D nb) {
+        MapViewPositionAndRotation inside = super.findLabelPlacement(path, nb);
+        if (inside != null) {
+            return inside;
+        }
+
+        double nbdx = Math.max(0, (nb.getWidth() - 20) / 2);
+        double nbdy = Math.max(0, (nb.getHeight() - 10) / 2);
+
+        if (nbdx < .5 && nbdy < .5) {
+            // we can't do any better
+            return null;
+        } else {
+            Rectangle2D smallNb = new Rectangle2D.Double(nb.getX() + nbdx, nb.getY() + nbdy,
+                    nb.getWidth() - 2 * nbdx, nb.getHeight() - 2 * nbdy);
+            return super.findLabelPlacement(path, smallNb);
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PositionForAreaStrategy.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PositionForAreaStrategy.java	(revision 11748)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/styleelement/placement/PositionForAreaStrategy.java	(revision 11748)
@@ -0,0 +1,80 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint.styleelement.placement;
+
+import java.awt.font.GlyphVector;
+import java.awt.geom.Rectangle2D;
+import java.util.List;
+
+import org.openstreetmap.josm.gui.draw.MapViewPath;
+import org.openstreetmap.josm.gui.draw.MapViewPositionAndRotation;
+import org.openstreetmap.josm.gui.mappaint.Keyword;
+
+/**
+ * This strategy defines how to place a label or icon inside the area.
+ *
+ * @author Michael Zangl
+ * @since 11722
+ * @since 11748 moved to own file
+ */
+public interface PositionForAreaStrategy {
+    /**
+     * Finds the correct position of a label / icon inside the area.
+     * @param path The area to search in
+     * @param nb The bounding box of the thing we are searching a place for.
+     * @return The position as rectangle with the same dimension as nb. <code>null</code> if none was found.
+     */
+    MapViewPositionAndRotation findLabelPlacement(MapViewPath path, Rectangle2D nb);
+
+    /**
+     * Checks whether this placement strategy supports more detailed (rotation / ...) placement using a glyph vector.
+     * @return <code>true</code> if it is supported.
+     */
+    boolean supportsGlyphVector();
+
+    /**
+     * Generates the transformed glyph vectors for the given text.
+     * @param path The path to place the text along
+     * @param nb The bounds of the text
+     * @param gvs The glyph vectors for the text. May be modified
+     * @param isDoubleTranslationBug <code>true</code> to fix a glyph placement bug.
+     *
+     * @return The glyph vectors. Or <code>null</code> if the text could not be transformed.
+     */
+    default List<GlyphVector> generateGlyphVectors(
+            MapViewPath path, Rectangle2D nb, List<GlyphVector> gvs, boolean isDoubleTranslationBug) {
+        return null;
+    }
+
+    /**
+     * Gets a strategy for the given keyword.
+     * @param keyword The text position keyword.
+     * @return The strategy or line if none was specified.
+     * @since 11722
+     */
+    static PositionForAreaStrategy forKeyword(Keyword keyword) {
+        return forKeyword(keyword, OnLineStrategy.INSTANCE);
+    }
+
+    /**
+     * Gets a strategy for the given keyword.
+     * @param keyword The text position keyword.
+     * @param defaultStrategy The default if no strategy was recognized.
+     * @return The strategy or line if none was specified.
+     * @since 11722
+     */
+    static PositionForAreaStrategy forKeyword(Keyword keyword, PositionForAreaStrategy defaultStrategy) {
+        if (keyword == null) {
+            return defaultStrategy;
+        }
+        switch (keyword.val) {
+        case "center":
+            return PartiallyInsideAreaStrategy.INSTANCE;
+        case "inside":
+            return CompletelyInsideAreaStrategy.INSTANCE;
+        case "line":
+            return OnLineStrategy.INSTANCE;
+        default:
+            return defaultStrategy;
+        }
+    }
+}
