File indexing completed on 2024-11-17 04:17:27

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "mapcssdeclaration_p.h"
0008 #include "logging.h"
0009 #include "mapcssproperty.h"
0010 
0011 #include <QDebug>
0012 #include <QIODevice>
0013 
0014 #include <cstring>
0015 
0016 using namespace KOSMIndoorMap;
0017 
0018 // keep this sorted by property name!
0019 struct {
0020     const char* name;
0021     MapCSSProperty property;
0022     int flags;
0023 } static constexpr const property_types[] = {
0024     // only those properties have their corresonding flag set that actually trigger emission of a scene graph item
0025     // e.g. for a label we either need a text or an icon, the visual properties for those on their own would be a no-op
0026     { "casing-color", MapCSSProperty::CasingColor, MapCSSDeclaration::NoFlag },
0027     { "casing-dashes", MapCSSProperty::CasingDashes, MapCSSDeclaration::NoFlag },
0028     { "casing-linecap", MapCSSProperty::CasingLineCap, MapCSSDeclaration::NoFlag },
0029     { "casing-linejoin", MapCSSProperty::CasingLineJoin, MapCSSDeclaration::NoFlag },
0030     { "casing-opacity", MapCSSProperty::CasingOpacity, MapCSSDeclaration::NoFlag },
0031     { "casing-width", MapCSSProperty::CasingWidth, MapCSSDeclaration::NoFlag },
0032     { "color", MapCSSProperty::Color, MapCSSDeclaration::LineProperty },
0033     { "dashes", MapCSSProperty::Dashes, MapCSSDeclaration::NoFlag },
0034     { "extrude", MapCSSProperty::Extrude, MapCSSDeclaration::ExtrudeProperty },
0035     { "fill-color", MapCSSProperty::FillColor, MapCSSDeclaration::AreaProperty | MapCSSDeclaration::CanvasProperty }, // TODO this also applies to lines
0036     { "fill-image", MapCSSProperty::FillImage, MapCSSDeclaration::AreaProperty | MapCSSDeclaration::CanvasProperty },
0037     { "fill-opacity", MapCSSProperty::FillOpacity, MapCSSDeclaration::AreaProperty },
0038     { "font-family", MapCSSProperty::FontFamily, MapCSSDeclaration::NoFlag },
0039     { "font-size", MapCSSProperty::FontSize, MapCSSDeclaration::NoFlag },
0040     { "font-style", MapCSSProperty::FontStyle, MapCSSDeclaration::NoFlag },
0041     { "font-variant", MapCSSProperty::FontVariant, MapCSSDeclaration::NoFlag },
0042     { "font-weight", MapCSSProperty::FontWeight, MapCSSDeclaration::NoFlag },
0043     { "icon-allow-icon-overlap", MapCSSProperty::IconAllowIconOverlap, MapCSSDeclaration::NoFlag },
0044     { "icon-allow-text-overlap", MapCSSProperty::IconAllowTextOverlap, MapCSSDeclaration::NoFlag },
0045     { "icon-color", MapCSSProperty::IconColor, MapCSSDeclaration::NoFlag },
0046     { "icon-height", MapCSSProperty::IconHeight, MapCSSDeclaration::NoFlag },
0047     { "icon-image", MapCSSProperty::IconImage, MapCSSDeclaration::LabelProperty },
0048     { "icon-opacity", MapCSSProperty::IconOpacity, MapCSSDeclaration::NoFlag },
0049     { "icon-width", MapCSSProperty::IconWidth, MapCSSDeclaration::NoFlag },
0050     { "image", MapCSSProperty::Image, MapCSSDeclaration::LineProperty },
0051     { "linecap", MapCSSProperty::LineCap, MapCSSDeclaration::NoFlag },
0052     { "linejoin", MapCSSProperty::LineJoin, MapCSSDeclaration::NoFlag },
0053     { "max-width", MapCSSProperty::MaxWidth, MapCSSDeclaration::NoFlag },
0054     { "opacity", MapCSSProperty::Opacity, MapCSSDeclaration::NoFlag },
0055     { "shield-casing-color", MapCSSProperty::ShieldCasingColor, MapCSSDeclaration::LabelProperty },
0056     { "shield-casing-width", MapCSSProperty::ShieldCasingWidth, MapCSSDeclaration::NoFlag },
0057     { "shield-color", MapCSSProperty::ShieldColor, MapCSSDeclaration::LabelProperty },
0058     { "shield-frame-color", MapCSSProperty::ShieldFrameColor, MapCSSDeclaration::LabelProperty },
0059     { "shield-frame-width", MapCSSProperty::ShieldFrameWidth, MapCSSDeclaration::NoFlag },
0060     { "shield-image", MapCSSProperty::ShieldImage, MapCSSDeclaration::LabelProperty },
0061     { "shield-opacity", MapCSSProperty::ShieldOpacity, MapCSSDeclaration::NoFlag },
0062     { "shield-shape", MapCSSProperty::ShieldShape, MapCSSDeclaration::NoFlag },
0063     { "shield-text", MapCSSProperty::ShieldText, MapCSSDeclaration::LabelProperty },
0064     { "text", MapCSSProperty::Text, MapCSSDeclaration::LabelProperty },
0065     { "text-color", MapCSSProperty::TextColor, MapCSSDeclaration::CanvasProperty },
0066     { "text-decoration", MapCSSProperty::TextDecoration, MapCSSDeclaration::NoFlag },
0067     { "text-halo-color", MapCSSProperty::TextHaloColor, MapCSSDeclaration::NoFlag },
0068     { "text-halo-radius", MapCSSProperty::TextHaloRadius, MapCSSDeclaration::NoFlag },
0069     { "text-offset", MapCSSProperty::TextOffset, MapCSSDeclaration::NoFlag },
0070     { "text-opacity", MapCSSProperty::TextOpacity, MapCSSDeclaration::NoFlag },
0071     { "text-position", MapCSSProperty::TextPosition, MapCSSDeclaration::NoFlag },
0072     { "text-transform", MapCSSProperty::TextTransform, MapCSSDeclaration::NoFlag },
0073     { "width", MapCSSProperty::Width, MapCSSDeclaration::LineProperty },
0074     { "z-index", MapCSSProperty::ZIndex, MapCSSDeclaration::NoFlag },
0075 };
0076 
0077 struct {
0078     const char *name;
0079     Qt::PenCapStyle capStyle;
0080 } static constexpr const capstyle_map[] = {
0081     { "none", Qt::FlatCap },
0082     { "round", Qt::RoundCap },
0083     { "square", Qt::SquareCap },
0084 };
0085 
0086 struct {
0087     const char *name;
0088     Qt::PenJoinStyle joinStyle;
0089 } static constexpr const joinstyle_map[] = {
0090     { "bevel", Qt::BevelJoin },
0091     { "miter", Qt::MiterJoin },
0092     { "round", Qt::RoundJoin },
0093 };
0094 
0095 struct {
0096     const char *name;
0097     QFont::Capitalization capitalizationStyle;
0098 } static constexpr const capitalizationstyle_map[] = {
0099     { "capitalize", QFont::Capitalize },
0100     { "lowercase", QFont::AllLowercase },
0101     { "none", QFont::MixedCase },
0102     { "normal", QFont::MixedCase },
0103     { "small-caps", QFont::SmallCaps },
0104     { "uppercase", QFont::AllUppercase },
0105 };
0106 
0107 struct {
0108     const char *name;
0109     MapCSSDeclaration::Unit unit;
0110 } static constexpr const unit_map[] = {
0111     { "m", MapCSSDeclaration::Meters },
0112     { "pt", MapCSSDeclaration::Point },
0113     { "px", MapCSSDeclaration::Pixels },
0114 };
0115 
0116 struct {
0117     const char *name;
0118     MapCSSDeclaration::Position position;
0119 } static constexpr const position_map[] = {
0120     { "center", MapCSSDeclaration::Position::Center },
0121     { "line", MapCSSDeclaration::Position::Line },
0122 };
0123 
0124 MapCSSDeclaration::MapCSSDeclaration(Type type)
0125     : m_type(type)
0126 {
0127 }
0128 
0129 MapCSSDeclaration::~MapCSSDeclaration() = default;
0130 
0131 bool MapCSSDeclaration::isValid() const
0132 {
0133     switch (m_type) {
0134         case PropertyDeclaration:
0135             return property() != MapCSSProperty::Unknown;
0136         case TagDeclaration:
0137             return !m_identValue.isEmpty();
0138         case ClassDeclaration:
0139             return !m_class.isNull();
0140     }
0141 
0142     Q_UNREACHABLE();
0143     return false;
0144 }
0145 
0146 MapCSSDeclaration::Type MapCSSDeclaration::type() const
0147 {
0148     return m_type;
0149 }
0150 
0151 MapCSSProperty MapCSSDeclaration::property() const
0152 {
0153     return m_property;
0154 }
0155 
0156 int MapCSSDeclaration::propertyFlags() const
0157 {
0158     return m_flags;
0159 }
0160 
0161 int MapCSSDeclaration::intValue() const
0162 {
0163     return m_doubleValue;
0164 }
0165 
0166 double MapCSSDeclaration::doubleValue() const
0167 {
0168     return m_doubleValue;
0169 }
0170 
0171 bool MapCSSDeclaration::boolValue() const
0172 {
0173     return m_boolValue;
0174 }
0175 
0176 QString MapCSSDeclaration::stringValue() const
0177 {
0178     return m_stringValue;
0179 }
0180 
0181 QColor MapCSSDeclaration::colorValue() const
0182 {
0183     if (!m_colorValue.isValid() && !m_stringValue.isEmpty()) {
0184         return QColor(m_stringValue);
0185     }
0186     return m_colorValue;
0187 }
0188 
0189 QByteArray MapCSSDeclaration::keyValue() const
0190 {
0191     return m_identValue;
0192 }
0193 
0194 QVector<double> MapCSSDeclaration::dashesValue() const
0195 {
0196     return m_dashValue;
0197 }
0198 
0199 OSM::TagKey MapCSSDeclaration::tagKey() const
0200 {
0201     return m_tagKey;
0202 }
0203 
0204 void MapCSSDeclaration::setDoubleValue(double val)
0205 {
0206     m_doubleValue = val;
0207 }
0208 
0209 void MapCSSDeclaration::setBoolValue(bool val)
0210 {
0211     m_boolValue = val;
0212 }
0213 
0214 void MapCSSDeclaration::setPropertyName(const char *name, std::size_t len)
0215 {
0216     const auto it = std::lower_bound(std::begin(property_types), std::end(property_types), name, [len](const auto &lhs, const char *rhs) {
0217         const auto lhsLen = std::strlen(lhs.name);
0218         const auto cmp = std::strncmp(lhs.name, rhs, std::min(lhsLen, len));
0219         return cmp < 0 || (cmp == 0 && lhsLen < len);
0220     });
0221     if (it == std::end(property_types) || std::strncmp((*it).name, name, std::max(len, std::strlen((*it).name))) != 0) {
0222         qCWarning(Log) << "Unknown property declaration:" << QByteArray::fromRawData(name, len);
0223         m_property = MapCSSProperty::Unknown;
0224         return;
0225     }
0226     m_property = (*it).property;
0227     m_flags = (*it).flags;
0228 }
0229 
0230 void MapCSSDeclaration::setIdentifierValue(const char *val, int len)
0231 {
0232     m_identValue = QByteArray(val, len);
0233 }
0234 
0235 void MapCSSDeclaration::setStringValue(char *str)
0236 {
0237     m_stringValue = QString::fromUtf8(str);
0238     free(str);
0239 }
0240 
0241 void MapCSSDeclaration::setColorRgba(uint32_t argb)
0242 {
0243     m_colorValue = QColor::fromRgba(argb);
0244     //qDebug() << m_colorValue << argb;
0245 }
0246 
0247 void MapCSSDeclaration::setDashesValue(const QVector<double> &dashes)
0248 {
0249     m_dashValue = dashes;
0250 }
0251 
0252 Qt::PenCapStyle MapCSSDeclaration::capStyle() const
0253 {
0254     for (const auto &c : capstyle_map) {
0255         if (std::strcmp(c.name, m_identValue.constData()) == 0) {
0256             return c.capStyle;
0257         }
0258     }
0259     qDebug() << "unknown line cap style:" << m_identValue;
0260     return Qt::FlatCap;
0261 }
0262 
0263 Qt::PenJoinStyle MapCSSDeclaration::joinStyle() const
0264 {
0265     for (const auto &j : joinstyle_map) {
0266         if (std::strcmp(j.name, m_identValue.constData()) == 0) {
0267             return j.joinStyle;
0268         }
0269     }
0270     return Qt::RoundJoin;
0271 }
0272 
0273 QFont::Capitalization MapCSSDeclaration::capitalizationStyle() const
0274 {
0275     for (const auto &c : capitalizationstyle_map) {
0276         if (std::strcmp(c.name, m_identValue.constData()) == 0) {
0277             return c.capitalizationStyle;
0278         }
0279     }
0280     return QFont::MixedCase;
0281 }
0282 
0283 bool MapCSSDeclaration::isBoldStyle() const
0284 {
0285     return m_identValue == "bold";
0286 }
0287 
0288 bool MapCSSDeclaration::isItalicStyle() const
0289 {
0290     return m_identValue == "italic";
0291 }
0292 
0293 bool MapCSSDeclaration::isUnderlineStyle() const
0294 {
0295     return m_identValue == "underline";
0296 }
0297 
0298 MapCSSDeclaration::Position MapCSSDeclaration::textPosition() const
0299 {
0300     for (const auto &p : position_map) {
0301         if (std::strcmp(p.name, m_identValue.constData()) == 0) {
0302             return p.position;
0303         }
0304     }
0305     return Position::NoPostion;
0306 }
0307 
0308 MapCSSDeclaration::Unit MapCSSDeclaration::unit() const
0309 {
0310     return m_unit;
0311 }
0312 
0313 void MapCSSDeclaration::setUnit(const char *val, int len)
0314 {
0315     for (const auto &u : unit_map) {
0316         if (std::strncmp(u.name, val, std::max<std::size_t>(std::strlen(u.name), len)) == 0) {
0317             m_unit = u.unit;
0318             return;
0319         }
0320     }
0321     qCWarning(Log) << "unknown unit:" << QByteArray(val, len);
0322     m_unit = NoUnit;
0323 }
0324 
0325 ClassSelectorKey MapCSSDeclaration::classSelectorKey() const
0326 {
0327     return m_class;
0328 }
0329 
0330 void MapCSSDeclaration::setClassSelectorKey(ClassSelectorKey key)
0331 {
0332     m_class = key;
0333 }
0334 
0335 void MapCSSDeclaration::compile(const OSM::DataSet &dataSet)
0336 {
0337     Q_UNUSED(dataSet);
0338     // TODO resolve tag key if m_identValue is one
0339     if (m_type == TagDeclaration) {
0340         // TODO handle the case that the tag isn't actually available in dataSet
0341         m_tagKey = dataSet.tagKey(m_identValue.constData());
0342     }
0343 }
0344 
0345 void MapCSSDeclaration::write(QIODevice *out) const
0346 {
0347     out->write("    ");
0348 
0349     switch (m_type) {
0350         case PropertyDeclaration:
0351             for (const auto &p : property_types) {
0352                 if (p.property == m_property) {
0353                     out->write(p.name);
0354                     break;
0355                 }
0356             }
0357 
0358             out->write(": ");
0359             if (!std::isnan(m_doubleValue)) {
0360                 out->write(QByteArray::number(m_doubleValue));
0361             } else if (m_colorValue.isValid()) {
0362                 out->write(m_colorValue.name(QColor::HexArgb).toUtf8());
0363             } else if (!m_dashValue.isEmpty()) {
0364                 for (const auto &d : m_dashValue) {
0365                     out->write(QByteArray::number(d));
0366                     out->write(", ");
0367                 }
0368             } else if (!m_stringValue.isNull()) {
0369                 out->write("\"");
0370                 out->write(m_stringValue.toUtf8()); // this would need to be quoted...
0371                 out->write("\"");
0372             } else if (!m_identValue.isEmpty()) {
0373                 out->write(m_identValue);
0374             } else {
0375                 out->write(m_boolValue ? "true" : "false");
0376             }
0377 
0378             for (const auto &u : unit_map) {
0379                 if (u.unit == m_unit) {
0380                     out->write(u.name);
0381                     break;
0382                 }
0383             }
0384             break;
0385         case TagDeclaration:
0386             out->write("set ");
0387             out->write(m_identValue);
0388             if (!std::isnan(m_doubleValue)) {
0389                 out->write(" = ");
0390                 out->write(QByteArray::number(m_doubleValue));
0391             } else if (!m_stringValue.isEmpty()) {
0392                 out->write(" = \"");
0393                 out->write(m_stringValue.toUtf8()); // this would need to be quoted...
0394                 out->write("\"");
0395             }
0396             break;
0397         case ClassDeclaration:
0398             out->write("set .");
0399             out->write(m_class.name());
0400             break;
0401     }
0402 
0403     out->write(";\n");
0404 }