#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace style { Parser::~Parser() = default; StyleParseResult Parser::parse(const std::string& json) { rapidjson::GenericDocument, rapidjson::CrtAllocator> document; document.Parse<0>(json.c_str()); if (document.HasParseError()) { return std::make_exception_ptr(std::runtime_error(formatJSONParseError(document))); } if (!document.IsObject()) { return std::make_exception_ptr(std::runtime_error("style must be an object")); } if (document.HasMember("version")) { const JSValue& versionValue = document["version"]; const int version = versionValue.IsNumber() ? versionValue.GetInt() : 0; if (version != 8) { Log::Warning(Event::ParseStyle, "current renderer implementation only supports style spec " "version 8; using an outdated style " "will cause rendering errors"); } } if (document.HasMember("name")) { const JSValue& value = document["name"]; if (value.IsString()) { name = {value.GetString(), value.GetStringLength()}; } } if (document.HasMember("center")) { const JSValue& value = document["center"]; conversion::Error error; auto convertedLatLng = conversion::convert(value, error); if (convertedLatLng) { latLng = *convertedLatLng; } else { Log::Warning(Event::ParseStyle, "center coordinate must be a longitude, latitude pair"); } } if (document.HasMember("centerAltitude")) { const JSValue& value = document["centerAltitude"]; if (value.IsNumber()) { centerAltitude = value.GetDouble(); } } if (document.HasMember("zoom")) { const JSValue& value = document["zoom"]; if (value.IsNumber()) { zoom = value.GetDouble(); } } if (document.HasMember("bearing")) { const JSValue& value = document["bearing"]; if (value.IsNumber()) { bearing = value.GetDouble(); } } if (document.HasMember("pitch")) { const JSValue& value = document["pitch"]; if (value.IsNumber()) { pitch = value.GetDouble(); } } if (document.HasMember("roll")) { const JSValue& value = document["roll"]; if (value.IsNumber()) { roll = value.GetDouble(); } } if (document.HasMember("transition")) { parseTransition(document["transition"]); } if (document.HasMember("light")) { parseLight(document["light"]); } if (document.HasMember("sources")) { parseSources(document["sources"]); } if (document.HasMember("layers")) { parseLayers(document["layers"]); } if (document.HasMember("sprite")) { parseSprites(document["sprite"]); } if (document.HasMember("glyphs")) { const JSValue& glyphs = document["glyphs"]; if (glyphs.IsString()) { glyphURL = {glyphs.GetString(), glyphs.GetStringLength()}; } } #ifdef MLN_TEXT_SHAPING_HARFBUZZ // Ignore font-faces if no harfbuzz if (document.HasMember("font-faces")) { const JSValue& faces = document["font-faces"]; if (faces.IsObject()) { fontFaces = std::make_shared(); for (auto it = faces.MemberBegin(); it != faces.MemberEnd(); ++it) { const std::string& faceName = it->name.GetString(); const JSValue& faceValue = it->value; if (faceValue.IsArray()) { // If the face is an array, we assume it is a list of font file objects. for (const auto& fontFile : faceValue.GetArray()) { if (fontFile.IsObject()) { FontFace fontFace(faceName, "", {{0, 0x10FFFF}}); // Parse url if (fontFile.HasMember("url")) { const JSValue& url = fontFile["url"]; if (url.IsString()) fontFace.url = url.GetString(); } // Parse unicode-range if (fontFile.HasMember("unicode-range")) { const JSValue& unicodeRange = fontFile["unicode-range"]; fontFace.ranges.clear(); if (unicodeRange.IsArray()) { for (auto& range : unicodeRange.GetArray()) { if (range.IsString()) { std::string rangeString = range.GetString(); if (rangeString.length() > 2) { rangeString = rangeString.substr(2); std::string::size_type pos = rangeString.find('-'); if (pos != std::string::npos) { std::string start = rangeString.substr(0, pos); std::string end = rangeString.substr(pos + 1); if (!start.empty() && !end.empty()) { auto startInt = util::parse(start, 16); auto endInt = util::parse(end, 16); if (startInt && endInt) { fontFace.ranges.emplace_back(*startInt, *endInt); } else { Log::Warning( Event::ParseStyle, "Invalid unicode-range in font-face: " + rangeString); } } } } } } } } // If valid, generate a unique glyph ID type for this font face // and add it to the font faces list. if (fontFace.valid()) { fontFace.type = genNewGlyphIDType( fontFace.url, FontStack{fontFace.name}, fontFace.ranges); fontFaces->emplace_back(std::move(fontFace)); } else { Log::Warning(Event::ParseStyle, "Invalid font-face definition for: " + faceName); } } } } else if (faceValue.IsString()) { FontFace fontFace(faceName, faceValue.GetString(), {{0, 0x10FFFF}}); fontFace.type = genNewGlyphIDType(fontFace.url, FontStack{fontFace.name}, fontFace.ranges); fontFaces->emplace_back(std::move(fontFace)); } else { Log::Warning(Event::ParseStyle, "font-face must be an object or array"); } } }; } #endif // Call for side effect of logging warnings for invalid values. fontStacks(); return nullptr; } void Parser::parseTransition(const JSValue& value) { conversion::Error error; std::optional converted = conversion::convert(value, error); if (!converted) { Log::Warning(Event::ParseStyle, error.message); return; } transition = std::move(*converted); } void Parser::parseLight(const JSValue& value) { conversion::Error error; std::optional converted = conversion::convert(value, error); if (!converted) { Log::Warning(Event::ParseStyle, error.message); return; } light = *converted; } void Parser::parseSources(const JSValue& value) { if (!value.IsObject()) { Log::Warning(Event::ParseStyle, "sources must be an object"); return; } for (const auto& property : value.GetObject()) { std::string id{property.name.GetString(), property.name.GetStringLength()}; conversion::Error error; std::optional> source = conversion::convert>( property.value, error, id); if (!source) { Log::Warning(Event::ParseStyle, error.message); continue; } sources.emplace_back(std::move(*source)); } } void Parser::parseSprites(const JSValue& value) { if (value.IsString()) { std::string url = {value.GetString(), value.GetStringLength()}; auto sprite = Sprite("default", url); sprites.emplace_back(sprite); } else if (value.IsArray()) { std::unordered_set spriteIds; for (auto& spriteValue : value.GetArray()) { if (!spriteValue.IsObject()) { Log::Warning(Event::ParseStyle, "sprite child must be an object"); continue; } conversion::Error error; std::optional sprite = conversion::convert(spriteValue, error); if (!sprite) { Log::Warning(Event::ParseStyle, error.message); continue; } if (spriteIds.find(sprite->id) != spriteIds.end()) { Log::Warning(Event::ParseStyle, "sprite ids must be unique"); continue; } spriteIds.insert(sprite->id); sprites.emplace_back(*sprite); } } else { Log::Warning(Event::ParseStyle, "sprite must be an object or string"); return; } } void Parser::parseLayers(const JSValue& value) { std::vector ids; if (!value.IsArray()) { Log::Warning(Event::ParseStyle, "layers must be an array"); return; } for (auto& layerValue : value.GetArray()) { if (!layerValue.IsObject()) { Log::Warning(Event::ParseStyle, "layer must be an object"); continue; } if (!layerValue.HasMember("id")) { Log::Warning(Event::ParseStyle, "layer must have an id"); continue; } const JSValue& id = layerValue.FindMember("id")->value; if (!id.IsString()) { Log::Warning(Event::ParseStyle, "layer id must be a string"); continue; } const std::string layerID = {id.GetString(), id.GetStringLength()}; if (layersMap.find(layerID) != layersMap.end()) { Log::Warning(Event::ParseStyle, "duplicate layer id " + layerID); continue; } layersMap.emplace(layerID, std::pair>{layerValue, nullptr}); ids.push_back(layerID); } for (const auto& id : ids) { auto it = layersMap.find(id); parseLayer(it->first, it->second.first, it->second.second); } for (const auto& id : ids) { auto it = layersMap.find(id); if (it->second.second) { layers.emplace_back(std::move(it->second.second)); } } } void Parser::parseLayer(const std::string& id, const JSValue& value, std::unique_ptr& layer) { if (layer) { // Skip parsing this again. We already have a valid layer definition. return; } // Make sure we have not previously attempted to parse this layer. if (std::find(stack.begin(), stack.end(), id) != stack.end()) { Log::Warning(Event::ParseStyle, "layer reference of '" + id + "' is circular"); return; } if (value.HasMember("ref")) { // This layer is referencing another layer. Recursively parse that layer. const JSValue& refVal = value["ref"]; if (!refVal.IsString()) { Log::Warning(Event::ParseStyle, "layer ref of '" + id + "' must be a string"); return; } const std::string ref{refVal.GetString(), refVal.GetStringLength()}; auto it = layersMap.find(ref); if (it == layersMap.end()) { Log::Warning(Event::ParseStyle, "layer '" + id + "' references unknown layer " + ref); return; } // Recursively parse the referenced layer. stack.push_front(id); parseLayer(it->first, it->second.first, it->second.second); stack.pop_front(); Layer* reference = it->second.second.get(); if (!reference) { return; } layer = reference->cloneRef(id); conversion::setPaintProperties(*layer, conversion::Convertible(&value)); } else { conversion::Error error; std::optional> converted = conversion::convert>(value, error); if (!converted) { Log::Warning(Event::ParseStyle, error.message); return; } layer = std::move(*converted); } } std::set Parser::fontStacks() const { std::vector> impls; impls.reserve(layers.size()); for (const auto& layer : layers) { impls.emplace_back(layer->baseImpl); } return mbgl::fontStacks(impls); } } // namespace style } // namespace mbgl