#include "node_conversion.hpp" #include "node_expression.hpp" #include "node_feature.hpp" #include #include #include #include #include #include using namespace mbgl::style; using namespace mbgl::style::expression; namespace node_mbgl { Nan::Persistent NodeExpression::constructor; void NodeExpression::Init(v8::Local target) { #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 93 v8::Local context = target->CreationContext(); #else v8::Local context = target->GetCreationContext().ToLocalChecked(); #endif v8::Local tpl = Nan::New(New); tpl->SetClassName(Nan::New("Expression").ToLocalChecked()); tpl->InstanceTemplate()->SetInternalFieldCount(1); // what is this doing? Nan::SetPrototypeMethod(tpl, "evaluate", Evaluate); Nan::SetPrototypeMethod(tpl, "getType", GetType); Nan::SetPrototypeMethod(tpl, "isFeatureConstant", IsFeatureConstant); Nan::SetPrototypeMethod(tpl, "isZoomConstant", IsZoomConstant); Nan::SetPrototypeMethod(tpl, "serialize", Serialize); Nan::SetMethod(tpl, "parse", Parse); constructor.Reset(tpl->GetFunction(context).ToLocalChecked()); // what is this doing? Nan::Set(target, Nan::New("Expression").ToLocalChecked(), tpl->GetFunction(context).ToLocalChecked()); } type::Type parseType(v8::Local type) { static std::unordered_map types = { {"string", type::String}, {"number", type::Number}, {"boolean", type::Boolean}, {"object", type::Object}, {"color", type::Color}, {"padding", type::Padding}, {"value", type::Value}, {"formatted", type::Formatted}, {"number-format", type::String}, {"resolvedImage", type::Image}, {"variableAnchorOffsetCollection", type::VariableAnchorOffsetCollection}, {"numberArray", type::Array(type::Number)}, {"colorArray", type::Array(type::Color)}}; v8::Local v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked(); std::string kind(*v8::String::Utf8Value(v8::Isolate::GetCurrent(), v8kind)); if (kind == "array") { #if defined NODE_MODULE_VERSION && NODE_MODULE_VERSION < 93 v8::Local context = type->CreationContext(); #else v8::Local context = type->GetCreationContext().ToLocalChecked(); #endif type::Type itemType = parseType( Nan::Get(type, Nan::New("itemType").ToLocalChecked()).ToLocalChecked()->ToObject(context).ToLocalChecked()); std::optional N; v8::Local Nkey = Nan::New("N").ToLocalChecked(); if (Nan::Has(type, Nkey).FromMaybe(false)) { N = Nan::To(Nan::Get(type, Nkey).ToLocalChecked()).ToLocalChecked()->Value(); } return type::Array(itemType, N); } return types[kind]; } void NodeExpression::Parse(const Nan::FunctionCallbackInfo& info) { v8::Local cons = Nan::New(constructor); v8::Local context = info.GetIsolate()->GetCurrentContext(); if (info.Length() < 1 || info[0]->IsUndefined()) { return Nan::ThrowTypeError("Requires a JSON style expression argument."); } std::optional expected; if (info.Length() > 1 && info[1]->IsObject()) { expected = parseType(info[1]->ToObject(context).ToLocalChecked()); } auto success = [&cons, &info](std::unique_ptr result) { auto nodeExpr = new NodeExpression(std::move(result)); auto wrapped = Nan::NewInstance(cons).ToLocalChecked(); nodeExpr->Wrap(wrapped); info.GetReturnValue().Set(wrapped); }; auto fail = [&info](const std::vector& errors) { v8::Local result = Nan::New(); for (std::size_t i = 0; i < errors.size(); ++i) { const auto& error = errors[i]; v8::Local err = Nan::New(); Nan::Set(err, Nan::New("key").ToLocalChecked(), Nan::New(error.key.c_str()).ToLocalChecked()); Nan::Set(err, Nan::New("error").ToLocalChecked(), Nan::New(error.message.c_str()).ToLocalChecked()); Nan::Set(result, Nan::New(static_cast(i)), err); } info.GetReturnValue().Set(result); }; auto expr = info[0]; try { mbgl::style::conversion::Convertible convertible(expr); if (expr->IsObject() && !expr->IsArray() && expected) { mbgl::style::conversion::Error error; auto func = convertFunctionToExpression(*expected, convertible, error, false); if (func) { return success(std::move(*func)); } return fail({{error.message, ""}}); } ParsingContext ctx = expected ? ParsingContext(*expected) : ParsingContext(); ParseResult parsed = ctx.parseLayerPropertyExpression(mbgl::style::conversion::Convertible(expr)); if (parsed) { assert(ctx.getErrors().empty()); return success(std::move(*parsed)); } return fail(ctx.getErrors()); } catch (std::exception& ex) { return Nan::ThrowError(ex.what()); } } void NodeExpression::New(const Nan::FunctionCallbackInfo& info) { if (!info.IsConstructCall()) { return Nan::ThrowTypeError("Use the new operator to create new Expression objects"); } info.GetReturnValue().Set(info.This()); } struct ToValue { v8::Local operator()(mbgl::NullValue) { Nan::EscapableHandleScope scope; return scope.Escape(Nan::Null()); } v8::Local operator()(bool t) { Nan::EscapableHandleScope scope; return scope.Escape(Nan::New(t)); } v8::Local operator()(double t) { Nan::EscapableHandleScope scope; return scope.Escape(Nan::New(t)); } v8::Local operator()(const std::string& t) { Nan::EscapableHandleScope scope; return scope.Escape(Nan::New(t).ToLocalChecked()); } v8::Local operator()(const std::vector& array) { Nan::EscapableHandleScope scope; v8::Local result = Nan::New(); for (std::uint32_t i = 0; i < array.size(); i++) { Nan::Set(result, i, toJS(array[i])); } return scope.Escape(result); } v8::Local operator()(const Collator&) { // Collators are excluded from constant folding and there's no Literal parser // for them so there shouldn't be any way to serialize this value. assert(false); Nan::EscapableHandleScope scope; return scope.Escape(Nan::Null()); } v8::Local operator()(const Formatted& formatted) { // This mimics the internal structure of the Formatted class in formatted.js // A better approach might be to use the explicit serialized form // both here and on the JS side? e.g. toJS(fromExpressionValue(formatted)) std::unordered_map serialized; std::vector sections; for (const auto& section : formatted.sections) { std::unordered_map serializedSection; serializedSection.emplace("text", section.text); if (section.fontScale) { serializedSection.emplace("scale", *section.fontScale); } else { serializedSection.emplace("scale", mbgl::NullValue()); } if (section.fontStack) { std::string fontStackString; serializedSection.emplace("fontStack", mbgl::fontStackToString(*section.fontStack)); } else { serializedSection.emplace("fontStack", mbgl::NullValue()); } if (section.textColor) { serializedSection.emplace("textColor", section.textColor->toObject()); } else { serializedSection.emplace("textColor", mbgl::NullValue()); } sections.emplace_back(serializedSection); } serialized.emplace("sections", sections); return toJS(serialized); } v8::Local operator()(const mbgl::Color& color) { return operator()(std::vector{static_cast(color.r), static_cast(color.g), static_cast(color.b), static_cast(color.a)}); } v8::Local operator()(const mbgl::Padding& padding) { return operator()(std::vector{static_cast(padding.top), static_cast(padding.right), static_cast(padding.bottom), static_cast(padding.left)}); } v8::Local operator()(const std::unordered_map& map) { Nan::EscapableHandleScope scope; v8::Local result = Nan::New(); for (const auto& entry : map) { Nan::Set(result, Nan::New(entry.first).ToLocalChecked(), toJS(entry.second)); } return scope.Escape(result); } v8::Local operator()(const Image& image) { return toJS(image.toValue()); } v8::Local operator()(const mbgl::VariableAnchorOffsetCollection& variableAnchorOffsets) { std::vector components; components.reserve(variableAnchorOffsets.size() * 2); for (const auto& variableAnchorOffset : variableAnchorOffsets) { components.emplace_back( std::string(mbgl::Enum::toString(variableAnchorOffset.anchorType))); components.emplace_back(std::vector{static_cast(variableAnchorOffset.offset[0]), static_cast(variableAnchorOffset.offset[1])}); } return operator()(components); } }; v8::Local toJS(const Value& value) { return Value::visit(value, ToValue()); } void NodeExpression::Evaluate(const Nan::FunctionCallbackInfo& info) { v8::Local context = info.GetIsolate()->GetCurrentContext(); auto* nodeExpr = ObjectWrap::Unwrap(info.Holder()); const std::unique_ptr& expression = nodeExpr->expression; if (info.Length() < 2 || !info[0]->IsObject()) { return Nan::ThrowTypeError("Requires globals and feature arguments."); } std::optional zoom; v8::Local v8zoom = Nan::Get(info[0]->ToObject(context).ToLocalChecked(), Nan::New("zoom").ToLocalChecked()).ToLocalChecked(); if (v8zoom->IsNumber()) zoom = static_cast(Nan::To(v8zoom).FromJust()); std::optional heatmapDensity; v8::Local v8heatmapDensity = Nan::Get(info[0]->ToObject(context).ToLocalChecked(), Nan::New("heatmapDensity").ToLocalChecked()) .ToLocalChecked(); if (v8heatmapDensity->IsNumber()) heatmapDensity = Nan::To(v8heatmapDensity).FromJust(); Nan::JSON NanJSON; conversion::Error conversionError; std::optional geoJSON = conversion::convert(info[1], conversionError); if (!geoJSON) { Nan::ThrowTypeError(conversionError.message.c_str()); return; } try { mapbox::geojson::feature feature = geoJSON->get(); auto result = expression->evaluate(zoom, feature, heatmapDensity); if (result) { info.GetReturnValue().Set(toJS(*result)); } else { v8::Local res = Nan::New(); Nan::Set( res, Nan::New("error").ToLocalChecked(), Nan::New(result.error().message.c_str()).ToLocalChecked()); info.GetReturnValue().Set(res); } } catch (std::exception& ex) { return Nan::ThrowTypeError(ex.what()); } } void NodeExpression::GetType(const Nan::FunctionCallbackInfo& info) { auto* nodeExpr = ObjectWrap::Unwrap(info.Holder()); const std::unique_ptr& expression = nodeExpr->expression; const type::Type type = expression->getType(); const std::string name = type.match([&](const auto& t) { return t.getName(); }); info.GetReturnValue().Set(Nan::New(name.c_str()).ToLocalChecked()); } void NodeExpression::IsFeatureConstant(const Nan::FunctionCallbackInfo& info) { auto* nodeExpr = ObjectWrap::Unwrap(info.Holder()); const std::unique_ptr& expression = nodeExpr->expression; info.GetReturnValue().Set(Nan::New(isFeatureConstant(*expression))); } void NodeExpression::IsZoomConstant(const Nan::FunctionCallbackInfo& info) { auto* nodeExpr = ObjectWrap::Unwrap(info.Holder()); const std::unique_ptr& expression = nodeExpr->expression; info.GetReturnValue().Set(Nan::New(isZoomConstant(*expression))); } void NodeExpression::Serialize(const Nan::FunctionCallbackInfo& info) { auto* nodeExpr = ObjectWrap::Unwrap(info.Holder()); const std::unique_ptr& expression = nodeExpr->expression; const mbgl::Value serialized = expression->serialize(); info.GetReturnValue().Set(toJS(serialized)); } } // namespace node_mbgl