Ticket #12957: patch-mappaint-code-cleanup.patch

File patch-mappaint-code-cleanup.patch, 26.9 KB (added by michael2402, 8 years ago)
  • src/org/openstreetmap/josm/gui/MapView.java

    diff --git a/src/org/openstreetmap/josm/gui/MapView.java b/src/org/openstreetmap/josm/gui/MapView.java
    index 5f4df08..d4d5e9c 100644
    a b LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    11821182        }
    11831183    };
    11841184
     1185    /**
     1186     * Destroy this map view panel. Should be called once when it is not needed any more.
     1187     */
    11851188    public void destroy() {
    11861189        layerManager.removeLayerChangeListener(this);
    11871190        layerManager.removeActiveLayerChangeListener(this);
    LayerManager.LayerChangeListener, MainLayerManager.ActiveLayerChangeListener {  
    11951198        synchronized (temporaryLayers) {
    11961199            temporaryLayers.clear();
    11971200        }
     1201        nonChangedLayersBuffer = null;
    11981202    }
    11991203
    12001204    /**
  • src/org/openstreetmap/josm/gui/mappaint/styleelement/BoxTextElement.java

    diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/BoxTextElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/BoxTextElement.java
    index 02843eb..971fe66 100644
    a b import org.openstreetmap.josm.tools.CheckParameterUtil;  
    2121 */
    2222public class BoxTextElement extends StyleElement {
    2323
     24    /**
     25     * MapCSS text-anchor-horizontal
     26     */
    2427    public enum HorizontalTextAlignment { LEFT, CENTER, RIGHT }
    2528
     29    /**
     30     * MapCSS text-anchor-vertical
     31     */
    2632    public enum VerticalTextAlignment { ABOVE, TOP, CENTER, BOTTOM, BELOW }
    2733
     34    /**
     35     * Something that provides us with a {@link BoxProviderResult}
     36     */
    2837    public interface BoxProvider {
     38        /**
     39         * Compute and get the {@link BoxProviderResult}. The temporary flag is set if the result of the computation may change in the future.
     40         * @return The result of the computation.
     41         */
    2942        BoxProviderResult get();
    3043    }
    3144
     45    /**
     46     * A box rectangle with a flag if it is temporary.
     47     */
    3248    public static class BoxProviderResult {
    3349        private final Rectangle box;
    3450        private final boolean temporary;
    public class BoxTextElement extends StyleElement {  
    5571        }
    5672    }
    5773
     74    /**
     75     * A {@link BoxProvider} that always returns the same non-temporary rectangle
     76     */
    5877    public static class SimpleBoxProvider implements BoxProvider {
    5978        private final Rectangle box;
    6079
    public class BoxTextElement extends StyleElement {  
    85104        }
    86105    }
    87106
     107    /**
     108     * A rectangle with size 0x0
     109     */
    88110    public static final Rectangle ZERO_BOX = new Rectangle(0, 0, 0, 0);
    89111
     112    /**
     113     * The default style a simple node should use for it's text
     114     */
     115    public static final BoxTextElement SIMPLE_NODE_TEXT_ELEMSTYLE;
     116    static {
     117        MultiCascade mc = new MultiCascade();
     118        Cascade c = mc.getOrCreateCascade("default");
     119        c.put(TEXT, Keyword.AUTO);
     120        Node n = new Node();
     121        n.put("name", "dummy");
     122        SIMPLE_NODE_TEXT_ELEMSTYLE = create(new Environment(n, mc, "default", null), NodeElement.SIMPLE_NODE_ELEMSTYLE.getBoxProvider());
     123        if (SIMPLE_NODE_TEXT_ELEMSTYLE == null) throw new AssertionError();
     124    }
     125
     126    /**
     127     * Caches the default text color from the preferences.
     128     *
     129     * FIXME: the cache isn't updated if the user changes the preference during a JOSM
     130     * session. There should be preference listener updating this cache.
     131     */
     132    private static volatile Color defaultTextColorCache;
     133
     134    /**
     135     * The text this element should display.
     136     */
    90137    public TextLabel text;
    91138    // Either boxProvider or box is not null. If boxProvider is different from
    92139    // null, this means, that the box can still change in future, otherwise
    93140    // it is fixed.
    94141    protected BoxProvider boxProvider;
    95142    protected Rectangle box;
     143    /**
     144     * The {@link HorizontalTextAlignment} for this text.
     145     */
    96146    public HorizontalTextAlignment hAlign;
     147    /**
     148     * The {@link VerticalTextAlignment} for this text.
     149     */
    97150    public VerticalTextAlignment vAlign;
    98151
     152    /**
     153     * Create a new {@link BoxTextElement}
     154     * @param c The current cascade
     155     * @param text The text to display
     156     * @param boxProvider The box provider to use
     157     * @param box The initial box to use.
     158     * @param hAlign The {@link HorizontalTextAlignment}
     159     * @param vAlign The {@link VerticalTextAlignment}
     160     */
    99161    public BoxTextElement(Cascade c, TextLabel text, BoxProvider boxProvider, Rectangle box,
    100162            HorizontalTextAlignment hAlign, VerticalTextAlignment vAlign) {
    101163        super(c, 5f);
    public class BoxTextElement extends StyleElement {  
    109171        this.vAlign = vAlign;
    110172    }
    111173
     174    /**
     175     * Create a new {@link BoxTextElement} with a dynamic box
     176     * @param env The MapCSS environment
     177     * @param boxProvider The box provider that computes the box.
     178     * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
     179     */
    112180    public static BoxTextElement create(Environment env, BoxProvider boxProvider) {
    113181        return create(env, boxProvider, null);
    114182    }
    115183
     184    /**
     185     * Create a new {@link BoxTextElement} with a fixed box
     186     * @param env The MapCSS environment
     187     * @param box The box
     188     * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
     189     */
    116190    public static BoxTextElement create(Environment env, Rectangle box) {
    117191        return create(env, null, box);
    118192    }
    119193
     194    /**
     195     * Create a new {@link BoxTextElement} with a boxprovider and a box.
     196     * @param env The MapCSS environment
     197     * @param boxProvider The box provider.
     198     * @param box The box. Only considered if boxProvider is null.
     199     * @return A new {@link BoxTextElement} or <code>null</code> if the creation failed.
     200     */
    120201    public static BoxTextElement create(Environment env, BoxProvider boxProvider, Rectangle box) {
    121202        initDefaultParameters();
    122203
    123         TextLabel text = TextLabel.create(env, DEFAULT_TEXT_COLOR, false);
     204        TextLabel text = TextLabel.create(env, defaultTextColorCache, false);
    124205        if (text == null) return null;
    125206        // Skip any primitives that don't have text to draw. (Styles are recreated for any tag change.)
    126207        // The concrete text to render is not cached in this object, but computed for each
    public class BoxTextElement extends StyleElement {  
    163244        return new BoxTextElement(c, text, boxProvider, box, hAlign, vAlign);
    164245    }
    165246
     247    /**
     248     * Get the box in which the content should be drawn.
     249     * @return The box.
     250     */
    166251    public Rectangle getBox() {
    167252        if (boxProvider != null) {
    168253            BoxProviderResult result = boxProvider.get();
    public class BoxTextElement extends StyleElement {  
    175260        return box;
    176261    }
    177262
    178     public static final BoxTextElement SIMPLE_NODE_TEXT_ELEMSTYLE;
    179     static {
    180         MultiCascade mc = new MultiCascade();
    181         Cascade c = mc.getOrCreateCascade("default");
    182         c.put(TEXT, Keyword.AUTO);
    183         Node n = new Node();
    184         n.put("name", "dummy");
    185         SIMPLE_NODE_TEXT_ELEMSTYLE = create(new Environment(n, mc, "default", null), NodeElement.SIMPLE_NODE_ELEMSTYLE.getBoxProvider());
    186         if (SIMPLE_NODE_TEXT_ELEMSTYLE == null) throw new AssertionError();
    187     }
    188 
    189     /*
    190      * Caches the default text color from the preferences.
    191      *
    192      * FIXME: the cache isn't updated if the user changes the preference during a JOSM
    193      * session. There should be preference listener updating this cache.
    194      */
    195     private static volatile Color DEFAULT_TEXT_COLOR;
    196263
    197264    private static void initDefaultParameters() {
    198         if (DEFAULT_TEXT_COLOR != null) return;
    199         DEFAULT_TEXT_COLOR = PaintColors.TEXT.get();
     265        if (defaultTextColorCache != null) return;
     266        defaultTextColorCache = PaintColors.TEXT.get();
    200267    }
    201268
    202269    @Override
  • src/org/openstreetmap/josm/gui/mappaint/styleelement/LineElement.java

    diff --git a/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineElement.java b/src/org/openstreetmap/josm/gui/mappaint/styleelement/LineElement.java
    index 350ae7f..c50b971 100644
    a b import org.openstreetmap.josm.gui.mappaint.MultiCascade;  
    2020import org.openstreetmap.josm.gui.mappaint.mapcss.Instruction.RelativeFloat;
    2121import org.openstreetmap.josm.tools.Utils;
    2222
     23/**
     24 * This is the style definition for a simple line.
     25 */
    2326public class LineElement extends StyleElement {
    24 
    25     public static LineElement createSimpleLineStyle(Color color, boolean isAreaEdge) {
    26         MultiCascade mc = new MultiCascade();
    27         Cascade c = mc.getOrCreateCascade("default");
    28         c.put(WIDTH, Keyword.DEFAULT);
    29         c.put(COLOR, color != null ? color : PaintColors.UNTAGGED.get());
    30         c.put(OPACITY, 1f);
    31         if (isAreaEdge) {
    32             c.put(Z_INDEX, -3f);
    33         }
    34         Way w = new Way();
    35         return createLine(new Environment(w, mc, "default", null));
    36     }
    37 
     27    /**
     28     * The default style for any untagged way.
     29     */
    3830    public static final LineElement UNTAGGED_WAY = createSimpleLineStyle(null, false);
    3931
    4032    private BasicStroke line;
    public class LineElement extends StyleElement {  
    7264        this.realWidth = realWidth;
    7365        this.wayDirectionArrows = wayDirectionArrows;
    7466    }
     67    @Override
     68    public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
     69            boolean selected, boolean outermember, boolean member) {
     70        Way w = (Way) primitive;
     71        /* show direction arrows, if draw.segment.relevant_directions_only is not set,
     72        the way is tagged with a direction key
     73        (even if the tag is negated as in oneway=false) or the way is selected */
     74        boolean showOrientation;
     75        if (defaultSelectedHandling) {
     76            showOrientation = !isModifier && (selected || paintSettings.isShowDirectionArrow()) && !paintSettings.isUseRealWidth();
     77        } else {
     78            showOrientation = wayDirectionArrows;
     79        }
     80        boolean showOneway = !isModifier && !selected &&
     81                !paintSettings.isUseRealWidth() &&
     82                paintSettings.isShowOnewayArrow() && w.hasDirectionKeys();
     83        boolean onewayReversed = w.reversedDirection();
     84        /* head only takes over control if the option is true,
     85        the direction should be shown at all and not only because it's selected */
     86        boolean showOnlyHeadArrowOnly = showOrientation && !selected && paintSettings.isShowHeadArrowOnly();
     87        Node lastN;
     88
     89        Color myDashedColor = dashesBackground;
     90        BasicStroke myLine = line, myDashLine = dashesLine;
     91        if (realWidth > 0 && paintSettings.isUseRealWidth() && !showOrientation) {
     92            float myWidth = (int) (100 /  (float) (painter.getCircum() / realWidth));
     93            if (myWidth < line.getLineWidth()) {
     94                myWidth = line.getLineWidth();
     95            }
     96            myLine = new BasicStroke(myWidth, line.getEndCap(), line.getLineJoin(),
     97                    line.getMiterLimit(), line.getDashArray(), line.getDashPhase());
     98            if (dashesLine != null) {
     99                myDashLine = new BasicStroke(myWidth, dashesLine.getEndCap(), dashesLine.getLineJoin(),
     100                        dashesLine.getMiterLimit(), dashesLine.getDashArray(), dashesLine.getDashPhase());
     101            }
     102        }
     103
     104        Color myColor = color;
     105        if (defaultSelectedHandling && selected) {
     106            myColor = paintSettings.getSelectedColor(color.getAlpha());
     107        } else if (member || outermember) {
     108            myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
     109        } else if (w.isDisabled()) {
     110            myColor = paintSettings.getInactiveColor();
     111            myDashedColor = paintSettings.getInactiveColor();
     112        }
     113
     114        painter.drawWay(w, myColor, myLine, myDashLine, myDashedColor, offset, showOrientation,
     115                showOnlyHeadArrowOnly, showOneway, onewayReversed);
     116
     117        if (paintSettings.isShowOrderNumber() && !painter.isInactiveMode()) {
     118            int orderNumber = 0;
     119            lastN = null;
     120            for (Node n : w.getNodes()) {
     121                if (lastN != null) {
     122                    orderNumber++;
     123                    painter.drawOrderNumber(lastN, n, orderNumber, myColor);
     124                }
     125                lastN = n;
     126            }
     127        }
     128    }
     129
     130    @Override
     131    public boolean isProperLineStyle() {
     132        return !isModifier;
     133    }
     134
     135    public String linejoinToString(int linejoin) {
     136        switch (linejoin) {
     137            case BasicStroke.JOIN_BEVEL: return "bevel";
     138            case BasicStroke.JOIN_ROUND: return "round";
     139            case BasicStroke.JOIN_MITER: return "miter";
     140            default: return null;
     141        }
     142    }
     143
     144    public String linecapToString(int linecap) {
     145        switch (linecap) {
     146            case BasicStroke.CAP_BUTT: return "none";
     147            case BasicStroke.CAP_ROUND: return "round";
     148            case BasicStroke.CAP_SQUARE: return "square";
     149            default: return null;
     150        }
     151    }
     152
     153    @Override
     154    public boolean equals(Object obj) {
     155        if (obj == null || getClass() != obj.getClass())
     156            return false;
     157        if (!super.equals(obj))
     158            return false;
     159        final LineElement other = (LineElement) obj;
     160        return Objects.equals(line, other.line) &&
     161            Objects.equals(color, other.color) &&
     162            Objects.equals(dashesLine, other.dashesLine) &&
     163            Objects.equals(dashesBackground, other.dashesBackground) &&
     164            offset == other.offset &&
     165            realWidth == other.realWidth &&
     166            wayDirectionArrows == other.wayDirectionArrows;
     167    }
     168
     169    @Override
     170    public int hashCode() {
     171        return Objects.hash(super.hashCode(), line, color, dashesBackground, offset, realWidth, wayDirectionArrows, dashesLine);
     172    }
     173
     174    @Override
     175    public String toString() {
     176        return "LineElemStyle{" + super.toString() + "width=" + line.getLineWidth() +
     177            " realWidth=" + realWidth + " color=" + Utils.toString(color) +
     178            " dashed=" + Arrays.toString(line.getDashArray()) +
     179            (line.getDashPhase() == 0 ? "" : " dashesOffses=" + line.getDashPhase()) +
     180            " dashedColor=" + Utils.toString(dashesBackground) +
     181            " linejoin=" + linejoinToString(line.getLineJoin()) +
     182            " linecap=" + linecapToString(line.getEndCap()) +
     183            (offset == 0 ? "" : " offset=" + offset) +
     184            '}';
     185    }
     186
     187    /**
     188     * Creates a simple line with default widt.
     189     * @param color The color to use
     190     * @param isAreaEdge If this is an edge for an area. Edges are drawn at lower Z-Index.
     191     * @return The line style.
     192     */
     193    public static LineElement createSimpleLineStyle(Color color, boolean isAreaEdge) {
     194        MultiCascade mc = new MultiCascade();
     195        Cascade c = mc.getOrCreateCascade("default");
     196        c.put(WIDTH, Keyword.DEFAULT);
     197        c.put(COLOR, color != null ? color : PaintColors.UNTAGGED.get());
     198        c.put(OPACITY, 1f);
     199        if (isAreaEdge) {
     200            c.put(Z_INDEX, -3f);
     201        }
     202        Way w = new Way();
     203        return createLine(new Environment(w, mc, "default", null));
     204    }
    75205
    76206    public static LineElement createLine(Environment env) {
    77207        return createImpl(env, LineType.NORMAL);
    public class LineElement extends StyleElement {  
    104234    private static LineElement createImpl(Environment env, LineType type) {
    105235        Cascade c = env.mc.getCascade(env.layer);
    106236        Cascade cDef = env.mc.getCascade("default");
    107         Float width;
    108         switch (type) {
    109             case NORMAL:
    110                 width = getWidth(c, WIDTH, getWidth(cDef, WIDTH, null));
    111                 break;
    112             case CASING:
    113                 Float casingWidth = c.get(type.prefix + WIDTH, null, Float.class, true);
    114                 if (casingWidth == null) {
    115                     RelativeFloat relCasingWidth = c.get(type.prefix + WIDTH, null, RelativeFloat.class, true);
    116                     if (relCasingWidth != null) {
    117                         casingWidth = relCasingWidth.val / 2;
    118                     }
    119                 }
    120                 if (casingWidth == null)
    121                     return null;
    122                 width = getWidth(c, WIDTH, getWidth(cDef, WIDTH, null));
    123                 if (width == null) {
    124                     width = 0f;
    125                 }
    126                 width += 2 * casingWidth;
    127                 break;
    128             case LEFT_CASING:
    129             case RIGHT_CASING:
    130                 width = getWidth(c, type.prefix + WIDTH, null);
    131                 break;
    132             default:
    133                 throw new AssertionError();
    134         }
     237        Float width = computeWidth(type, c, cDef);
    135238        if (width == null)
    136239            return null;
    137240
    138         float realWidth = c.get(type.prefix + REAL_WIDTH, 0f, Float.class);
    139         if (realWidth > 0 && MapPaintSettings.INSTANCE.isUseRealWidth()) {
     241        float realWidth = computeRealWidth(env, type, c);
    140242
    141             /* if we have a "width" tag, try use it */
    142             String widthTag = env.osm.get("width");
    143             if (widthTag == null) {
    144                 widthTag = env.osm.get("est_width");
    145             }
    146             if (widthTag != null) {
    147                 try {
    148                     realWidth = Float.parseFloat(widthTag);
    149                 } catch (NumberFormatException nfe) {
    150                     Main.warn(nfe);
    151                 }
    152             }
    153         }
    154 
    155         Float offset = c.get(OFFSET, 0f, Float.class);
    156         switch (type) {
    157             case NORMAL:
    158                 break;
    159             case CASING:
    160                 offset += c.get(type.prefix + OFFSET, 0f, Float.class);
    161                 break;
    162             case LEFT_CASING:
    163             case RIGHT_CASING:
    164                 Float baseWidthOnDefault = getWidth(cDef, WIDTH, null);
    165                 Float baseWidth = getWidth(c, WIDTH, baseWidthOnDefault);
    166                 if (baseWidth == null || baseWidth < 2f) {
    167                     baseWidth = 2f;
    168                 }
    169                 float casingOffset = c.get(type.prefix + OFFSET, 0f, Float.class);
    170                 casingOffset += baseWidth / 2 + width / 2;
    171                 /* flip sign for the right-casing-offset */
    172                 if (type == LineType.RIGHT_CASING) {
    173                     casingOffset *= -1f;
    174                 }
    175                 offset += casingOffset;
    176                 break;
    177         }
     243        Float offset = computeOffset(type, c, cDef, width);
    178244
    179245        int alpha = 255;
    180246        Color color = c.get(type.prefix + COLOR, null, Color.class);
    public class LineElement extends StyleElement {  
    272338                offset, realWidth, wayDirectionArrows);
    273339    }
    274340
    275     @Override
    276     public void paintPrimitive(OsmPrimitive primitive, MapPaintSettings paintSettings, StyledMapRenderer painter,
    277             boolean selected, boolean outermember, boolean member) {
    278         Way w = (Way) primitive;
    279         /* show direction arrows, if draw.segment.relevant_directions_only is not set,
    280         the way is tagged with a direction key
    281         (even if the tag is negated as in oneway=false) or the way is selected */
    282         boolean showOrientation;
    283         if (defaultSelectedHandling) {
    284             showOrientation = !isModifier && (selected || paintSettings.isShowDirectionArrow()) && !paintSettings.isUseRealWidth();
    285         } else {
    286             showOrientation = wayDirectionArrows;
    287         }
    288         boolean showOneway = !isModifier && !selected &&
    289                 !paintSettings.isUseRealWidth() &&
    290                 paintSettings.isShowOnewayArrow() && w.hasDirectionKeys();
    291         boolean onewayReversed = w.reversedDirection();
    292         /* head only takes over control if the option is true,
    293         the direction should be shown at all and not only because it's selected */
    294         boolean showOnlyHeadArrowOnly = showOrientation && !selected && paintSettings.isShowHeadArrowOnly();
    295         Node lastN;
    296 
    297         Color myDashedColor = dashesBackground;
    298         BasicStroke myLine = line, myDashLine = dashesLine;
    299         if (realWidth > 0 && paintSettings.isUseRealWidth() && !showOrientation) {
    300             float myWidth = (int) (100 /  (float) (painter.getCircum() / realWidth));
    301             if (myWidth < line.getLineWidth()) {
    302                 myWidth = line.getLineWidth();
    303             }
    304             myLine = new BasicStroke(myWidth, line.getEndCap(), line.getLineJoin(),
    305                     line.getMiterLimit(), line.getDashArray(), line.getDashPhase());
    306             if (dashesLine != null) {
    307                 myDashLine = new BasicStroke(myWidth, dashesLine.getEndCap(), dashesLine.getLineJoin(),
    308                         dashesLine.getMiterLimit(), dashesLine.getDashArray(), dashesLine.getDashPhase());
    309             }
    310         }
    311 
    312         Color myColor = color;
    313         if (defaultSelectedHandling && selected) {
    314             myColor = paintSettings.getSelectedColor(color.getAlpha());
    315         } else if (member || outermember) {
    316             myColor = paintSettings.getRelationSelectedColor(color.getAlpha());
    317         } else if (w.isDisabled()) {
    318             myColor = paintSettings.getInactiveColor();
    319             myDashedColor = paintSettings.getInactiveColor();
     341    private static Float computeWidth(LineType type, Cascade c, Cascade cDef) {
     342        Float width;
     343        switch (type) {
     344            case NORMAL:
     345                width = getWidth(c, WIDTH, getWidth(cDef, WIDTH, null));
     346                break;
     347            case CASING:
     348                Float casingWidth = c.get(type.prefix + WIDTH, null, Float.class, true);
     349                if (casingWidth == null) {
     350                    RelativeFloat relCasingWidth = c.get(type.prefix + WIDTH, null, RelativeFloat.class, true);
     351                    if (relCasingWidth != null) {
     352                        casingWidth = relCasingWidth.val / 2;
     353                    }
     354                }
     355                if (casingWidth == null)
     356                    return null;
     357                width = getWidth(c, WIDTH, getWidth(cDef, WIDTH, null));
     358                if (width == null) {
     359                    width = 0f;
     360                }
     361                width += 2 * casingWidth;
     362                break;
     363            case LEFT_CASING:
     364            case RIGHT_CASING:
     365                width = getWidth(c, type.prefix + WIDTH, null);
     366                break;
     367            default:
     368                throw new AssertionError();
    320369        }
     370        return width;
     371    }
    321372
    322         painter.drawWay(w, myColor, myLine, myDashLine, myDashedColor, offset, showOrientation,
    323                 showOnlyHeadArrowOnly, showOneway, onewayReversed);
     373    private static float computeRealWidth(Environment env, LineType type, Cascade c) {
     374        float realWidth = c.get(type.prefix + REAL_WIDTH, 0f, Float.class);
     375        if (realWidth > 0 && MapPaintSettings.INSTANCE.isUseRealWidth()) {
    324376
    325         if (paintSettings.isShowOrderNumber() && !painter.isInactiveMode()) {
    326             int orderNumber = 0;
    327             lastN = null;
    328             for (Node n : w.getNodes()) {
    329                 if (lastN != null) {
    330                     orderNumber++;
    331                     painter.drawOrderNumber(lastN, n, orderNumber, myColor);
     377            /* if we have a "width" tag, try use it */
     378            String widthTag = env.osm.get("width");
     379            if (widthTag == null) {
     380                widthTag = env.osm.get("est_width");
     381            }
     382            if (widthTag != null) {
     383                try {
     384                    realWidth = Float.parseFloat(widthTag);
     385                } catch (NumberFormatException nfe) {
     386                    Main.warn(nfe);
    332387                }
    333                 lastN = n;
    334388            }
    335389        }
     390        return realWidth;
    336391    }
    337392
    338     @Override
    339     public boolean isProperLineStyle() {
    340         return !isModifier;
    341     }
    342 
    343     @Override
    344     public boolean equals(Object obj) {
    345         if (obj == null || getClass() != obj.getClass())
    346             return false;
    347         if (!super.equals(obj))
    348             return false;
    349         final LineElement other = (LineElement) obj;
    350         return Objects.equals(line, other.line) &&
    351             Objects.equals(color, other.color) &&
    352             Objects.equals(dashesLine, other.dashesLine) &&
    353             Objects.equals(dashesBackground, other.dashesBackground) &&
    354             offset == other.offset &&
    355             realWidth == other.realWidth &&
    356             wayDirectionArrows == other.wayDirectionArrows;
    357     }
    358 
    359     @Override
    360     public int hashCode() {
    361         return Objects.hash(super.hashCode(), line, color, dashesBackground, offset, realWidth, wayDirectionArrows, dashesLine);
    362     }
    363 
    364     @Override
    365     public String toString() {
    366         return "LineElemStyle{" + super.toString() + "width=" + line.getLineWidth() +
    367             " realWidth=" + realWidth + " color=" + Utils.toString(color) +
    368             " dashed=" + Arrays.toString(line.getDashArray()) +
    369             (line.getDashPhase() == 0 ? "" : " dashesOffses=" + line.getDashPhase()) +
    370             " dashedColor=" + Utils.toString(dashesBackground) +
    371             " linejoin=" + linejoinToString(line.getLineJoin()) +
    372             " linecap=" + linecapToString(line.getEndCap()) +
    373             (offset == 0 ? "" : " offset=" + offset) +
    374             '}';
    375     }
    376 
    377     public String linejoinToString(int linejoin) {
    378         switch (linejoin) {
    379             case BasicStroke.JOIN_BEVEL: return "bevel";
    380             case BasicStroke.JOIN_ROUND: return "round";
    381             case BasicStroke.JOIN_MITER: return "miter";
    382             default: return null;
     393    private static Float computeOffset(LineType type, Cascade c, Cascade cDef, Float width) {
     394        Float offset = c.get(OFFSET, 0f, Float.class);
     395        switch (type) {
     396            case NORMAL:
     397                break;
     398            case CASING:
     399                offset += c.get(type.prefix + OFFSET, 0f, Float.class);
     400                break;
     401            case LEFT_CASING:
     402            case RIGHT_CASING:
     403                Float baseWidthOnDefault = getWidth(cDef, WIDTH, null);
     404                Float baseWidth = getWidth(c, WIDTH, baseWidthOnDefault);
     405                if (baseWidth == null || baseWidth < 2f) {
     406                    baseWidth = 2f;
     407                }
     408                float casingOffset = c.get(type.prefix + OFFSET, 0f, Float.class);
     409                casingOffset += baseWidth / 2 + width / 2;
     410                /* flip sign for the right-casing-offset */
     411                if (type == LineType.RIGHT_CASING) {
     412                    casingOffset *= -1f;
     413                }
     414                offset += casingOffset;
     415                break;
    383416        }
     417        return offset;
    384418    }
    385419
    386     public String linecapToString(int linecap) {
    387         switch (linecap) {
    388             case BasicStroke.CAP_BUTT: return "none";
    389             case BasicStroke.CAP_ROUND: return "round";
    390             case BasicStroke.CAP_SQUARE: return "square";
    391             default: return null;
    392         }
    393     }
    394420}