2 #include <QXmlStreamReader>
5 #include <QImageReader>
6 #include "common/programpaths.h"
9 using namespace Mapsforge
;
11 static QString
resourcePath(const QString
&src
, const QString
&dir
)
14 if (url
.scheme().isEmpty())
17 return dir
+ "/" + url
.toLocalFile();
20 static QImage
image(const QString
&path
, int width
, int height
, qreal ratio
)
22 QImageReader
ir(path
, "svg");
25 if (!height
&& !width
) {
29 width
= ir
.size().height() / (ir
.size().height() / (double)height
);
31 height
= ir
.size().width() / (ir
.size().width() / (double)width
);
33 ir
.setScaledSize(QSize(width
* ratio
, height
* ratio
));
34 QImage
img(ir
.read());
35 img
.setDevicePixelRatio(ratio
);
41 static QList
<unsigned> keyList(const MapData
&data
, const QList
<QByteArray
> &in
)
45 for (int i
= 0; i
< in
.size(); i
++) {
49 unsigned key
= data
.tagId(in
.at(i
));
58 static QList
<QByteArray
> valList(const QList
<QByteArray
> &in
)
60 QList
<QByteArray
> out
;
62 for (int i
= 0; i
< in
.size(); i
++) {
64 out
.append(QByteArray());
72 Style::Rule::Filter::Filter(const MapData
&data
, const QList
<QByteArray
> &keys
,
73 const QList
<QByteArray
> &vals
) : _neg(false)
75 _keys
= keyList(data
, keys
);
77 QList
<QByteArray
> vc(vals
);
78 if (vc
.removeAll("~"))
83 bool Style::Rule::match(const QVector
<MapData::Tag
> &tags
) const
85 for (int i
= 0; i
< _filters
.size(); i
++)
86 if (!_filters
.at(i
).match(tags
))
92 bool Style::Rule::match(bool closed
, const QVector
<MapData::Tag
> &tags
) const
94 Closed cl
= closed
? YesClosed
: NoClosed
;
96 if (_closed
&& cl
!= _closed
)
99 for (int i
= 0; i
< _filters
.size(); i
++)
100 if (!_filters
.at(i
).match(tags
))
106 bool Style::Rule::match(int zoom
, bool closed
,
107 const QVector
<MapData::Tag
> &tags
) const
109 Closed cl
= closed
? YesClosed
: NoClosed
;
111 if (!_zooms
.contains(zoom
))
113 if (_closed
&& cl
!= _closed
)
116 for (int i
= 0; i
< _filters
.size(); i
++)
117 if (!_filters
.at(i
).match(tags
))
123 bool Style::Rule::match(int zoom
, const QVector
<MapData::Tag
> &tags
) const
125 if (!_zooms
.contains(zoom
))
128 for (int i
= 0; i
< _filters
.size(); i
++)
129 if (!_filters
.at(i
).match(tags
))
135 void Style::area(QXmlStreamReader
&reader
, const QString
&dir
, qreal ratio
,
138 PathRender
ri(rule
, _paths
.size() + _circles
.size());
139 const QXmlStreamAttributes
&attr
= reader
.attributes();
142 int height
= 0, width
= 0;
146 if (attr
.hasAttribute("fill"))
147 fillColor
= QColor(attr
.value("fill").toString());
148 if (attr
.hasAttribute("stroke"))
149 ri
._strokeColor
= QColor(attr
.value("stroke").toString());
150 if (attr
.hasAttribute("stroke-width")) {
151 ri
._strokeWidth
= attr
.value("stroke-width").toFloat(&ok
);
152 if (!ok
|| ri
._strokeWidth
< 0) {
153 reader
.raiseError("invalid stroke-width value");
157 if (attr
.hasAttribute("scale")) {
158 QString
scale(attr
.value("scale").toString());
160 ri
._scale
= PathRender::Scale::All
;
161 else if (scale
== "none")
162 ri
._scale
= PathRender::Scale::None
;
164 if (attr
.hasAttribute("src"))
165 file
= resourcePath(attr
.value("src").toString(), dir
);
166 if (attr
.hasAttribute("symbol-height")) {
167 height
= attr
.value("symbol-height").toInt(&ok
);
168 if (!ok
|| height
< 0) {
169 reader
.raiseError("invalid symbol-height value");
173 if (attr
.hasAttribute("symbol-width")) {
174 width
= attr
.value("symbol-width").toInt(&ok
);
175 if (!ok
|| width
< 0) {
176 reader
.raiseError("invalid symbol-width value");
182 ri
._brush
= QBrush(image(file
, width
, height
, ratio
));
183 else if (fillColor
.isValid())
184 ri
._brush
= QBrush(fillColor
);
186 if (ri
.rule()._type
== Rule::AnyType
|| ri
.rule()._type
== Rule::WayType
)
189 reader
.skipCurrentElement();
192 void Style::line(QXmlStreamReader
&reader
, const Rule
&rule
)
194 PathRender
ri(rule
, _paths
.size() + _circles
.size());
195 const QXmlStreamAttributes
&attr
= reader
.attributes();
198 if (attr
.hasAttribute("stroke"))
199 ri
._strokeColor
= QColor(attr
.value("stroke").toString());
200 if (attr
.hasAttribute("stroke-width")) {
201 ri
._strokeWidth
= attr
.value("stroke-width").toFloat(&ok
);
202 if (!ok
|| ri
._strokeWidth
< 0) {
203 reader
.raiseError("invalid stroke-width value");
207 if (attr
.hasAttribute("stroke-dasharray")) {
208 QStringList
l(attr
.value("stroke-dasharray").toString().split(','));
209 ri
._strokeDasharray
.resize(l
.size());
210 for (int i
= 0; i
< l
.size(); i
++) {
211 ri
._strokeDasharray
[i
] = l
.at(i
).toDouble(&ok
);
212 if (!ok
|| ri
._strokeDasharray
[i
] < 0) {
213 reader
.raiseError("invalid stroke-dasharray value");
218 if (attr
.hasAttribute("stroke-linecap")) {
219 QString
cap(attr
.value("stroke-linecap").toString());
221 ri
._strokeCap
= Qt::FlatCap
;
222 else if (cap
== "round")
223 ri
._strokeCap
= Qt::RoundCap
;
224 else if (cap
== "square")
225 ri
._strokeCap
= Qt::SquareCap
;
227 if (attr
.hasAttribute("stroke-linejoin")) {
228 QString
join(attr
.value("stroke-linejoin").toString());
230 ri
._strokeJoin
= Qt::MiterJoin
;
231 else if (join
== "round")
232 ri
._strokeJoin
= Qt::RoundJoin
;
233 else if (join
== "bevel")
234 ri
._strokeJoin
= Qt::BevelJoin
;
236 if (attr
.hasAttribute("scale")) {
237 QString
scale(attr
.value("scale").toString());
239 ri
._scale
= PathRender::Scale::All
;
240 else if (scale
== "none")
241 ri
._scale
= PathRender::Scale::None
;
243 if (attr
.hasAttribute("curve")) {
244 QString
curve(attr
.value("curve").toString());
245 if (curve
== "cubic")
248 if (attr
.hasAttribute("dy")) {
249 ri
._dy
= attr
.value("dy").toDouble(&ok
);
251 reader
.raiseError("invalid dy value");
256 if (ri
.rule()._type
== Rule::AnyType
|| ri
.rule()._type
== Rule::WayType
)
259 reader
.skipCurrentElement();
262 void Style::circle(QXmlStreamReader
&reader
, const Rule
&rule
)
264 CircleRender
ri(rule
, _paths
.size() + _circles
.size());
265 const QXmlStreamAttributes
&attr
= reader
.attributes();
267 QColor fillColor
, strokeColor
;
268 qreal strokeWidth
= 0;
270 if (attr
.hasAttribute("fill"))
271 fillColor
= QColor(attr
.value("fill").toString());
272 if (attr
.hasAttribute("stroke"))
273 strokeColor
= QColor(attr
.value("stroke").toString());
274 if (attr
.hasAttribute("stroke-width")) {
275 strokeWidth
= attr
.value("stroke-width").toFloat(&ok
);
276 if (!ok
|| strokeWidth
< 0) {
277 reader
.raiseError("invalid stroke-width value");
281 if (attr
.hasAttribute("radius")) {
282 ri
._radius
= attr
.value("radius").toDouble(&ok
);
283 if (!ok
|| ri
._radius
<= 0) {
284 reader
.raiseError("invalid radius value");
288 reader
.raiseError("missing radius");
291 if (attr
.hasAttribute("scale-radius")) {
292 if (attr
.value("scale-radius").toString() == "true")
296 ri
._pen
= (strokeColor
.isValid() && strokeWidth
> 0)
297 ? QPen(QBrush(strokeColor
), strokeWidth
) : Qt::NoPen
;
298 ri
._brush
= fillColor
.isValid() ? QBrush(fillColor
) : Qt::NoBrush
;
300 if (ri
.rule()._type
== Rule::AnyType
|| ri
.rule()._type
== Rule::NodeType
)
303 reader
.skipCurrentElement();
306 void Style::text(QXmlStreamReader
&reader
, const MapData
&data
, const Rule
&rule
,
307 QList
<QList
<TextRender
>*> &lists
)
310 const QXmlStreamAttributes
&attr
= reader
.attributes();
312 bool bold
= false, italic
= false;
313 QString
fontFamily("Helvetica");
314 QFont::Capitalization capitalization
= QFont::MixedCase
;
317 if (attr
.hasAttribute("k"))
318 ri
._key
= data
.tagId(attr
.value("k").toLatin1());
319 if (attr
.hasAttribute("fill"))
320 ri
._fillColor
= QColor(attr
.value("fill").toString());
321 if (attr
.hasAttribute("stroke"))
322 ri
._strokeColor
= QColor(attr
.value("stroke").toString());
323 if (attr
.hasAttribute("stroke-width")) {
324 ri
._strokeWidth
= attr
.value("stroke-width").toFloat(&ok
);
325 if (!ok
|| ri
._strokeWidth
< 0) {
326 reader
.raiseError("invalid stroke-width value");
330 if (attr
.hasAttribute("font-size")) {
331 fontSize
= attr
.value("font-size").toFloat(&ok
);
332 if (!ok
|| fontSize
< 0) {
333 reader
.raiseError("invalid font-size value");
337 if (attr
.hasAttribute("font-style")) {
338 QString
style(attr
.value("font-style").toString());
341 else if (style
== "italic")
343 else if (style
== "bold_italic") {
348 if (attr
.hasAttribute("font-family")) {
349 QString
family(attr
.value("font-family").toString());
350 if (family
== "monospace")
351 fontFamily
= "Courier New";
352 else if (family
== "serif")
353 fontFamily
= "Times New Roman";
355 if (attr
.hasAttribute("text-transform")) {
356 QString
transform(attr
.value("text-transform").toString());
357 if (transform
== "uppercase")
358 capitalization
= QFont::AllUppercase
;
359 else if (transform
== "lowercase")
360 capitalization
= QFont::AllLowercase
;
361 else if (transform
== "capitalize")
362 capitalization
= QFont::Capitalize
;
364 if (attr
.hasAttribute("priority")) {
365 ri
._priority
= attr
.value("priority").toInt(&ok
);
367 reader
.raiseError("invalid priority value");
372 ri
._font
.setFamily(fontFamily
);
373 ri
._font
.setPixelSize(fontSize
);
374 ri
._font
.setBold(bold
);
375 ri
._font
.setItalic(italic
);
376 ri
._font
.setCapitalization(capitalization
);
379 for (int i
= 0; i
< lists
.size(); i
++)
380 lists
[i
]->append(ri
);
382 reader
.skipCurrentElement();
385 void Style::symbol(QXmlStreamReader
&reader
, const QString
&dir
, qreal ratio
,
389 const QXmlStreamAttributes
&attr
= reader
.attributes();
391 int height
= 0, width
= 0;
394 if (attr
.hasAttribute("src"))
395 file
= resourcePath(attr
.value("src").toString(), dir
);
397 reader
.raiseError("missing src value");
400 if (attr
.hasAttribute("symbol-height")) {
401 height
= attr
.value("symbol-height").toInt(&ok
);
402 if (!ok
|| height
< 0) {
403 reader
.raiseError("invalid symbol-height value");
407 if (attr
.hasAttribute("symbol-width")) {
408 width
= attr
.value("symbol-width").toInt(&ok
);
409 if (!ok
|| width
< 0) {
410 reader
.raiseError("invalid symbol-width value");
414 if (attr
.hasAttribute("priority")) {
415 ri
._priority
= attr
.value("priority").toInt(&ok
);
417 reader
.raiseError("invalid priority value");
422 ri
._img
= image(file
, width
, height
, ratio
);
426 reader
.skipCurrentElement();
429 void Style::rule(QXmlStreamReader
&reader
, const QString
&dir
,
430 const MapData
&data
, qreal ratio
, const QSet
<QString
> &cats
,
434 const QXmlStreamAttributes
&attr
= reader
.attributes();
437 if (attr
.hasAttribute("cat")
438 && !cats
.contains(attr
.value("cat").toString())) {
439 reader
.skipCurrentElement();
443 if (attr
.value("e").toString() == "way")
444 r
.setType(Rule::WayType
);
445 else if (attr
.value("e").toString() == "node")
446 r
.setType(Rule::NodeType
);
448 if (attr
.hasAttribute("zoom-min")) {
449 r
.setMinZoom(attr
.value("zoom-min").toInt(&ok
));
450 if (!ok
|| r
._zooms
.min() < 0) {
451 reader
.raiseError("invalid zoom-min value");
455 if (attr
.hasAttribute("zoom-max")) {
456 r
.setMaxZoom(attr
.value("zoom-max").toInt(&ok
));
457 if (!ok
|| r
._zooms
.max() < 0) {
458 reader
.raiseError("invalid zoom-max value");
463 if (attr
.hasAttribute("closed")) {
464 if (attr
.value("closed").toString() == "yes")
465 r
.setClosed(Rule::YesClosed
);
466 else if (attr
.value("closed").toString() == "no")
467 r
.setClosed(Rule::NoClosed
);
470 QList
<QByteArray
> keys(attr
.value("k").toLatin1().split('|'));
471 QList
<QByteArray
> vals(attr
.value("v").toLatin1().split('|'));
472 r
.addFilter(Rule::Filter(data
, keys
, vals
));
474 while (reader
.readNextStartElement()) {
475 if (reader
.name() == QLatin1String("rule"))
476 rule(reader
, dir
, data
, ratio
, cats
, r
);
477 else if (reader
.name() == QLatin1String("area"))
478 area(reader
, dir
, ratio
, r
);
479 else if (reader
.name() == QLatin1String("line"))
481 else if (reader
.name() == QLatin1String("circle"))
483 else if (reader
.name() == QLatin1String("pathText")) {
484 QList
<QList
<TextRender
>*> list
;
485 list
.append(&_pathLabels
);
486 text(reader
, data
, r
, list
);
487 } else if (reader
.name() == QLatin1String("caption")) {
488 QList
<QList
<TextRender
>*> list
;
489 if (r
._type
== Rule::WayType
|| r
._type
== Rule::AnyType
)
490 list
.append(&_areaLabels
);
491 if (r
._type
== Rule::NodeType
|| r
._type
== Rule::AnyType
)
492 list
.append(&_pointLabels
);
493 text(reader
, data
, r
, list
);
495 else if (reader
.name() == QLatin1String("symbol"))
496 symbol(reader
, dir
, ratio
, r
);
498 reader
.skipCurrentElement();
502 void Style::cat(QXmlStreamReader
&reader
, QSet
<QString
> &cats
)
504 const QXmlStreamAttributes
&attr
= reader
.attributes();
506 cats
.insert(attr
.value("id").toString());
508 reader
.skipCurrentElement();
511 void Style::layer(QXmlStreamReader
&reader
, QSet
<QString
> &cats
)
513 const QXmlStreamAttributes
&attr
= reader
.attributes();
514 bool enabled
= (attr
.value("enabled").toString() == "true");
516 while (reader
.readNextStartElement()) {
517 if (enabled
&& reader
.name() == QLatin1String("cat"))
520 reader
.skipCurrentElement();
524 void Style::stylemenu(QXmlStreamReader
&reader
, QSet
<QString
> &cats
)
526 while (reader
.readNextStartElement()) {
527 if (reader
.name() == QLatin1String("layer"))
530 reader
.skipCurrentElement();
534 void Style::rendertheme(QXmlStreamReader
&reader
, const QString
&dir
,
535 const MapData
&data
, qreal ratio
)
540 while (reader
.readNextStartElement()) {
541 if (reader
.name() == QLatin1String("rule"))
542 rule(reader
, dir
, data
, ratio
, cats
, r
);
543 else if (reader
.name() == QLatin1String("stylemenu"))
544 stylemenu(reader
, cats
);
546 reader
.skipCurrentElement();
550 bool Style::loadXml(const QString
&path
, const MapData
&data
, qreal ratio
)
553 if (!file
.open(QFile::ReadOnly
))
555 QXmlStreamReader
reader(&file
);
558 if (reader
.readNextStartElement()) {
559 if (reader
.name() == QLatin1String("rendertheme"))
560 rendertheme(reader
, fi
.absolutePath(), data
, ratio
);
562 reader
.raiseError("Not a Mapsforge style file");
566 qWarning("%s:%lld %s", qPrintable(path
), reader
.lineNumber(),
567 qPrintable(reader
.errorString()));
569 return !reader
.error();
572 void Style::load(const MapData
&data
, qreal ratio
)
574 QString
path(ProgramPaths::renderthemeFile());
576 if (!QFileInfo::exists(path
) || !loadXml(path
, data
, ratio
))
577 loadXml(":/mapsforge/default.xml", data
, ratio
);
585 _pointLabels
.clear();
590 QList
<const Style::PathRender
*> Style::paths(int zoom
, bool closed
,
591 const QVector
<MapData::Tag
> &tags
) const
593 QList
<const PathRender
*> ri
;
595 for (int i
= 0; i
< _paths
.size(); i
++)
596 if (_paths
.at(i
).rule().match(zoom
, closed
, tags
))
597 ri
.append(&_paths
.at(i
));
602 QList
<const Style::CircleRender
*> Style::circles(int zoom
,
603 const QVector
<MapData::Tag
> &tags
) const
605 QList
<const CircleRender
*> ri
;
607 for (int i
= 0; i
< _circles
.size(); i
++)
608 if (_circles
.at(i
).rule().match(zoom
, tags
))
609 ri
.append(&_circles
.at(i
));
614 QList
<const Style::TextRender
*> Style::pathLabels(int zoom
) const
616 QList
<const TextRender
*> list
;
618 for (int i
= 0; i
< _pathLabels
.size(); i
++)
619 if (_pathLabels
.at(i
).rule()._zooms
.contains(zoom
))
620 list
.append(&_pathLabels
.at(i
));
625 QList
<const Style::TextRender
*> Style::pointLabels(int zoom
) const
627 QList
<const TextRender
*> list
;
629 for (int i
= 0; i
< _pointLabels
.size(); i
++)
630 if (_pointLabels
.at(i
).rule()._zooms
.contains(zoom
))
631 list
.append(&_pointLabels
.at(i
));
636 QList
<const Style::TextRender
*> Style::areaLabels(int zoom
) const
638 QList
<const TextRender
*> list
;
640 for (int i
= 0; i
< _areaLabels
.size(); i
++)
641 if (_areaLabels
.at(i
).rule()._zooms
.contains(zoom
))
642 list
.append(&_areaLabels
.at(i
));
647 QList
<const Style::Symbol
*> Style::pointSymbols(int zoom
) const
649 QList
<const Symbol
*> list
;
651 for (int i
= 0; i
< _symbols
.size(); i
++) {
652 const Symbol
&symbol
= _symbols
.at(i
);
653 const Rule
& rule
= symbol
.rule();
654 if (rule
._zooms
.contains(zoom
) && (rule
._type
== Rule::AnyType
655 || rule
._type
== Rule::NodeType
))
656 list
.append(&symbol
);
662 QList
<const Style::Symbol
*> Style::areaSymbols(int zoom
) const
664 QList
<const Symbol
*> list
;
666 for (int i
= 0; i
< _symbols
.size(); i
++) {
667 const Symbol
&symbol
= _symbols
.at(i
);
668 const Rule
&rule
= symbol
.rule();
669 if (rule
._zooms
.contains(zoom
) && (rule
._type
== Rule::AnyType
670 || rule
._type
== Rule::WayType
))
671 list
.append(&symbol
);
677 QPen
Style::PathRender::pen(int zoom
) const
679 if (_strokeColor
.isValid()) {
680 qreal width
= (_scale
> None
&& zoom
>= 12)
681 ? pow(1.5, zoom
- 12) * _strokeWidth
: _strokeWidth
;
682 QPen
p(QBrush(_strokeColor
), width
, Qt::SolidLine
, _strokeCap
,
684 if (!_strokeDasharray
.isEmpty()) {
685 QVector
<qreal
>pattern(_strokeDasharray
);
686 for (int i
= 0; i
< _strokeDasharray
.size(); i
++) {
687 if (_scale
> Stroke
&& zoom
>= 12)
688 pattern
[i
] = (pow(1.5, zoom
- 12) * pattern
[i
]);
689 // QPainter pattern is specified in units of the pens width!
692 p
.setDashPattern(pattern
);
699 qreal
Style::PathRender::dy(int zoom
) const
701 return (_scale
&& zoom
>= 12) ? pow(1.5, zoom
- 12) * _dy
: _dy
;
704 qreal
Style::CircleRender::radius(int zoom
) const
706 return (_scale
&& zoom
>= 12) ? pow(1.5, zoom
- 12) * _radius
: _radius
;