diff --git a/vtm-android-example/AndroidManifest.xml b/vtm-android-example/AndroidManifest.xml
index 2c8f41d4..4321c70f 100644
--- a/vtm-android-example/AndroidManifest.xml
+++ b/vtm-android-example/AndroidManifest.xml
@@ -61,6 +61,9 @@
         <activity
             android:name=".MapsforgeMapActivity$MapFilePicker"
             android:configChanges="keyboardHidden|orientation|screenSize" />
+        <activity
+            android:name=".MapsforgeMapActivity$ThemeFilePicker"
+            android:configChanges="keyboardHidden|orientation|screenSize" />
         <activity
             android:name=".MapsforgeStyleActivity"
             android:configChanges="keyboardHidden|orientation|screenSize" />
diff --git a/vtm-android-example/res/menu/theme_menu.xml b/vtm-android-example/res/menu/theme_menu.xml
index fb2b8dfb..319b5428 100644
--- a/vtm-android-example/res/menu/theme_menu.xml
+++ b/vtm-android-example/res/menu/theme_menu.xml
@@ -20,6 +20,9 @@
         <item
             android:id="@+id/theme_newtron"
             android:title="@string/theme_newtron" />
+        <item
+            android:id="@+id/theme_load"
+            android:title="@string/theme_load" />
     </group>
 
     <item
diff --git a/vtm-android-example/res/values/strings.xml b/vtm-android-example/res/values/strings.xml
index 929f5a52..a232eb14 100644
--- a/vtm-android-example/res/values/strings.xml
+++ b/vtm-android-example/res/values/strings.xml
@@ -17,5 +17,6 @@
     <string name="style_1">Show nature</string>
     <string name="style_2">Hide nature</string>
     <string name="menu_gridlayer">Grid</string>
+    <string name="theme_load">load theme extern</string>
 
 </resources>
diff --git a/vtm-android-example/src/org/oscim/android/filepicker/ValidRenderTheme.java b/vtm-android-example/src/org/oscim/android/filepicker/ValidRenderTheme.java
index 93ff8f4d..ca88d0c4 100644
--- a/vtm-android-example/src/org/oscim/android/filepicker/ValidRenderTheme.java
+++ b/vtm-android-example/src/org/oscim/android/filepicker/ValidRenderTheme.java
@@ -1,6 +1,7 @@
 /*
  * Copyright 2010, 2011, 2012 mapsforge.org
  * Copyright 2016 devemux86
+ * Copyright 2017 Longri
  *
  * This program is free software: you can redistribute it and/or modify it under the
  * terms of the GNU Lesser General Public License as published by the Free Software
@@ -17,12 +18,16 @@ package org.oscim.android.filepicker;
 
 import org.oscim.theme.ExternalRenderTheme;
 import org.oscim.theme.ThemeFile;
+import org.oscim.theme.ThemeUtils;
+import org.oscim.theme.XmlMapsforgeThemeBuilder;
 import org.oscim.theme.XmlThemeBuilder;
 import org.oscim.tiling.TileSource.OpenResult;
 import org.xml.sax.InputSource;
 import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
 
 import java.io.File;
+import java.io.FileInputStream;
 
 import javax.xml.parsers.SAXParserFactory;
 
@@ -34,9 +39,15 @@ public final class ValidRenderTheme implements ValidFileFilter {
 
     @Override
     public boolean accept(File file) {
+
         try {
             ThemeFile theme = new ExternalRenderTheme(file.getAbsolutePath());
-            XmlThemeBuilder renderThemeHandler = new XmlThemeBuilder(theme);
+            DefaultHandler renderThemeHandler;
+            if(ThemeUtils.isMapsforgeTheme(new FileInputStream(file))) {
+                renderThemeHandler = new XmlMapsforgeThemeBuilder(theme);
+            }else{
+                renderThemeHandler = new XmlThemeBuilder(theme);
+            }
             XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
             xmlReader.setContentHandler(renderThemeHandler);
             xmlReader.parse(new InputSource(theme.getRenderThemeAsStream()));
diff --git a/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java b/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
index bd1785cc..74a4c458 100644
--- a/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
@@ -1,6 +1,7 @@
 /*
  * Copyright 2014 Hannes Janetzek
  * Copyright 2016-2017 devemux86
+ * Copyright 2017 Longri
  *
  * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
  *
@@ -25,6 +26,7 @@ import android.view.MenuItem;
 import org.oscim.android.filepicker.FilePicker;
 import org.oscim.android.filepicker.FilterByFileExtension;
 import org.oscim.android.filepicker.ValidMapFile;
+import org.oscim.android.filepicker.ValidRenderTheme;
 import org.oscim.core.MapPosition;
 import org.oscim.core.Tile;
 import org.oscim.layers.TileGridLayer;
@@ -38,12 +40,14 @@ import org.oscim.scalebar.ImperialUnitAdapter;
 import org.oscim.scalebar.MapScaleBar;
 import org.oscim.scalebar.MapScaleBarLayer;
 import org.oscim.scalebar.MetricUnitAdapter;
+import org.oscim.theme.ExternalRenderTheme;
 import org.oscim.theme.VtmThemes;
 import org.oscim.tiling.source.mapfile.MapFileTileSource;
 import org.oscim.tiling.source.mapfile.MapInfo;
 
 public class MapsforgeMapActivity extends MapActivity {
     private static final int SELECT_MAP_FILE = 0;
+    private static final int SELECT_THEME_FILE = 1;
 
     private TileGridLayer mGridLayer;
     private DefaultMapScaleBar mMapScaleBar;
@@ -71,6 +75,13 @@ public class MapsforgeMapActivity extends MapActivity {
         }
     }
 
+    public static class ThemeFilePicker extends FilePicker {
+        public ThemeFilePicker() {
+            setFileDisplayFilter(new FilterByFileExtension(".xml"));
+            setFileSelectFilter(new ValidRenderTheme());
+        }
+    }
+
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         getMenuInflater().inflate(R.menu.theme_menu, menu);
@@ -106,6 +117,11 @@ public class MapsforgeMapActivity extends MapActivity {
                 item.setChecked(true);
                 return true;
 
+            case R.id.theme_load:
+                startActivityForResult(new Intent(MapsforgeMapActivity.this, ThemeFilePicker.class),
+                        SELECT_THEME_FILE);
+                return true;
+
             case R.id.gridlayer:
                 if (item.isChecked()) {
                     item.setChecked(false);
@@ -163,6 +179,20 @@ public class MapsforgeMapActivity extends MapActivity {
 
                 mPrefs.clear();
             }
+        } else if (requestCode == SELECT_THEME_FILE) {
+            if (resultCode != RESULT_OK || intent == null || intent.getStringExtra(FilePicker.SELECTED_FILE) == null) {
+                finish();
+                return;
+            }
+
+            String themePath = intent.getStringExtra(FilePicker.SELECTED_FILE);
+
+            ExternalRenderTheme externalRenderTheme = new ExternalRenderTheme(themePath);
+            try {
+                mMap.setTheme(externalRenderTheme, true);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
         }
     }
 
diff --git a/vtm-android/src/org/oscim/android/canvas/AndroidCanvas.java b/vtm-android/src/org/oscim/android/canvas/AndroidCanvas.java
index 407f581c..2151233a 100644
--- a/vtm-android/src/org/oscim/android/canvas/AndroidCanvas.java
+++ b/vtm-android/src/org/oscim/android/canvas/AndroidCanvas.java
@@ -21,6 +21,7 @@ package org.oscim.android.canvas;
 
 import android.graphics.Color;
 import android.graphics.PorterDuff;
+import android.graphics.RectF;
 
 import org.oscim.backend.canvas.Bitmap;
 import org.oscim.backend.canvas.Canvas;
@@ -96,4 +97,12 @@ public class AndroidCanvas implements Canvas {
     public int getWidth() {
         return canvas.getWidth();
     }
+
+    @Override
+    public void fillRectangle(int x, int y, int width, int height, int color) {
+        RectF rec = new RectF(x, y, x + width, y + height);
+        android.graphics.Paint paint = new android.graphics.Paint();
+        paint.setColor(color);
+        canvas.drawRect(rec, paint);
+    }
 }
diff --git a/vtm-desktop/src/org/oscim/awt/AwtCanvas.java b/vtm-desktop/src/org/oscim/awt/AwtCanvas.java
index 054ba68d..57502a9a 100644
--- a/vtm-desktop/src/org/oscim/awt/AwtCanvas.java
+++ b/vtm-desktop/src/org/oscim/awt/AwtCanvas.java
@@ -183,12 +183,7 @@ public class AwtCanvas implements Canvas {
 
     @Override
     public void fillColor(int color) {
-        java.awt.Color awtColor = color == Color.TRANSPARENT ? TRANSPARENT : new java.awt.Color(color);
-        Composite originalComposite = this.canvas.getComposite();
-        this.canvas.setComposite(AlphaComposite.getInstance(color == Color.TRANSPARENT ? AlphaComposite.CLEAR : AlphaComposite.SRC_OVER));
-        this.canvas.setColor(awtColor);
-        this.canvas.fillRect(0, 0, getWidth(), getHeight());
-        this.canvas.setComposite(originalComposite);
+        fillRectangle(0, 0, getWidth(), getHeight(), color);
     }
 
     @Override
@@ -200,4 +195,14 @@ public class AwtCanvas implements Canvas {
     public int getWidth() {
         return this.bitmap != null ? this.bitmap.getWidth() : 0;
     }
+
+    @Override
+    public void fillRectangle(int x, int y, int width, int height, int color) {
+        java.awt.Color awtColor = color == Color.TRANSPARENT ? TRANSPARENT : new java.awt.Color(color);
+        Composite originalComposite = this.canvas.getComposite();
+        this.canvas.setComposite(AlphaComposite.getInstance(color == Color.TRANSPARENT ? AlphaComposite.CLEAR : AlphaComposite.SRC_OVER));
+        this.canvas.setColor(awtColor);
+        this.canvas.fillRect(x, y, width, height);
+        this.canvas.setComposite(originalComposite);
+    }
 }
diff --git a/vtm-ios/src/org/oscim/ios/backend/IosCanvas.java b/vtm-ios/src/org/oscim/ios/backend/IosCanvas.java
index b42f17f8..6a8c3fae 100644
--- a/vtm-ios/src/org/oscim/ios/backend/IosCanvas.java
+++ b/vtm-ios/src/org/oscim/ios/backend/IosCanvas.java
@@ -159,4 +159,12 @@ public class IosCanvas implements Canvas {
     public int getWidth() {
         return this.cgBitmapContext != null ? (int) this.cgBitmapContext.getWidth() : 0;
     }
+
+    @Override
+    public void fillRectangle(int x, int y, int width, int height, int color) {
+        CGRect rect = new CGRect(x, y, width, height);
+        setFillColor(this.cgBitmapContext, (color));
+        this.cgBitmapContext.setBlendMode(CGBlendMode.Normal);
+        this.cgBitmapContext.fillRect(rect);
+    }
 }
diff --git a/vtm-theme-comparator/src/org/oscim/theme/comparator/logging/BaseAppender.java b/vtm-theme-comparator/src/org/oscim/theme/comparator/logging/BaseAppender.java
index 262d4ff1..18757e4f 100644
--- a/vtm-theme-comparator/src/org/oscim/theme/comparator/logging/BaseAppender.java
+++ b/vtm-theme-comparator/src/org/oscim/theme/comparator/logging/BaseAppender.java
@@ -51,13 +51,18 @@ public abstract class BaseAppender extends AppenderBase<ILoggingEvent> {
     }
 
     @Override
-    protected void append(ILoggingEvent eventObject) {
-        if (eventObject != null && canLogClass(eventObject.getLoggerName())) {
-            stringBuilder.append(doLayout(eventObject));
-            String areaText = stringBuilder.toString();
-            this.textArea.setText(areaText);
-            this.textArea.setCaretPosition(areaText.length());
-        }
+    protected void append(final ILoggingEvent eventObject) {
+        Thread thread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                if (eventObject != null && canLogClass(eventObject.getLoggerName())) {
+                    textArea.append(doLayout(eventObject));
+                    textArea.setCaretPosition(textArea.getDocument().getLength());
+                }
+            }
+        });
+        thread.start();
+
 //TODO set Highlight for LogLevel [WARN], [ERROR]
     }
 
diff --git a/vtm-web/src/org/oscim/gdx/client/GwtCanvas.java b/vtm-web/src/org/oscim/gdx/client/GwtCanvas.java
index 2ef2ce10..c3425aa4 100644
--- a/vtm-web/src/org/oscim/gdx/client/GwtCanvas.java
+++ b/vtm-web/src/org/oscim/gdx/client/GwtCanvas.java
@@ -2,6 +2,7 @@
  * Copyright 2013 Hannes Janetzek
  * Copyright 2016-2017 devemux86
  * Copyright 2017 nebular
+ * Copyright 2017 Longri
  *
  * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
  *
@@ -124,4 +125,9 @@ public class GwtCanvas implements org.oscim.backend.canvas.Canvas {
     public int getWidth() {
         return this.bitmap != null ? this.bitmap.getWidth() : 0;
     }
+
+    @Override
+    public void fillRectangle(int x, int y, int width, int height, int color) {
+        // TODO
+    }
 }
diff --git a/vtm/resources/assets/shaders/linetex_layer_tex.glsl b/vtm/resources/assets/shaders/linetex_layer_tex.glsl
index 9b1ab66d..98a400dc 100644
--- a/vtm/resources/assets/shaders/linetex_layer_tex.glsl
+++ b/vtm/resources/assets/shaders/linetex_layer_tex.glsl
@@ -43,10 +43,15 @@ uniform sampler2D tex;
 uniform float u_mode;
 void
 main(){
-  if (u_mode == 1.0) {
-    vec4 c=texture2D(tex,vec2(abs(mod(v_st.s+1.0,2.0)),(v_st.t+1.0)*0.5));
+  if (u_mode >= 1.0) {
+
+    float step= 2.0;
+    if (u_mode == 3.0){// dashed texture
+        step =1.0;
+    }
+    vec4 c=texture2D(tex,vec2(abs(mod(v_st.s+1.0,step)),(v_st.t+1.0)*0.5));
     float fuzz=fwidth(c.a);
-    gl_FragColor=(c * u_color) *smoothstep(0.5-fuzz,0.5+fuzz,c.a);
+    gl_FragColor=(c * u_color) * smoothstep(0.5-fuzz,0.5+fuzz,c.a);
   }
   else {
     /* distance on perpendicular to the line */
diff --git a/vtm/src/org/oscim/backend/canvas/Canvas.java b/vtm/src/org/oscim/backend/canvas/Canvas.java
index 93f86ea2..c74eb39e 100644
--- a/vtm/src/org/oscim/backend/canvas/Canvas.java
+++ b/vtm/src/org/oscim/backend/canvas/Canvas.java
@@ -58,4 +58,6 @@ public interface Canvas {
     int getHeight();
 
     int getWidth();
+
+    void fillRectangle(int x, int y, int width, int height, int color);
 }
diff --git a/vtm/src/org/oscim/renderer/bucket/LineTexBucket.java b/vtm/src/org/oscim/renderer/bucket/LineTexBucket.java
index 12443e5f..42fddf70 100644
--- a/vtm/src/org/oscim/renderer/bucket/LineTexBucket.java
+++ b/vtm/src/org/oscim/renderer/bucket/LineTexBucket.java
@@ -359,7 +359,7 @@ public final class LineTexBucket extends LineBucket {
                 LineTexBucket lb = (LineTexBucket) b;
                 LineStyle line = lb.line.current();
 
-                gl.uniform1f(shader.uMode, line.texture != null ? 1 : 0);
+                gl.uniform1f(shader.uMode, line.dashTexture? 3 : line.texture != null ? 1 : 0);
 
                 if (line.texture != null)
                     line.texture.bind();
diff --git a/vtm/src/org/oscim/theme/ThemeLoader.java b/vtm/src/org/oscim/theme/ThemeLoader.java
index be58129d..88347c0c 100644
--- a/vtm/src/org/oscim/theme/ThemeLoader.java
+++ b/vtm/src/org/oscim/theme/ThemeLoader.java
@@ -18,11 +18,21 @@
  */
 package org.oscim.theme;
 
+
 import org.oscim.backend.CanvasAdapter;
 import org.oscim.theme.IRenderTheme.ThemeException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import javax.xml.parsers.ParserConfigurationException;
 
 public class ThemeLoader {
 
+    private static final Logger log = LoggerFactory.getLogger(ThemeLoader.class);
+
     public static boolean USE_ATLAS;
     public static boolean POT_TEXTURES;
 
@@ -46,8 +56,21 @@ public class ThemeLoader {
         return load(theme, null);
     }
 
+
+
     public static IRenderTheme load(ThemeFile theme, ThemeCallback themeCallback) throws ThemeException {
-        IRenderTheme t = USE_ATLAS ? XmlAtlasThemeBuilder.read(theme, themeCallback) : XmlThemeBuilder.read(theme, themeCallback);
+        IRenderTheme t = null;
+
+        try {
+            if(ThemeUtils.isMapsforgeTheme(theme.getRenderThemeAsStream())){
+                t = USE_ATLAS ? XmlMapsforgeAtlasThemeBuilder.read(theme, themeCallback) : XmlMapsforgeThemeBuilder.read(theme, themeCallback);
+            }else{
+                t = USE_ATLAS ? XmlAtlasThemeBuilder.read(theme, themeCallback) : XmlThemeBuilder.read(theme, themeCallback);
+            }
+        } catch (IOException | ParserConfigurationException | SAXException e) {
+            e.printStackTrace();
+        }
+
         if (t != null)
             t.scaleTextSize(CanvasAdapter.textScale + (CanvasAdapter.dpi / CanvasAdapter.DEFAULT_DPI - 1));
         return t;
diff --git a/vtm/src/org/oscim/theme/ThemeUtils.java b/vtm/src/org/oscim/theme/ThemeUtils.java
new file mode 100644
index 00000000..75d227ca
--- /dev/null
+++ b/vtm/src/org/oscim/theme/ThemeUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017 Longri
+ *
+ * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.oscim.theme;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParserFactory;
+
+/**
+ * Created by Longri on 30.08.2017.
+ */
+
+public class ThemeUtils {
+
+    public static class SAXTerminatorException extends SAXException {
+        public SAXTerminatorException() {
+            super();
+        }
+    }
+
+
+    /**
+     * Return true, if the given InputStream a Mapsforge render theme!
+     *
+     * @param stream
+     * @return TRUE or FALSE
+     * @throws IOException
+     * @throws SAXException
+     * @throws ParserConfigurationException
+     */
+    public static boolean isMapsforgeTheme(InputStream stream) throws IOException, SAXException, ParserConfigurationException {
+        final AtomicBoolean isMapsforgeTheme = new AtomicBoolean(false);
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+
+        XMLReader xmlReader = factory.newSAXParser().getXMLReader();
+        xmlReader.setContentHandler(new DefaultHandler() {
+            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+                if (localName.equals("rendertheme")) {
+                    isMapsforgeTheme.set(uri.equals("http://mapsforge.org/renderTheme"));
+                    //we have all info's, break parsing
+                    throw new SAXTerminatorException();
+                }
+            }
+        });
+        try {
+            xmlReader.parse(new InputSource(stream));
+        } catch (SAXTerminatorException e) {
+            // do nothing
+        }
+        stream.close();
+        return isMapsforgeTheme.get();
+    }
+
+}
diff --git a/vtm/src/org/oscim/theme/XmlMapsforgeAtlasThemeBuilder.java b/vtm/src/org/oscim/theme/XmlMapsforgeAtlasThemeBuilder.java
new file mode 100644
index 00000000..c71666c5
--- /dev/null
+++ b/vtm/src/org/oscim/theme/XmlMapsforgeAtlasThemeBuilder.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017 Longri
+ * Copyright 2017 devemux86
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.oscim.theme;
+
+import org.oscim.backend.CanvasAdapter;
+import org.oscim.backend.Platform;
+import org.oscim.backend.XMLReaderAdapter;
+import org.oscim.backend.canvas.Bitmap;
+import org.oscim.renderer.atlas.TextureAtlas;
+import org.oscim.renderer.atlas.TextureRegion;
+import org.oscim.theme.IRenderTheme.ThemeException;
+import org.oscim.theme.rule.Rule;
+import org.oscim.theme.styles.RenderStyle;
+import org.oscim.theme.styles.SymbolStyle;
+import org.oscim.theme.styles.SymbolStyle.SymbolBuilder;
+import org.oscim.utils.TextureAtlasUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class XmlMapsforgeAtlasThemeBuilder extends XmlMapsforgeThemeBuilder {
+
+    /**
+     * @param theme an input theme containing valid render theme XML data.
+     * @return a new RenderTheme which is created by parsing the XML data from the input theme.
+     * @throws ThemeException if an error occurs while parsing the render theme XML.
+     */
+    public static IRenderTheme read(ThemeFile theme) throws ThemeException {
+        return read(theme, null);
+    }
+
+    /**
+     * @param theme         an input theme containing valid render theme XML data.
+     * @param themeCallback the theme callback.
+     * @return a new RenderTheme which is created by parsing the XML data from the input theme.
+     * @throws ThemeException if an error occurs while parsing the render theme XML.
+     */
+    public static IRenderTheme read(ThemeFile theme, ThemeCallback themeCallback) throws ThemeException {
+        Map<Object, TextureRegion> outputMap = new HashMap<>();
+        List<TextureAtlas> atlasList = new ArrayList<>();
+        XmlMapsforgeAtlasThemeBuilder renderThemeHandler = new XmlMapsforgeAtlasThemeBuilder(theme, themeCallback, outputMap, atlasList);
+
+        try {
+            new XMLReaderAdapter().parse(renderThemeHandler, theme.getRenderThemeAsStream());
+        } catch (Exception e) {
+            throw new ThemeException(e.getMessage());
+        }
+
+        TextureAtlasUtils.createTextureRegions(renderThemeHandler.bitmapMap, outputMap, atlasList,
+                true, CanvasAdapter.platform == Platform.IOS);
+
+        return replaceThemeSymbols(renderThemeHandler.mRenderTheme, outputMap);
+    }
+
+    private static IRenderTheme replaceThemeSymbols(RenderTheme renderTheme, Map<Object, TextureRegion> regionMap) {
+        SymbolBuilder<?> symbolBuilder = SymbolStyle.builder();
+        for (Rule rule : renderTheme.getRules()) {
+            replaceRuleSymbols(rule, regionMap, symbolBuilder);
+        }
+        return renderTheme;
+    }
+
+    private static void replaceRuleSymbols(Rule rule, Map<Object, TextureRegion> regionMap, SymbolBuilder<?> symbolBuilder) {
+        for (int i = 0, n = rule.styles.length; i < n; i++) {
+            RenderStyle style = rule.styles[i];
+            if (style instanceof SymbolStyle) {
+                int hash = ((SymbolStyle) style).hash;
+                TextureRegion region = regionMap.get(hash);
+                if (region != null) {
+                    SymbolBuilder<?> b = symbolBuilder.reset();
+                    rule.styles[i] = b.texture(region).build();
+                }
+            }
+        }
+        for (Rule subRule : rule.subRules) {
+            replaceRuleSymbols(subRule, regionMap, symbolBuilder);
+        }
+    }
+
+    private final Map<Object, TextureRegion> regionMap;
+    private final List<TextureAtlas> atlasList;
+
+    private final Map<Object, Bitmap> bitmapMap = new HashMap<>();
+
+    public XmlMapsforgeAtlasThemeBuilder(ThemeFile theme,
+                                         Map<Object, TextureRegion> regionMap, List<TextureAtlas> atlasList) {
+        this(theme, null, regionMap, atlasList);
+    }
+
+    public XmlMapsforgeAtlasThemeBuilder(ThemeFile theme, ThemeCallback themeCallback,
+                                         Map<Object, TextureRegion> regionMap, List<TextureAtlas> atlasList) {
+        super(theme, themeCallback);
+        this.regionMap = regionMap;
+        this.atlasList = atlasList;
+    }
+
+    @Override
+    RenderTheme createTheme(Rule[] rules) {
+        return new AtlasRenderTheme(mMapBackground, mTextScale, rules, mLevels, regionMap, atlasList);
+    }
+
+    @Override
+    SymbolStyle buildSymbol(SymbolBuilder<?> b, String src, Bitmap bitmap) {
+        // we need to hash with the width/height included as the same symbol could be required
+        // in a different size and must be cached with a size-specific hash
+        String absoluteName = CanvasAdapter.getAbsoluteFile(mTheme.getRelativePathPrefix(), src).getAbsolutePath();
+        int hash = new StringBuilder().append(absoluteName).append(b.symbolWidth).append(b.symbolHeight).append(b.symbolPercent).toString().hashCode();
+        bitmapMap.put(hash, bitmap);
+        return b.hash(hash).build();
+    }
+}
diff --git a/vtm/src/org/oscim/theme/XmlMapsforgeThemeBuilder.java b/vtm/src/org/oscim/theme/XmlMapsforgeThemeBuilder.java
new file mode 100644
index 00000000..22aae9ad
--- /dev/null
+++ b/vtm/src/org/oscim/theme/XmlMapsforgeThemeBuilder.java
@@ -0,0 +1,1226 @@
+/*
+ * Copyright 2010, 2011, 2012 mapsforge.org
+ * Copyright 2013 Hannes Janetzek
+ * Copyright 2016-2017 devemux86
+ * Copyright 2016-2017 Longri
+ * Copyright 2016 Andrey Novikov
+ *
+ * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
+ *
+ * This program is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU Lesser General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package org.oscim.theme;
+
+import org.oscim.backend.CanvasAdapter;
+import org.oscim.backend.XMLReaderAdapter;
+import org.oscim.backend.canvas.Bitmap;
+import org.oscim.backend.canvas.Canvas;
+import org.oscim.backend.canvas.Color;
+import org.oscim.backend.canvas.Paint;
+import org.oscim.backend.canvas.Paint.Cap;
+import org.oscim.backend.canvas.Paint.FontFamily;
+import org.oscim.backend.canvas.Paint.FontStyle;
+import org.oscim.renderer.atlas.TextureAtlas;
+import org.oscim.renderer.atlas.TextureAtlas.Rect;
+import org.oscim.renderer.atlas.TextureRegion;
+import org.oscim.renderer.bucket.TextureItem;
+import org.oscim.theme.IRenderTheme.ThemeException;
+import org.oscim.theme.rule.Rule;
+import org.oscim.theme.rule.Rule.Closed;
+import org.oscim.theme.rule.Rule.Selector;
+import org.oscim.theme.rule.RuleBuilder;
+import org.oscim.theme.styles.AreaStyle;
+import org.oscim.theme.styles.AreaStyle.AreaBuilder;
+import org.oscim.theme.styles.CircleStyle;
+import org.oscim.theme.styles.CircleStyle.CircleBuilder;
+import org.oscim.theme.styles.ExtrusionStyle;
+import org.oscim.theme.styles.ExtrusionStyle.ExtrusionBuilder;
+import org.oscim.theme.styles.LineStyle;
+import org.oscim.theme.styles.LineStyle.LineBuilder;
+import org.oscim.theme.styles.RenderStyle;
+import org.oscim.theme.styles.SymbolStyle;
+import org.oscim.theme.styles.SymbolStyle.SymbolBuilder;
+import org.oscim.theme.styles.TextStyle;
+import org.oscim.theme.styles.TextStyle.TextBuilder;
+import org.oscim.utils.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Pattern;
+
+import static java.lang.Boolean.parseBoolean;
+import static java.lang.Float.parseFloat;
+import static java.lang.Integer.parseInt;
+
+public class XmlMapsforgeThemeBuilder extends DefaultHandler {
+    private static final Logger log = LoggerFactory.getLogger(XmlMapsforgeThemeBuilder.class);
+
+    private static final int RENDER_THEME_VERSION = 4;
+    private static final Pattern SPLIT_PATTERN = Pattern.compile(",");
+    private static final float REPEAT_GAP_DEFAULT = 200f;
+    private static final float REPEAT_START_DEFAULT = 30f;
+
+
+    private enum Element {
+        RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE, ATLAS, RENDERING_STYLE
+    }
+
+    private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme";
+    private static final String ELEMENT_NAME_STYLE_MENU = "stylemenu";
+    private static final String ELEMENT_NAME_MATCH = "rule";
+    private static final String UNEXPECTED_ELEMENT = "unexpected element: ";
+
+    private static final String LINE_STYLE = "L";
+    private static final String OUTLINE_STYLE = "O";
+    private static final String AREA_STYLE = "A";
+
+    /**
+     * @param theme an input theme containing valid render theme XML data.
+     * @return a new RenderTheme which is created by parsing the XML data from the input theme.
+     * @throws ThemeException if an error occurs while parsing the render theme XML.
+     */
+    public static IRenderTheme read(ThemeFile theme) throws ThemeException {
+        return read(theme, null);
+    }
+
+    /**
+     * @param theme         an input theme containing valid render theme XML data.
+     * @param themeCallback the theme callback.
+     * @return a new RenderTheme which is created by parsing the XML data from the input theme.
+     * @throws ThemeException if an error occurs while parsing the render theme XML.
+     */
+    public static IRenderTheme read(ThemeFile theme, ThemeCallback themeCallback) throws ThemeException {
+        XmlMapsforgeThemeBuilder renderThemeHandler = new XmlMapsforgeThemeBuilder(theme, themeCallback);
+
+        try {
+            new XMLReaderAdapter().parse(renderThemeHandler, theme.getRenderThemeAsStream());
+        } catch (Exception e) {
+            throw new ThemeException(e.getMessage());
+        }
+
+        return renderThemeHandler.mRenderTheme;
+    }
+
+    /**
+     * Logs the given information about an unknown XML attribute.
+     *
+     * @param element        the XML element name.
+     * @param name           the XML attribute name.
+     * @param value          the XML attribute value.
+     * @param attributeIndex the XML attribute index position.
+     */
+    private static void logUnknownAttribute(String element, String name,
+                                            String value, int attributeIndex) {
+        log.debug("unknown attribute in element {} () : {} = {}",
+                element, attributeIndex, name, value);
+    }
+
+    private final ArrayList<RuleBuilder> mRulesList = new ArrayList<>();
+    private final Stack<Element> mElementStack = new Stack<>();
+    private final Stack<RuleBuilder> mRuleStack = new Stack<>();
+    private final HashMap<String, RenderStyle> mStyles = new HashMap<>(10);
+
+    private final HashMap<String, TextBuilder<?>> mTextStyles = new HashMap<>(10);
+
+    private final AreaBuilder<?> mAreaBuilder = AreaStyle.builder();
+    private final CircleBuilder<?> mCircleBuilder = CircleStyle.builder();
+    private final ExtrusionBuilder<?> mExtrusionBuilder = ExtrusionStyle.builder();
+    private final LineBuilder<?> mLineBuilder = LineStyle.builder();
+    private final SymbolBuilder<?> mSymbolBuilder = SymbolStyle.builder();
+    private final TextBuilder<?> mTextBuilder = TextStyle.builder();
+
+    private RuleBuilder mCurrentRule;
+    private TextureAtlas mTextureAtlas;
+
+    int mLevels = 0;
+    int mMapBackground = 0xffffffff;
+    float mTextScale = 1;
+
+    final ThemeFile mTheme;
+    private final ThemeCallback mThemeCallback;
+    RenderTheme mRenderTheme;
+
+    private final float mScale, mScale2;
+
+    private Set<String> mCategories;
+    private XmlRenderThemeStyleLayer mCurrentLayer;
+    private XmlRenderThemeStyleMenu mRenderThemeStyleMenu;
+
+    public XmlMapsforgeThemeBuilder(ThemeFile theme) {
+        this(theme, null);
+    }
+
+    public XmlMapsforgeThemeBuilder(ThemeFile theme, ThemeCallback themeCallback) {
+        mTheme = theme;
+        mThemeCallback = themeCallback;
+        mScale = CanvasAdapter.scale + (CanvasAdapter.dpi / CanvasAdapter.DEFAULT_DPI - 1);
+        mScale2 = CanvasAdapter.scale + (CanvasAdapter.dpi / CanvasAdapter.DEFAULT_DPI - 1) * 0.5f;
+    }
+
+    @Override
+    public void endDocument() {
+        Rule[] rules = new Rule[mRulesList.size()];
+        for (int i = 0, n = rules.length; i < n; i++)
+            rules[i] = mRulesList.get(i).onComplete(null);
+
+        mRenderTheme = createTheme(rules);
+
+        mRulesList.clear();
+        mStyles.clear();
+        mRuleStack.clear();
+        mElementStack.clear();
+
+        mTextureAtlas = null;
+    }
+
+    RenderTheme createTheme(Rule[] rules) {
+        return new RenderTheme(mMapBackground, mTextScale, rules, mLevels);
+    }
+
+    @Override
+    public void endElement(String uri, String localName, String qName) {
+        mElementStack.pop();
+
+        if (ELEMENT_NAME_MATCH.equals(localName)) {
+            mRuleStack.pop();
+            if (mRuleStack.empty()) {
+                if (isVisible(mCurrentRule)) {
+                    mRulesList.add(mCurrentRule);
+                }
+            } else {
+                mCurrentRule = mRuleStack.peek();
+            }
+        } else if (ELEMENT_NAME_STYLE_MENU.equals(localName)) {
+            // when we are finished parsing the menu part of the file, we can get the
+            // categories to render from the initiator. This allows the creating action
+            // to select which of the menu options to choose
+            if (null != mTheme.getMenuCallback()) {
+                // if there is no callback, there is no menu, so the categories will be null
+                mCategories = mTheme.getMenuCallback().getCategories(mRenderThemeStyleMenu);
+            }
+        }
+    }
+
+    @Override
+    public void error(SAXParseException exception) {
+        log.debug(exception.getMessage());
+    }
+
+    @Override
+    public void warning(SAXParseException exception) {
+        log.debug(exception.getMessage());
+    }
+
+    @Override
+    public void startElement(String uri, String localName, String qName,
+                             Attributes attributes) throws ThemeException {
+        try {
+            if (ELEMENT_NAME_RENDER_THEME.equals(localName)) {
+                checkState(localName, Element.RENDER_THEME);
+                createRenderTheme(localName, attributes);
+
+            } else if (ELEMENT_NAME_MATCH.equals(localName)) {
+                checkState(localName, Element.RULE);
+                RuleBuilder rule = createRule(localName, attributes);
+                if (!mRuleStack.empty() && isVisible(rule)) {
+                    mCurrentRule.addSubRule(rule);
+                }
+                mCurrentRule = rule;
+                mRuleStack.push(mCurrentRule);
+
+            } else if ("style-text".equals(localName)) {
+                checkState(localName, Element.STYLE);
+                handleTextElement(localName, attributes, true, false);
+
+            } else if ("style-area".equals(localName)) {
+                checkState(localName, Element.STYLE);
+                handleAreaElement(localName, attributes, true);
+
+            } else if ("style-line".equals(localName)) {
+                checkState(localName, Element.STYLE);
+                handleLineElement(localName, attributes, true, false);
+
+            } else if ("outline-layer".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                LineStyle line = createLine(null, localName, attributes, mLevels++, true, false);
+                mStyles.put(OUTLINE_STYLE + line.style, line);
+
+            } else if ("area".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                handleAreaElement(localName, attributes, false);
+
+            } else if ("caption".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                handleTextElement(localName, attributes, false, true);
+
+            } else if ("circle".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                CircleStyle circle = createCircle(localName, attributes, mLevels++);
+                if (isVisible(circle))
+                    mCurrentRule.addStyle(circle);
+
+            } else if ("line".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                handleLineElement(localName, attributes, false, false);
+
+            } else if ("pathText".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                handleTextElement(localName, attributes, false, false);
+
+            } else if ("symbol".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                SymbolStyle symbol = createSymbol(localName, attributes);
+                if (symbol != null && isVisible(symbol))
+                    mCurrentRule.addStyle(symbol);
+
+            } else if ("outline".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                LineStyle outline = createOutline(attributes.getValue("use"), attributes);
+                if (outline != null && isVisible(outline))
+                    mCurrentRule.addStyle(outline);
+
+            } else if ("extrusion".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                ExtrusionStyle extrusion = createExtrusion(localName, attributes, mLevels++);
+                if (isVisible(extrusion))
+                    mCurrentRule.addStyle(extrusion);
+
+            } else if ("lineSymbol".equals(localName)) {
+                checkState(localName, Element.RENDERING_INSTRUCTION);
+                handleLineElement(localName, attributes, false, true);
+
+            } else if ("atlas".equals(localName)) {
+                checkState(localName, Element.ATLAS);
+                createAtlas(localName, attributes);
+
+            } else if ("rect".equals(localName)) {
+                checkState(localName, Element.ATLAS);
+                createTextureRegion(localName, attributes);
+
+            } else if ("cat".equals(localName)) {
+                checkState(qName, Element.RENDERING_STYLE);
+                mCurrentLayer.addCategory(getStringAttribute(attributes, "id"));
+
+            } else if ("layer".equals(localName)) {
+                // render theme menu layer
+                checkState(qName, Element.RENDERING_STYLE);
+                boolean enabled = false;
+                if (getStringAttribute(attributes, "enabled") != null) {
+                    enabled = Boolean.valueOf(getStringAttribute(attributes, "enabled"));
+                }
+                boolean visible = Boolean.valueOf(getStringAttribute(attributes, "visible"));
+                mCurrentLayer = mRenderThemeStyleMenu.createLayer(getStringAttribute(attributes, "id"), visible, enabled);
+                String parent = getStringAttribute(attributes, "parent");
+                if (null != parent) {
+                    XmlRenderThemeStyleLayer parentEntry = mRenderThemeStyleMenu.getLayer(parent);
+                    if (null != parentEntry) {
+                        for (String cat : parentEntry.getCategories()) {
+                            mCurrentLayer.addCategory(cat);
+                        }
+                        for (XmlRenderThemeStyleLayer overlay : parentEntry.getOverlays()) {
+                            mCurrentLayer.addOverlay(overlay);
+                        }
+                    }
+                }
+
+            } else if ("name".equals(localName)) {
+                // render theme menu name
+                checkState(qName, Element.RENDERING_STYLE);
+                mCurrentLayer.addTranslation(getStringAttribute(attributes, "lang"), getStringAttribute(attributes, "value"));
+
+            } else if ("overlay".equals(localName)) {
+                // render theme menu overlay
+                checkState(qName, Element.RENDERING_STYLE);
+                XmlRenderThemeStyleLayer overlay = mRenderThemeStyleMenu.getLayer(getStringAttribute(attributes, "id"));
+                if (overlay != null) {
+                    mCurrentLayer.addOverlay(overlay);
+                }
+
+            } else if ("stylemenu".equals(localName)) {
+                checkState(qName, Element.RENDERING_STYLE);
+                mRenderThemeStyleMenu = new XmlRenderThemeStyleMenu(getStringAttribute(attributes, "id"),
+                        getStringAttribute(attributes, "defaultlang"), getStringAttribute(attributes, "defaultvalue"));
+
+            } else {
+                log.error("unknown element: {}", localName);
+                throw new SAXException("unknown element: " + localName);
+            }
+        } catch (SAXException e) {
+            throw new ThemeException(e.getMessage());
+        } catch (IOException e) {
+            throw new ThemeException(e.getMessage());
+        }
+    }
+
+    private RuleBuilder createRule(String localName, Attributes attributes) {
+        String cat = null;
+        int element = Rule.Element.ANY;
+        int closed = Closed.ANY;
+        String keys = null;
+        String values = null;
+        byte zoomMin = 0;
+        byte zoomMax = Byte.MAX_VALUE;
+        int selector = 0;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("e".equals(name)) {
+                String val = value.toUpperCase(Locale.ENGLISH);
+                if ("WAY".equals(val))
+                    element = Rule.Element.WAY;
+                else if ("NODE".equals(val))
+                    element = Rule.Element.NODE;
+            } else if ("k".equals(name)) {
+                if (!"*".equals(value))
+                    keys = value;
+            } else if ("v".equals(name)) {
+                if (!"*".equals(value))
+                    values = value;
+            } else if ("cat".equals(name)) {
+                cat = value;
+            } else if ("closed".equals(name)) {
+                String val = value.toUpperCase(Locale.ENGLISH);
+                if ("YES".equals(val))
+                    closed = Closed.YES;
+                else if ("NO".equals(val))
+                    closed = Closed.NO;
+            } else if ("zoom-min".equals(name)) {
+                zoomMin = Byte.parseByte(value);
+            } else if ("zoom-max".equals(name)) {
+                zoomMax = Byte.parseByte(value);
+            } else if ("select".equals(name)) {
+                if ("first".equals(value))
+                    selector |= Selector.FIRST;
+                if ("when-matched".equals(value))
+                    selector |= Selector.WHEN_MATCHED;
+            } else {
+                XmlMapsforgeThemeBuilder.logUnknownAttribute(localName, name, value, i);
+            }
+        }
+
+        if (closed == Closed.YES)
+            element = Rule.Element.POLY;
+        else if (closed == Closed.NO)
+            element = Rule.Element.LINE;
+
+        XmlMapsforgeThemeBuilder.validateNonNegative("zoom-min", zoomMin);
+        XmlMapsforgeThemeBuilder.validateNonNegative("zoom-max", zoomMax);
+        if (zoomMin > zoomMax)
+            throw new ThemeException("zoom-min must be less or equal zoom-max: " + zoomMin);
+
+        RuleBuilder b = RuleBuilder.create(keys, values);
+        b.cat(cat);
+        b.zoom(zoomMin, zoomMax);
+        b.element(element);
+        b.select(selector);
+        return b;
+    }
+
+    private TextureRegion getAtlasRegion(String src) {
+        if (mTextureAtlas == null)
+            return null;
+
+        TextureRegion texture = mTextureAtlas.getTextureRegion(src);
+
+        if (texture == null)
+            log.debug("missing texture atlas item '" + src + "'");
+
+        return texture;
+    }
+
+    private void handleLineElement(String localName, Attributes attributes, boolean isStyle, boolean symbolLine)
+            throws SAXException {
+
+        String use = attributes.getValue("use");
+        LineStyle style = null;
+
+        if (use != null) {
+            style = (LineStyle) mStyles.get(LINE_STYLE + use);
+            if (style == null) {
+                log.debug("missing line style 'use': " + use);
+                return;
+            }
+        }
+
+        LineStyle line = createLine(style, localName, attributes, mLevels++, false, symbolLine);
+
+        if (isStyle) {
+            mStyles.put(LINE_STYLE + line.style, line);
+        } else {
+            if (isVisible(line)) {
+                mCurrentRule.addStyle(line);
+                /* Note 'outline' will not be inherited, it's just a
+                 * shortcut to add the outline RenderInstruction. */
+                String outlineValue = attributes.getValue("outline");
+                if (outlineValue != null) {
+                    LineStyle outline = createOutline(outlineValue, attributes);
+                    if (outline != null)
+                        mCurrentRule.addStyle(outline);
+                }
+            }
+        }
+    }
+
+    /**
+     * @param line      optional: line style defaults
+     * @param level     the drawing level of this instruction.
+     * @param isOutline is outline layer
+     * @return a new Line with the given rendering attributes.
+     */
+    private LineStyle createLine(LineStyle line, String elementName, Attributes attributes,
+                                 int level, boolean isOutline, boolean symbolLine) {
+        LineBuilder<?> b = mLineBuilder.set(line);
+        b.isOutline(isOutline);
+        b.level(level);
+        b.themeCallback(mThemeCallback);
+        String src = null;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("id".equals(name))
+                b.style = value;
+
+            else if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("src".equals(name))
+                src = value;
+
+            else if ("use".equals(name))
+                ;// ignore
+
+            else if ("outline".equals(name))
+                ;// ignore
+
+            else if ("stroke".equals(name))
+                b.color(value);
+
+            else if ("width".equals(name) || "stroke-width".equals(name)) {
+                b.strokeWidth = parseFloat(value) * mScale2;
+                if (line == null) {
+                    if (!isOutline)
+                        validateNonNegative("width", b.strokeWidth);
+                } else {
+                    /* use stroke width relative to 'line' */
+                    b.strokeWidth += line.width;
+                    if (b.strokeWidth <= 0)
+                        b.strokeWidth = 1;
+                }
+            } else if ("cap".equals(name) || "stroke-linecap".equals(name))
+                b.cap = Cap.valueOf(value.toUpperCase(Locale.ENGLISH));
+
+            else if ("fix".equals(name))
+                b.fixed = parseBoolean(value);
+
+            else if ("stipple".equals(name))
+                b.stipple = Math.round(parseInt(value) * mScale2);
+
+            else if ("stipple-stroke".equals(name))
+                b.stippleColor(value);
+
+            else if ("stipple-width".equals(name))
+                b.stippleWidth = parseFloat(value);
+
+            else if ("fade".equals(name))
+                b.fadeScale = Integer.parseInt(value);
+
+            else if ("min".equals(name))
+                ; //min = Float.parseFloat(value);
+
+            else if ("blur".equals(name))
+                b.blur = parseFloat(value);
+
+            else if ("style".equals(name))
+                ; // ignore
+
+            else if ("dasharray".equals(name))
+                ; // TBD
+
+            else if ("symbol-width".equals(name))
+                b.symbolWidth = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-height".equals(name))
+                b.symbolHeight = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-percent".equals(name))
+                b.symbolPercent = Integer.parseInt(value);
+
+            else if ("stroke-dasharray".equals(name)) {
+                b.strokeDasharray = parseFloatArray(value);
+                for (int j = 0; j < b.strokeDasharray.length; ++j) {
+                    b.strokeDasharray[j] = b.strokeDasharray[j] * mScale;
+                }
+            } else if ("dy".equals(name)) {
+                // NB: minus..
+                //TODO   b.dy = -Float.parseFloat(value) * mScale;
+            } else if ("align-center".equals(name)) {
+                //TODO   handle align-center
+            } else if ("repeat".equals(name)) {
+                //TODO   handle repeat
+            } else if ("display".equals(name)) {
+                //TODO  handle display
+            } else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+
+        if (b.strokeDasharray != null) {//create a dashed texture
+            int bmpWidth = 0;
+            int bmpHeight = (int) (b.strokeWidth);
+            if (bmpHeight < 1) bmpHeight = 2;
+            for (float f : b.strokeDasharray) {
+                if (f < 1) f = 1;
+                bmpWidth += f;
+            }
+
+            int factor = 10;
+            Bitmap bmp = CanvasAdapter.newBitmap(bmpWidth * factor, bmpHeight * factor, 0);
+            Canvas canvas = CanvasAdapter.newCanvas();
+            canvas.setBitmap(bmp);
+
+            boolean bw = false;
+            int x = 0;
+            for (float f : b.strokeDasharray) {
+                if (f < 1) f = 1;
+                canvas.fillRectangle(x * factor, 0, (int) f * factor, bmpHeight * factor, (bw ? Color.TRANSPARENT : Color.WHITE));
+                x += f;
+                bw = !bw;
+            }
+            b.texture = new TextureItem(bmp);
+            b.texture.mipmap = false;
+            b.stipple = (int) (bmpWidth * 1.2f);
+            b.stippleWidth = bmpWidth;
+            b.fixed = false;
+            b.randomOffset = false;
+
+            b.stippleColor = b.fillColor;
+            b.fillColor = Color.TRANSPARENT;
+            b.strokeColor = Color.TRANSPARENT;
+
+        } else {
+            b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
+            if (symbolLine) {
+
+                // we have no way to set a repeatGap for the renderer,
+                // so we create a texture that already contains this repeatGap.
+                float repeatGap = REPEAT_GAP_DEFAULT * mScale;
+                float repeatStart = REPEAT_START_DEFAULT * mScale;
+                int width = (int) (b.texture.width + repeatGap);
+                int height = b.texture.height;
+                Bitmap bmp = CanvasAdapter.newBitmap(width, height, 0);
+                Canvas canvas = CanvasAdapter.newCanvas();
+                canvas.setBitmap(bmp);
+                canvas.drawBitmap(b.texture.bitmap, repeatStart, 0);
+                b.texture = new TextureItem(bmp);
+
+                // we must set stipple values
+                // The multipliers are determined empirically to
+                // correspond to the representation at Mapsforge!
+                b.stipple = b.texture.width * 3;
+                b.strokeWidth *= 2 * mScale;
+
+                // use texture color
+                b.stippleColor = Color.WHITE;
+                b.fillColor = Color.TRANSPARENT;
+                b.strokeColor = Color.TRANSPARENT;
+
+                b.fixed = false;
+            }
+        }
+
+        return b.build();
+    }
+
+    private void handleAreaElement(String localName, Attributes attributes, boolean isStyle)
+            throws SAXException {
+
+        String use = attributes.getValue("use");
+        AreaStyle style = null;
+
+        if (use != null) {
+            style = (AreaStyle) mStyles.get(AREA_STYLE + use);
+            if (style == null) {
+                log.debug("missing area style 'use': " + use);
+                return;
+            }
+        }
+
+        AreaStyle area = createArea(style, localName, attributes, mLevels++);
+
+        if (isStyle) {
+            mStyles.put(AREA_STYLE + area.style, area);
+        } else {
+            if (isVisible(area))
+                mCurrentRule.addStyle(area);
+        }
+    }
+
+    /**
+     * @return a new Area with the given rendering attributes.
+     */
+    private AreaStyle createArea(AreaStyle area, String elementName, Attributes attributes,
+                                 int level) {
+        AreaBuilder<?> b = mAreaBuilder.set(area);
+        b.level(level);
+        b.themeCallback(mThemeCallback);
+        String src = null;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("id".equals(name))
+                b.style = value;
+
+            else if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("use".equals(name))
+                ;// ignore
+
+            else if ("src".equals(name))
+                src = value;
+
+            else if ("fill".equals(name))
+                b.color(value);
+
+            else if ("stroke".equals(name))
+                b.strokeColor(value);
+
+            else if ("stroke-width".equals(name)) {
+                float strokeWidth = Float.parseFloat(value);
+                validateNonNegative("stroke-width", strokeWidth);
+                b.strokeWidth = strokeWidth * mScale2;
+
+            } else if ("fade".equals(name))
+                b.fadeScale = Integer.parseInt(value);
+
+            else if ("blend".equals(name))
+                b.blendScale = Integer.parseInt(value);
+
+            else if ("blend-fill".equals(name))
+                b.blendColor(value);
+
+            else if ("mesh".equals(name))
+                b.mesh(Boolean.parseBoolean(value));
+
+            else if ("symbol-width".equals(name))
+                b.symbolWidth = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-height".equals(name))
+                b.symbolHeight = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-percent".equals(name))
+                b.symbolPercent = Integer.parseInt(value);
+
+            else if ("symbol-scaling".equals(name)) {
+                // no-op
+            } else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+        b.texture = Utils.loadTexture(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
+
+        return b.build();
+    }
+
+    private LineStyle createOutline(String style, Attributes attributes) {
+        if (style != null) {
+            LineStyle line = (LineStyle) mStyles.get(OUTLINE_STYLE + style);
+            if (line != null && line.outline) {
+                String cat = null;
+
+                for (int i = 0; i < attributes.getLength(); i++) {
+                    String name = attributes.getLocalName(i);
+                    String value = attributes.getValue(i);
+
+                    if ("cat".equals(name)) {
+                        cat = value;
+                        break;
+                    }
+                }
+
+                return line
+                        .setCat(cat);
+            }
+        }
+        log.debug("BUG not an outline style: " + style);
+        return null;
+    }
+
+    private void createAtlas(String elementName, Attributes attributes) throws IOException {
+        String img = null;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("img".equals(name)) {
+                img = value;
+            } else {
+                XmlMapsforgeThemeBuilder.logUnknownAttribute(elementName, name, value, i);
+            }
+        }
+        validateExists("img", img, elementName);
+
+        Bitmap bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), img);
+        if (bitmap != null)
+            mTextureAtlas = new TextureAtlas(bitmap);
+    }
+
+    private void createTextureRegion(String elementName, Attributes attributes) {
+        if (mTextureAtlas == null)
+            return;
+
+        String regionName = null;
+        Rect r = null;
+
+        for (int i = 0, n = attributes.getLength(); i < n; i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("id".equals(name)) {
+                regionName = value;
+            } else if ("pos".equals(name)) {
+                String[] pos = value.split(" ");
+                if (pos.length == 4) {
+                    r = new Rect(Integer.parseInt(pos[0]),
+                            Integer.parseInt(pos[1]),
+                            Integer.parseInt(pos[2]),
+                            Integer.parseInt(pos[3]));
+                }
+            } else {
+                XmlMapsforgeThemeBuilder.logUnknownAttribute(elementName, name, value, i);
+            }
+        }
+        validateExists("id", regionName, elementName);
+        validateExists("pos", r, elementName);
+
+        mTextureAtlas.addTextureRegion(regionName.intern(), r);
+    }
+
+    private void checkElement(String elementName, Element element) throws SAXException {
+        Element parentElement;
+        switch (element) {
+            case RENDER_THEME:
+                if (!mElementStack.empty()) {
+                    throw new SAXException(UNEXPECTED_ELEMENT + elementName);
+                }
+                return;
+
+            case RULE:
+                parentElement = mElementStack.peek();
+                if (parentElement != Element.RENDER_THEME
+                        && parentElement != Element.RULE) {
+                    throw new SAXException(UNEXPECTED_ELEMENT + elementName);
+                }
+                return;
+
+            case STYLE:
+                parentElement = mElementStack.peek();
+                if (parentElement != Element.RENDER_THEME) {
+                    throw new SAXException(UNEXPECTED_ELEMENT + elementName);
+                }
+                return;
+
+            case RENDERING_INSTRUCTION:
+                if (mElementStack.peek() != Element.RULE) {
+                    throw new SAXException(UNEXPECTED_ELEMENT + elementName);
+                }
+                return;
+
+            case ATLAS:
+                parentElement = mElementStack.peek();
+                // FIXME
+                if (parentElement != Element.RENDER_THEME
+                        && parentElement != Element.ATLAS) {
+                    throw new SAXException(UNEXPECTED_ELEMENT + elementName);
+                }
+                return;
+
+            case RENDERING_STYLE:
+                return;
+        }
+
+        throw new SAXException("unknown enum value: " + element);
+    }
+
+    private void checkState(String elementName, Element element) throws SAXException {
+        checkElement(elementName, element);
+        mElementStack.push(element);
+    }
+
+    private void createRenderTheme(String elementName, Attributes attributes) {
+        Integer version = null;
+        int mapBackground = Color.WHITE;
+        float baseStrokeWidth = 1;
+        float baseTextScale = 1;
+
+        for (int i = 0; i < attributes.getLength(); ++i) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("schemaLocation".equals(name))
+                continue;
+
+            if ("version".equals(name))
+                version = Integer.parseInt(value);
+
+            else if ("map-background".equals(name)) {
+                mapBackground = Color.parseColor(value);
+                if (mThemeCallback != null)
+                    mapBackground = mThemeCallback.getColor(mapBackground);
+            } else if ("base-stroke-width".equals(name))
+                baseStrokeWidth = Float.parseFloat(value);
+
+            else if ("base-text-scale".equals(name))
+                baseTextScale = Float.parseFloat(value);
+
+            else if ("map-background-outside".equals(name)) {
+                //TODO handle map-background-outside
+            } else
+                XmlMapsforgeThemeBuilder.logUnknownAttribute(elementName, name, value, i);
+
+        }
+
+        validateExists("version", version, elementName);
+
+        if (version != RENDER_THEME_VERSION)
+            throw new ThemeException("invalid render theme version:"
+                    + version);
+
+        validateNonNegative("base-stroke-width", baseStrokeWidth);
+        validateNonNegative("base-text-scale", baseTextScale);
+
+        mMapBackground = mapBackground;
+        mTextScale = baseTextScale;
+    }
+
+    private void handleTextElement(String localName, Attributes attributes, boolean isStyle,
+                                   boolean isCaption) throws SAXException {
+
+        String style = attributes.getValue("use");
+        TextBuilder<?> pt = null;
+
+        if (style != null) {
+            pt = mTextStyles.get(style);
+            if (pt == null) {
+                log.debug("missing text style: " + style);
+                return;
+            }
+        }
+
+        TextBuilder<?> b = createText(localName, attributes, isCaption, pt);
+        if (isStyle) {
+            log.debug("put style {}", b.style);
+            mTextStyles.put(b.style, TextStyle.builder().from(b));
+        } else {
+            TextStyle text = b.buildInternal();
+            if (isVisible(text))
+                mCurrentRule.addStyle(text);
+        }
+    }
+
+    /**
+     * @param caption ...
+     * @return a new Text with the given rendering attributes.
+     */
+    private TextBuilder<?> createText(String elementName, Attributes attributes,
+                                      boolean caption, TextBuilder<?> style) {
+        TextBuilder<?> b;
+        if (style == null) {
+            b = mTextBuilder.reset();
+            b.caption = caption;
+        } else
+            b = mTextBuilder.from(style);
+        b.themeCallback(mThemeCallback);
+        String symbol = null;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("id".equals(name))
+                b.style = value;
+
+            else if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("k".equals(name))
+                b.textKey = value.intern();
+
+            else if ("font-family".equals(name))
+                b.fontFamily = FontFamily.valueOf(value.toUpperCase(Locale.ENGLISH));
+
+            else if ("font-style".equals(name))
+                b.fontStyle = FontStyle.valueOf(value.toUpperCase(Locale.ENGLISH));
+
+            else if ("font-size".equals(name))
+                b.fontSize = Float.parseFloat(value);
+
+            else if ("fill".equals(name))
+                b.fillColor = Color.parseColor(value);
+
+            else if ("stroke".equals(name))
+                b.strokeColor = Color.parseColor(value);
+
+            else if ("stroke-width".equals(name))
+                b.strokeWidth = Float.parseFloat(value) * mScale2;
+
+            else if ("caption".equals(name))
+                b.caption = Boolean.parseBoolean(value);
+
+            else if ("priority".equals(name))
+                b.priority = Integer.parseInt(value);
+
+            else if ("area-size".equals(name))
+                b.areaSize = Float.parseFloat(value);
+
+            else if ("dy".equals(name))
+                // NB: minus..
+                b.dy = -Float.parseFloat(value) * mScale;
+
+            else if ("symbol".equals(name))
+                symbol = value;
+
+            else if ("use".equals(name))
+                ;/* ignore */
+
+            else if ("symbol-width".equals(name))
+                b.symbolWidth = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-height".equals(name))
+                b.symbolHeight = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-percent".equals(name))
+                b.symbolPercent = Integer.parseInt(value);
+
+            else if ("display".equals(name)) {
+                //TODO Handle display attribute NEVER, ALWAYS, IFSPACE;
+            } else if ("symbol-id".equals(name)) {
+                //TODO Handle  symbol-id;
+            } else if ("position".equals(name)) {
+                //TODO Handle  position: AUTO, CENTER, BELOW, BELOW_LEFT, BELOW_RIGHT, ABOVE, ABOVE_LEFT, ABOVE_RIGHT, LEFT, RIGHT
+            } else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+        validateExists("k", b.textKey, elementName);
+        validateNonNegative("size", b.fontSize);
+        validateNonNegative("stroke-width", b.strokeWidth);
+
+        if (symbol != null && symbol.length() > 0) {
+            String lowValue = symbol.toLowerCase(Locale.ENGLISH);
+            if (lowValue.endsWith(".png") || lowValue.endsWith(".svg")) {
+                try {
+                    b.bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), symbol, b.symbolWidth, b.symbolHeight, b.symbolPercent);
+                } catch (Exception e) {
+                    log.debug(e.getMessage());
+                }
+            } else
+                b.texture = getAtlasRegion(symbol);
+        }
+
+        return b;
+    }
+
+    /**
+     * @param level the drawing level of this instruction.
+     * @return a new Circle with the given rendering attributes.
+     */
+    private CircleStyle createCircle(String elementName, Attributes attributes, int level) {
+        CircleBuilder<?> b = mCircleBuilder.reset();
+        b.level(level);
+        b.themeCallback(mThemeCallback);
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("r".equals(name) || "radius".equals(name))
+                b.radius(Float.parseFloat(value) * mScale2);
+
+            else if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("scale-radius".equals(name))
+                b.scaleRadius(Boolean.parseBoolean(value));
+
+            else if ("fill".equals(name))
+                b.color(Color.parseColor(value));
+
+            else if ("stroke".equals(name))
+                b.strokeColor(Color.parseColor(value));
+
+            else if ("stroke-width".equals(name))
+                b.strokeWidth(Float.parseFloat(value) * mScale2);
+
+            else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+        validateExists("radius", b.radius, elementName);
+        validateNonNegative("radius", b.radius);
+        validateNonNegative("stroke-width", b.strokeWidth);
+
+        return b.build();
+    }
+
+    /**
+     * @return a new Symbol with the given rendering attributes.
+     */
+    private SymbolStyle createSymbol(String elementName, Attributes attributes) {
+        SymbolBuilder<?> b = mSymbolBuilder.reset();
+        String src = null;
+
+        for (int i = 0; i < attributes.getLength(); i++) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("src".equals(name))
+                src = value;
+
+            else if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("symbol-width".equals(name))
+                b.symbolWidth = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-height".equals(name))
+                b.symbolHeight = (int) (Integer.parseInt(value) * mScale);
+
+            else if ("symbol-percent".equals(name))
+                b.symbolPercent = Integer.parseInt(value);
+
+            else if ("symbol-scaling".equals(name)) {
+                // no-op
+            } else if ("display".equals(name)) {
+                //TODO Handle display attribute NEVER, ALWAYS, IFSPACE;
+            } else if ("id".equals(name)) {
+                //TODO Handle 'id'
+            } else if ("priority".equals(name)) {
+                //TODO Handle 'priority'
+            } else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+        validateExists("src", src, elementName);
+
+        String lowSrc = src.toLowerCase(Locale.ENGLISH);
+        if (lowSrc.endsWith(".png") || lowSrc.endsWith(".svg")) {
+            try {
+                Bitmap bitmap = CanvasAdapter.getBitmapAsset(mTheme.getRelativePathPrefix(), src, b.symbolWidth, b.symbolHeight, b.symbolPercent);
+                if (bitmap != null)
+                    return buildSymbol(b, src, bitmap);
+            } catch (Exception e) {
+                log.debug(e.getMessage());
+            }
+            return null;
+        }
+        return b.texture(getAtlasRegion(src)).build();
+    }
+
+    SymbolStyle buildSymbol(SymbolBuilder<?> b, String src, Bitmap bitmap) {
+        return b.bitmap(bitmap).build();
+    }
+
+    private ExtrusionStyle createExtrusion(String elementName, Attributes attributes, int level) {
+        ExtrusionBuilder<?> b = mExtrusionBuilder.reset();
+        b.level(level);
+        b.themeCallback(mThemeCallback);
+
+        for (int i = 0; i < attributes.getLength(); ++i) {
+            String name = attributes.getLocalName(i);
+            String value = attributes.getValue(i);
+
+            if ("cat".equals(name))
+                b.cat(value);
+
+            else if ("side-color".equals(name))
+                b.colorSide(Color.parseColor(value));
+
+            else if ("top-color".equals(name))
+                b.colorTop(Color.parseColor(value));
+
+            else if ("line-color".equals(name))
+                b.colorLine(Color.parseColor(value));
+
+            else if ("default-height".equals(name))
+                b.defaultHeight(Integer.parseInt(value));
+
+            else
+                logUnknownAttribute(elementName, name, value, i);
+        }
+
+        return b.build();
+    }
+
+    private String getStringAttribute(Attributes attributes, String name) {
+        for (int i = 0; i < attributes.getLength(); ++i) {
+            if (attributes.getLocalName(i).equals(name)) {
+                return attributes.getValue(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * A style is visible if categories is not set or the style has no category
+     * or the categories contain the style's category.
+     */
+    private boolean isVisible(RenderStyle renderStyle) {
+        return mCategories == null || renderStyle.cat == null || mCategories.contains(renderStyle.cat);
+    }
+
+    /**
+     * A rule is visible if categories is not set or the rule has no category
+     * or the categories contain the rule's category.
+     */
+    private boolean isVisible(RuleBuilder rule) {
+        return mCategories == null || rule.cat == null || mCategories.contains(rule.cat);
+    }
+
+    private static void validateNonNegative(String name, float value) {
+        if (value < 0)
+            throw new ThemeException(name + " must not be negative: "
+                    + value);
+    }
+
+    private static void validateExists(String name, Object obj, String elementName) {
+        if (obj == null)
+            throw new ThemeException("missing attribute " + name
+                    + " for element: " + elementName);
+    }
+
+    private static float[] parseFloatArray(String dashString) {
+        String[] dashEntries = SPLIT_PATTERN.split(dashString);
+        float[] dashIntervals = new float[dashEntries.length];
+        for (int i = 0; i < dashEntries.length; ++i) {
+            dashIntervals[i] = Float.parseFloat(dashEntries[i]);
+        }
+        return dashIntervals;
+    }
+}
diff --git a/vtm/src/org/oscim/theme/styles/LineStyle.java b/vtm/src/org/oscim/theme/styles/LineStyle.java
index da65cfc5..0021c560 100644
--- a/vtm/src/org/oscim/theme/styles/LineStyle.java
+++ b/vtm/src/org/oscim/theme/styles/LineStyle.java
@@ -2,6 +2,7 @@
  * Copyright 2010, 2011, 2012 mapsforge.org
  * Copyright 2013 Hannes Janetzek
  * Copyright 2016-2017 devemux86
+ * Copyright 2017 Longri
  *
  * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
  *
@@ -47,6 +48,7 @@ public final class LineStyle extends RenderStyle<LineStyle> {
     public final int symbolWidth;
     public final int symbolHeight;
     public final int symbolPercent;
+    public boolean dashTexture;
 
     public LineStyle(int stroke, float width) {
         this(0, "", stroke, width, Cap.BUTT, true, 0, 0, 0, -1, 0, false, null, true);
@@ -112,6 +114,7 @@ public final class LineStyle extends RenderStyle<LineStyle> {
         this.symbolWidth = b.symbolWidth;
         this.symbolHeight = b.symbolHeight;
         this.symbolPercent = b.symbolPercent;
+        this.dashTexture = b.strokeDasharray != null;
     }
 
     @Override
@@ -143,6 +146,7 @@ public final class LineStyle extends RenderStyle<LineStyle> {
         public int symbolWidth;
         public int symbolHeight;
         public int symbolPercent;
+        public float[] strokeDasharray;
 
         public LineBuilder() {
         }
@@ -273,7 +277,7 @@ public final class LineStyle extends RenderStyle<LineStyle> {
             symbolWidth = 0;
             symbolHeight = 0;
             symbolPercent = 100;
-
+            strokeDasharray = null;
             return self();
         }