[core] Initial implementation of 'format' expression
This commit is contained in:
@@ -465,6 +465,8 @@ include/mbgl/style/expression/dsl.hpp
|
||||
include/mbgl/style/expression/error.hpp
|
||||
include/mbgl/style/expression/expression.hpp
|
||||
include/mbgl/style/expression/find_zoom_curve.hpp
|
||||
include/mbgl/style/expression/format_expression.hpp
|
||||
include/mbgl/style/expression/formatted.hpp
|
||||
include/mbgl/style/expression/get_covering_stops.hpp
|
||||
include/mbgl/style/expression/interpolate.hpp
|
||||
include/mbgl/style/expression/interpolator.hpp
|
||||
@@ -491,6 +493,8 @@ src/mbgl/style/expression/compound_expression.cpp
|
||||
src/mbgl/style/expression/dsl.cpp
|
||||
src/mbgl/style/expression/expression.cpp
|
||||
src/mbgl/style/expression/find_zoom_curve.cpp
|
||||
src/mbgl/style/expression/format_expression.cpp
|
||||
src/mbgl/style/expression/formatted.cpp
|
||||
src/mbgl/style/expression/get_covering_stops.cpp
|
||||
src/mbgl/style/expression/interpolate.cpp
|
||||
src/mbgl/style/expression/is_constant.cpp
|
||||
@@ -620,6 +624,8 @@ src/mbgl/text/quads.cpp
|
||||
src/mbgl/text/quads.hpp
|
||||
src/mbgl/text/shaping.cpp
|
||||
src/mbgl/text/shaping.hpp
|
||||
src/mbgl/text/tagged_string.cpp
|
||||
src/mbgl/text/tagged_string.hpp
|
||||
|
||||
# tile
|
||||
include/mbgl/tile/tile_id.hpp
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace style {
|
||||
namespace conversion {
|
||||
|
||||
bool hasTokens(const std::string&);
|
||||
std::unique_ptr<expression::Expression> convertTokenStringToFormatExpression(const std::string&);
|
||||
std::unique_ptr<expression::Expression> convertTokenStringToExpression(const std::string&);
|
||||
|
||||
optional<std::unique_ptr<expression::Expression>> convertFunctionToExpression(expression::type::Type, const Convertible&, Error&, bool convertTokens);
|
||||
|
||||
@@ -28,6 +28,17 @@ struct Converter<PropertyValue<T>> {
|
||||
? PropertyValue<T>(PropertyExpression<T>(convertTokenStringToExpression(t)))
|
||||
: PropertyValue<T>(t);
|
||||
}
|
||||
|
||||
PropertyValue<T> maybeConvertTokens(const expression::Formatted& t) const {
|
||||
// This only works with a single-section `Formatted` created automatically
|
||||
// by parsing a plain-text `text-field` property.
|
||||
// Token conversion happens later than the initial string->Formatted conversion
|
||||
// General purpose `format` expressions with embedded tokens are not supported
|
||||
const std::string& firstUnformattedSection = t.sections[0].text;
|
||||
return hasTokens(firstUnformattedSection)
|
||||
? PropertyValue<T>(PropertyExpression<T>(convertTokenStringToFormatExpression(firstUnformattedSection)))
|
||||
: PropertyValue<T>(t);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace conversion
|
||||
|
||||
@@ -10,11 +10,6 @@ namespace mbgl {
|
||||
namespace style {
|
||||
namespace expression {
|
||||
|
||||
/**
|
||||
* Special form for error-coalescing coercion expressions "to-number",
|
||||
* "to-color". Since these coercions can fail at runtime, they accept multiple
|
||||
* arguments, only evaluating one at a time until one succeeds.
|
||||
*/
|
||||
class Coercion : public Expression {
|
||||
public:
|
||||
Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_);
|
||||
@@ -23,6 +18,8 @@ public:
|
||||
|
||||
EvaluationResult evaluate(const EvaluationContext& params) const override;
|
||||
void eachChild(const std::function<void(const Expression&)>& visit) const override;
|
||||
|
||||
mbgl::Value serialize() const override;
|
||||
|
||||
bool operator==(const Expression& e) const override;
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ enum class Kind : int32_t {
|
||||
Any,
|
||||
All,
|
||||
Comparison,
|
||||
FormatExpression,
|
||||
};
|
||||
|
||||
class Expression {
|
||||
|
||||
52
include/mbgl/style/expression/format_expression.hpp
Normal file
52
include/mbgl/style/expression/format_expression.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/expression/expression.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
#include <mbgl/style/expression/parsing_context.hpp>
|
||||
#include <mbgl/style/conversion.hpp>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
namespace expression {
|
||||
|
||||
struct FormatExpressionSection {
|
||||
FormatExpressionSection(std::unique_ptr<Expression> text_,
|
||||
optional<std::unique_ptr<Expression>> fontScale_,
|
||||
optional<std::unique_ptr<Expression>> textFont_);
|
||||
|
||||
std::shared_ptr<Expression> text;
|
||||
optional<std::shared_ptr<Expression>> fontScale;
|
||||
optional<std::shared_ptr<Expression>> textFont;
|
||||
};
|
||||
|
||||
class FormatExpression : public Expression {
|
||||
public:
|
||||
FormatExpression(std::vector<FormatExpressionSection> sections);
|
||||
|
||||
EvaluationResult evaluate(const EvaluationContext&) const override;
|
||||
static ParseResult parse(const mbgl::style::conversion::Convertible&, ParsingContext&);
|
||||
|
||||
void eachChild(const std::function<void(const Expression&)>&) const override;
|
||||
|
||||
bool operator==(const Expression& e) const override;
|
||||
|
||||
std::vector<optional<Value>> possibleOutputs() const override {
|
||||
// Technically the combinatoric set of all children
|
||||
// Usually, this.text will be undefined anyway
|
||||
return { nullopt };
|
||||
}
|
||||
|
||||
mbgl::Value serialize() const override;
|
||||
std::string getOperator() const override { return "format"; }
|
||||
private:
|
||||
std::vector<FormatExpressionSection> sections;
|
||||
std::unique_ptr<Expression> text;
|
||||
optional<std::unique_ptr<Expression>> fontScale;
|
||||
optional<std::unique_ptr<Expression>> textFont;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
62
include/mbgl/style/expression/formatted.hpp
Normal file
62
include/mbgl/style/expression/formatted.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/conversion.hpp>
|
||||
#include <mbgl/util/font_stack.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/util/variant.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
namespace expression {
|
||||
|
||||
struct FormattedSection {
|
||||
FormattedSection(std::string text_, optional<double> fontScale_, optional<FontStack> fontStack_)
|
||||
: text(std::move(text_))
|
||||
, fontScale(std::move(fontScale_))
|
||||
, fontStack(std::move(fontStack_))
|
||||
{}
|
||||
std::string text;
|
||||
optional<double> fontScale;
|
||||
optional<FontStack> fontStack;
|
||||
};
|
||||
|
||||
class Formatted {
|
||||
public:
|
||||
Formatted() = default;
|
||||
|
||||
Formatted(const char* plainU8String) {
|
||||
sections.emplace_back(std::string(plainU8String), nullopt, nullopt);
|
||||
}
|
||||
|
||||
Formatted(std::vector<FormattedSection> sections_)
|
||||
: sections(std::move(sections_))
|
||||
{}
|
||||
|
||||
bool operator==(const Formatted& ) const;
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
bool empty() const {
|
||||
return sections.empty() || sections.at(0).text.empty();
|
||||
}
|
||||
|
||||
std::vector<FormattedSection> sections;
|
||||
};
|
||||
|
||||
} // namespace expression
|
||||
|
||||
namespace conversion {
|
||||
|
||||
template <>
|
||||
struct Converter<mbgl::style::expression::Formatted> {
|
||||
public:
|
||||
optional<mbgl::style::expression::Formatted> operator()(const Convertible& value, Error& error) const;
|
||||
};
|
||||
|
||||
} // namespace conversion
|
||||
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
@@ -66,6 +66,13 @@ struct CollatorType {
|
||||
std::string getName() const { return "collator"; }
|
||||
bool operator==(const CollatorType&) const { return true; }
|
||||
};
|
||||
|
||||
struct FormattedType {
|
||||
constexpr FormattedType() {}; // NOLINT
|
||||
std::string getName() const { return "formatted"; }
|
||||
bool operator==(const FormattedType&) const { return true; }
|
||||
};
|
||||
|
||||
|
||||
constexpr NullType Null;
|
||||
constexpr NumberType Number;
|
||||
@@ -75,6 +82,7 @@ constexpr ColorType Color;
|
||||
constexpr ValueType Value;
|
||||
constexpr ObjectType Object;
|
||||
constexpr CollatorType Collator;
|
||||
constexpr FormattedType Formatted;
|
||||
constexpr ErrorType Error;
|
||||
|
||||
struct Array;
|
||||
@@ -89,6 +97,7 @@ using Type = variant<
|
||||
ValueType,
|
||||
mapbox::util::recursive_wrapper<Array>,
|
||||
CollatorType,
|
||||
FormattedType,
|
||||
ErrorType>;
|
||||
|
||||
struct Array {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/style/expression/collator.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
#include <mbgl/style/expression/type.hpp>
|
||||
#include <mbgl/style/position.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
@@ -25,6 +26,7 @@ using ValueBase = variant<
|
||||
std::string,
|
||||
Color,
|
||||
Collator,
|
||||
Formatted,
|
||||
mapbox::util::recursive_wrapper<std::vector<Value>>,
|
||||
mapbox::util::recursive_wrapper<std::unordered_map<std::string, Value>>>;
|
||||
struct Value : ValueBase {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <mbgl/style/layer.hpp>
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
|
||||
#include <mbgl/util/color.hpp>
|
||||
|
||||
@@ -121,9 +122,9 @@ public:
|
||||
PropertyValue<AlignmentType> getTextRotationAlignment() const;
|
||||
void setTextRotationAlignment(PropertyValue<AlignmentType>);
|
||||
|
||||
static PropertyValue<std::string> getDefaultTextField();
|
||||
PropertyValue<std::string> getTextField() const;
|
||||
void setTextField(PropertyValue<std::string>);
|
||||
static PropertyValue<expression::Formatted> getDefaultTextField();
|
||||
PropertyValue<expression::Formatted> getTextField() const;
|
||||
void setTextField(PropertyValue<expression::Formatted>);
|
||||
|
||||
static PropertyValue<std::vector<std::string>> getDefaultTextFont();
|
||||
PropertyValue<std::vector<std::string>> getTextFont() const;
|
||||
|
||||
@@ -11,11 +11,12 @@ namespace mbgl {
|
||||
|
||||
// An array of font names
|
||||
using FontStack = std::vector<std::string>;
|
||||
using FontStackHash = std::size_t;
|
||||
|
||||
std::string fontStackToString(const FontStack&);
|
||||
|
||||
struct FontStackHash {
|
||||
std::size_t operator()(const FontStack&) const;
|
||||
struct FontStackHasher {
|
||||
FontStackHash operator()(const FontStack&) const;
|
||||
};
|
||||
|
||||
// Statically evaluate layer properties to determine what font stacks are used.
|
||||
|
||||
@@ -37,10 +37,11 @@ type::Type parseType(v8::Local<v8::Object> type) {
|
||||
static std::unordered_map<std::string, type::Type> types = {
|
||||
{"string", type::String},
|
||||
{"number", type::Number},
|
||||
{"noolean", type::Boolean},
|
||||
{"boolean", type::Boolean},
|
||||
{"object", type::Object},
|
||||
{"color", type::Color},
|
||||
{"value", type::Value}
|
||||
{"value", type::Value},
|
||||
{"formatted", type::Formatted}
|
||||
};
|
||||
|
||||
v8::Local<v8::Value> v8kind = Nan::Get(type, Nan::New("kind").ToLocalChecked()).ToLocalChecked();
|
||||
@@ -151,6 +152,33 @@ struct ToValue {
|
||||
Nan::EscapableHandleScope scope;
|
||||
return scope.Escape(Nan::Null());
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> 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<mbgl::Value>(formatted))
|
||||
std::unordered_map<std::string, mbgl::Value> serialized;
|
||||
std::vector<mbgl::Value> sections;
|
||||
for (const auto& section : formatted.sections) {
|
||||
std::unordered_map<std::string, mbgl::Value> 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());
|
||||
}
|
||||
sections.push_back(serializedSection);
|
||||
}
|
||||
serialized.emplace("sections", sections);
|
||||
|
||||
return toJS(serialized);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> operator()(const mbgl::Color& color) {
|
||||
return operator()(std::vector<Value> {
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
{
|
||||
"expression-tests/collator/accent-equals-de": "Locale-specific behavior changes based on platform.",
|
||||
"expression-tests/format/basic": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/coercion": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/implicit": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/implicit-assert": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/implicit-coerce": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/implicit-omit": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/to-string": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/to-string": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/to-string": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/to-string": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/basic": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/format/to-string": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"expression-tests/interpolate-hcl/linear": "https://github.com/mapbox/mapbox-gl-native/issues/8720",
|
||||
"expression-tests/interpolate-lab/linear": "https://github.com/mapbox/mapbox-gl-native/issues/8720",
|
||||
"expression-tests/is-supported-script/default": "This tests RTL text plugin behavior specific to GL JS",
|
||||
@@ -82,6 +70,7 @@
|
||||
"query-tests/feature-state/default": "skip - port https://github.com/mapbox/mapbox-gl-js/pull/6263 - needs issue",
|
||||
"query-tests/regressions/mapbox-gl-js#6555": "skip - no querySourceFeatures in mbgl-node; needs issue",
|
||||
"render-tests/background-color/transition": "https://github.com/mapbox/mapbox-gl-native/issues/10619",
|
||||
"render-tests/canvas/default": "skip - js specific",
|
||||
"render-tests/collator/resolved-locale": "Some test platforms don't resolve 'en' locale",
|
||||
"render-tests/collator/default": "Some test platforms don't resolve 'en' locale",
|
||||
"render-tests/custom-layer-js/null-island": "skip - js specific",
|
||||
@@ -121,6 +110,7 @@
|
||||
"render-tests/regressions/mapbox-gl-js#6706": "https://github.com/mapbox/mapbox-gl-native/issues/10619",
|
||||
"render-tests/regressions/mapbox-gl-js#6806": "pending https://github.com/mapbox/mapbox-gl-js/pull/6812",
|
||||
"render-tests/regressions/mapbox-gl-js#7271": "https://github.com/mapbox/mapbox-gl-native/issues/12888",
|
||||
"render-tests/regressions/mapbox-gl-js#7302": "skip - js specific",
|
||||
"render-tests/regressions/mapbox-gl-native#7357": "https://github.com/mapbox/mapbox-gl-native/issues/7357",
|
||||
"render-tests/runtime-styling/image-add-sdf": "https://github.com/mapbox/mapbox-gl-native/issues/9847",
|
||||
"render-tests/runtime-styling/paint-property-fill-flat-to-extrude": "https://github.com/mapbox/mapbox-gl-native/issues/6745",
|
||||
@@ -128,9 +118,6 @@
|
||||
"render-tests/symbol-cross-fade/chinese": "https://github.com/mapbox/mapbox-gl-native/issues/10619",
|
||||
"render-tests/symbol-placement/line-overscaled": "https://github.com/mapbox/mapbox-gl-js/issues/5654",
|
||||
"render-tests/symbol-visibility/visible": "https://github.com/mapbox/mapbox-gl-native/issues/10409",
|
||||
"render-tests/text-field/formatted-arabic": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"render-tests/text-field/formatted-line": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"render-tests/text-field/formatted": "skip - https://github.com/mapbox/mapbox-gl-native/pull/12624",
|
||||
"render-tests/text-pitch-alignment/auto-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
|
||||
"render-tests/text-pitch-alignment/map-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
|
||||
"render-tests/text-pitch-alignment/viewport-text-rotation-alignment-map": "https://github.com/mapbox/mapbox-gl-native/issues/9732",
|
||||
|
||||
@@ -38,6 +38,7 @@ global.evaluatedType = function (property) {
|
||||
case 'number':
|
||||
return 'float';
|
||||
case 'formatted':
|
||||
return 'expression::Formatted';
|
||||
case 'string':
|
||||
return 'std::string';
|
||||
case 'enum':
|
||||
|
||||
@@ -57,12 +57,14 @@ void mergeLines(std::vector<SymbolFeature>& features) {
|
||||
SymbolFeature& feature = features[k];
|
||||
GeometryCollection& geometry = feature.geometry;
|
||||
|
||||
if (!feature.text || geometry.empty() || geometry[0].empty()) {
|
||||
if (!feature.formattedText || geometry.empty() || geometry[0].empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Key should include formatting options (see https://github.com/mapbox/mapbox-gl-js/issues/3645)
|
||||
|
||||
const size_t leftKey = getKey(*feature.text, geometry[0].front());
|
||||
const size_t rightKey = getKey(*feature.text, geometry[0].back());
|
||||
const size_t leftKey = getKey(feature.formattedText->rawText(), geometry[0].front());
|
||||
const size_t rightKey = getKey(feature.formattedText->rawText(), geometry[0].back());
|
||||
|
||||
const auto left = rightIndex.find(leftKey);
|
||||
const auto right = leftIndex.find(rightKey);
|
||||
@@ -75,7 +77,7 @@ void mergeLines(std::vector<SymbolFeature>& features) {
|
||||
|
||||
leftIndex.erase(leftKey);
|
||||
rightIndex.erase(rightKey);
|
||||
rightIndex[getKey(*feature.text, features[i].geometry[0].back())] = i;
|
||||
rightIndex[getKey(feature.formattedText->rawText(), features[i].geometry[0].back())] = i;
|
||||
|
||||
} else if (left != rightIndex.end()) {
|
||||
// found mergeable line adjacent to the start of the current line, merge
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <mbgl/tile/geometry_tile_data.hpp>
|
||||
#include <mbgl/util/optional.hpp>
|
||||
#include <mbgl/text/tagged_string.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
@@ -23,7 +24,7 @@ public:
|
||||
|
||||
std::unique_ptr<GeometryTileFeature> feature;
|
||||
GeometryCollection geometry;
|
||||
optional<std::u16string> text;
|
||||
optional<TaggedString> formattedText;
|
||||
optional<std::string> icon;
|
||||
std::size_t index;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ SymbolInstance::SymbolInstance(Anchor& anchor_,
|
||||
const float iconBoxScale,
|
||||
const float iconPadding,
|
||||
const std::array<float, 2> iconOffset_,
|
||||
const GlyphPositionMap& positions,
|
||||
const GlyphPositions& positions,
|
||||
const IndexedSubfeature& indexedFeature,
|
||||
const std::size_t layoutFeatureIndex_,
|
||||
const std::size_t dataFeatureIndex_,
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
const float iconBoxScale,
|
||||
const float iconPadding,
|
||||
const std::array<float, 2> iconOffset,
|
||||
const GlyphPositionMap&,
|
||||
const GlyphPositions&,
|
||||
const IndexedSubfeature&,
|
||||
const std::size_t layoutFeatureIndex,
|
||||
const std::size_t dataFeatureIndex,
|
||||
|
||||
@@ -108,28 +108,40 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
|
||||
ft.index = i;
|
||||
|
||||
if (hasText) {
|
||||
std::string u8string = layout.evaluate<TextField>(zoom, ft);
|
||||
|
||||
auto formatted = layout.evaluate<TextField>(zoom, ft);
|
||||
auto textTransform = layout.evaluate<TextTransform>(zoom, ft);
|
||||
if (textTransform == TextTransformType::Uppercase) {
|
||||
u8string = platform::uppercase(u8string);
|
||||
} else if (textTransform == TextTransformType::Lowercase) {
|
||||
u8string = platform::lowercase(u8string);
|
||||
FontStack baseFontStack = layout.evaluate<TextFont>(zoom, ft);
|
||||
FontStackHash baseFontStackHash = FontStackHasher()(baseFontStack);
|
||||
|
||||
ft.formattedText = TaggedString();
|
||||
for (std::size_t j = 0; j < formatted.sections.size(); j++) {
|
||||
const auto& section = formatted.sections[j];
|
||||
std::string u8string = section.text;
|
||||
if (textTransform == TextTransformType::Uppercase) {
|
||||
u8string = platform::uppercase(u8string);
|
||||
} else if (textTransform == TextTransformType::Lowercase) {
|
||||
u8string = platform::lowercase(u8string);
|
||||
}
|
||||
|
||||
ft.formattedText->addSection(applyArabicShaping(util::utf8_to_utf16::convert(u8string)),
|
||||
section.fontScale ? *section.fontScale : 1.0,
|
||||
section.fontStack ? FontStackHasher()(*section.fontStack) : baseFontStackHash);
|
||||
|
||||
}
|
||||
|
||||
ft.text = applyArabicShaping(util::utf8_to_utf16::convert(u8string));
|
||||
|
||||
const bool canVerticalizeText = layout.get<TextRotationAlignment>() == AlignmentType::Map
|
||||
&& layout.get<SymbolPlacement>() != SymbolPlacementType::Point
|
||||
&& util::i18n::allowsVerticalWritingMode(*ft.text);
|
||||
|
||||
FontStack fontStack = layout.evaluate<TextFont>(zoom, ft);
|
||||
GlyphIDs& dependencies = glyphDependencies[fontStack];
|
||||
|
||||
&& util::i18n::allowsVerticalWritingMode(ft.formattedText->rawText());
|
||||
|
||||
// Loop through all characters of this text and collect unique codepoints.
|
||||
for (char16_t chr : *ft.text) {
|
||||
dependencies.insert(chr);
|
||||
for (std::size_t j = 0; j < ft.formattedText->length(); j++) {
|
||||
const auto& sectionFontStack = formatted.sections[ft.formattedText->getSectionIndex(j)].fontStack;
|
||||
GlyphIDs& dependencies = glyphDependencies[sectionFontStack ? *sectionFontStack : baseFontStack];
|
||||
char16_t codePoint = ft.formattedText->getCharCodeAt(j);
|
||||
dependencies.insert(codePoint);
|
||||
if (canVerticalizeText) {
|
||||
if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) {
|
||||
if (char16_t verticalChr = util::i18n::verticalizePunctuation(codePoint)) {
|
||||
dependencies.insert(verticalChr);
|
||||
}
|
||||
}
|
||||
@@ -141,7 +153,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters,
|
||||
imageDependencies.emplace(*ft.icon, ImageType::Icon);
|
||||
}
|
||||
|
||||
if (ft.text || ft.icon) {
|
||||
if (ft.formattedText || ft.icon) {
|
||||
features.push_back(std::move(ft));
|
||||
}
|
||||
}
|
||||
@@ -168,44 +180,35 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
|
||||
auto& feature = *it;
|
||||
if (feature.geometry.empty()) continue;
|
||||
|
||||
FontStack fontStack = layout.evaluate<TextFont>(zoom, feature);
|
||||
|
||||
auto glyphMapIt = glyphMap.find(fontStack);
|
||||
const Glyphs& glyphs = glyphMapIt != glyphMap.end()
|
||||
? glyphMapIt->second : Glyphs();
|
||||
|
||||
auto glyphPositionsIt = glyphPositions.find(fontStack);
|
||||
const GlyphPositionMap& glyphPositionMap = glyphPositionsIt != glyphPositions.end()
|
||||
? glyphPositionsIt->second : GlyphPositionMap();
|
||||
|
||||
std::pair<Shaping, Shaping> shapedTextOrientations;
|
||||
optional<PositionedIcon> shapedIcon;
|
||||
|
||||
// if feature has text, shape the text
|
||||
if (feature.text) {
|
||||
auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) {
|
||||
if (feature.formattedText) {
|
||||
auto applyShaping = [&] (const TaggedString& formattedText, WritingModeType writingMode) {
|
||||
const float oneEm = 24.0f;
|
||||
const Shaping result = getShaping(
|
||||
/* string */ text,
|
||||
/* string */ formattedText,
|
||||
/* maxWidth: ems */ layout.get<SymbolPlacement>() == SymbolPlacementType::Point ?
|
||||
layout.evaluate<TextMaxWidth>(zoom, feature) * oneEm : 0,
|
||||
/* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm,
|
||||
/* anchor */ layout.evaluate<TextAnchor>(zoom, feature),
|
||||
/* justify */ layout.evaluate<TextJustify>(zoom, feature),
|
||||
/* spacing: ems */ util::i18n::allowsLetterSpacing(*feature.text) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * oneEm : 0.0f,
|
||||
/* spacing: ems */ util::i18n::allowsLetterSpacing(feature.formattedText->rawText()) ? layout.evaluate<TextLetterSpacing>(zoom, feature) * oneEm : 0.0f,
|
||||
/* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm),
|
||||
/* verticalHeight */ oneEm,
|
||||
/* writingMode */ writingMode,
|
||||
/* bidirectional algorithm object */ bidi,
|
||||
/* glyphs */ glyphs);
|
||||
/* glyphs */ glyphMap);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal);
|
||||
shapedTextOrientations.first = applyShaping(*feature.formattedText, WritingModeType::Horizontal);
|
||||
|
||||
if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) {
|
||||
shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical);
|
||||
if (util::i18n::allowsVerticalWritingMode(feature.formattedText->rawText()) && textAlongLine) {
|
||||
feature.formattedText->verticalizePunctuation();
|
||||
shapedTextOrientations.second = applyShaping(*feature.formattedText, WritingModeType::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +234,7 @@ void SymbolLayout::prepareSymbols(const GlyphMap& glyphMap, const GlyphPositions
|
||||
|
||||
// if either shapedText or icon position is present, add the feature
|
||||
if (shapedTextOrientations.first || shapedIcon) {
|
||||
addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap);
|
||||
addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositions);
|
||||
}
|
||||
|
||||
feature.geometry.clear();
|
||||
@@ -244,7 +247,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
|
||||
const SymbolFeature& feature,
|
||||
const std::pair<Shaping, Shaping>& shapedTextOrientations,
|
||||
optional<PositionedIcon> shapedIcon,
|
||||
const GlyphPositionMap& glyphPositionMap) {
|
||||
const GlyphPositions& glyphPositions) {
|
||||
const float minScale = 0.5f;
|
||||
const float glyphSize = 24.0f;
|
||||
|
||||
@@ -287,7 +290,8 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
|
||||
layout.evaluate(zoom, feature), layoutTextSize,
|
||||
textBoxScale, textPadding, textPlacement, textOffset,
|
||||
iconBoxScale, iconPadding, iconOffset,
|
||||
glyphPositionMap, indexedFeature, layoutFeatureIndex, feature.index, feature.text.value_or(std::u16string()), overscaling);
|
||||
glyphPositions, indexedFeature, layoutFeatureIndex, feature.index,
|
||||
feature.formattedText ? feature.formattedText->rawText() : std::u16string(), overscaling);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -308,7 +312,7 @@ void SymbolLayout::addFeature(const std::size_t layoutFeatureIndex,
|
||||
overscaling);
|
||||
|
||||
for (auto& anchor : anchors) {
|
||||
if (!feature.text || !anchorIsTooClose(*feature.text, textRepeatDistance, anchor)) {
|
||||
if (!feature.formattedText || !anchorIsTooClose(feature.formattedText->rawText(), textRepeatDistance, anchor)) {
|
||||
addSymbolInstance(line, anchor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ private:
|
||||
const SymbolFeature&,
|
||||
const std::pair<Shaping, Shaping>& shapedTextOrientations,
|
||||
optional<PositionedIcon> shapedIcon,
|
||||
const GlyphPositionMap&);
|
||||
const GlyphPositions&);
|
||||
|
||||
bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&);
|
||||
std::map<std::u16string, std::vector<Anchor>> compareText;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <mbgl/style/expression/interpolate.hpp>
|
||||
#include <mbgl/style/expression/match.hpp>
|
||||
#include <mbgl/style/expression/case.hpp>
|
||||
#include <mbgl/style/expression/format_expression.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
#include <cassert>
|
||||
@@ -36,6 +37,13 @@ bool hasTokens(const std::string& source) {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> convertTokenStringToFormatExpression(const std::string& source) {
|
||||
auto textExpression = convertTokenStringToExpression(source);
|
||||
std::vector<FormatExpressionSection> sections;
|
||||
sections.emplace_back(std::move(textExpression), nullopt, nullopt);
|
||||
return std::make_unique<FormatExpression>(sections);
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> convertTokenStringToExpression(const std::string& source) {
|
||||
std::vector<std::unique_ptr<Expression>> inputs;
|
||||
@@ -138,6 +146,9 @@ template optional<PropertyExpression<TextTransformType>>
|
||||
convertFunctionToExpression<TextTransformType>(const Convertible&, Error&, bool);
|
||||
template optional<PropertyExpression<TranslateAnchorType>>
|
||||
convertFunctionToExpression<TranslateAnchorType>(const Convertible&, Error&, bool);
|
||||
|
||||
template optional<PropertyExpression<Formatted>>
|
||||
convertFunctionToExpression<Formatted>(const Convertible&, Error&, bool);
|
||||
|
||||
// Ad-hoc Converters for double and int64_t. We should replace float with double wholesale,
|
||||
// and promote the int64_t Converter to general use (and it should check that the input is
|
||||
@@ -280,6 +291,15 @@ static optional<std::unique_ptr<Expression>> convertLiteral(type::Type type, con
|
||||
[&] (const type::CollatorType&) -> optional<std::unique_ptr<Expression>> {
|
||||
assert(false); // No properties use this type.
|
||||
return nullopt;
|
||||
},
|
||||
[&] (const type::FormattedType&) -> optional<std::unique_ptr<Expression>> {
|
||||
auto result = convert<std::string>(value, error);
|
||||
if (!result) {
|
||||
return nullopt;
|
||||
}
|
||||
return convertTokens ?
|
||||
convertTokenStringToFormatExpression(*result) :
|
||||
literal(Formatted(result->c_str()));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ template optional<PropertyValue<SymbolZOrderType>> Converter<PropertyValue<Symbo
|
||||
template optional<PropertyValue<TextJustifyType>> Converter<PropertyValue<TextJustifyType>>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
template optional<PropertyValue<TextTransformType>> Converter<PropertyValue<TextTransformType>>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
template optional<PropertyValue<TranslateAnchorType>> Converter<PropertyValue<TranslateAnchorType>>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
template optional<PropertyValue<mbgl::style::expression::Formatted>> Converter<PropertyValue<mbgl::style::expression::Formatted>>::operator()(conversion::Convertible const&, conversion::Error&, bool, bool) const;
|
||||
|
||||
} // namespace conversion
|
||||
} // namespace style
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <mbgl/style/filter.hpp>
|
||||
#include <mbgl/style/property_value.hpp>
|
||||
#include <mbgl/style/expression/value.hpp>
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
#include <mbgl/util/enum.hpp>
|
||||
#include <mbgl/util/color.hpp>
|
||||
#include <mbgl/util/feature.hpp>
|
||||
@@ -129,6 +131,15 @@ void stringify(Writer& writer, const Filter& filter) {
|
||||
if (!filter.expression) writer.Null();
|
||||
else stringify(writer, (*filter.expression)->serialize());
|
||||
}
|
||||
|
||||
|
||||
template <class Writer>
|
||||
void stringify(Writer& writer, const expression::Formatted& v) {
|
||||
// Convert to mbgl::Value and then use the existing stringify
|
||||
// Serialization strategy for Formatted objects is to return the constant
|
||||
// expression that would generate them.
|
||||
stringify(writer, expression::ValueConverter<mbgl::Value>::fromExpressionValue(v));
|
||||
}
|
||||
|
||||
template <class Writer>
|
||||
void stringify(Writer& writer, const Undefined&) {
|
||||
|
||||
@@ -40,6 +40,7 @@ optional<std::string> checkSubtype(const Type& expected, const Type& t) {
|
||||
String,
|
||||
Object,
|
||||
Color,
|
||||
Formatted,
|
||||
Array(Value)
|
||||
};
|
||||
|
||||
|
||||
@@ -82,6 +82,10 @@ EvaluationResult toColor(const Value& colorValue) {
|
||||
);
|
||||
}
|
||||
|
||||
EvaluationResult toFormatted(const Value& formattedValue) {
|
||||
return Formatted(toString(formattedValue).c_str());
|
||||
}
|
||||
|
||||
Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> inputs_) :
|
||||
Expression(Kind::Coercion, std::move(type_)),
|
||||
inputs(std::move(inputs_))
|
||||
@@ -96,11 +100,26 @@ Coercion::Coercion(type::Type type_, std::vector<std::unique_ptr<Expression>> in
|
||||
coerceSingleValue = toNumber;
|
||||
} else if (t.is<type::StringType>()) {
|
||||
coerceSingleValue = [] (const Value& v) -> EvaluationResult { return toString(v); };
|
||||
} else if (t.is<type::FormattedType>()) {
|
||||
coerceSingleValue = toFormatted;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
mbgl::Value Coercion::serialize() const {
|
||||
if (getType().is<type::FormattedType>()) {
|
||||
// Since there's no explicit "to-formatted" coercion, the only coercions should be created
|
||||
// by string expressions that get implicitly coerced to "formatted".
|
||||
std::vector<mbgl::Value> serialized{{ std::string("format") }};
|
||||
serialized.push_back(inputs[0]->serialize());
|
||||
serialized.push_back(std::unordered_map<std::string, mbgl::Value>());
|
||||
return serialized;
|
||||
} else {
|
||||
return Expression::serialize();
|
||||
}
|
||||
};
|
||||
|
||||
std::string Coercion::getOperator() const {
|
||||
return getType().match(
|
||||
[](const type::BooleanType&) { return "to-boolean"; },
|
||||
@@ -129,10 +148,16 @@ ParseResult Coercion::parse(const Convertible& value, ParsingContext& ctx) {
|
||||
auto it = types.find(*toString(arrayMember(value, 0)));
|
||||
assert(it != types.end());
|
||||
|
||||
if ((it->second == type::Boolean || it->second == type::String) && length != 2) {
|
||||
if ((it->second == type::Boolean || it->second == type::String || it->second == type::Formatted) && length != 2) {
|
||||
ctx.error("Expected one argument.");
|
||||
return ParseResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Special form for error-coalescing coercion expressions "to-number",
|
||||
* "to-color". Since these coercions can fail at runtime, they accept multiple
|
||||
* arguments, only evaluating one at a time until one succeeds.
|
||||
*/
|
||||
|
||||
std::vector<std::unique_ptr<Expression>> parsed;
|
||||
parsed.reserve(length - 1);
|
||||
@@ -186,6 +211,3 @@ std::vector<optional<Value>> Coercion::possibleOutputs() const {
|
||||
} // namespace expression
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <mbgl/style/expression/step.hpp>
|
||||
#include <mbgl/style/expression/interpolate.hpp>
|
||||
#include <mbgl/style/expression/compound_expression.hpp>
|
||||
#include <mbgl/style/expression/format_expression.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
@@ -77,6 +78,10 @@ std::unique_ptr<Expression> toString(std::unique_ptr<Expression> value) {
|
||||
return std::make_unique<Coercion>(type::String, vec(std::move(value)));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> toFormatted(std::unique_ptr<Expression> value) {
|
||||
return std::make_unique<Coercion>(type::Formatted, vec(std::move(value)));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> get(const char* value) {
|
||||
return get(literal(value));
|
||||
}
|
||||
@@ -181,6 +186,16 @@ std::unique_ptr<Expression> concat(std::vector<std::unique_ptr<Expression>> inpu
|
||||
return compound("concat", std::move(inputs));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> format(const char* value) {
|
||||
return std::make_unique<Literal>(Formatted(value));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expression> format(std::unique_ptr<Expression> input) {
|
||||
std::vector<FormatExpressionSection> sections;
|
||||
sections.emplace_back(std::move(input), nullopt, nullopt);
|
||||
return std::make_unique<FormatExpression>(sections);
|
||||
}
|
||||
|
||||
} // namespace dsl
|
||||
} // namespace expression
|
||||
} // namespace style
|
||||
|
||||
175
src/mbgl/style/expression/format_expression.cpp
Normal file
175
src/mbgl/style/expression/format_expression.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include <mbgl/style/conversion_impl.hpp>
|
||||
#include <mbgl/style/expression/format_expression.hpp>
|
||||
#include <mbgl/style/expression/literal.hpp>
|
||||
#include <mbgl/util/font_stack.hpp>
|
||||
#include <mbgl/util/string.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
namespace expression {
|
||||
|
||||
FormatExpressionSection::FormatExpressionSection(std::unique_ptr<Expression> text_,
|
||||
optional<std::unique_ptr<Expression>> fontScale_,
|
||||
optional<std::unique_ptr<Expression>> textFont_)
|
||||
: text(std::move(text_))
|
||||
{
|
||||
if (fontScale_) {
|
||||
fontScale = std::shared_ptr<Expression>(std::move(*fontScale_));
|
||||
}
|
||||
if (textFont_) {
|
||||
textFont = std::shared_ptr<Expression>(std::move(*textFont_));
|
||||
}
|
||||
}
|
||||
|
||||
FormatExpression::FormatExpression(std::vector<FormatExpressionSection> sections_)
|
||||
: Expression(Kind::FormatExpression, type::Formatted)
|
||||
, sections(std::move(sections_))
|
||||
{}
|
||||
|
||||
using namespace mbgl::style::conversion;
|
||||
|
||||
ParseResult FormatExpression::parse(const Convertible& value, ParsingContext& ctx) {
|
||||
std::size_t argsLength = arrayLength(value);
|
||||
if (argsLength < 3) {
|
||||
ctx.error("Expected at least two arguments.");
|
||||
return ParseResult();
|
||||
}
|
||||
|
||||
if ((argsLength - 1) % 2 != 0) {
|
||||
ctx.error("Expected an even number of arguments.");
|
||||
return ParseResult();
|
||||
}
|
||||
|
||||
std::vector<FormatExpressionSection> sections;
|
||||
for (std::size_t i = 1; i < argsLength - 1; i += 2) {
|
||||
auto textArg = arrayMember(value, i);
|
||||
ParseResult text = ctx.parse(textArg, 1, {type::Value});
|
||||
if (!text) {
|
||||
return ParseResult();
|
||||
}
|
||||
auto options = arrayMember(value, i + 1);
|
||||
if (!isObject(options)) {
|
||||
ctx.error("Format options argument must be an object.");
|
||||
return ParseResult();
|
||||
}
|
||||
|
||||
const optional<Convertible> fontScaleOption = objectMember(options, "font-scale");
|
||||
ParseResult fontScale;
|
||||
if (fontScaleOption) {
|
||||
fontScale = ctx.parse(*fontScaleOption, 1, {type::Number});
|
||||
if (!fontScale) {
|
||||
return ParseResult();
|
||||
}
|
||||
}
|
||||
|
||||
const optional<Convertible> textFontOption = objectMember(options, "text-font");
|
||||
ParseResult textFont;
|
||||
if (textFontOption) {
|
||||
textFont = ctx.parse(*textFontOption, 1, {type::Array(type::String)});
|
||||
if (!textFont) {
|
||||
return ParseResult();
|
||||
}
|
||||
}
|
||||
sections.emplace_back(std::move(*text), std::move(fontScale), std::move(textFont));
|
||||
}
|
||||
|
||||
return ParseResult(std::make_unique<FormatExpression>(std::move(sections)));
|
||||
}
|
||||
|
||||
void FormatExpression::eachChild(const std::function<void(const Expression&)>& fn) const {
|
||||
for (auto& section : sections) {
|
||||
fn(*section.text);
|
||||
if (section.fontScale) {
|
||||
fn(**section.fontScale);
|
||||
}
|
||||
if (section.textFont) {
|
||||
fn(**section.textFont);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FormatExpression::operator==(const Expression& e) const {
|
||||
if (e.getKind() == Kind::FormatExpression) {
|
||||
auto rhs = static_cast<const FormatExpression*>(&e);
|
||||
if (sections.size() != rhs->sections.size()) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < sections.size(); i++) {
|
||||
const auto& lhsSection = sections.at(i);
|
||||
const auto& rhsSection = rhs->sections.at(i);
|
||||
if (*lhsSection.text != *rhsSection.text) {
|
||||
return false;
|
||||
}
|
||||
if ((lhsSection.fontScale && (!rhsSection.fontScale || **lhsSection.fontScale != **rhsSection.fontScale)) ||
|
||||
(!lhsSection.fontScale && rhsSection.fontScale)) {
|
||||
return false;
|
||||
}
|
||||
if ((lhsSection.textFont && (!rhsSection.textFont || **lhsSection.textFont != **rhsSection.textFont)) ||
|
||||
(!lhsSection.textFont && rhsSection.textFont)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mbgl::Value FormatExpression::serialize() const {
|
||||
std::vector<mbgl::Value> serialized{{ std::string("format") }};
|
||||
for (const auto& section : sections) {
|
||||
serialized.push_back(section.text->serialize());
|
||||
std::unordered_map<std::string, mbgl::Value> options;
|
||||
if (section.fontScale) {
|
||||
options.emplace("font-scale", (*section.fontScale)->serialize());
|
||||
}
|
||||
if (section.textFont) {
|
||||
options.emplace("text-font", (*section.textFont)->serialize());
|
||||
}
|
||||
serialized.push_back(options);
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
EvaluationResult FormatExpression::evaluate(const EvaluationContext& params) const {
|
||||
std::vector<FormattedSection> evaluatedSections;
|
||||
for (const auto& section : sections) {
|
||||
auto textResult = section.text->evaluate(params);
|
||||
if (!textResult) {
|
||||
return textResult.error();
|
||||
}
|
||||
|
||||
optional<std::string> evaluatedText = toString(*textResult);
|
||||
if (!evaluatedText) {
|
||||
return EvaluationError({ "Could not coerce format expression text input to string." });
|
||||
}
|
||||
|
||||
optional<double> evaluatedFontScale;
|
||||
if (section.fontScale) {
|
||||
auto fontScaleResult = (*section.fontScale)->evaluate(params);
|
||||
if (!fontScaleResult) {
|
||||
return fontScaleResult.error();
|
||||
}
|
||||
evaluatedFontScale = fontScaleResult->get<double>();
|
||||
}
|
||||
|
||||
optional<FontStack> evaluatedTextFont;
|
||||
if (section.textFont) {
|
||||
auto textFontResult = (*section.textFont)->evaluate(params);
|
||||
if (!textFontResult) {
|
||||
return textFontResult.error();
|
||||
}
|
||||
auto textFontValue = ValueConverter<std::vector<std::string>>::fromExpressionValue(*textFontResult);
|
||||
if (!textFontValue) {
|
||||
return EvaluationError { "Format text-font option must evaluate to an array of strings" };
|
||||
}
|
||||
evaluatedTextFont = *textFontValue;
|
||||
}
|
||||
evaluatedSections.emplace_back(*evaluatedText, evaluatedFontScale, evaluatedTextFont);
|
||||
}
|
||||
return Formatted(evaluatedSections);
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
|
||||
61
src/mbgl/style/expression/formatted.cpp
Normal file
61
src/mbgl/style/expression/formatted.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include <mbgl/style/expression/formatted.hpp>
|
||||
#include <mbgl/style/conversion_impl.hpp>
|
||||
#include <mbgl/style/expression/is_constant.hpp>
|
||||
#include <mbgl/style/expression/is_expression.hpp>
|
||||
#include <mbgl/style/expression/literal.hpp>
|
||||
#include <mbgl/style/expression/expression.hpp>
|
||||
#include <mbgl/style/expression/type.hpp>
|
||||
#include <mbgl/style/expression/compound_expression.hpp>
|
||||
#include <mbgl/style/expression/boolean_operator.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
|
||||
namespace expression {
|
||||
|
||||
bool Formatted::operator==(const Formatted& other) const {
|
||||
if (other.sections.size() != sections.size()) {
|
||||
return false;
|
||||
}
|
||||
for (std::size_t i = 0; i < sections.size(); i++) {
|
||||
const auto& thisSection = sections.at(i);
|
||||
const auto& otherSection = other.sections.at(i);
|
||||
if (thisSection.text != otherSection.text ||
|
||||
thisSection.fontScale != otherSection.fontScale ||
|
||||
thisSection.fontStack != otherSection.fontStack) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string Formatted::toString() const {
|
||||
std::string result;
|
||||
for (const auto& section : sections) {
|
||||
result += section.text;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace expression
|
||||
|
||||
namespace conversion {
|
||||
|
||||
using namespace mbgl::style::expression;
|
||||
|
||||
optional<Formatted> Converter<Formatted>::operator()(const Convertible& value, Error&) const {
|
||||
using namespace mbgl::style::expression;
|
||||
|
||||
auto result = toString(value);
|
||||
if (result) {
|
||||
return Formatted(result->c_str());
|
||||
} else {
|
||||
return nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace conversion
|
||||
} // namespace style
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <mbgl/style/expression/coercion.hpp>
|
||||
#include <mbgl/style/expression/compound_expression.hpp>
|
||||
#include <mbgl/style/expression/comparison.hpp>
|
||||
#include <mbgl/style/expression/format_expression.hpp>
|
||||
#include <mbgl/style/expression/interpolate.hpp>
|
||||
#include <mbgl/style/expression/length.hpp>
|
||||
#include <mbgl/style/expression/let.hpp>
|
||||
@@ -110,6 +111,7 @@ const ExpressionRegistry& getExpressionRegistry() {
|
||||
{"case", Case::parse},
|
||||
{"coalesce", Coalesce::parse},
|
||||
{"collator", CollatorExpression::parse},
|
||||
{"format", FormatExpression::parse},
|
||||
{"interpolate", parseInterpolate},
|
||||
{"length", Length::parse},
|
||||
{"let", Let::parse},
|
||||
@@ -183,7 +185,7 @@ ParseResult ParsingContext::parse(const Convertible& value, optional<TypeAnnotat
|
||||
const type::Type actual = (*parsed)->getType();
|
||||
if ((*expected == type::String || *expected == type::Number || *expected == type::Boolean || *expected == type::Object || expected->is<type::Array>()) && actual == type::Value) {
|
||||
parsed = { annotate(std::move(*parsed), *expected, typeAnnotationOption.value_or(TypeAnnotationOption::assert)) };
|
||||
} else if (*expected == type::Color && (actual == type::Value || actual == type::String)) {
|
||||
} else if ((*expected == type::Color || *expected == type::Formatted) && (actual == type::Value || actual == type::String)) {
|
||||
parsed = { annotate(std::move(*parsed), *expected, typeAnnotationOption.value_or(TypeAnnotationOption::coerce)) };
|
||||
} else {
|
||||
checkType((*parsed)->getType());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <rapidjson/writer.h>
|
||||
#include <rapidjson/stringbuffer.h>
|
||||
#include <mbgl/style/expression/value.hpp>
|
||||
#include <mbgl/style/conversion/stringify.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
namespace style {
|
||||
@@ -13,6 +14,7 @@ type::Type typeOf(const Value& value) {
|
||||
[&](const std::string&) -> type::Type { return type::String; },
|
||||
[&](const Color&) -> type::Type { return type::Color; },
|
||||
[&](const Collator&) -> type::Type { return type::Collator; },
|
||||
[&](const Formatted&) -> type::Type { return type::Formatted; },
|
||||
[&](const NullValue&) -> type::Type { return type::Null; },
|
||||
[&](const std::unordered_map<std::string, Value>&) -> type::Type { return type::Object; },
|
||||
[&](const std::vector<Value>& arr) -> type::Type {
|
||||
@@ -38,6 +40,7 @@ std::string toString(const Value& value) {
|
||||
return value.match(
|
||||
[](const NullValue&) { return std::string(); },
|
||||
[](const Color& c) { return c.stringify(); }, // avoid quoting
|
||||
[](const Formatted& f) { return f.toString(); },
|
||||
[](const std::string& s) { return s; }, // avoid quoting
|
||||
[](const auto& v_) { return stringify(v_); }
|
||||
);
|
||||
@@ -58,6 +61,12 @@ void writeJSON(rapidjson::Writer<rapidjson::StringBuffer>& writer, const Value&
|
||||
// for them so there shouldn't be any way to serialize this value.
|
||||
assert(false);
|
||||
},
|
||||
[&] (const Formatted& f) {
|
||||
// `stringify` in turns calls ValueConverter::fromExpressionValue below
|
||||
// Serialization strategy for Formatted objects is to return the constant
|
||||
// expression that would generate them.
|
||||
mbgl::style::conversion::stringify(writer, f);
|
||||
},
|
||||
[&] (const std::vector<Value>& arr) {
|
||||
writer.StartArray();
|
||||
for(const auto& item : arr) {
|
||||
@@ -136,6 +145,31 @@ mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value)
|
||||
assert(false);
|
||||
return mbgl::Value();
|
||||
},
|
||||
[&](const Formatted& formatted)->mbgl::Value {
|
||||
// Serialization strategy for Formatted objects is to return the constant
|
||||
// expression that would generate them.
|
||||
std::vector<mbgl::Value> serialized;
|
||||
static std::string formatOperator("format");
|
||||
serialized.emplace_back(formatOperator);
|
||||
for (const auto& section : formatted.sections) {
|
||||
serialized.emplace_back(section.text);
|
||||
std::unordered_map<std::string, mbgl::Value> options;
|
||||
|
||||
if (section.fontScale) {
|
||||
options.emplace("font-scale", *section.fontScale);
|
||||
}
|
||||
|
||||
if (section.fontStack) {
|
||||
std::vector<mbgl::Value> fontStack;
|
||||
for (const auto& font : *section.fontStack) {
|
||||
fontStack.emplace_back(font);
|
||||
}
|
||||
options.emplace("text-font", std::vector<mbgl::Value>{ std::string("literal"), fontStack });
|
||||
}
|
||||
serialized.push_back(options);
|
||||
}
|
||||
return serialized;
|
||||
},
|
||||
[&](const std::vector<Value>& values)->mbgl::Value {
|
||||
std::vector<mbgl::Value> converted;
|
||||
converted.reserve(values.size());
|
||||
@@ -262,6 +296,7 @@ template <> type::Type valueTypeToExpressionType<double>() { return type::Number
|
||||
template <> type::Type valueTypeToExpressionType<std::string>() { return type::String; }
|
||||
template <> type::Type valueTypeToExpressionType<Color>() { return type::Color; }
|
||||
template <> type::Type valueTypeToExpressionType<Collator>() { return type::Collator; }
|
||||
template <> type::Type valueTypeToExpressionType<Formatted>() { return type::Formatted; }
|
||||
template <> type::Type valueTypeToExpressionType<std::unordered_map<std::string, Value>>() { return type::Object; }
|
||||
template <> type::Type valueTypeToExpressionType<std::vector<Value>>() { return type::Array(type::Value); }
|
||||
|
||||
|
||||
@@ -421,15 +421,15 @@ void SymbolLayer::setTextRotationAlignment(PropertyValue<AlignmentType> value) {
|
||||
baseImpl = std::move(impl_);
|
||||
observer->onLayerChanged(*this);
|
||||
}
|
||||
PropertyValue<std::string> SymbolLayer::getDefaultTextField() {
|
||||
PropertyValue<expression::Formatted> SymbolLayer::getDefaultTextField() {
|
||||
return TextField::defaultValue();
|
||||
}
|
||||
|
||||
PropertyValue<std::string> SymbolLayer::getTextField() const {
|
||||
PropertyValue<expression::Formatted> SymbolLayer::getTextField() const {
|
||||
return impl().layout.get<TextField>();
|
||||
}
|
||||
|
||||
void SymbolLayer::setTextField(PropertyValue<std::string> value) {
|
||||
void SymbolLayer::setTextField(PropertyValue<expression::Formatted> value) {
|
||||
if (value == getTextField())
|
||||
return;
|
||||
auto impl_ = mutableImpl();
|
||||
@@ -1928,22 +1928,15 @@ optional<Error> SymbolLayer::setLayoutProperty(const std::string& name, const Co
|
||||
|
||||
}
|
||||
|
||||
if (property == Property::IconImage || property == Property::TextField) {
|
||||
if (property == Property::IconImage) {
|
||||
Error error;
|
||||
optional<PropertyValue<std::string>> typedValue = convert<PropertyValue<std::string>>(value, error, true, true);
|
||||
if (!typedValue) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (property == Property::IconImage) {
|
||||
setIconImage(*typedValue);
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
if (property == Property::TextField) {
|
||||
setTextField(*typedValue);
|
||||
return nullopt;
|
||||
}
|
||||
setIconImage(*typedValue);
|
||||
return nullopt;
|
||||
|
||||
}
|
||||
|
||||
@@ -1985,6 +1978,18 @@ optional<Error> SymbolLayer::setLayoutProperty(const std::string& name, const Co
|
||||
|
||||
}
|
||||
|
||||
if (property == Property::TextField) {
|
||||
Error error;
|
||||
optional<PropertyValue<expression::Formatted>> typedValue = convert<PropertyValue<expression::Formatted>>(value, error, true, true);
|
||||
if (!typedValue) {
|
||||
return error;
|
||||
}
|
||||
|
||||
setTextField(*typedValue);
|
||||
return nullopt;
|
||||
|
||||
}
|
||||
|
||||
if (property == Property::TextFont) {
|
||||
Error error;
|
||||
optional<PropertyValue<std::vector<std::string>>> typedValue = convert<PropertyValue<std::vector<std::string>>>(value, error, true, false);
|
||||
|
||||
@@ -112,9 +112,9 @@ struct TextRotationAlignment : LayoutProperty<AlignmentType> {
|
||||
static AlignmentType defaultValue() { return AlignmentType::Auto; }
|
||||
};
|
||||
|
||||
struct TextField : DataDrivenLayoutProperty<std::string> {
|
||||
struct TextField : DataDrivenLayoutProperty<expression::Formatted> {
|
||||
static constexpr const char * key = "text-field";
|
||||
static std::string defaultValue() { return ""; }
|
||||
static expression::Formatted defaultValue() { return ""; }
|
||||
};
|
||||
|
||||
struct TextFont : DataDrivenLayoutProperty<std::vector<std::string>> {
|
||||
|
||||
@@ -14,6 +14,10 @@ class BiDiImpl;
|
||||
|
||||
std::u16string applyArabicShaping(const std::u16string&);
|
||||
|
||||
// StyledText pairs each code point in a string with an integer indicating
|
||||
// the styling options to use for rendering that code point
|
||||
// The data structure is intended to accomodate the reordering/interleaving
|
||||
// of formatting that can happen when BiDi rearranges inputs
|
||||
using StyledText = std::pair<std::u16string, std::vector<uint8_t>>;
|
||||
|
||||
class BiDi : private util::noncopyable {
|
||||
@@ -21,7 +25,10 @@ public:
|
||||
BiDi();
|
||||
~BiDi();
|
||||
|
||||
// Given text in logical ordering and a set of line break points,
|
||||
// return a set of lines in visual order with bidi and line breaking applied
|
||||
std::vector<std::u16string> processText(const std::u16string&, std::set<std::size_t>);
|
||||
// Same as processText but preserves per-code-point formatting information
|
||||
std::vector<StyledText> processStyledText(const StyledText&, std::set<std::size_t>);
|
||||
|
||||
private:
|
||||
|
||||
@@ -55,17 +55,21 @@ public:
|
||||
};
|
||||
|
||||
using Glyphs = std::map<GlyphID, optional<Immutable<Glyph>>>;
|
||||
using GlyphMap = std::map<FontStack, Glyphs>;
|
||||
using GlyphMap = std::map<FontStackHash, Glyphs>;
|
||||
|
||||
class PositionedGlyph {
|
||||
public:
|
||||
explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, bool vertical_)
|
||||
: glyph(glyph_), x(x_), y(y_), vertical(vertical_) {}
|
||||
explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, bool vertical_, FontStackHash font_, float scale_)
|
||||
: glyph(glyph_), x(x_), y(y_), vertical(vertical_), font(font_), scale(scale_)
|
||||
{}
|
||||
|
||||
GlyphID glyph = 0;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
bool vertical = false;
|
||||
|
||||
FontStackHash font = 0;
|
||||
float scale = 0.0;
|
||||
};
|
||||
|
||||
enum class WritingModeType : uint8_t;
|
||||
|
||||
@@ -14,7 +14,7 @@ GlyphAtlas makeGlyphAtlas(const GlyphMap& glyphs) {
|
||||
mapbox::ShelfPack pack(0, 0, options);
|
||||
|
||||
for (const auto& glyphMapEntry : glyphs) {
|
||||
const FontStack& fontStack = glyphMapEntry.first;
|
||||
FontStackHash fontStack = glyphMapEntry.first;
|
||||
GlyphPositionMap& positions = result.positions[fontStack];
|
||||
|
||||
for (const auto& entry : glyphMapEntry.second) {
|
||||
|
||||
@@ -12,7 +12,7 @@ struct GlyphPosition {
|
||||
};
|
||||
|
||||
using GlyphPositionMap = std::map<GlyphID, GlyphPosition>;
|
||||
using GlyphPositions = std::map<FontStack, GlyphPositionMap>;
|
||||
using GlyphPositions = std::map<FontStackHash, GlyphPositionMap>;
|
||||
|
||||
class GlyphAtlas {
|
||||
public:
|
||||
|
||||
@@ -130,7 +130,7 @@ void GlyphManager::notify(GlyphRequestor& requestor, const GlyphDependencies& gl
|
||||
const FontStack& fontStack = dependency.first;
|
||||
const GlyphIDs& glyphIDs = dependency.second;
|
||||
|
||||
Glyphs& glyphs = response[fontStack];
|
||||
Glyphs& glyphs = response[FontStackHasher()(fontStack)];
|
||||
Entry& entry = entries[fontStack];
|
||||
|
||||
for (const auto& glyphID : glyphIDs) {
|
||||
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
std::map<GlyphID, Immutable<Glyph>> glyphs;
|
||||
};
|
||||
|
||||
std::unordered_map<FontStack, Entry, FontStackHash> entries;
|
||||
std::unordered_map<FontStack, Entry, FontStackHasher> entries;
|
||||
|
||||
void requestRange(GlyphRequest&, const FontStack&, const GlyphRange&);
|
||||
void processResponse(const Response&, const FontStack&, const GlyphRange&);
|
||||
|
||||
@@ -94,7 +94,7 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
|
||||
SymbolQuads getGlyphQuads(const Shaping& shapedText,
|
||||
const SymbolLayoutProperties::Evaluated& layout,
|
||||
const style::SymbolPlacementType placement,
|
||||
const GlyphPositionMap& positions) {
|
||||
const GlyphPositions& positions) {
|
||||
const float textRotate = layout.get<TextRotate>() * util::DEG2RAD;
|
||||
|
||||
const float oneEm = 24.0;
|
||||
@@ -105,8 +105,12 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
|
||||
SymbolQuads quads;
|
||||
|
||||
for (const PositionedGlyph &positionedGlyph: shapedText.positionedGlyphs) {
|
||||
auto positionsIt = positions.find(positionedGlyph.glyph);
|
||||
if (positionsIt == positions.end())
|
||||
auto fontPositions = positions.find(positionedGlyph.font);
|
||||
if (fontPositions == positions.end())
|
||||
continue;
|
||||
|
||||
auto positionsIt = fontPositions->second.find(positionedGlyph.glyph);
|
||||
if (positionsIt == fontPositions->second.end())
|
||||
continue;
|
||||
|
||||
const GlyphPosition& glyph = positionsIt->second;
|
||||
@@ -116,7 +120,7 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
|
||||
const float glyphPadding = 1.0f;
|
||||
const float rectBuffer = 3.0f + glyphPadding;
|
||||
|
||||
const float halfAdvance = glyph.metrics.advance / 2.0;
|
||||
const float halfAdvance = glyph.metrics.advance * positionedGlyph.scale / 2.0;
|
||||
const bool alongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && placement != SymbolPlacementType::Point;
|
||||
|
||||
const Point<float> glyphOffset = alongLine ?
|
||||
@@ -128,10 +132,10 @@ SymbolQuads getGlyphQuads(const Shaping& shapedText,
|
||||
Point<float>{ positionedGlyph.x + halfAdvance + textOffset[0], positionedGlyph.y + textOffset[1] };
|
||||
|
||||
|
||||
const float x1 = glyph.metrics.left - rectBuffer - halfAdvance + builtInOffset.x;
|
||||
const float y1 = -glyph.metrics.top - rectBuffer + builtInOffset.y;
|
||||
const float x2 = x1 + rect.w;
|
||||
const float y2 = y1 + rect.h;
|
||||
const float x1 = (glyph.metrics.left - rectBuffer) * positionedGlyph.scale - halfAdvance + builtInOffset.x;
|
||||
const float y1 = (-glyph.metrics.top - rectBuffer) * positionedGlyph.scale + builtInOffset.y;
|
||||
const float x2 = x1 + rect.w * positionedGlyph.scale;
|
||||
const float y2 = y1 + rect.h * positionedGlyph.scale;
|
||||
|
||||
Point<float> tl{x1, y1};
|
||||
Point<float> tr{x2, y1};
|
||||
|
||||
@@ -48,6 +48,6 @@ SymbolQuad getIconQuad(const PositionedIcon& shapedIcon,
|
||||
SymbolQuads getGlyphQuads(const Shaping& shapedText,
|
||||
const style::SymbolLayoutProperties::Evaluated&,
|
||||
style::SymbolPlacementType placement,
|
||||
const GlyphPositionMap& positions);
|
||||
const GlyphPositions& positions);
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
@@ -91,7 +91,7 @@ void align(Shaping& shaping,
|
||||
|
||||
// justify left = 0, right = 1, center = .5
|
||||
void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
|
||||
const Glyphs& glyphs,
|
||||
const GlyphMap& glyphMap,
|
||||
std::size_t start,
|
||||
std::size_t end,
|
||||
float justify) {
|
||||
@@ -100,9 +100,13 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
|
||||
}
|
||||
|
||||
PositionedGlyph& glyph = positionedGlyphs[end];
|
||||
auto it = glyphs.find(glyph.glyph);
|
||||
if (it != glyphs.end() && it->second) {
|
||||
const uint32_t lastAdvance = (*it->second)->metrics.advance;
|
||||
auto glyphs = glyphMap.find(glyph.font);
|
||||
if (glyphs == glyphMap.end()) {
|
||||
return;
|
||||
}
|
||||
auto it = glyphs->second.find(glyph.glyph);
|
||||
if (it != glyphs->second.end() && it->second) {
|
||||
const float lastAdvance = (*it->second)->metrics.advance * glyph.scale;
|
||||
const float lineIndent = float(glyph.x + lastAdvance) * justify;
|
||||
|
||||
for (std::size_t j = start; j <= end; j++) {
|
||||
@@ -111,17 +115,25 @@ void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs,
|
||||
}
|
||||
}
|
||||
|
||||
float determineAverageLineWidth(const std::u16string& logicalInput,
|
||||
float determineAverageLineWidth(const TaggedString& logicalInput,
|
||||
const float spacing,
|
||||
float maxWidth,
|
||||
const Glyphs& glyphs) {
|
||||
const GlyphMap& glyphMap) {
|
||||
float totalWidth = 0;
|
||||
|
||||
for (char16_t chr : logicalInput) {
|
||||
auto it = glyphs.find(chr);
|
||||
if (it != glyphs.end() && it->second) {
|
||||
totalWidth += (*it->second)->metrics.advance + spacing;
|
||||
for (std::size_t i = 0; i < logicalInput.length(); i++) {
|
||||
const SectionOptions& section = logicalInput.getSection(i);
|
||||
char16_t codePoint = logicalInput.getCharCodeAt(i);
|
||||
auto glyphs = glyphMap.find(section.fontStackHash);
|
||||
if (glyphs == glyphMap.end()) {
|
||||
continue;
|
||||
}
|
||||
auto it = glyphs->second.find(codePoint);
|
||||
if (it == glyphs->second.end() || !it->second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalWidth += (*it->second)->metrics.advance * section.scale + spacing;
|
||||
}
|
||||
|
||||
int32_t targetLineCount = ::fmax(1, std::ceil(totalWidth / maxWidth));
|
||||
@@ -209,11 +221,11 @@ std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) {
|
||||
|
||||
// We determine line breaks based on shaped text in logical order. Working in visual order would be
|
||||
// more intuitive, but we can't do that because the visual order may be changed by line breaks!
|
||||
std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
|
||||
std::set<std::size_t> determineLineBreaks(const TaggedString& logicalInput,
|
||||
const float spacing,
|
||||
float maxWidth,
|
||||
const WritingModeType writingMode,
|
||||
const Glyphs& glyphs) {
|
||||
const GlyphMap& glyphMap) {
|
||||
if (!maxWidth || writingMode != WritingModeType::Horizontal) {
|
||||
return {};
|
||||
}
|
||||
@@ -222,40 +234,45 @@ std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput,
|
||||
return {};
|
||||
}
|
||||
|
||||
const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs);
|
||||
const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphMap);
|
||||
|
||||
std::list<PotentialBreak> potentialBreaks;
|
||||
float currentX = 0;
|
||||
|
||||
for (std::size_t i = 0; i < logicalInput.size(); i++) {
|
||||
const char16_t codePoint = logicalInput[i];
|
||||
auto it = glyphs.find(codePoint);
|
||||
if (it != glyphs.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
|
||||
currentX += (*it->second)->metrics.advance + spacing;
|
||||
for (std::size_t i = 0; i < logicalInput.length(); i++) {
|
||||
const SectionOptions& section = logicalInput.getSection(i);
|
||||
char16_t codePoint = logicalInput.getCharCodeAt(i);
|
||||
auto glyphs = glyphMap.find(section.fontStackHash);
|
||||
if (glyphs == glyphMap.end()) {
|
||||
continue;
|
||||
}
|
||||
auto it = glyphs->second.find(codePoint);
|
||||
if (it != glyphs->second.end() && it->second && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) {
|
||||
currentX += (*it->second)->metrics.advance * section.scale + spacing;
|
||||
}
|
||||
|
||||
// Ideographic characters, spaces, and word-breaking punctuation that often appear without
|
||||
// surrounding spaces.
|
||||
if ((i < logicalInput.size() - 1) &&
|
||||
if ((i < logicalInput.length() - 1) &&
|
||||
(util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) {
|
||||
potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks,
|
||||
calculatePenalty(codePoint, logicalInput[i+1]),
|
||||
calculatePenalty(codePoint, logicalInput.getCharCodeAt(i+1)),
|
||||
false));
|
||||
}
|
||||
}
|
||||
|
||||
return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true));
|
||||
return leastBadBreaks(evaluateBreak(logicalInput.length(), currentX, targetWidth, potentialBreaks, 0, true));
|
||||
}
|
||||
|
||||
void shapeLines(Shaping& shaping,
|
||||
const std::vector<std::u16string>& lines,
|
||||
const float spacing,
|
||||
const float lineHeight,
|
||||
const style::SymbolAnchorType textAnchor,
|
||||
const style::TextJustifyType textJustify,
|
||||
const float verticalHeight,
|
||||
const WritingModeType writingMode,
|
||||
const Glyphs& glyphs) {
|
||||
std::vector<TaggedString>& lines,
|
||||
const float spacing,
|
||||
const float lineHeight,
|
||||
const style::SymbolAnchorType textAnchor,
|
||||
const style::TextJustifyType textJustify,
|
||||
const float verticalHeight,
|
||||
const WritingModeType writingMode,
|
||||
const GlyphMap& glyphMap) {
|
||||
|
||||
// the y offset *should* be part of the font metadata
|
||||
const int32_t yOffset = -17;
|
||||
@@ -265,13 +282,16 @@ void shapeLines(Shaping& shaping,
|
||||
|
||||
float maxLineLength = 0;
|
||||
|
||||
|
||||
const float justify = textJustify == style::TextJustifyType::Right ? 1 :
|
||||
textJustify == style::TextJustifyType::Left ? 0 :
|
||||
0.5;
|
||||
|
||||
for (std::u16string line : lines) {
|
||||
for (TaggedString& line : lines) {
|
||||
// Collapse whitespace so it doesn't throw off justification
|
||||
boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r"));
|
||||
line.trim();
|
||||
|
||||
const double lineMaxScale = line.getMaxScale();
|
||||
|
||||
if (line.empty()) {
|
||||
y += lineHeight; // Still need a line feed after empty line
|
||||
@@ -279,20 +299,31 @@ void shapeLines(Shaping& shaping,
|
||||
}
|
||||
|
||||
std::size_t lineStartIndex = shaping.positionedGlyphs.size();
|
||||
for (char16_t chr : line) {
|
||||
auto it = glyphs.find(chr);
|
||||
if (it == glyphs.end() || !it->second) {
|
||||
for (std::size_t i = 0; i < line.length(); i++) {
|
||||
const SectionOptions& section = line.getSection(i);
|
||||
char16_t codePoint = line.getCharCodeAt(i);
|
||||
auto glyphs = glyphMap.find(section.fontStackHash);
|
||||
if (glyphs == glyphMap.end()) {
|
||||
continue;
|
||||
}
|
||||
auto it = glyphs->second.find(codePoint);
|
||||
if (it == glyphs->second.end() || !it->second) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We don't know the baseline, but since we're laying out
|
||||
// at 24 points, we can calculate how much it will move when
|
||||
// we scale up or down.
|
||||
const double baselineOffset = (lineMaxScale - section.scale) * 24;
|
||||
|
||||
const Glyph& glyph = **it->second;
|
||||
|
||||
if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) {
|
||||
shaping.positionedGlyphs.emplace_back(chr, x, y, false);
|
||||
x += glyph.metrics.advance + spacing;
|
||||
if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(codePoint)) {
|
||||
shaping.positionedGlyphs.emplace_back(codePoint, x, y + baselineOffset, false, section.fontStackHash, section.scale);
|
||||
x += glyph.metrics.advance * section.scale + spacing;
|
||||
} else {
|
||||
shaping.positionedGlyphs.emplace_back(chr, x, 0, true);
|
||||
x += verticalHeight + spacing;
|
||||
shaping.positionedGlyphs.emplace_back(codePoint, x, baselineOffset, true, section.fontStackHash, section.scale);
|
||||
x += verticalHeight * section.scale + spacing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,19 +332,19 @@ void shapeLines(Shaping& shaping,
|
||||
float lineLength = x - spacing; // Don't count trailing spacing
|
||||
maxLineLength = util::max(lineLength, maxLineLength);
|
||||
|
||||
justifyLine(shaping.positionedGlyphs, glyphs, lineStartIndex,
|
||||
justifyLine(shaping.positionedGlyphs, glyphMap, lineStartIndex,
|
||||
shaping.positionedGlyphs.size() - 1, justify);
|
||||
}
|
||||
|
||||
x = 0;
|
||||
y += lineHeight;
|
||||
y += lineHeight * lineMaxScale;
|
||||
}
|
||||
|
||||
auto anchorAlign = getAnchorAlignment(textAnchor);
|
||||
|
||||
align(shaping, justify, anchorAlign.horizontalAlign, anchorAlign.verticalAlign, maxLineLength,
|
||||
lineHeight, lines.size());
|
||||
const float height = lines.size() * lineHeight;
|
||||
const float height = y - yOffset;
|
||||
|
||||
// Calculate the bounding box
|
||||
shaping.top += -anchorAlign.verticalAlign * height;
|
||||
@@ -322,7 +353,7 @@ void shapeLines(Shaping& shaping,
|
||||
shaping.right = shaping.left + maxLineLength;
|
||||
}
|
||||
|
||||
const Shaping getShaping(const std::u16string& logicalInput,
|
||||
const Shaping getShaping(const TaggedString& formattedString,
|
||||
const float maxWidth,
|
||||
const float lineHeight,
|
||||
const style::SymbolAnchorType textAnchor,
|
||||
@@ -332,12 +363,23 @@ const Shaping getShaping(const std::u16string& logicalInput,
|
||||
const float verticalHeight,
|
||||
const WritingModeType writingMode,
|
||||
BiDi& bidi,
|
||||
const Glyphs& glyphs) {
|
||||
const GlyphMap& glyphs) {
|
||||
Shaping shaping(translate.x, translate.y, writingMode);
|
||||
|
||||
std::vector<std::u16string> reorderedLines =
|
||||
bidi.processText(logicalInput,
|
||||
determineLineBreaks(logicalInput, spacing, maxWidth, writingMode, glyphs));
|
||||
std::vector<TaggedString> reorderedLines;
|
||||
if (formattedString.sectionCount() == 1) {
|
||||
auto untaggedLines = bidi.processText(formattedString.rawText(),
|
||||
determineLineBreaks(formattedString, spacing, maxWidth, writingMode, glyphs));
|
||||
for (const auto& line : untaggedLines) {
|
||||
reorderedLines.emplace_back(line, formattedString.sectionAt(0));
|
||||
}
|
||||
} else {
|
||||
auto processedLines = bidi.processStyledText(formattedString.getStyledText(),
|
||||
determineLineBreaks(formattedString, spacing, maxWidth, writingMode, glyphs));
|
||||
for (const auto& line : processedLines) {
|
||||
reorderedLines.emplace_back(line, formattedString.getSections());
|
||||
}
|
||||
}
|
||||
|
||||
shapeLines(shaping, reorderedLines, spacing, lineHeight, textAnchor,
|
||||
textJustify, verticalHeight, writingMode, glyphs);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/text/glyph.hpp>
|
||||
#include <mbgl/text/tagged_string.hpp>
|
||||
#include <mbgl/renderer/image_atlas.hpp>
|
||||
#include <mbgl/style/types.hpp>
|
||||
|
||||
@@ -45,7 +46,7 @@ public:
|
||||
float angle() const { return _angle; }
|
||||
};
|
||||
|
||||
const Shaping getShaping(const std::u16string& string,
|
||||
const Shaping getShaping(const TaggedString& string,
|
||||
float maxWidth,
|
||||
float lineHeight,
|
||||
style::SymbolAnchorType textAnchor,
|
||||
@@ -55,6 +56,6 @@ const Shaping getShaping(const std::u16string& string,
|
||||
float verticalHeight,
|
||||
const WritingModeType,
|
||||
BiDi& bidi,
|
||||
const Glyphs& glyphs);
|
||||
const GlyphMap& glyphs);
|
||||
|
||||
} // namespace mbgl
|
||||
|
||||
42
src/mbgl/text/tagged_string.cpp
Normal file
42
src/mbgl/text/tagged_string.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <mbgl/text/tagged_string.hpp>
|
||||
#include <mbgl/util/i18n.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
void TaggedString::addSection(const std::u16string& sectionText, double scale, FontStackHash fontStack) {
|
||||
styledText.first += sectionText;
|
||||
sections.emplace_back(scale, fontStack);
|
||||
styledText.second.resize(styledText.first.size(), sections.size() - 1);
|
||||
}
|
||||
|
||||
void TaggedString::trim() {
|
||||
auto whiteSpace = boost::algorithm::is_any_of(u" \t\n\v\f\r");
|
||||
std::size_t beginningWhitespace = styledText.first.find_first_not_of(u" \t\n\v\f\r");
|
||||
if (beginningWhitespace == std::u16string::npos) {
|
||||
// Entirely whitespace
|
||||
styledText.first.clear();
|
||||
styledText.second.clear();
|
||||
} else {
|
||||
std::size_t trailingWhitespace = styledText.first.find_last_not_of(u" \t\n\v\f\r") + 1;
|
||||
|
||||
styledText.first = styledText.first.substr(beginningWhitespace, trailingWhitespace - beginningWhitespace);
|
||||
styledText.second = std::vector<uint8_t>(styledText.second.begin() + beginningWhitespace, styledText.second.begin() + trailingWhitespace);
|
||||
}
|
||||
}
|
||||
|
||||
double TaggedString::getMaxScale() const {
|
||||
double maxScale = 0.0;
|
||||
for (std::size_t i = 0; i < styledText.first.length(); i++) {
|
||||
maxScale = std::max(maxScale, getSection(i).scale);
|
||||
}
|
||||
return maxScale;
|
||||
}
|
||||
|
||||
void TaggedString::verticalizePunctuation() {
|
||||
// Relies on verticalization changing characters in place so that style indices don't need updating
|
||||
styledText.first = util::i18n::verticalizePunctuation(styledText.first);
|
||||
}
|
||||
|
||||
} // namespace mbgl
|
||||
97
src/mbgl/text/tagged_string.hpp
Normal file
97
src/mbgl/text/tagged_string.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include <mbgl/text/glyph.hpp>
|
||||
#include <mbgl/text/bidi.hpp>
|
||||
|
||||
namespace mbgl {
|
||||
|
||||
struct SectionOptions {
|
||||
SectionOptions(double scale_, FontStackHash fontStackHash_)
|
||||
: scale(scale_), fontStackHash(fontStackHash_)
|
||||
{}
|
||||
|
||||
double scale;
|
||||
FontStackHash fontStackHash;
|
||||
};
|
||||
|
||||
/**
|
||||
* A TaggedString is the shaping-code counterpart of the Formatted type
|
||||
* Whereas Formatted matches the logical structure of a 'format' expression,
|
||||
* a TaggedString represents the same data at a per-character level so that
|
||||
* character-rearranging operations (e.g. BiDi) preserve formatting.
|
||||
* Text is represented as:
|
||||
* - A string of characters
|
||||
* - A matching array of indices, pointing to:
|
||||
* - An array of SectionsOptions, representing the evaluated formatting
|
||||
* options of the original sections.
|
||||
*
|
||||
* Once the guts of a TaggedString have been re-arranged by BiDi, you can
|
||||
* iterate over the contents in order, using getCharCodeAt and getSection
|
||||
* to get the formatting options for each character in turn.
|
||||
*/
|
||||
struct TaggedString {
|
||||
TaggedString() = default;
|
||||
|
||||
TaggedString(std::u16string text_, SectionOptions options)
|
||||
: styledText(std::move(text_),
|
||||
std::vector<uint8_t>(text_.size(), 0)) {
|
||||
sections.push_back(std::move(options));
|
||||
}
|
||||
|
||||
TaggedString(StyledText styledText_, std::vector<SectionOptions> sections_)
|
||||
: styledText(std::move(styledText_))
|
||||
, sections(std::move(sections_)) {
|
||||
}
|
||||
|
||||
std::size_t length() const {
|
||||
return styledText.first.length();
|
||||
}
|
||||
|
||||
std::size_t sectionCount() const {
|
||||
return sections.size();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return styledText.first.empty();
|
||||
}
|
||||
|
||||
const SectionOptions& getSection(std::size_t index) const {
|
||||
return sections.at(styledText.second.at(index));
|
||||
}
|
||||
|
||||
char16_t getCharCodeAt(std::size_t index) const {
|
||||
return styledText.first[index];
|
||||
}
|
||||
|
||||
const std::u16string& rawText() const {
|
||||
return styledText.first;
|
||||
}
|
||||
|
||||
const StyledText& getStyledText() const {
|
||||
return styledText;
|
||||
}
|
||||
|
||||
void addSection(const std::u16string& text, double scale, FontStackHash fontStack);
|
||||
const SectionOptions& sectionAt(std::size_t index) const {
|
||||
return sections.at(index);
|
||||
}
|
||||
|
||||
const std::vector<SectionOptions>& getSections() const {
|
||||
return sections;
|
||||
}
|
||||
|
||||
uint8_t getSectionIndex(std::size_t characterIndex) const {
|
||||
return styledText.second.at(characterIndex);
|
||||
}
|
||||
|
||||
double getMaxScale() const;
|
||||
void trim();
|
||||
|
||||
void verticalizePunctuation();
|
||||
|
||||
private:
|
||||
StyledText styledText;
|
||||
std::vector<SectionOptions> sections;
|
||||
};
|
||||
|
||||
} // namespace mbgl
|
||||
@@ -252,18 +252,24 @@ void GeometryTileWorker::coalesce() {
|
||||
|
||||
void GeometryTileWorker::onGlyphsAvailable(GlyphMap newGlyphMap) {
|
||||
for (auto& newFontGlyphs : newGlyphMap) {
|
||||
const FontStack& fontStack = newFontGlyphs.first;
|
||||
FontStackHash fontStack = newFontGlyphs.first;
|
||||
Glyphs& newGlyphs = newFontGlyphs.second;
|
||||
|
||||
Glyphs& glyphs = glyphMap[fontStack];
|
||||
GlyphIDs& pendingGlyphIDs = pendingGlyphDependencies[fontStack];
|
||||
for (auto& pendingGlyphDependency : pendingGlyphDependencies) {
|
||||
// Linear lookup here to handle reverse of FontStackHash -> FontStack,
|
||||
// since dependencies need the full font stack name to make a request
|
||||
// There should not be many fontstacks to look through
|
||||
if (FontStackHasher()(pendingGlyphDependency.first) == fontStack) {
|
||||
GlyphIDs& pendingGlyphIDs = pendingGlyphDependency.second;
|
||||
for (auto& newGlyph : newGlyphs) {
|
||||
const GlyphID& glyphID = newGlyph.first;
|
||||
optional<Immutable<Glyph>>& glyph = newGlyph.second;
|
||||
|
||||
for (auto& newGlyph : newGlyphs) {
|
||||
const GlyphID& glyphID = newGlyph.first;
|
||||
optional<Immutable<Glyph>>& glyph = newGlyph.second;
|
||||
|
||||
if (pendingGlyphIDs.erase(glyphID)) {
|
||||
glyphs.emplace(glyphID, std::move(glyph));
|
||||
if (pendingGlyphIDs.erase(glyphID)) {
|
||||
glyphs.emplace(glyphID, std::move(glyph));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,7 +288,7 @@ void GeometryTileWorker::onImagesAvailable(ImageMap newIconMap, ImageMap newPatt
|
||||
|
||||
void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependencies) {
|
||||
for (auto& fontDependencies : glyphDependencies) {
|
||||
auto fontGlyphs = glyphMap.find(fontDependencies.first);
|
||||
auto fontGlyphs = glyphMap.find(FontStackHasher()(fontDependencies.first));
|
||||
for (auto glyphID : fontDependencies.second) {
|
||||
if (fontGlyphs == glyphMap.end() || fontGlyphs->second.find(glyphID) == fontGlyphs->second.end()) {
|
||||
pendingGlyphDependencies[fontDependencies.first].insert(glyphID);
|
||||
|
||||
@@ -13,7 +13,7 @@ std::string fontStackToString(const FontStack& fontStack) {
|
||||
return boost::algorithm::join(fontStack, ",");
|
||||
}
|
||||
|
||||
std::size_t FontStackHash::operator()(const FontStack& fontStack) const {
|
||||
FontStackHash FontStackHasher::operator()(const FontStack& fontStack) const {
|
||||
return boost::hash_range(fontStack.begin(), fontStack.end());
|
||||
}
|
||||
|
||||
|
||||
@@ -556,6 +556,9 @@ bool hasRotatedVerticalOrientation(char16_t chr) {
|
||||
return !(hasUprightVerticalOrientation(chr) || hasNeutralVerticalOrientation(chr));
|
||||
}
|
||||
|
||||
// Replaces "horizontal" with "vertical" punctuation in place
|
||||
// Does not re-order or change length of string
|
||||
// (TaggedString::verticalizePunctuation depends on this behavior)
|
||||
std::u16string verticalizePunctuation(const std::u16string& input) {
|
||||
std::u16string output;
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@ using namespace mbgl;
|
||||
|
||||
SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) {
|
||||
GeometryCoordinates line;
|
||||
GlyphPositionMap gpm;
|
||||
GlyphPositions positions;
|
||||
const std::pair<Shaping, Shaping> shaping(Shaping{}, Shaping{});
|
||||
style::SymbolLayoutProperties::Evaluated layout_;
|
||||
IndexedSubfeature subfeature(0, "", "", 0);
|
||||
Anchor anchor(x, y, 0, 0);
|
||||
return {anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, gpm, subfeature, 0, 0, key, 0 };
|
||||
return {anchor, line, shaping, {}, layout_, 0, 0, 0, style::SymbolPlacementType::Point, {{0, 0}}, 0, 0, {{0, 0}}, positions, subfeature, 0, 0, key, 0 };
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ TEST(GlyphManager, LoadingSuccess) {
|
||||
};
|
||||
|
||||
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
|
||||
const auto& testPositions = glyphs.at({{"Test Stack"}});
|
||||
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
|
||||
|
||||
ASSERT_EQ(testPositions.size(), 3u);
|
||||
ASSERT_EQ(testPositions.count(u'a'), 1u);
|
||||
@@ -224,7 +224,7 @@ TEST(GlyphManager, LoadLocalCJKGlyph) {
|
||||
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
|
||||
EXPECT_EQ(glyphResponses, 0); // Local generation should prevent requesting any glyphs
|
||||
|
||||
const auto& testPositions = glyphs.at({{"Test Stack"}});
|
||||
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
|
||||
|
||||
ASSERT_EQ(testPositions.size(), 1u);
|
||||
ASSERT_EQ(testPositions.count(u'中'), 1u);
|
||||
@@ -275,7 +275,7 @@ TEST(GlyphManager, LoadingInvalid) {
|
||||
};
|
||||
|
||||
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
|
||||
const auto& testPositions = glyphs.at({{"Test Stack"}});
|
||||
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
|
||||
|
||||
ASSERT_EQ(testPositions.size(), 2u);
|
||||
ASSERT_FALSE(bool(testPositions.at(u'A')));
|
||||
|
||||
@@ -50,7 +50,7 @@ TEST(getIconQuads, style) {
|
||||
shapedText.bottom = 30.0f;
|
||||
shapedText.left = -60.0f;
|
||||
shapedText.right = 20.0f;
|
||||
shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false));
|
||||
shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
|
||||
|
||||
// none
|
||||
{
|
||||
|
||||
@@ -24,7 +24,9 @@ public:
|
||||
optional<std::string> icon_, std::size_t index_) :
|
||||
SymbolFeature(std::make_unique<StubGeometryTileFeature>(std::move(id_), type_, std::move(geometry_), std::move(properties_)))
|
||||
{
|
||||
text = std::move(text_);
|
||||
if (text_) {
|
||||
formattedText = TaggedString(*text_, SectionOptions(1.0, 0));
|
||||
}
|
||||
icon = std::move(icon_);
|
||||
index = index_;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user