Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPaintSettings.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPaintSettings.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/MapPaintSettings.java	(revision 9099)
@@ -49,9 +49,4 @@
     /** Preference: should only the data area outline be drawn */
     private boolean outlineOnly;
-    /** Preference: parameter to avoid partial fill on small area objects:
-     * If more than a certain percentage of the total area would be filled by 
-     * partial fill, then fill this area completely (avoiding narrow gap in the
-     * center) */
-    private double partialFillThreshold;
     /** Color Preference for selected objects */
     private Color selectedColor;
@@ -111,5 +106,4 @@
 
         outlineOnly = Main.pref.getBoolean("draw.data.area_outline_only", false);
-        partialFillThreshold = Main.pref.getDouble("draw.area.partial_fill_threshold", 70);
     }
 
@@ -352,15 +346,3 @@
         return outlineOnly;
     }
-
-    /**
-     * Returns the partial fill threshold.
-     * This parameter is used to avoid partial fill on small area objects:
-     * If more than a certain percentage of the total area would be filled by 
-     * partial fill, then fill this area completely (avoiding narrow gap in the
-     * center)
-     * @return the partial fill threshold
-     */
-    public double getPartialFillThreshold() {
-        return partialFillThreshold;
-    }
 }
Index: trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/data/osm/visitor/paint/StyledMapRenderer.java	(revision 9099)
@@ -336,5 +336,4 @@
     private boolean showIcons;
     private boolean isOutlineOnly;
-    private double partialFillThreshold;
 
     private Font orderFont;
@@ -467,5 +466,6 @@
      * far to fill from the boundary towards the center of the area;
      * if null, area will be filled completely
-     * @param pfClip clipping area for partial fill
+     * @param pfClip clipping area for partial fill (only needed for unclosed
+     * polygons)
      * @param disabled If this should be drawn with a special disabled style.
      * @param text The text to write on the area.
@@ -492,5 +492,5 @@
                     }
                     g.clip(clip);
-                    g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER));
+                    g.setStroke(new BasicStroke(2 * extent, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 4));
                     g.draw(area);
                     g.setClip(oldClip);
@@ -607,8 +607,10 @@
      * far to fill from the boundary towards the center of the area;
      * if null, area will be filled completely
+     * @param extentThreshold if not null, determines if the partial filled should
+     * be replaced by plain fill, when it covers a certain fraction of the total area
      * @param disabled If this should be drawn with a special disabled style.
      * @param text The text to write on the area.
      */
-    public void drawArea(Relation r, Color color, MapImage fillImage, Float extent, boolean disabled, TextElement text) {
+    public void drawArea(Relation r, Color color, MapImage fillImage, Float extent, Float extentThreshold, boolean disabled, TextElement text) {
         Multipolygon multipolygon = MultipolygonCache.getInstance().get(nc, r);
         if (!r.isDisabled() && !multipolygon.getOuterWays().isEmpty()) {
@@ -620,12 +622,7 @@
                 }
                 if (extent != null) {
-                    if (pd.isClosed()) {
-                        AreaAndPerimeter ap = pd.getAreaAndPerimeter();
-                        // if partial fill would only leave a small gap in the center ...
-                        if (ap.getPerimeter() * extent * scale > partialFillThreshold / 100 * ap.getArea()) {
-                            // ... turn it off and fill completely
-                            extent = null;
-                        }
-                    } else {
+                    if (!usePartialFill(pd.getAreaAndPerimeter(), extent, extentThreshold)) {
+                        extent = null;
+                    } else if (!pd.isClosed()) {
                         pfClip = getPFClip(pd, extent * scale);
                     }
@@ -646,22 +643,37 @@
      * far to fill from the boundary towards the center of the area;
      * if null, area will be filled completely
+     * @param extentThreshold if not null, determines if the partial filled should
+     * be replaced by plain fill, when it covers a certain fraction of the total area
      * @param disabled If this should be drawn with a special disabled style.
      * @param text The text to write on the area.
      */
-    public void drawArea(Way w, Color color, MapImage fillImage, Float extent, boolean disabled, TextElement text) {
+    public void drawArea(Way w, Color color, MapImage fillImage, Float extent, Float extentThreshold, boolean disabled, TextElement text) {
         Path2D.Double pfClip = null;
         if (extent != null) {
-            if (w.isClosed()) {
-                AreaAndPerimeter ap = Geometry.getAreaAndPerimeter(w.getNodes());
-                // if partial fill would only leave a small gap in the center ...
-                if (ap.getPerimeter() * extent * scale > partialFillThreshold / 100 * ap.getArea()) {
-                    // ... turn it off and fill completely
-                    extent = null;
-                }
-            } else {
+            if (!usePartialFill(Geometry.getAreaAndPerimeter(w.getNodes()), extent, extentThreshold)) {
+                extent = null;
+            } else if (!w.isClosed()) {
                 pfClip = getPFClip(w, extent * scale);
             }
         }
         drawArea(w, getPath(w), color, fillImage, extent, pfClip, disabled, text);
+    }
+
+    /**
+     * Determine, if partial fill should be turned off for this object, because
+     * only a small unfilled gap in the center of the area would be left.
+     *
+     * This is used to get a cleaner look for urban regions with many small
+     * areas like buildings, etc.
+     * @param ap the area and the perimeter of the object
+     * @param extent the "width" of partial fill
+     * @param threshold when the partial fill covers that much of the total
+     * area, the partial fill is turned off; can be greater than 100% as the
+     * covered area is estimated as <code>perimeter * extent</code>
+     * @return true, if the partial fill should be used, false otherwise
+     */
+    private boolean usePartialFill(AreaAndPerimeter ap, float extent, Float threshold) {
+        if (threshold == null) return true;
+        return ap.getPerimeter() * extent * scale < threshold * ap.getArea();
     }
 
@@ -1519,5 +1531,4 @@
         showIcons = paintSettings.getShowIconsDistance() > circum;
         isOutlineOnly = paintSettings.isOutlineOnly();
-        partialFillThreshold = paintSettings.getPartialFillThreshold();
         orderFont = new Font(Main.pref.get("mappaint.font", "Droid Sans"), Font.PLAIN, Main.pref.getInteger("mappaint.fontsize", 8));
 
@@ -1596,4 +1607,19 @@
     }
 
+    /**
+     * Fix the clipping area of unclosed polygons for partial fill.
+     *
+     * The current algorithm for partial fill simply strokes the polygon with a
+     * large stroke width after masking the outside with a clipping area.
+     * This works, but for unclosed polygons, the mask can crop the corners at
+     * both ends (see #12104).
+     *
+     * This method fixes the clipping area by sort of adding the corners to the
+     * clip outline.
+     *
+     * @param clip the clipping area to modify (initially empty)
+     * @param nodes nodes of the polygon
+     * @param extent the extent
+     */
     private static void buildPFClip(Path2D.Double clip, List<Node> nodes, double extent) {
         boolean initial = true;
@@ -1629,4 +1655,21 @@
     }
 
+    /**
+     * Get the point to add to the clipping area for partial fill of unclosed polygons.
+     *
+     * <code>(p1,p2)</code> is the first or last way segment and <code>p3</code> the
+     * opposite endpoint.
+     *
+     * @param p1 1st point
+     * @param p2 2nd point
+     * @param p3 3rd point
+     * @param extent the extent
+     * @return a point q, such that p1,p2,q form a right angle
+     * and the distance of q to p2 is <code>extent</code>. The point q lies on
+     * the same side of the line p1,p2 as the point p3.
+     * Returns null if p1,p2,p3 forms an angle greater 90 degrees. (In this case
+     * the corner of the partial fill would not be cut off by the mask, so an
+     * additional point is not necessary.)
+     */
     private static EastNorth getPFDisplacedEndPoint(EastNorth p1, EastNorth p2, EastNorth p3, double extent) {
         double dx1 = p2.getX() - p1.getX();
@@ -1636,4 +1679,5 @@
         if (dx1 * dx2 + dy1 * dy2 < 0) {
             double len = Math.sqrt(dx1 * dx1 + dy1 * dy1);
+            if (len == 0) return null;
             double dxm = -dy1 * extent / len;
             double dym = dx1 * extent / len;
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/AreaElemStyle.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/AreaElemStyle.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/AreaElemStyle.java	(revision 9099)
@@ -26,11 +26,13 @@
     public TextElement text;
     public Float extent;
+    public Float extentThreshold;
 
-    protected AreaElemStyle(Cascade c, Color color, MapImage fillImage, Float extent, TextElement text) {
+    protected AreaElemStyle(Cascade c, Color color, MapImage fillImage, Float extent, Float extentThreshold, TextElement text) {
         super(c, 1f);
         CheckParameterUtil.ensureParameterNotNull(color);
         this.color = color;
+        this.fillImage = fillImage;
         this.extent = extent;
-        this.fillImage = fillImage;
+        this.extentThreshold = extentThreshold;
         this.text = text;
     }
@@ -39,6 +41,5 @@
         final Cascade c = env.mc.getCascade(env.layer);
         MapImage fillImage = null;
-        Color color = null;
-        Float extent = null;
+        Color color;
 
         IconReference iconRef = c.get(FILL_IMAGE, null, IconReference.class);
@@ -81,8 +82,9 @@
         }
 
-        extent = c.get(FILL_EXTENT, null, float.class);
+        Float extent = c.get(FILL_EXTENT, null, float.class);
+        Float extentThreshold = c.get(FILL_EXTENT_THRESHOLD, null, float.class);
 
         if (color != null)
-            return new AreaElemStyle(c, color, fillImage, extent, text);
+            return new AreaElemStyle(c, color, fillImage, extent, extentThreshold, text);
         else
             return null;
@@ -101,10 +103,10 @@
                 }
             }
-            painter.drawArea((Way) osm, myColor, fillImage, extent, painter.isInactiveMode() || osm.isDisabled(), text);
+            painter.drawArea((Way) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled(), text);
         } else if (osm instanceof Relation) {
             if (color != null && (selected || outermember)) {
                 myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
             }
-            painter.drawArea((Relation) osm, myColor, fillImage, extent, painter.isInactiveMode() || osm.isDisabled(), text);
+            painter.drawArea((Relation) osm, myColor, fillImage, extent, extentThreshold, painter.isInactiveMode() || osm.isDisabled(), text);
         }
     }
@@ -124,4 +126,6 @@
         if (extent != other.extent)
             return false;
+        if (extentThreshold != other.extentThreshold)
+            return false;
         if (!Objects.equals(text, other.text))
             return false;
@@ -134,4 +138,5 @@
         hash = 61 * hash + color.hashCode();
         hash = 61 * hash + (extent != null ? Float.floatToIntBits(extent) : 0);
+        hash = 61 * hash + (extentThreshold != null ? Float.floatToIntBits(extent) : 0);
         hash = 61 * hash + (fillImage != null ? fillImage.hashCode() : 0);
         hash = 61 * hash + (text != null ? text.hashCode() : 0);
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/StyleKeys.java	(revision 9099)
@@ -11,4 +11,5 @@
     String FILL_COLOR = "fill-color";
     String FILL_EXTENT = "fill-extent";
+    String FILL_EXTENT_THRESHOLD = "fill-extent-threshold";
     String FILL_IMAGE = "fill-image";
     String FILL_OPACITY = "fill-opacity";
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Condition.java	(revision 9099)
@@ -633,4 +633,20 @@
             return IN_DOWNLOADED_AREA.evaluate(e.osm);
         }
+
+        static boolean completely_downloaded(Environment e) {
+            if (e.osm instanceof Relation) {
+                return !((Relation) e.osm).hasIncompleteMembers();
+            } else {
+                return true;
+            }
+        }
+
+        static boolean closed2(Environment e) {
+            if (e.osm instanceof Way && ((Way) e.osm).isClosed())
+                return true;
+            if (e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon())
+                return MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) e.osm).getOpenEnds().isEmpty();
+            return false;
+        }
     }
 
Index: trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj	(revision 9099)
@@ -204,4 +204,5 @@
 |   < ELEMENT_OF: "∈" >
 |   < CROSSING: "⧉" >
+|   < PERCENT: "%" >
 |   < COMMENT_START: "/*" > : COMMENT
 |   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
@@ -1126,5 +1127,5 @@
 }
 {
-    f=ufloat() ( u=ident() | <DEG> { u = "°"; } )
+    f=ufloat() ( u=ident() | <DEG> { u = "°"; } | <PERCENT> { u = "%"; } )
     {
         Double m = unit_factor(u);
@@ -1143,4 +1144,5 @@
         case "grad": return Math.PI / 200;
         case "turn": return 2 * Math.PI;
+        case "%": return 0.01;
         case "px": return 1.;
         case "cm": return 96/2.54;
Index: trunk/src/org/openstreetmap/josm/tools/Geometry.java
===================================================================
--- trunk/src/org/openstreetmap/josm/tools/Geometry.java	(revision 9098)
+++ trunk/src/org/openstreetmap/josm/tools/Geometry.java	(revision 9099)
@@ -978,23 +978,20 @@
      * Uses current projection; units are that of the projected coordinates.
      * 
-     * @param nodes the list of nodes representing the polygon (must be
-     * closed, i.e. first node equals last node)
+     * @param nodes the list of nodes representing the polygon
      * @return area and perimeter
      */
     public static AreaAndPerimeter getAreaAndPerimeter(List<Node> nodes) {
-        if (nodes.get(0) != nodes.get(nodes.size() - 1)) {
-            throw new IllegalArgumentException();
-        }
         double area = 0;
         double perimeter = 0;
-        Node lastN = null;
-        for (Node n : nodes) {
-            if (lastN != null) {
-                EastNorth p1 = lastN.getEastNorth();
-                EastNorth p2 = n.getEastNorth();
+        if (!nodes.isEmpty()) {
+            boolean closed = nodes.get(0) == nodes.get(nodes.size() - 1);
+            int numSegments = closed ? nodes.size() - 1 : nodes.size();
+            EastNorth p1 = nodes.get(0).getEastNorth();
+            for (int i=1; i<=numSegments; i++) {
+                EastNorth p2 = nodes.get(i == numSegments ? 0 : i).getEastNorth();
                 area += p1.east() * p2.north() - p2.east() * p1.north();
                 perimeter += p1.distance(p2);
-            }
-            lastN = n;
+                p1 = p2;
+            }
         }
         return new AreaAndPerimeter(Math.abs(area) / 2, perimeter);
