commit 12c9c5ea7e92f812fcd0ed03f81f3a273f176646
Author: Simon Legner <Simon.Legner@gmail.com>
Date:   Sat Jan 18 19:12:55 2020 +0100

    fox #18468 - MapCSS: add support for text-rotation

diff --git a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
index c477314de..af1bc7929 100644
--- a/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
+++ b/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
@@ -87,6 +87,7 @@
 import org.openstreetmap.josm.tools.ImageProvider;
 import org.openstreetmap.josm.tools.JosmRuntimeException;
 import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.RotationAngle;
 import org.openstreetmap.josm.tools.ShapeClipper;
 import org.openstreetmap.josm.tools.Utils;
 import org.openstreetmap.josm.tools.bugreport.BugReport;
@@ -620,8 +621,8 @@ public void drawBoxText(INode n, BoxTextElement bs) {
         FontRenderContext frc = g.getFontRenderContext();
         Rectangle2D bounds = text.font.getStringBounds(s, frc);
 
-        double x = Math.round(p.getInViewX()) + bs.xOffset + bounds.getCenterX();
-        double y = Math.round(p.getInViewY()) + bs.yOffset + bounds.getCenterY();
+        double x = p.getInViewX() + bs.xOffset;
+        double y = p.getInViewY() + bs.yOffset;
         /**
          *
          *       left-above __center-above___ right-above
@@ -660,7 +661,15 @@ public void drawBoxText(INode n, BoxTextElement bs) {
             } else throw new AssertionError();
         }
 
-        displayText(n, text, s, bounds, new MapViewPositionAndRotation(mapState.getForView(x, y), 0));
+        final MapViewPoint viewPoint = mapState.getForView(x, y);
+        final AffineTransform at = new AffineTransform();
+        at.setToTranslation(
+                Math.round(viewPoint.getInViewX()),
+                Math.round(viewPoint.getInViewY()));
+        if (!RotationAngle.NO_ROTATION.equals(text.rotationAngle)) {
+            at.rotate(text.rotationAngle.getRotationAngle(n));
+        }
+        displayText(n, text, s, at);
         g.setFont(defaultFont);
     }
 
@@ -1188,13 +1197,20 @@ private void displayText(IPrimitive osm, TextLabel text, String name, Rectangle2
         AffineTransform at = new AffineTransform();
         if (Math.abs(center.getRotation()) < .01) {
             // Explicitly no rotation: move to full pixels.
-            at.setToTranslation(Math.round(center.getPoint().getInViewX() - nb.getCenterX()),
+            at.setToTranslation(
+                    Math.round(center.getPoint().getInViewX() - nb.getCenterX()),
                     Math.round(center.getPoint().getInViewY() - nb.getCenterY()));
         } else {
-            at.setToTranslation(center.getPoint().getInViewX(), center.getPoint().getInViewY());
+            at.setToTranslation(
+                    center.getPoint().getInViewX(),
+                    center.getPoint().getInViewY());
             at.rotate(center.getRotation());
             at.translate(-nb.getCenterX(), -nb.getCenterY());
         }
+        displayText(osm, text, name, at);
+    }
+
+    private void displayText(IPrimitive osm, TextLabel text, String name, AffineTransform at) {
         displayText(() -> {
             AffineTransform defaultTransform = g.getTransform();
             g.transform(at);
diff --git a/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java b/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java
index 1b45272cb..53a216257 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java
@@ -90,6 +90,10 @@
      * MapCSS icon-rotation property key
      */
     String ICON_ROTATION = "icon-rotation";
+    /**
+     * MapCSS text-rotation property key
+     */
+    String TEXT_ROTATION = "text-rotation";
     /**
      * MapCSS icon-width property key
      */
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
index a0a184537..d39fa21b3 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/NodeElement.java
@@ -86,14 +86,28 @@ static NodeElement create(Environment env, float defaultMajorZindex, boolean all
      * @since 11670
      */
     public static RotationAngle createRotationAngle(Environment env) {
+        return createRotationAngle(env, ICON_ROTATION);
+    }
+
+    /**
+     * Reads the text-rotation property and creates a rotation angle from it.
+     * @param env The environment
+     * @return The angle
+     * @since xxx
+     */
+    public static RotationAngle createTextRotationAngle(Environment env) {
+        return createRotationAngle(env, TEXT_ROTATION);
+    }
+
+    private static RotationAngle createRotationAngle(Environment env, String key) {
         Cascade c = env.mc.getCascade(env.layer);
 
         RotationAngle rotationAngle = RotationAngle.NO_ROTATION;
-        final Float angle = c.get(ICON_ROTATION, null, Float.class, true);
+        final Float angle = c.get(key, null, Float.class, true);
         if (angle != null) {
             rotationAngle = RotationAngle.buildStaticRotation(angle);
         } else {
-            final Keyword rotationKW = c.get(ICON_ROTATION, null, Keyword.class);
+            final Keyword rotationKW = c.get(key, null, Keyword.class);
             if (rotationKW != null) {
                 if ("way".equals(rotationKW.val)) {
                     rotationAngle = RotationAngle.buildWayDirectionRotation();
diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java
index 829385f8a..8e84b3555 100644
--- a/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java
+++ b/src/org/openstreetmap/josm/gui/mappaint/styleelement/TextLabel.java
@@ -17,6 +17,7 @@
 import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.StaticLabelCompositionStrategy;
 import org.openstreetmap.josm.gui.mappaint.styleelement.LabelCompositionStrategy.TagLookupCompositionStrategy;
 import org.openstreetmap.josm.tools.CheckParameterUtil;
+import org.openstreetmap.josm.tools.RotationAngle;
 import org.openstreetmap.josm.tools.Utils;
 
 /**
@@ -38,6 +39,10 @@
      * the font to be used when rendering
      */
     public Font font;
+    /**
+     * The rotation angle to be used when rendering
+     */
+    public RotationAngle rotationAngle;
     /**
      * The color to draw the text in, includes alpha.
      */
@@ -57,14 +62,16 @@
      * @param strategy the strategy indicating how the text is composed for a specific {@link OsmPrimitive} to be rendered.
      * If null, no label is rendered.
      * @param font the font to be used. Must not be null.
+     * @param rotationAngle the rotation angle to be used. Must not be null.
      * @param color the color to be used. Must not be null
      * @param haloRadius halo radius
      * @param haloColor halo color
      */
-    protected TextLabel(LabelCompositionStrategy strategy, Font font, Color color, Float haloRadius,
-            Color haloColor) {
+    protected TextLabel(LabelCompositionStrategy strategy, Font font, RotationAngle rotationAngle, Color color, Float haloRadius,
+                        Color haloColor) {
         this.labelCompositionStrategy = strategy;
         this.font = Objects.requireNonNull(font, "font");
+        this.rotationAngle = Objects.requireNonNull(rotationAngle, "rotationAngle");
         this.color = Objects.requireNonNull(color, "color");
         this.haloRadius = haloRadius;
         this.haloColor = haloColor;
@@ -78,6 +85,7 @@ protected TextLabel(LabelCompositionStrategy strategy, Font font, Color color, F
     public TextLabel(TextLabel other) {
         this.labelCompositionStrategy = other.labelCompositionStrategy;
         this.font = other.font;
+        this.rotationAngle = other.rotationAngle;
         this.color = other.color;
         this.haloColor = other.haloColor;
         this.haloRadius = other.haloRadius;
@@ -136,6 +144,7 @@ public static TextLabel create(Environment env, Color defaultTextColor, boolean
         String s = strategy.compose(env.osm);
         if (s == null) return null;
         Font font = StyleElement.getFont(c, s);
+        RotationAngle rotationAngle = NodeElement.createTextRotationAngle(env);
 
         Color color = c.get(TEXT_COLOR, defaultTextColor, Color.class);
         float alpha = c.get(TEXT_OPACITY, 1f, Float.class);
@@ -152,7 +161,7 @@ public static TextLabel create(Environment env, Color defaultTextColor, boolean
             haloColor = Utils.alphaMultiply(haloColor, haloAlphaFactor);
         }
 
-        return new TextLabel(strategy, font, color, haloRadius, haloColor);
+        return new TextLabel(strategy, font, rotationAngle, color, haloRadius, haloColor);
     }
 
     /**
@@ -198,7 +207,8 @@ protected String toStringImpl() {
         StringBuilder sb = new StringBuilder(96);
         sb.append("labelCompositionStrategy=").append(labelCompositionStrategy)
           .append(" font=").append(font)
-          .append(" color=").append(Utils.toString(color));
+          .append(" color=").append(Utils.toString(color))
+          .append(" rotationAngle=").append(rotationAngle);
         if (haloRadius != null) {
             sb.append(" haloRadius=").append(haloRadius)
               .append(" haloColor=").append(haloColor);
@@ -208,7 +218,7 @@ protected String toStringImpl() {
 
     @Override
     public int hashCode() {
-        return Objects.hash(labelCompositionStrategy, font, color, haloRadius, haloColor);
+        return Objects.hash(labelCompositionStrategy, font, rotationAngle, color, haloRadius, haloColor);
     }
 
     @Override
@@ -218,6 +228,7 @@ public boolean equals(Object obj) {
         TextLabel textLabel = (TextLabel) obj;
         return Objects.equals(labelCompositionStrategy, textLabel.labelCompositionStrategy) &&
                 Objects.equals(font, textLabel.font) &&
+                Objects.equals(rotationAngle, textLabel.rotationAngle) &&
                 Objects.equals(color, textLabel.color) &&
                 Objects.equals(haloRadius, textLabel.haloRadius) &&
                 Objects.equals(haloColor, textLabel.haloColor);
