Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

Commit

Permalink
[core] Add number-format expression
Browse files Browse the repository at this point in the history
  • Loading branch information
Juha Alanen committed Jun 27, 2019
1 parent 98a5c64 commit 50bdbea
Show file tree
Hide file tree
Showing 25 changed files with 533 additions and 7 deletions.
7 changes: 6 additions & 1 deletion cmake/core.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@ target_link_libraries(mbgl-core PRIVATE
wagyu
)

# linux uses ICU from mason, other platforms use vendored ICU
if(NOT MBGL_PLATFORM STREQUAL "linux")
set(ICU_LIBRARY "icu")
endif()

# FIXME: We should not leak these many
# libraries in our public interface.
target_link_libraries(mbgl-core PUBLIC
boost
geojson.hpp
geometry.hpp
icu
${ICU_LIBRARY}
optional
polylabel
protozero
Expand Down
1 change: 1 addition & 0 deletions cmake/mason-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ elseif(MBGL_PLATFORM STREQUAL "linux")
mason_use(libuv VERSION 1.9.1)
mason_use(libpng VERSION 1.6.25)
mason_use(libjpeg-turbo VERSION 1.5.0)
mason_use(icu VERSION 63.1-min-static-data)

if(WITH_EGL)
mason_use(swiftshader VERSION 2018-05-31)
Expand Down
3 changes: 2 additions & 1 deletion include/mbgl/style/expression/expression.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ enum class Kind : int32_t {
All,
Comparison,
FormatExpression,
FormatSectionOverride
FormatSectionOverride,
NumberFormat
};

class Expression {
Expand Down
40 changes: 40 additions & 0 deletions include/mbgl/style/expression/number_format.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include <mbgl/style/expression/expression.hpp>
#include <mbgl/style/expression/parsing_context.hpp>

namespace mbgl {
namespace style {
namespace expression {

class NumberFormat final : public Expression {
public:
NumberFormat(std::unique_ptr<Expression> number_,
std::unique_ptr<Expression> locale_,
std::unique_ptr<Expression> currency_,
std::unique_ptr<Expression> minFractionDigits_,
std::unique_ptr<Expression> maxFractionDigits_);

~NumberFormat();

static ParseResult parse(const mbgl::style::conversion::Convertible& value, ParsingContext& ctx);

EvaluationResult evaluate(const EvaluationContext& params) const override;
void eachChild(const std::function<void(const Expression&)>& visit) const override;
bool operator==(const Expression& e) const override;
std::vector<optional<Value>> possibleOutputs() const override;

mbgl::Value serialize() const override;
std::string getOperator() const override { return "number-format"; }

private:
std::unique_ptr<Expression> number;
std::unique_ptr<Expression> locale;
std::unique_ptr<Expression> currency;
std::unique_ptr<Expression> minFractionDigits;
std::unique_ptr<Expression> maxFractionDigits;
};

} // namespace expression
} // namespace style
} // namespace mbgl
3 changes: 3 additions & 0 deletions include/mbgl/util/platform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ std::string lowercase(const std::string &string);
// Gets the name of the current thread.
std::string getCurrentThreadName();

std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits);

// Set the name of the current thread, truncated at 15.
void setCurrentThreadName(const std::string& name);

Expand Down
1 change: 1 addition & 0 deletions platform/android/core-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"platform/android/src/style/value.cpp",
"platform/android/src/text/collator.cpp",
"platform/android/src/text/local_glyph_rasterizer.cpp",
"platform/android/src/text/format_number.cpp",
"platform/android/src/gl_functions.cpp",
"platform/android/src/thread.cpp",
"platform/android/src/timer.cpp",
Expand Down
2 changes: 2 additions & 0 deletions platform/android/src/jni.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#endif
#include "text/collator_jni.hpp"
#include "text/local_glyph_rasterizer_jni.hpp"
#include "text/format_number_jni.hpp"
#include "logger.hpp"

namespace mbgl {
Expand Down Expand Up @@ -200,6 +201,7 @@ void registerNatives(JavaVM *vm) {
Locale::registerNative(env);
Collator::registerNative(env);
StringUtils::registerNative(env);
NumberFormat::registerNative(env);

// Logger
Logger::registerNative(env);
Expand Down
81 changes: 81 additions & 0 deletions platform/android/src/text/format_number.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#include <mbgl/style/expression/collator.hpp>
#include <mbgl/text/language_tag.hpp>
#include <mbgl/util/platform.hpp>

#include <jni/jni.hpp>

#include "../attach_env.hpp"
#include "format_number_jni.hpp"

namespace mbgl {
namespace android {

void NumberFormat::registerNative(jni::JNIEnv& env) {
jni::Class<NumberFormat>::Singleton(env);
}

jni::Local<jni::Object<NumberFormat>> NumberFormat::getInstance(jni::JNIEnv& env, const jni::Object<Locale>& locale) {
static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
static auto method = javaClass.GetStaticMethod<jni::Object<NumberFormat> (jni::Object<Locale>)>(env, "getInstance");
return javaClass.Call(env, method, locale);
}

jni::Local<jni::Object<NumberFormat>> NumberFormat::getCurrencyInstance(jni::JNIEnv& env, const jni::Object<Locale>& locale) {
static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
static auto method = javaClass.GetStaticMethod<jni::Object<NumberFormat> (jni::Object<Locale>)>(env, "getCurrencyInstance");
return javaClass.Call(env, method, locale);
}

jni::Local<jni::String> NumberFormat::format(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jdouble number) {
static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
static auto method = javaClass.GetMethod<jni::String (jni::jdouble)>(env, "format");
return nf.Call(env, method, number);
}

void NumberFormat::setMinimumFractionDigits(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jint value) {
static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
static auto method = javaClass.GetMethod<void (jni::jint)>(env, "setMinimumFractionDigits");
return nf.Call(env, method, value);
}

void NumberFormat::setMaximumFractionDigits(jni::JNIEnv& env, const jni::Object<NumberFormat>& nf, jni::jint value) {
static auto& javaClass = jni::Class<NumberFormat>::Singleton(env);
static auto method = javaClass.GetMethod<void (jni::jint)>(env, "setMaximumFractionDigits");
return nf.Call(env, method, value);
}

} // namespace android

namespace platform {

std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits) {

auto env{ android::AttachEnv() };

jni::Global<jni::Object<android::Locale>> locale;
LanguageTag languageTag = !localeId.empty() ? LanguageTag::fromBCP47(localeId) : LanguageTag();
if (!languageTag.language) {
locale = jni::NewGlobal(*env, android::Locale::getDefault(*env));
} else if (!languageTag.region) {
locale = jni::NewGlobal(*env, android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language)));
} else {
locale = jni::NewGlobal(*env, android::Locale::New(*env, jni::Make<jni::String>(*env, *languageTag.language),
jni::Make<jni::String>(*env, *languageTag.region)));
}

jni::Global<jni::Object<android::NumberFormat>> formatter;
if (currency.empty()) {
formatter = jni::NewGlobal(*env, android::NumberFormat::getInstance(*env, locale));
android::NumberFormat::setMinimumFractionDigits(*env, formatter, static_cast<jni::jint>(minFractionDigits));
android::NumberFormat::setMaximumFractionDigits(*env, formatter, static_cast<jni::jint>(maxFractionDigits));
} else {
formatter = jni::NewGlobal(*env, android::NumberFormat::getCurrencyInstance(*env, locale));
}

auto result = android::NumberFormat::format(*env, formatter, static_cast<jni::jdouble>(number));
return jni::Make<std::string>(*env, result);
}

} // namespace platform
} // namespace mbgl
29 changes: 29 additions & 0 deletions platform/android/src/text/format_number_jni.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#pragma once

#include <jni/jni.hpp>

#include "collator_jni.hpp"

/*
android::NumberFormat is the JNI wrapper
of java/text/NumberFormat.
*/

namespace mbgl {
namespace android {

class NumberFormat {
public:
static constexpr auto Name() { return "java/text/NumberFormat"; };

static jni::Local<jni::Object<NumberFormat>> getInstance(jni::JNIEnv&, const jni::Object<Locale>&);
static jni::Local<jni::Object<NumberFormat>> getCurrencyInstance(jni::JNIEnv&, const jni::Object<Locale>&);
static jni::Local<jni::String> format(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jdouble);
static void setMinimumFractionDigits(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jint);
static void setMaximumFractionDigits(jni::JNIEnv&, const jni::Object<NumberFormat>&, jni::jint);

static void registerNative(jni::JNIEnv&);
};

} // namespace android
} // namespace mbgl
23 changes: 23 additions & 0 deletions platform/darwin/src/string_nsstring.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,28 @@
return result;
}

std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits) {

static NSNumberFormatter *numberFormatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
numberFormatter = [[NSNumberFormatter alloc] init];
});

numberFormatter.locale = !localeId.empty() ? [NSLocale localeWithLocaleIdentifier:@(localeId.c_str())] : nil;
numberFormatter.currencyCode = !currency.empty() ? @(currency.c_str()) : nil;
if (currency.empty()) {
numberFormatter.minimumFractionDigits = minFractionDigits;
numberFormatter.maximumFractionDigits = maxFractionDigits;
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
} else {
numberFormatter.numberStyle = NSNumberFormatterCurrencyStyle;
}
NSString *formatted = [numberFormatter stringFromNumber:@(number)];
std::string result = std::string([formatted UTF8String]);
return result;
}

}
}
35 changes: 35 additions & 0 deletions platform/default/src/mbgl/util/format_number.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <mbgl/util/platform.hpp>

#include <unicode/numberformatter.h>

namespace mbgl {
namespace platform {

std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits) {

UErrorCode status = U_ZERO_ERROR;
icu::UnicodeString ustr;
std::string formatted;

icu::Locale locale = icu::Locale(localeId.c_str());
// Print the value as currency
if (!currency.empty()) {
icu::UnicodeString ucurrency = icu::UnicodeString::fromUTF8(currency);
ustr = icu::number::NumberFormatter::with()
.unit(icu::CurrencyUnit(ucurrency.getBuffer(), status))
.locale(locale)
.formatDouble(number, status)
.toString();
} else {
ustr = icu::number::NumberFormatter::with()
.precision(icu::number::Precision::minMaxFraction(minFractionDigits, maxFractionDigits))
.locale(locale)
.formatDouble(number, status)
.toString();
}
return ustr.toUTF8String(formatted);
}

} // namespace platform
} // namespace mbgl
14 changes: 13 additions & 1 deletion platform/linux/config.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ macro(mbgl_platform_core)
PRIVATE platform/default/src/mbgl/text/unaccent.cpp
PRIVATE platform/default/include/mbgl/text/unaccent.hpp
PRIVATE platform/default/src/mbgl/util/utf.cpp
PRIVATE platform/default/src/mbgl/util/format_number.cpp

# Image handling
PRIVATE platform/default/src/mbgl/util/image.cpp
Expand Down Expand Up @@ -83,10 +84,21 @@ macro(mbgl_platform_core)

target_add_mason_package(mbgl-core PUBLIC libpng)
target_add_mason_package(mbgl-core PUBLIC libjpeg-turbo)
target_add_mason_package(mbgl-core PRIVATE icu)

# Ignore warning caused by ICU header unistr.h in some CI environments
set_source_files_properties(platform/default/src/mbgl/util/format_number.cpp PROPERTIES COMPILE_FLAGS -Wno-error=shadow)

# Link all ICU libraries (by default only libicuuc is linked)
find_library(LIBICUI18N NAMES icui18n HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
find_library(LIBICUUC NAMES icuuc HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)
find_library(LIBICUDATA NAMES icudata HINTS ${MASON_PACKAGE_icu_INCLUDE_DIRS}/../lib)

target_link_libraries(mbgl-core
PRIVATE ${LIBICUI18N}
PRIVATE ${LIBICUUC}
PRIVATE ${LIBICUDATA}
PRIVATE nunicode
PRIVATE icu
PUBLIC -lz
)

Expand Down
3 changes: 2 additions & 1 deletion platform/node/src/node_expression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ type::Type parseType(v8::Local<v8::Object> type) {
{"object", type::Object},
{"color", type::Color},
{"value", type::Value},
{"formatted", type::Formatted}
{"formatted", type::Formatted},
{"number-format", type::String}
};

v8::Local<v8::Value> v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked();
Expand Down
1 change: 1 addition & 0 deletions platform/qt/qt.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ set(MBGL_QT_CORE_FILES
PRIVATE platform/qt/src/timer_impl.hpp
PRIVATE platform/qt/src/utf.cpp
PRIVATE platform/qt/src/gl_functions.cpp
PRIVATE platform/qt/src/format_number.cpp

PRIVATE platform/default/src/mbgl/text/collator.cpp
PRIVATE platform/default/src/mbgl/text/unaccent.cpp
Expand Down
26 changes: 26 additions & 0 deletions platform/qt/src/format_number.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#include <mbgl/util/platform.hpp>

#include <QLocale>
#include <QString>

namespace mbgl {
namespace platform {

std::string formatNumber(double number, const std::string& localeId, const std::string& currency,
uint8_t minFractionDigits, uint8_t maxFractionDigits) {

QString formatted;
// Qt Locale::toString() API takes only one precision argument
(void)minFractionDigits;
QLocale locale = QLocale(QString::fromStdString(localeId));

if (!currency.empty()) {
formatted = locale.toCurrencyString(number);
} else {
formatted = locale.toString(number, 'f', maxFractionDigits);
}
return formatted.toStdString();
}

} // namespace platform
} // namespace mbgl
2 changes: 2 additions & 0 deletions src/core-files.json
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
"src/mbgl/style/expression/let.cpp",
"src/mbgl/style/expression/literal.cpp",
"src/mbgl/style/expression/match.cpp",
"src/mbgl/style/expression/number_format.cpp",
"src/mbgl/style/expression/parsing_context.cpp",
"src/mbgl/style/expression/step.cpp",
"src/mbgl/style/expression/util.cpp",
Expand Down Expand Up @@ -416,6 +417,7 @@
"mbgl/style/expression/let.hpp": "include/mbgl/style/expression/let.hpp",
"mbgl/style/expression/literal.hpp": "include/mbgl/style/expression/literal.hpp",
"mbgl/style/expression/match.hpp": "include/mbgl/style/expression/match.hpp",
"mbgl/style/expression/number_format.hpp": "include/mbgl/style/expression/number_format.hpp",
"mbgl/style/expression/parsing_context.hpp": "include/mbgl/style/expression/parsing_context.hpp",
"mbgl/style/expression/step.hpp": "include/mbgl/style/expression/step.hpp",
"mbgl/style/expression/type.hpp": "include/mbgl/style/expression/type.hpp",
Expand Down
Loading

0 comments on commit 50bdbea

Please sign in to comment.