Marble

OsmcSymbol.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2017 Sergey Popov <sergobot@protonmail.com>
4//
5
6#include "OsmcSymbol.h"
7
8#include <QDebug>
9#include <QDomDocument>
10#include <QFile>
11#include <QPainter>
12
13OsmcSymbol::OsmcSymbol(const QString &tag, int size)
14 : m_wayColor(Qt::white)
15 , m_backgroundColor(Qt::black)
16 , m_foreground(nullptr)
17 , m_foreground2(nullptr)
18 , m_textColor(Qt::black)
19 , m_side(size)
20{
21 m_backgroundTypes
22 << "round" << "circle" << "frame";
23
24 m_foregroundTypes
25 << "dot" << "bowl" << "circle" << "bar"
26 << "stripe" << "cross" << "x" << "slash"
27 << "backslash" << "rectangle" << "rectangle_line"
28 << "triangle" << "triangle_turned" << "triangle_line"
29 << "diamond" << "pointer" << "fork" << "arch"
30 << "turned_T" << "L" << "lower" << "corner"
31 << "drop_line" << "horse" << "hiker";
32
33 m_precoloredForegroundTypes
34 << "wolfshook" << "shell" << "shell_modern" << "ammonit"
35 << "mine" << "hiker" << "heart" << "tower" << "bridleway";
36
37 if (parseTag(tag)) {
38 render();
39 }
40}
41
42OsmcSymbol::~OsmcSymbol()
43{
44 delete m_foreground;
45 delete m_foreground2;
46}
47
48bool OsmcSymbol::parseTag(const QString &tag)
49{
50 QStringList parts = tag.split(':');
51
52 if (parts.size() < 2) {
53 return false;
54 }
55
56 if (m_foreground) {
57 delete m_foreground;
58 m_foreground = nullptr;
59 }
60 if (m_foreground2) {
61 delete m_foreground2;
62 m_foreground2 = nullptr;
63 }
64
65 // Determine way color
66 if (QColor::isValidColor(parts.at(0))) {
67 m_wayColor.setNamedColor(parts.at(0));
68 } else {
69 return false;
70 }
71
72 if (!parseBackground(parts.at(1))) {
73 return false;
74 }
75
76 if (parts.size() == 3) {
77 m_foreground = parseForeground(parts.at(2));
78 } else if (parts.size() == 4) {
79 if (QColor::isValidColor(parts.at(3))) {
80 m_text = parts.at(2);
81 m_textColor = parts.at(3);
82 } else {
83 m_foreground = parseForeground(parts.at(2));
84 m_foreground2 = parseForeground(parts.at(3));
85 }
86 } else if (parts.size() == 5) {
87 m_foreground = parseForeground(parts.at(2));
88 if (QColor::isValidColor(parts.at(4))) {
89 m_text = parts.at(3);
90 m_textColor = parts.at(4);
91 } else {
92 return false;
93 }
94 } else if (parts.size() == 6) {
95 m_foreground = parseForeground(parts.at(2));
96 m_foreground2 = parseForeground(parts.at(3));
97 if (QColor::isValidColor(parts.at(5))) {
98 m_text = parts.at(4);
99 m_textColor.setNamedColor(parts.at(5));
100 } else {
101 return false;
102 }
103 } else {
104 return false;
105 }
106
107 return true;
108}
109
110bool OsmcSymbol::parseBackground(const QString &bg)
111{
112 QString color = bg.section("_", 0, 0);
113 QString type = bg.section("_", 1, -1);
114
115 if (!QColor::isValidColor(color)) {
116 return false;
117 }
118
119 // Plain color was provided
120 if (type.isEmpty()) {
121 m_backgroundColor.setNamedColor(color);
122 m_backgroundType = type;
123 } else if (m_backgroundTypes.contains(type)) {
124 m_backgroundColor.setNamedColor(color);
125 m_backgroundType = type;
126 } else {
127 return false;
128 }
129
130 return true;
131}
132
133void setXMLAttribute(QDomElement &elem, const QString& tag, const QString& attr, const QString& attrValue);
134
135QSvgRenderer* OsmcSymbol::parseForeground(const QString &fg)
136{
137 if (m_precoloredForegroundTypes.contains(fg)) {
138 return new QSvgRenderer(QString(":/osmc-symbols/%1.svg").arg(fg));
139 }
140
141 QString color = fg.section('_', 0, 0);
142 QString type = fg.section('_', 1, -1);
143 if (QColor::isValidColor(color) && m_foregroundTypes.contains(type)) {
144 // Open svg resource and load contents to QByteArray
145 QFile file(QString(":/osmc-symbols/%1.svg").arg(type));
146 file.open(QIODevice::ReadOnly);
147 QByteArray baData = file.readAll();
148
149 // Load svg contents to xml document
150 QDomDocument doc;
151 doc.setContent(baData);
152
153 // Recursively change color
154 QDomElement rootElement = doc.documentElement();
155 setXMLAttribute(rootElement, "path", "fill", color);
156
157 // Create and return svg renderer with edited contents
158 return new QSvgRenderer(doc.toByteArray());
159 }
160
161 return nullptr;
162}
163
164void OsmcSymbol::render()
165{
166 m_image = QImage(m_side, m_side, QImage::Format_ARGB32);
167 m_image.fill(Qt::transparent);
168
169 QPainter painter(&m_image);
170 painter.setRenderHint(QPainter::Antialiasing);
171
172 // Default size of background
173 int w = m_side, h = m_side;
174
175 // If there is some text, our background size must be recalculated
176 if (!m_text.isEmpty()) {
177 QFont font = painter.font();
178 font.setPixelSize(int(m_side * 0.8));
179 font.setBold(true);
180 painter.setFont(font);
181 QFontMetrics fm = QFontMetrics(font);
182
183 h = fm.height();
184 w = qMax(h, fm.horizontalAdvance(m_text));
185 }
186
187 const QRect bgRect = QRect((m_side - w) / 2, (m_side - h) / 2, w, h);
188
189 // Draw symbol's background
190 if (m_backgroundType.isEmpty()) {
191 painter.fillRect(bgRect, m_backgroundColor);
192 } else if (m_backgroundType == "round") {
193 painter.setBrush(m_backgroundColor);
194 painter.setPen(m_backgroundColor);
195 painter.drawEllipse(bgRect);
196 } else if (m_backgroundType == "circle") {
197 painter.setBrush(Qt::white);
198 painter.setPen(QPen(m_backgroundColor, m_side / 10));
199 painter.drawEllipse(bgRect);
200 } else if (m_backgroundType == "frame") {
201 painter.setPen(QPen(m_backgroundColor, m_side / 10));
202 painter.fillRect(bgRect, Qt::white);
203 painter.drawRect(bgRect);
204 }
205
206 QPixmap foregrounds(bgRect.size());
207 foregrounds.fill(Qt::transparent);
208 QPainter fgPainter(&foregrounds);
209 m_foreground ? m_foreground->render(&fgPainter) : void();
210 m_foreground2 ? m_foreground2->render(&fgPainter) : void();
211 painter.drawPixmap(bgRect, foregrounds);
212
213 if (!m_text.isEmpty()) {
214 // Draw text with provided color
215 painter.setPen(m_textColor);
216 painter.drawText(bgRect, Qt::AlignCenter, m_text);
217 }
218
219 painter.end();
220}
221
222QImage OsmcSymbol::icon() const
223{
224 return m_image;
225}
226
227QColor OsmcSymbol::wayColor() const
228{
229 return m_wayColor;
230}
231
232void setXMLAttribute(QDomElement &elem, const QString& tag, const QString& attr, const QString& attrValue)
233{
234 // If elem's tag is equal to the provided one then overwrite desired attribute
235 if (elem.tagName() == tag) {
236 elem.setAttribute(attr, attrValue);
237 }
238
239 // Do the same for all the child nodes
240 for (int i = 0; i < elem.childNodes().count(); ++i) {
241 if (!elem.childNodes().at(i).isElement()) {
242 continue;
243 }
244
245 QDomElement child = elem.childNodes().at(i).toElement();
246 setXMLAttribute(child, tag, attr, attrValue);
247 }
248}
VehicleSection::Type type(QStringView coachNumber, QStringView coachClassification)
bool isValidColor(QLatin1StringView name)
void setNamedColor(QLatin1StringView name)
QDomElement documentElement() const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QByteArray toByteArray(int indent) const const
void setAttribute(const QString &name, const QString &value)
QString tagName() const const
QDomNodeList childNodes() const const
bool isElement() const const
QDomElement toElement() const const
QDomNode at(int index) const const
void setBold(bool enable)
void setPixelSize(int pixelSize)
int height() const const
int horizontalAdvance(QChar ch) const const
void fill(Qt::GlobalColor color)
const_reference at(qsizetype i) const const
qsizetype size() const const
QSize size() const const
bool isEmpty() const const
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
void render(QPainter *painter)
AlignCenter
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 21 2024 12:00:07 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.