File indexing completed on 2024-04-21 03:49:49

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2017 Sergey Popov <sergobot@protonmail.com>
0004 //
0005 
0006 #include "OsmcSymbol.h"
0007 
0008 #include <QDebug>
0009 #include <QDomDocument>
0010 #include <QFile>
0011 #include <QPainter>
0012 
0013 OsmcSymbol::OsmcSymbol(const QString &tag, int size)
0014     : m_wayColor(Qt::white)
0015     , m_backgroundColor(Qt::black)
0016     , m_foreground(nullptr)
0017     , m_foreground2(nullptr)
0018     , m_textColor(Qt::black)
0019     , m_side(size)
0020 {
0021     m_backgroundTypes
0022             << "round" << "circle" << "frame";
0023 
0024     m_foregroundTypes
0025             << "dot" << "bowl" << "circle" << "bar"
0026             << "stripe" << "cross" << "x" << "slash"
0027             << "backslash" << "rectangle" << "rectangle_line"
0028             << "triangle" << "triangle_turned" << "triangle_line"
0029             << "diamond" << "pointer" << "fork" << "arch"
0030             << "turned_T" << "L" << "lower" << "corner"
0031             << "drop_line" << "horse" << "hiker";
0032 
0033     m_precoloredForegroundTypes
0034             << "wolfshook" << "shell" << "shell_modern" << "ammonit"
0035             << "mine" << "hiker" << "heart" << "tower" << "bridleway";
0036 
0037     if (parseTag(tag)) {
0038         render();
0039     }
0040 }
0041 
0042 OsmcSymbol::~OsmcSymbol()
0043 {
0044     delete m_foreground;
0045     delete m_foreground2;
0046 }
0047 
0048 bool OsmcSymbol::parseTag(const QString &tag)
0049 {
0050     QStringList parts = tag.split(':');
0051 
0052     if (parts.size() < 2) {
0053         return false;
0054     }
0055 
0056     if (m_foreground) {
0057         delete m_foreground;
0058         m_foreground = nullptr;
0059     }
0060     if (m_foreground2) {
0061         delete m_foreground2;
0062         m_foreground2 = nullptr;
0063     }
0064 
0065     // Determine way color
0066     if (QColor::isValidColor(parts.at(0))) {
0067         m_wayColor.setNamedColor(parts.at(0));
0068     } else {
0069         return false;
0070     }
0071 
0072     if (!parseBackground(parts.at(1))) {
0073         return false;
0074     }
0075 
0076     if (parts.size() == 3) {
0077         m_foreground = parseForeground(parts.at(2));
0078     } else if (parts.size() == 4) {
0079         if (QColor::isValidColor(parts.at(3))) {
0080             m_text = parts.at(2);
0081             m_textColor = parts.at(3);
0082         } else {
0083             m_foreground = parseForeground(parts.at(2));
0084             m_foreground2 = parseForeground(parts.at(3));
0085         }
0086     } else if (parts.size() == 5) {
0087         m_foreground = parseForeground(parts.at(2));
0088         if (QColor::isValidColor(parts.at(4))) {
0089             m_text = parts.at(3);
0090             m_textColor = parts.at(4);
0091         } else {
0092             return false;
0093         }
0094     } else if (parts.size() == 6) {
0095         m_foreground = parseForeground(parts.at(2));
0096         m_foreground2 = parseForeground(parts.at(3));
0097         if (QColor::isValidColor(parts.at(5))) {
0098             m_text = parts.at(4);
0099             m_textColor.setNamedColor(parts.at(5));
0100         } else {
0101             return false;
0102         }
0103     } else {
0104         return false;
0105     }
0106 
0107     return true;
0108 }
0109 
0110 bool OsmcSymbol::parseBackground(const QString &bg)
0111 {
0112     QString color = bg.section("_", 0, 0);
0113     QString type = bg.section("_", 1, -1);
0114 
0115     if (!QColor::isValidColor(color)) {
0116         return false;
0117     }
0118 
0119     // Plain color was provided
0120     if (type.isEmpty()) {
0121         m_backgroundColor.setNamedColor(color);
0122         m_backgroundType = type;
0123     } else if (m_backgroundTypes.contains(type)) {
0124         m_backgroundColor.setNamedColor(color);
0125         m_backgroundType = type;
0126     } else {
0127         return false;
0128     }
0129 
0130     return true;
0131 }
0132 
0133 void setXMLAttribute(QDomElement &elem, const QString& tag, const QString& attr, const QString& attrValue);
0134 
0135 QSvgRenderer* OsmcSymbol::parseForeground(const QString &fg)
0136 {
0137     if (m_precoloredForegroundTypes.contains(fg)) {
0138         return new QSvgRenderer(QString(":/osmc-symbols/%1.svg").arg(fg));
0139     }
0140 
0141     QString color = fg.section('_', 0, 0);
0142     QString type = fg.section('_', 1, -1);
0143     if (QColor::isValidColor(color) && m_foregroundTypes.contains(type)) {
0144         // Open svg resource and load contents to QByteArray
0145         QFile file(QString(":/osmc-symbols/%1.svg").arg(type));
0146         file.open(QIODevice::ReadOnly);
0147         QByteArray baData = file.readAll();
0148 
0149         // Load svg contents to xml document
0150         QDomDocument doc;
0151         doc.setContent(baData);
0152 
0153         // Recursively change color
0154         QDomElement rootElement = doc.documentElement();
0155         setXMLAttribute(rootElement, "path", "fill", color);
0156 
0157         // Create and return svg renderer with edited contents
0158         return new QSvgRenderer(doc.toByteArray());
0159     }
0160 
0161     return nullptr;
0162 }
0163 
0164 void OsmcSymbol::render()
0165 {
0166     m_image = QImage(m_side, m_side, QImage::Format_ARGB32);
0167     m_image.fill(Qt::transparent);
0168 
0169     QPainter painter(&m_image);
0170     painter.setRenderHint(QPainter::Antialiasing);
0171 
0172     // Default size of background
0173     int w = m_side, h = m_side;
0174 
0175     // If there is some text, our background size must be recalculated
0176     if (!m_text.isEmpty()) {
0177         QFont font = painter.font();
0178         font.setPixelSize(int(m_side * 0.8));
0179         font.setBold(true);
0180         painter.setFont(font);
0181         QFontMetrics fm = QFontMetrics(font);
0182 
0183         h = fm.height();
0184         w = qMax(h, fm.horizontalAdvance(m_text));
0185     }
0186 
0187     const QRect bgRect = QRect((m_side - w) / 2, (m_side - h) / 2, w, h);
0188 
0189     // Draw symbol's background
0190     if (m_backgroundType.isEmpty()) {
0191         painter.fillRect(bgRect, m_backgroundColor);
0192     } else if (m_backgroundType == "round") {
0193         painter.setBrush(m_backgroundColor);
0194         painter.setPen(m_backgroundColor);
0195         painter.drawEllipse(bgRect);
0196     } else if (m_backgroundType == "circle") {
0197         painter.setBrush(Qt::white);
0198         painter.setPen(QPen(m_backgroundColor, m_side / 10));
0199         painter.drawEllipse(bgRect);
0200     } else if (m_backgroundType == "frame") {
0201         painter.setPen(QPen(m_backgroundColor, m_side / 10));
0202         painter.fillRect(bgRect, Qt::white);
0203         painter.drawRect(bgRect);
0204     }
0205 
0206     QPixmap foregrounds(bgRect.size());
0207     foregrounds.fill(Qt::transparent);
0208     QPainter fgPainter(&foregrounds);
0209     m_foreground ? m_foreground->render(&fgPainter) : void();
0210     m_foreground2 ? m_foreground2->render(&fgPainter) : void();
0211     painter.drawPixmap(bgRect, foregrounds);
0212 
0213     if (!m_text.isEmpty()) {
0214         // Draw text with provided color
0215         painter.setPen(m_textColor);
0216         painter.drawText(bgRect, Qt::AlignCenter, m_text);
0217     }
0218 
0219     painter.end();
0220 }
0221 
0222 QImage OsmcSymbol::icon() const
0223 {
0224     return m_image;
0225 }
0226 
0227 QColor OsmcSymbol::wayColor() const
0228 {
0229     return m_wayColor;
0230 }
0231 
0232 void setXMLAttribute(QDomElement &elem, const QString& tag, const QString& attr, const QString& attrValue)
0233 {
0234     // If elem's tag is equal to the provided one then overwrite desired attribute
0235     if (elem.tagName() == tag) {
0236         elem.setAttribute(attr, attrValue);
0237     }
0238 
0239     // Do the same for all the child nodes
0240     for (int i = 0; i < elem.childNodes().count(); ++i) {
0241         if (!elem.childNodes().at(i).isElement()) {
0242             continue;
0243         }
0244 
0245         QDomElement child = elem.childNodes().at(i).toElement();
0246         setXMLAttribute(child, tag, attr, attrValue);
0247     }
0248 }