[core] Initial implementation of 'format' expression

This commit is contained in:
Chris Loer
2018-10-02 17:03:50 -07:00
committed by Chris Loer
parent bc71825774
commit ce76bde13d
60 changed files with 906 additions and 175 deletions

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -133,6 +133,7 @@ enum class Kind : int32_t {
Any,
All,
Comparison,
FormatExpression,
};
class Expression {

View 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

View 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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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.

View File

@@ -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> {

View File

@@ -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",

View File

@@ -38,6 +38,7 @@ global.evaluatedType = function (property) {
case 'number':
return 'float';
case 'formatted':
return 'expression::Formatted';
case 'string':
return 'std::string';
case 'enum':

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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_,

View File

@@ -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,

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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()));
}
);
}

View File

@@ -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

View File

@@ -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&) {

View File

@@ -40,6 +40,7 @@ optional<std::string> checkSubtype(const Type& expected, const Type& t) {
String,
Object,
Color,
Formatted,
Array(Value)
};

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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());

View File

@@ -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); }

View File

@@ -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);

View File

@@ -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>> {

View File

@@ -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:

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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&);

View File

@@ -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};

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View 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

View 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

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -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 };
}

View File

@@ -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')));

View File

@@ -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
{

View File

@@ -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_;
}