Files
MapLibreParticleLab/src/style/style_parser.cpp
2014-06-02 11:14:09 +02:00

765 lines
26 KiB
C++

#include <llmr/style/style_parser.hpp>
#include <llmr/util/constants.hpp>
#include <csscolorparser/csscolorparser.hpp>
using namespace llmr;
using JSVal = const rapidjson::Value&;
void StyleParser::parseBuckets(JSVal value, std::map<std::string, BucketDescription>& buckets) {
if (value.IsObject()) {
rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
for (; itr != value.MemberEnd(); ++itr) {
buckets.emplace(
std::forward<std::string>({ itr->name.GetString(), itr->name.GetStringLength() }),
std::forward<BucketDescription>(parseBucket(itr->value))
);
}
} else {
throw Style::exception("buckets must be an object");
}
}
BucketDescription StyleParser::parseBucket(JSVal value) {
BucketDescription bucket;
rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
for (; itr != value.MemberEnd(); ++itr) {
const std::string name(itr->name.GetString(), itr->name.GetStringLength());
JSVal value = itr->value;
if (name == "type") {
if (value.IsString()) {
bucket.type = bucketType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("bucket type must be a string");
}
} else if (name == "feature_type") {
if (value.IsString()) {
bucket.feature_type = bucketType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("feature type must be a string");
}
} else if (name == "source") {
if (value.IsString()) {
bucket.source_name = { value.GetString(), value.GetStringLength() };
} else {
throw Style::exception("source name must be a string");
}
} else if (name == "layer") {
if (value.IsString()) {
bucket.source_layer = { value.GetString(), value.GetStringLength() };
} else {
throw Style::exception("layer name must be a string");
}
} else if (name == "cap") {
if (value.IsString()) {
bucket.geometry.cap = capType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("cap type must be a string");
}
} else if (name == "join") {
if (value.IsString()) {
bucket.geometry.join = joinType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("join type must be a string");
}
} else if (name == "font") {
if (value.IsString()) {
bucket.geometry.font = { value.GetString(), value.GetStringLength() };
} else {
throw Style::exception("font stack must be a string");
}
} else if (name == "fontSize" || name == "size") {
if (value.IsNumber()) {
bucket.geometry.size = value.GetDouble();
} else {
throw Style::exception("font size must be a number");
}
} else if (name == "text_field" || name == "icon") {
if (value.IsString()) {
bucket.geometry.field = { value.GetString(), value.GetStringLength() };
} else {
throw Style::exception("text field must be a string");
}
} else if (name == "path") {
if (value.IsString()) {
bucket.geometry.path = textPathType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("curve must be a string");
}
} else if (name == "alignment") {
if (value.IsString()) {
bucket.geometry.alignment = alignmentType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("alignment must be a string");
}
} else if (name == "translate") {
if (value.IsArray()) {
bucket.geometry.translate.x = value[(rapidjson::SizeType)0].GetDouble() * 24;
bucket.geometry.translate.y = value[(rapidjson::SizeType)1].GetDouble() * -24;
} else {
throw Style::exception("translate must be a string");
}
} else if (name == "verticalAlignment") {
if (value.IsString()) {
bucket.geometry.vertical_alignment = verticalAlignmentType({ value.GetString(), value.GetStringLength() });
} else {
throw Style::exception("verticalAlignment must be a string");
}
} else if (name == "lineHeight") {
if (value.IsNumber()) {
bucket.geometry.line_height = value.GetDouble() * 24;
} else {
throw Style::exception("line height must be a number");
}
} else if (name == "maxWidth") {
if (value.IsNumber()) {
bucket.geometry.max_width = value.GetDouble() * 24;
} else {
throw Style::exception("max width must be a number");
}
} else if (name == "letterSpacing") {
if (value.IsNumber()) {
bucket.geometry.letter_spacing = value.GetDouble() * 24;
} else {
throw Style::exception("letter spacing must be a number");
}
} else if (name == "miterLimit") {
if (value.IsNumber()) {
bucket.geometry.miter_limit = value.GetDouble();
} else {
throw Style::exception("miter limit must be a number");
}
} else if (name == "roundLimit") {
if (value.IsNumber()) {
bucket.geometry.round_limit = value.GetDouble();
} else {
throw Style::exception("round limit must be a number");
}
} else if (name == "textMinDistance") {
if (value.IsNumber()) {
bucket.geometry.textMinDistance = value.GetDouble();
} else {
throw Style::exception("text min distance must be a number");
}
} else if (name == "maxAngleDelta") {
if (value.IsNumber()) {
bucket.geometry.maxAngleDelta = value.GetDouble();
} else {
throw Style::exception("max angle delta must be a number");
}
}
}
if (value.HasMember("field") && value.HasMember("value")) {
JSVal field = value["field"];
JSVal val = value["value"];
if (!field.IsString()) {
throw Style::exception("field name must be a string");
}
const std::string field_name { field.GetString(), field.GetStringLength() };
bucket.filter = {};
if (val.IsArray()) {
for (rapidjson::SizeType i = 0; i < val.Size(); ++i) {
bucket.filter.operands.emplace_back(field_name, parseValue(val[i]));
}
} else {
bucket.filter.operands.emplace_back(field_name, parseValue(val));
}
}
if (bucket.feature_type == BucketType::None) {
bucket.feature_type = bucket.type;
}
return bucket;
}
void StyleParser::parseLayers(JSVal value, std::vector<LayerDescription>& layers) {
if (value.IsArray()) {
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
layers.push_back(parseLayer(value[i]));
}
} else {
throw Style::exception("structure must be an array");
}
}
LayerDescription StyleParser::parseLayer(JSVal value) {
LayerDescription layer;
if (value.IsObject()) {
if (value.HasMember("name")) {
JSVal name = value["name"];
if (name.IsString()) {
layer.name = { name.GetString(), name.GetStringLength() };
} else {
throw Style::exception("structure element name must be a string");
}
} else {
throw Style::exception("structure element must have a name");
}
if (value.HasMember("bucket")) {
JSVal bucket = value["bucket"];
if (bucket.IsString()) {
layer.bucket_name = { bucket.GetString(), bucket.GetStringLength() };
} else {
throw Style::exception("structure element bucket must be a string");
}
} else if (value.HasMember("layers")) {
parseLayers(value["layers"], layer.child_layer);
} else {
throw Style::exception("structure element must have either a bucket name or child layers");
}
} else {
throw Style::exception("structure element must be an object");
}
return layer;
}
void StyleParser::parseConstants(JSVal value) {
if (value.IsObject()) {
rapidjson::Value::ConstMemberIterator itr = value.MemberBegin();
for (; itr != value.MemberEnd(); ++itr) {
constants.emplace(
std::forward<std::string>({ itr->name.GetString(), itr->name.GetStringLength() }),
&itr->value
);
}
} else {
throw Style::exception("constants must be an object");
}
}
TranslateAnchor parseTranslateAnchor(JSVal anchor) {
if (anchor.IsString()) {
std::string a { anchor.GetString(), anchor.GetStringLength() };
if (a == "viewport") {
return TranslateAnchor::Viewport;
} else {
return TranslateAnchor::Map;
}
} else {
throw Style::exception("translate anchor must be a string");
}
}
void collectLayerBuckets(std::map<std::string, std::string> &layerBuckets,
const std::vector<LayerDescription> &layers) {
for (const auto& layer : layers) {
layerBuckets.emplace(layer.name, layer.bucket_name);
if (layer.child_layer.size()) {
collectLayerBuckets(layerBuckets, layer.child_layer);
}
}
}
void StyleParser::parseClasses(JSVal value,
std::map<std::string, ClassDescription> &classes,
const std::map<std::string, BucketDescription> &buckets,
const std::vector<LayerDescription> &layers) {
// Build a non-recursive mapping of layer => bucket.
std::map<std::string, std::string> layerBuckets;
collectLayerBuckets(layerBuckets, layers);
if (value.IsArray()) {
for (rapidjson::SizeType i = 0; i < value.Size(); ++i) {
classes.emplace(parseClassDescription(value[i], buckets, layerBuckets));
}
} else {
throw Style::exception("classes must be an array");
}
}
std::pair<std::string, ClassDescription>
StyleParser::parseClassDescription(JSVal value,
const std::map<std::string, BucketDescription> &buckets,
const std::map<std::string, std::string> &layerBuckets) {
ClassDescription klass;
std::string klass_name;
if (value.IsObject()) {
if (value.HasMember("name")) {
JSVal name = value["name"];
if (name.IsString()) {
klass_name = { name.GetString(), name.GetStringLength() };
} else {
throw Style::exception("class name must be a string");
}
} else {
throw Style::exception("class must have a name");
}
if (value.HasMember("layers")) {
JSVal layers = value["layers"];
if (layers.IsObject()) {
rapidjson::Value::ConstMemberIterator itr = layers.MemberBegin();
for (; itr != layers.MemberEnd(); ++itr) {
const std::string name {
itr->name.GetString(), itr->name.GetStringLength()
};
parseClass(name, itr->value, klass, buckets, layerBuckets);
}
} else {
throw Style::exception("class layer styles must be an object");
}
} else {
throw Style::exception("class must have layer styles");
}
}
return { klass_name, std::forward<ClassDescription>(klass) };
}
void StyleParser::parseClass(const std::string &name, JSVal value, ClassDescription &class_desc,
const std::map<std::string, BucketDescription> &buckets,
const std::map<std::string, std::string> &layerBuckets) {
if (!value.IsObject()) {
throw Style::exception("style class must be an object");
}
auto layer_bucket_it = layerBuckets.find(name);
if (layer_bucket_it == layerBuckets.end()) {
if (debug::styleParseWarnings) {
fprintf(stderr, "[WARNING] there is no layer associated with '%s'\n", name.c_str());
}
return;
}
const std::string &bucket_name = layer_bucket_it->second;
if (bucket_name == "background") {
// background buckets are fake
class_desc.background = parseBackgroundClass(value);
} else if (bucket_name.length() == 0) {
// no bucket name == composite bucket.
class_desc.composite.insert({ name, std::forward<CompositeClass>(parseCompositeClass(value)) });
} else {
auto bucket_it = buckets.find(bucket_name);
if (bucket_it == buckets.end()) {
if (debug::styleParseWarnings) {
fprintf(stderr, "[WARNING] there is no bucket named '%s'\n", bucket_name.c_str());
}
return;
}
BucketType type = bucket_it->second.type;
if (type == BucketType::Fill) {
class_desc.fill.insert({ name, std::forward<FillClass>(parseFillClass(value)) });
} else if (type == BucketType::Line) {
class_desc.line.insert({ name, std::forward<LineClass>(parseLineClass(value)) });
} else if (type == BucketType::Icon) {
class_desc.icon.insert({ name, std::forward<IconClass>(parseIconClass(value)) });
} else if (type == BucketType::Text) {
class_desc.text.insert({ name, std::forward<TextClass>(parseTextClass(value)) });
} else if (type == BucketType::Raster) {
class_desc.raster.insert({ name, std::forward<RasterClass>(parseRasterClass(value)) });
} else {
throw Style::exception("unknown class type name");
}
}
}
bool StyleParser::parseBoolean(JSVal value) {
if (!value.IsBool()) {
throw Style::exception("boolean value must be a boolean");
}
return value.GetBool();
}
std::string StyleParser::parseString(JSVal value) {
if (!value.IsString()) {
throw Style::exception("string value must be a string");
}
return { value.GetString(), value.GetStringLength() };
}
JSVal StyleParser::replaceConstant(JSVal value) {
if (value.IsString()) {
const std::string string_value { value.GetString(), value.GetStringLength() };
auto it = constants.find(string_value);
if (it != constants.end()) {
return *it->second;
}
}
return value;
}
std::vector<FunctionProperty> StyleParser::parseArray(JSVal value, uint16_t expected_count) {
JSVal rvalue = replaceConstant(value);
if (!rvalue.IsArray()) {
throw Style::exception("array value must be an array");
}
if (rvalue.IsArray() && rvalue.Size() != expected_count) {
throw Style::exception("array value has unexpected number of elements");
}
std::vector<FunctionProperty> values;
values.reserve(expected_count);
for (uint16_t i = 0; i < expected_count; i++) {
values.push_back(parseFunction(rvalue[(rapidjson::SizeType)i]));
}
return values;
}
Color StyleParser::parseColor(JSVal value) {
JSVal rvalue = replaceConstant(value);
if (rvalue.IsArray()) {
// [ r, g, b, a] array
if (rvalue.Size() != 4) {
throw Style::exception("color array must have four elements");
}
JSVal r = rvalue[(rapidjson::SizeType)0], g = rvalue[1], b = rvalue[2], a = rvalue[3];
if (!r.IsNumber() || !g.IsNumber() || !b.IsNumber() || !a.IsNumber()) {
throw Style::exception("color values must be numbers");
}
// Premultiply the color.
const double alpha = a.GetDouble();
return {{static_cast<float>(alpha * r.GetDouble()),
static_cast<float>(alpha * g.GetDouble()),
static_cast<float>(alpha * b.GetDouble()),
static_cast<float>(alpha)}};
} else if (!rvalue.IsString()) {
throw Style::exception("color value must be a string");
}
CSSColorParser::Color css_color = CSSColorParser::parse({ rvalue.GetString(), rvalue.GetStringLength() });
// Premultiply the color.
const float factor = css_color.a / 255;
return {{(float)css_color.r * factor,
(float)css_color.g * factor,
(float)css_color.b * factor,
css_color.a}};
}
FunctionProperty::fn StyleParser::parseFunctionType(JSVal type) {
if (type.IsString()) {
std::string t { type.GetString(), type.GetStringLength() };
if (t == "constant") {
return &functions::constant;
} else if (t == "linear") {
return &functions::linear;
} else if (t == "stops") {
return &functions::stops;
} else if (t == "exponential") {
return &functions::exponential;
} else if (t == "min") {
return &functions::min;
} else if (t == "max") {
return &functions::max;
} else {
throw Style::exception("unknown function type");
}
} else {
throw Style::exception("function type must be a string");
}
}
FunctionProperty StyleParser::parseFunction(JSVal value) {
JSVal rvalue = replaceConstant(value);
FunctionProperty property;
if (rvalue.IsArray()) {
if (rvalue.Size() < 1) {
throw Style::exception("value function does not have arguments");
}
property.function = parseFunctionType(rvalue[(rapidjson::SizeType)0]);
for (rapidjson::SizeType i = 1; i < rvalue.Size(); ++i) {
JSVal stop = rvalue[i];
if (stop.IsObject()) {
if (!stop.HasMember("z")) {
throw Style::exception("stop must have zoom level specification");
}
JSVal z = stop["z"];
if (!z.IsNumber()) {
throw Style::exception("zoom level in stops must be a number");
}
property.values.push_back(z.GetDouble());
if (!stop.HasMember("val")) {
throw Style::exception("stop must have value specification");
}
JSVal val = stop["val"];
if (!val.IsNumber()) {
throw Style::exception("value in stops must be a number");
}
property.values.push_back(val.GetDouble());
} else if (stop.IsNumber()) {
property.values.push_back(stop.GetDouble());
} else if (stop.IsBool()) {
property.values.push_back(stop.GetBool());
} else {
throw Style::exception("function argument must be a numeric value");
}
}
} else if (rvalue.IsNumber()) {
property.function = &functions::constant;
property.values.push_back(rvalue.GetDouble());
}
return property;
}
boost::optional<PropertyTransition> StyleParser::parseTransition(JSVal value, std::string property_name) {
uint16_t duration = 0, delay = 0;
std::string transition_property = std::string("transition-").append(property_name);
if (value.HasMember(transition_property.c_str())) {
JSVal elements = value[transition_property.c_str()];
if (elements.IsObject()) {
if (elements.HasMember("duration") && elements["duration"].IsNumber()) {
duration = elements["duration"].GetUint();
}
if (elements.HasMember("delay") && elements["delay"].IsNumber()) {
delay = elements["delay"].GetUint();
}
}
}
if (duration || delay) {
return boost::optional<PropertyTransition>(PropertyTransition { duration, delay });
} else {
return boost::optional<PropertyTransition>();
}
}
void StyleParser::parseGenericClass(GenericClass &klass, JSVal value) {
if (value.HasMember("enabled")) {
klass.enabled = parseFunction(value["enabled"]);
}
if (value.HasMember("translate")) {
std::vector<FunctionProperty> values = parseArray(value["translate"], 2);
klass.translate = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.translate_transition = parseTransition(value, "translate");
}
if (value.HasMember("translate-anchor")) {
klass.translateAnchor = parseTranslateAnchor(value["translate-anchor"]);
}
if (value.HasMember("opacity")) {
klass.opacity = parseFunction(value["opacity"]);
klass.opacity_transition = parseTransition(value, "opacity");
}
if (value.HasMember("prerender")) {
klass.prerender = parseFunction(value["prerender"]);
}
if (value.HasMember("prerender-buffer")) {
klass.prerenderBuffer = parseFunction(value["prerender-buffer"]);
}
if (value.HasMember("prerender-size")) {
klass.prerenderSize = parseFunction(value["prerender-size"]);
}
if (value.HasMember("prerender-blur")) {
klass.prerenderBlur = parseFunction(value["prerender-blur"]);
}
}
FillClass StyleParser::parseFillClass(JSVal value) {
FillClass klass;
parseGenericClass(klass, value);
if (value.HasMember("winding")) {
throw std::runtime_error("winding in stylesheets not yet supported");
}
if (value.HasMember("color")) {
klass.fill_color = parseColor(value["color"]);
klass.fill_color_transition = parseTransition(value, "color");
}
if (value.HasMember("stroke")) {
klass.stroke_color = parseColor(value["stroke"]);
klass.stroke_color_transition = parseTransition(value, "stroke");
} else {
klass.stroke_color = klass.fill_color;
}
if (value.HasMember("antialias")) {
klass.antialias = parseBoolean(value["antialias"]);
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
}
return klass;
}
LineClass StyleParser::parseLineClass(JSVal value) {
LineClass klass;
parseGenericClass(klass, value);
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
}
if (value.HasMember("width")) {
klass.width = parseFunction(value["width"]);
klass.width_transition = parseTransition(value, "width");
}
if (value.HasMember("dasharray")) {
std::vector<FunctionProperty> values = parseArray(value["dasharray"], 2);
klass.dash_array = std::array<FunctionProperty, 2> {{ values[0], values[1] }};
klass.dash_array_transition = parseTransition(value, "dasharray");
}
return klass;
}
IconClass StyleParser::parseIconClass(JSVal value) {
IconClass klass;
parseGenericClass(klass, value);
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
}
if (value.HasMember("image")) {
klass.image = parseString(value["image"]);
}
if (value.HasMember("size")) {
klass.size = parseFunction(value["size"]);
}
if (value.HasMember("radius")) {
klass.radius = parseFunction(value["radius"]);
klass.radius_transition = parseTransition(value, "radius");
}
if (value.HasMember("blur")) {
klass.blur = parseFunction(value["blur"]);
klass.blur_transition = parseTransition(value, "blur");
}
return klass;
}
TextClass StyleParser::parseTextClass(JSVal value) {
TextClass klass;
parseGenericClass(klass, value);
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
}
if (value.HasMember("stroke")) {
klass.halo = parseColor(value["stroke"]);
klass.halo_transition = parseTransition(value, "stroke");
}
if (value.HasMember("strokeWidth")) {
klass.halo_radius = parseFunction(value["strokeWidth"]);
klass.halo_radius_transition = parseTransition(value, "strokeWidth");
}
if (value.HasMember("strokeBlur")) {
klass.halo_blur = parseFunction(value["strokeBlur"]);
klass.halo_blur_transition = parseTransition(value, "strokeBlur");
}
if (value.HasMember("size")) {
klass.size = parseFunction(value["size"]);
}
if (value.HasMember("rotate")) {
klass.rotate = parseFunction(value["rotate"]);
}
if (value.HasMember("alwaysVisible")) {
klass.always_visible = parseFunction(value["alwaysVisible"]);
}
return klass;
}
RasterClass StyleParser::parseRasterClass(JSVal value) {
RasterClass klass;
parseGenericClass(klass, value);
return klass;
}
CompositeClass StyleParser::parseCompositeClass(JSVal value) {
CompositeClass klass;
parseGenericClass(klass, value);
return klass;
}
BackgroundClass StyleParser::parseBackgroundClass(JSVal value) {
BackgroundClass klass;
parseGenericClass(klass, value);
if (value.HasMember("color")) {
klass.color = parseColor(value["color"]);
klass.color_transition = parseTransition(value, "color");
}
return klass;
}
Value StyleParser::parseValue(JSVal value) {
switch (value.GetType()) {
case rapidjson::kNullType:
case rapidjson::kFalseType:
return false;
case rapidjson::kTrueType:
return true;
case rapidjson::kStringType:
return std::string { value.GetString(), value.GetStringLength() };
case rapidjson::kNumberType:
if (value.IsUint64()) return value.GetUint64();
if (value.IsInt64()) return value.GetInt64();
return value.GetDouble();
case rapidjson::kObjectType:
case rapidjson::kArrayType:
throw Style::exception("value cannot be an object or array");
return false;
}
throw Style::exception("unhandled value type in style");
return false;
}