884 lines
25 KiB
Java
884 lines
25 KiB
Java
/*
|
|
* Copyright 2010, 2011, 2012 mapsforge.org
|
|
* Copyright 2013 Hannes Janetzek
|
|
*
|
|
* 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 static java.lang.Boolean.parseBoolean;
|
|
import static java.lang.Float.parseFloat;
|
|
import static java.lang.Integer.parseInt;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Stack;
|
|
|
|
import org.oscim.backend.CanvasAdapter;
|
|
import org.oscim.backend.XMLReaderAdapter;
|
|
import org.oscim.backend.canvas.Bitmap;
|
|
import org.oscim.backend.canvas.Color;
|
|
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.elements.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.ExtrusionStyle;
|
|
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.TextStyle;
|
|
import org.oscim.theme.styles.TextStyle.TextBuilder;
|
|
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;
|
|
|
|
public class XmlThemeBuilder extends DefaultHandler {
|
|
static final Logger log = LoggerFactory.getLogger(XmlThemeBuilder.class);
|
|
|
|
private static final int RENDER_THEME_VERSION = 1;
|
|
|
|
private static enum Element {
|
|
RENDER_THEME, RENDERING_INSTRUCTION, RULE, STYLE, ATLAS;
|
|
}
|
|
|
|
private static final String ELEMENT_NAME_RENDER_THEME = "rendertheme";
|
|
private static final String ELEMENT_NAME_MATCH = "m";
|
|
private static final String UNEXPECTED_ELEMENT = "unexpected element: ";
|
|
|
|
//private static final String IMG_PATH = "styles/";
|
|
private static final String IMG_PATH = "";
|
|
|
|
private static final String LINE_STYLE = "L";
|
|
private static final String OUTLINE_STYLE = "O";
|
|
private static final String AREA_STYLE = "A";
|
|
|
|
/**
|
|
* @param inputStream
|
|
* an input stream containing valid render theme XML data.
|
|
* @return a new RenderTheme which is created by parsing the XML data from
|
|
* the input stream.
|
|
* @throws ThemeException
|
|
* if an error occurs while parsing the render theme XML.
|
|
* @throws IOException
|
|
* if an I/O error occurs while reading from the input stream.
|
|
*/
|
|
public static IRenderTheme read(InputStream inputStream)
|
|
throws ThemeException {
|
|
|
|
XmlThemeBuilder renderThemeHandler = new XmlThemeBuilder();
|
|
|
|
try {
|
|
new XMLReaderAdapter().parse(renderThemeHandler, inputStream);
|
|
} catch (IOException 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.
|
|
*/
|
|
public 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<RuleBuilder>();
|
|
private final Stack<Element> mElementStack = new Stack<Element>();
|
|
private final Stack<RuleBuilder> mRuleStack = new Stack<RuleBuilder>();
|
|
private final HashMap<String, RenderStyle> mStyles =
|
|
new HashMap<String, RenderStyle>(10);
|
|
|
|
private final HashMap<String, TextStyle.TextBuilder> mTextStyles =
|
|
new HashMap<String, TextStyle.TextBuilder>(10);
|
|
|
|
private final TextBuilder mTextBuilder = new TextBuilder();
|
|
private final AreaBuilder mAreaBuilder = new AreaBuilder();
|
|
private final LineBuilder mLineBuilder = new LineBuilder();
|
|
|
|
private RuleBuilder mCurrentRule;
|
|
private TextureAtlas mTextureAtlas;
|
|
|
|
private int mLevels = 0;
|
|
private int mMapBackground = 0xffffffff;
|
|
private float mTextScale = 1;
|
|
|
|
private RenderTheme mRenderTheme;
|
|
|
|
@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 = new RenderTheme(mMapBackground, mTextScale, rules, mLevels);
|
|
|
|
mRulesList.clear();
|
|
mStyles.clear();
|
|
mRuleStack.clear();
|
|
mElementStack.clear();
|
|
|
|
mTextureAtlas = null;
|
|
}
|
|
|
|
@Override
|
|
public void endElement(String uri, String localName, String qName) {
|
|
mElementStack.pop();
|
|
|
|
if (ELEMENT_NAME_MATCH.equals(localName)) {
|
|
mRuleStack.pop();
|
|
if (mRuleStack.empty()) {
|
|
mRulesList.add(mCurrentRule);
|
|
} else {
|
|
mCurrentRule = mRuleStack.peek();
|
|
}
|
|
}
|
|
}
|
|
|
|
@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, mRuleStack);
|
|
if (!mRuleStack.empty()) {
|
|
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);
|
|
|
|
} else if ("outline-layer".equals(localName)) {
|
|
checkState(localName, Element.RENDERING_INSTRUCTION);
|
|
LineStyle line = createLine(null, localName, attributes, mLevels++, true);
|
|
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++);
|
|
mCurrentRule.addStyle(circle);
|
|
|
|
} else if ("line".equals(localName)) {
|
|
checkState(localName, Element.RENDERING_INSTRUCTION);
|
|
handleLineElement(localName, attributes, false);
|
|
|
|
} else if ("text".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);
|
|
mCurrentRule.addStyle(symbol);
|
|
|
|
} else if ("outline".equals(localName)) {
|
|
checkState(localName, Element.RENDERING_INSTRUCTION);
|
|
addOutline(attributes.getValue("use"));
|
|
|
|
} else if ("extrusion".equals(localName)) {
|
|
checkState(localName, Element.RENDERING_INSTRUCTION);
|
|
ExtrusionStyle extrusion = createExtrusion(localName, attributes, mLevels++);
|
|
mCurrentRule.addStyle(extrusion);
|
|
|
|
} else if ("lineSymbol".equals(localName)) {
|
|
checkState(localName, Element.RENDERING_INSTRUCTION);
|
|
log.error("unknown element: {}", localName);
|
|
|
|
} 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 {
|
|
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,
|
|
Stack<RuleBuilder> ruleStack) {
|
|
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();
|
|
if ("WAY".equals(val))
|
|
element = Rule.Element.WAY;
|
|
else if ("NODE".equals(val))
|
|
element = Rule.Element.NODE;
|
|
} else if ("k".equals(name)) {
|
|
keys = value;
|
|
} else if ("v".equals(name)) {
|
|
values = value;
|
|
} else if ("closed".equals(name)) {
|
|
String val = value.toUpperCase();
|
|
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 {
|
|
XmlThemeBuilder.logUnknownAttribute(localName, name, value, i);
|
|
}
|
|
}
|
|
|
|
if (closed == Closed.YES)
|
|
element = Rule.Element.POLY;
|
|
else if (closed == Closed.NO)
|
|
element = Rule.Element.LINE;
|
|
|
|
XmlThemeBuilder.validateNonNegative("zoom-min", zoomMin);
|
|
XmlThemeBuilder.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.setZoom(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)
|
|
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);
|
|
|
|
if (isStyle) {
|
|
mStyles.put(LINE_STYLE + line.style, line);
|
|
} else {
|
|
mCurrentRule.addStyle(line);
|
|
/* Note 'outline' will not be inherited, it's just a
|
|
* shorcut to add the outline RenderInstruction. */
|
|
addOutline(attributes.getValue("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) {
|
|
LineBuilder b = mLineBuilder.set(line);
|
|
b.isOutline(isOutline);
|
|
b.level(level);
|
|
|
|
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 ("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.width = parseFloat(value);
|
|
if (line == null) {
|
|
if (!isOutline)
|
|
validateNonNegative("width", b.width);
|
|
} else {
|
|
/* use stroke width relative to 'line' */
|
|
b.width += line.width;
|
|
if (b.width <= 0)
|
|
b.width = 1;
|
|
}
|
|
}
|
|
else if ("cap".equals(name) || "stroke-linecap".equals(name))
|
|
b.cap = Cap.valueOf(value.toUpperCase());
|
|
|
|
else if ("fix".equals(name))
|
|
b.fixed = parseBoolean(value);
|
|
|
|
else if ("stipple".equals(name))
|
|
b.stipple = parseInt(value);
|
|
|
|
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
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
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 {
|
|
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);
|
|
|
|
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 ("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;
|
|
|
|
} 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
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
|
|
if (src != null) {
|
|
try {
|
|
Bitmap bitmap = CanvasAdapter.getBitmapAsset(src);
|
|
if (bitmap != null)
|
|
b.texture = new TextureItem(bitmap, true);
|
|
} catch (Exception e) {
|
|
log.debug(e.getMessage());
|
|
}
|
|
}
|
|
return b.build();
|
|
}
|
|
|
|
private void addOutline(String style) {
|
|
if (style != null) {
|
|
LineStyle line = (LineStyle) mStyles.get(OUTLINE_STYLE + style);
|
|
if (line != null && line.outline)
|
|
mCurrentRule.addStyle(line);
|
|
else
|
|
log.debug("BUG not an outline style: " + style);
|
|
}
|
|
}
|
|
|
|
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 {
|
|
XmlThemeBuilder.logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
}
|
|
validateExists("img", img, elementName);
|
|
|
|
Bitmap bitmap = CanvasAdapter.getBitmapAsset(IMG_PATH + img);
|
|
mTextureAtlas = new TextureAtlas(bitmap);
|
|
}
|
|
|
|
private void createTextureRegion(String elementName, Attributes attributes) {
|
|
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 {
|
|
XmlThemeBuilder.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;
|
|
}
|
|
|
|
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;
|
|
|
|
else if ("version".equals(name))
|
|
version = Integer.valueOf(Integer.parseInt(value));
|
|
|
|
else if ("map-background".equals(name))
|
|
mapBackground = Color.parseColor(value);
|
|
|
|
else if ("base-stroke-width".equals(name))
|
|
baseStrokeWidth = Float.parseFloat(value);
|
|
|
|
else if ("base-text-scale".equals(name))
|
|
baseTextScale = Float.parseFloat(value);
|
|
|
|
else
|
|
XmlThemeBuilder.logUnknownAttribute(elementName, name, value, i);
|
|
|
|
}
|
|
|
|
validateExists("version", version, elementName);
|
|
|
|
if (version.intValue() != 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");
|
|
TextStyle.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, new TextBuilder().setFrom(b));
|
|
} else {
|
|
mCurrentRule.addStyle(b.buildInternal());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param caption
|
|
* ...
|
|
* @return a new Text with the given rendering attributes.
|
|
*/
|
|
private TextStyle.TextBuilder createText(String elementName, Attributes attributes,
|
|
boolean caption, TextBuilder style) {
|
|
TextBuilder b;
|
|
if (style == null) {
|
|
b = mTextBuilder.reset();
|
|
b.caption = caption;
|
|
} else
|
|
b = mTextBuilder.setFrom(style);
|
|
|
|
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 ("k".equals(name))
|
|
b.textKey = value.intern();
|
|
|
|
else if ("font-family".equals(name))
|
|
b.fontFamily = FontFamily.valueOf(value.toUpperCase());
|
|
|
|
else if ("style".equals(name))
|
|
b.fontStyle = FontStyle.valueOf(value.toUpperCase());
|
|
|
|
else if ("size".equals(name))
|
|
b.fontSize = Float.parseFloat(value);
|
|
|
|
else if ("fill".equals(name))
|
|
b.color = Color.parseColor(value);
|
|
|
|
else if ("stroke".equals(name))
|
|
b.stroke = Color.parseColor(value);
|
|
|
|
else if ("stroke-width".equals(name))
|
|
b.strokeWidth = Float.parseFloat(value);
|
|
|
|
else if ("caption".equals(name))
|
|
b.caption = Boolean.parseBoolean(value);
|
|
|
|
else if ("priority".equals(name))
|
|
b.priority = Integer.parseInt(value);
|
|
|
|
else if ("dy".equals(name))
|
|
// NB: minus..
|
|
b.dy = -Float.parseFloat(value);
|
|
|
|
else if ("symbol".equals(name))
|
|
b.texture = getAtlasRegion(value);
|
|
else if ("use".equals(name))
|
|
;/* ignore */
|
|
else
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
|
|
validateExists("k", b.textKey, elementName);
|
|
validateNonNegative("size", b.fontSize);
|
|
validateNonNegative("stroke-width", b.strokeWidth);
|
|
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
* @param level
|
|
* the drawing level of this instruction.
|
|
* @return a new Circle with the given rendering attributes.
|
|
*/
|
|
private static CircleStyle createCircle(String elementName, Attributes attributes, int level) {
|
|
Float radius = null;
|
|
boolean scaleRadius = false;
|
|
int fill = Color.TRANSPARENT;
|
|
int stroke = Color.TRANSPARENT;
|
|
float strokeWidth = 0;
|
|
|
|
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))
|
|
radius = Float.valueOf(Float.parseFloat(value));
|
|
|
|
else if ("scale-radius".equals(name))
|
|
scaleRadius = Boolean.parseBoolean(value);
|
|
|
|
else if ("fill".equals(name))
|
|
fill = Color.parseColor(value);
|
|
|
|
else if ("stroke".equals(name))
|
|
stroke = Color.parseColor(value);
|
|
|
|
else if ("stroke-width".equals(name))
|
|
strokeWidth = Float.parseFloat(value);
|
|
|
|
else
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
|
|
validateExists("r", radius, elementName);
|
|
validateNonNegative("radius", radius);
|
|
validateNonNegative("stroke-width", strokeWidth);
|
|
|
|
return new CircleStyle(radius, scaleRadius, fill, stroke, strokeWidth, level);
|
|
}
|
|
|
|
/**
|
|
* @return a new Symbol with the given rendering attributes.
|
|
*/
|
|
private SymbolStyle createSymbol(String elementName, Attributes attributes) {
|
|
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
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
|
|
validateExists("src", src, elementName);
|
|
|
|
return new SymbolStyle(getAtlasRegion(src));
|
|
}
|
|
|
|
private ExtrusionStyle createExtrusion(String elementName, Attributes attributes, int level) {
|
|
int colorSide = 0;
|
|
int colorTop = 0;
|
|
int colorLine = 0;
|
|
int defaultHeight = 0;
|
|
|
|
for (int i = 0; i < attributes.getLength(); ++i) {
|
|
String name = attributes.getLocalName(i);
|
|
String value = attributes.getValue(i);
|
|
|
|
if ("side-color".equals(name))
|
|
colorSide = Color.parseColor(value);
|
|
|
|
else if ("top-color".equals(name))
|
|
colorTop = Color.parseColor(value);
|
|
|
|
else if ("line-color".equals(name))
|
|
colorLine = Color.parseColor(value);
|
|
|
|
else if ("default-height".equals(name))
|
|
defaultHeight = Integer.parseInt(value);
|
|
|
|
else
|
|
logUnknownAttribute(elementName, name, value, i);
|
|
}
|
|
|
|
return new ExtrusionStyle(level, colorSide, colorTop, colorLine, defaultHeight);
|
|
}
|
|
|
|
public static void validateNonNegative(String name, float value) {
|
|
if (value < 0)
|
|
throw new ThemeException(name + " must not be negative: "
|
|
+ value);
|
|
}
|
|
|
|
public static void validateExists(String name, Object obj, String elementName) {
|
|
if (obj == null)
|
|
throw new ThemeException("missing attribute " + name
|
|
+ " for element: " + elementName);
|
|
}
|
|
}
|