summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChris Loer <chris.loer@gmail.com>2018-10-02 17:03:50 -0700
committerChris Loer <chris.loer@mapbox.com>2018-10-15 13:15:46 -0700
commitce76bde13d0f4381ee861f81daf636defaff0bc5 (patch)
treee37d93d14fc64620069bac5488bae871af2fa431 /src
parentbc718257748f1ad87658e85f8c31b574afca57a9 (diff)
downloadqtlocation-mapboxgl-ce76bde13d0f4381ee861f81daf636defaff0bc5.tar.gz
[core] Initial implementation of 'format' expression
Diffstat (limited to 'src')
-rw-r--r--src/mbgl/layout/merge_lines.cpp10
-rw-r--r--src/mbgl/layout/symbol_feature.hpp3
-rw-r--r--src/mbgl/layout/symbol_instance.cpp2
-rw-r--r--src/mbgl/layout/symbol_instance.hpp2
-rw-r--r--src/mbgl/layout/symbol_layout.cpp80
-rw-r--r--src/mbgl/layout/symbol_layout.hpp2
-rw-r--r--src/mbgl/style/conversion/function.cpp20
-rw-r--r--src/mbgl/style/conversion/property_value.cpp1
-rw-r--r--src/mbgl/style/conversion/stringify.hpp11
-rw-r--r--src/mbgl/style/expression/check_subtype.cpp1
-rw-r--r--src/mbgl/style/expression/coercion.cpp30
-rw-r--r--src/mbgl/style/expression/dsl.cpp15
-rw-r--r--src/mbgl/style/expression/format_expression.cpp175
-rw-r--r--src/mbgl/style/expression/formatted.cpp61
-rw-r--r--src/mbgl/style/expression/parsing_context.cpp4
-rw-r--r--src/mbgl/style/expression/value.cpp35
-rw-r--r--src/mbgl/style/layers/symbol_layer.cpp31
-rw-r--r--src/mbgl/style/layers/symbol_layer_properties.hpp4
-rw-r--r--src/mbgl/text/bidi.hpp7
-rw-r--r--src/mbgl/text/glyph.hpp10
-rw-r--r--src/mbgl/text/glyph_atlas.cpp2
-rw-r--r--src/mbgl/text/glyph_atlas.hpp2
-rw-r--r--src/mbgl/text/glyph_manager.cpp2
-rw-r--r--src/mbgl/text/glyph_manager.hpp2
-rw-r--r--src/mbgl/text/quads.cpp20
-rw-r--r--src/mbgl/text/quads.hpp2
-rw-r--r--src/mbgl/text/shaping.cpp136
-rw-r--r--src/mbgl/text/shaping.hpp5
-rw-r--r--src/mbgl/text/tagged_string.cpp42
-rw-r--r--src/mbgl/text/tagged_string.hpp97
-rw-r--r--src/mbgl/tile/geometry_tile_worker.cpp26
-rw-r--r--src/mbgl/util/font_stack.cpp2
-rw-r--r--src/mbgl/util/i18n.cpp3
33 files changed, 703 insertions, 142 deletions
diff --git a/src/mbgl/layout/merge_lines.cpp b/src/mbgl/layout/merge_lines.cpp
index 807ecb868f..2a3afa42b2 100644
--- a/src/mbgl/layout/merge_lines.cpp
+++ b/src/mbgl/layout/merge_lines.cpp
@@ -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
diff --git a/src/mbgl/layout/symbol_feature.hpp b/src/mbgl/layout/symbol_feature.hpp
index f4dc1680bc..ff498f3c2a 100644
--- a/src/mbgl/layout/symbol_feature.hpp
+++ b/src/mbgl/layout/symbol_feature.hpp
@@ -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;
};
diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp
index a9b4b929ec..2d60020dd4 100644
--- a/src/mbgl/layout/symbol_instance.cpp
+++ b/src/mbgl/layout/symbol_instance.cpp
@@ -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_,
diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp
index ae79311790..6be19a0595 100644
--- a/src/mbgl/layout/symbol_instance.hpp
+++ b/src/mbgl/layout/symbol_instance.hpp
@@ -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,
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp
index 74abd74f01..f4a55a1f91 100644
--- a/src/mbgl/layout/symbol_layout.cpp
+++ b/src/mbgl/layout/symbol_layout.cpp
@@ -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);
}
}
diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp
index b866703e6c..7045eebc22 100644
--- a/src/mbgl/layout/symbol_layout.hpp
+++ b/src/mbgl/layout/symbol_layout.hpp
@@ -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;
diff --git a/src/mbgl/style/conversion/function.cpp b/src/mbgl/style/conversion/function.cpp
index 69fb0725d8..34ac52ec1b 100644
--- a/src/mbgl/style/conversion/function.cpp
+++ b/src/mbgl/style/conversion/function.cpp
@@ -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()));
}
);
}
diff --git a/src/mbgl/style/conversion/property_value.cpp b/src/mbgl/style/conversion/property_value.cpp
index 3b79ecc0db..ff038908b6 100644
--- a/src/mbgl/style/conversion/property_value.cpp
+++ b/src/mbgl/style/conversion/property_value.cpp
@@ -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
diff --git a/src/mbgl/style/conversion/stringify.hpp b/src/mbgl/style/conversion/stringify.hpp
index 3d188b6390..c5cd52093f 100644
--- a/src/mbgl/style/conversion/stringify.hpp
+++ b/src/mbgl/style/conversion/stringify.hpp
@@ -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&) {
diff --git a/src/mbgl/style/expression/check_subtype.cpp b/src/mbgl/style/expression/check_subtype.cpp
index 73f7d18a34..73e6e3bff7 100644
--- a/src/mbgl/style/expression/check_subtype.cpp
+++ b/src/mbgl/style/expression/check_subtype.cpp
@@ -40,6 +40,7 @@ optional<std::string> checkSubtype(const Type& expected, const Type& t) {
String,
Object,
Color,
+ Formatted,
Array(Value)
};
diff --git a/src/mbgl/style/expression/coercion.cpp b/src/mbgl/style/expression/coercion.cpp
index 4a17895991..75a6056081 100644
--- a/src/mbgl/style/expression/coercion.cpp
+++ b/src/mbgl/style/expression/coercion.cpp
@@ -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
-
-
-
diff --git a/src/mbgl/style/expression/dsl.cpp b/src/mbgl/style/expression/dsl.cpp
index c6318fb637..f5ff83a9e7 100644
--- a/src/mbgl/style/expression/dsl.cpp
+++ b/src/mbgl/style/expression/dsl.cpp
@@ -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
diff --git a/src/mbgl/style/expression/format_expression.cpp b/src/mbgl/style/expression/format_expression.cpp
new file mode 100644
index 0000000000..144df4b160
--- /dev/null
+++ b/src/mbgl/style/expression/format_expression.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/formatted.cpp b/src/mbgl/style/expression/formatted.cpp
new file mode 100644
index 0000000000..6eb106dfec
--- /dev/null
+++ b/src/mbgl/style/expression/formatted.cpp
@@ -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
+
diff --git a/src/mbgl/style/expression/parsing_context.cpp b/src/mbgl/style/expression/parsing_context.cpp
index 0373b9721f..34fbc5c380 100644
--- a/src/mbgl/style/expression/parsing_context.cpp
+++ b/src/mbgl/style/expression/parsing_context.cpp
@@ -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());
diff --git a/src/mbgl/style/expression/value.cpp b/src/mbgl/style/expression/value.cpp
index 4bac8116c2..436ed83ecd 100644
--- a/src/mbgl/style/expression/value.cpp
+++ b/src/mbgl/style/expression/value.cpp
@@ -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); }
diff --git a/src/mbgl/style/layers/symbol_layer.cpp b/src/mbgl/style/layers/symbol_layer.cpp
index c116d5b7e9..848678b5f1 100644
--- a/src/mbgl/style/layers/symbol_layer.cpp
+++ b/src/mbgl/style/layers/symbol_layer.cpp
@@ -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);
diff --git a/src/mbgl/style/layers/symbol_layer_properties.hpp b/src/mbgl/style/layers/symbol_layer_properties.hpp
index 10d059e787..6c147f440d 100644
--- a/src/mbgl/style/layers/symbol_layer_properties.hpp
+++ b/src/mbgl/style/layers/symbol_layer_properties.hpp
@@ -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>> {
diff --git a/src/mbgl/text/bidi.hpp b/src/mbgl/text/bidi.hpp
index d90f3e5d1b..5ce2887db8 100644
--- a/src/mbgl/text/bidi.hpp
+++ b/src/mbgl/text/bidi.hpp
@@ -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:
diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp
index 2c03da308a..55cd50fd9b 100644
--- a/src/mbgl/text/glyph.hpp
+++ b/src/mbgl/text/glyph.hpp
@@ -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;
diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp
index 1b98ea36bf..da65aea8a9 100644
--- a/src/mbgl/text/glyph_atlas.cpp
+++ b/src/mbgl/text/glyph_atlas.cpp
@@ -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) {
diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp
index bb9115e4b4..9dd063ef69 100644
--- a/src/mbgl/text/glyph_atlas.hpp
+++ b/src/mbgl/text/glyph_atlas.hpp
@@ -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:
diff --git a/src/mbgl/text/glyph_manager.cpp b/src/mbgl/text/glyph_manager.cpp
index 8e7cfe5ba7..c4a7a2de66 100644
--- a/src/mbgl/text/glyph_manager.cpp
+++ b/src/mbgl/text/glyph_manager.cpp
@@ -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) {
diff --git a/src/mbgl/text/glyph_manager.hpp b/src/mbgl/text/glyph_manager.hpp
index 642471bbf2..831d84719c 100644
--- a/src/mbgl/text/glyph_manager.hpp
+++ b/src/mbgl/text/glyph_manager.hpp
@@ -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&);
diff --git a/src/mbgl/text/quads.cpp b/src/mbgl/text/quads.cpp
index ec4461ac6d..9d582f14d6 100644
--- a/src/mbgl/text/quads.cpp
+++ b/src/mbgl/text/quads.cpp
@@ -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};
diff --git a/src/mbgl/text/quads.hpp b/src/mbgl/text/quads.hpp
index 33d003c935..44a35a5014 100644
--- a/src/mbgl/text/quads.hpp
+++ b/src/mbgl/text/quads.hpp
@@ -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
diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp
index a8232836b6..d6cbb2a4f8 100644
--- a/src/mbgl/text/shaping.cpp
+++ b/src/mbgl/text/shaping.cpp
@@ -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);
diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp
index 0a961849e5..50ac893098 100644
--- a/src/mbgl/text/shaping.hpp
+++ b/src/mbgl/text/shaping.hpp
@@ -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
diff --git a/src/mbgl/text/tagged_string.cpp b/src/mbgl/text/tagged_string.cpp
new file mode 100644
index 0000000000..e199a7c962
--- /dev/null
+++ b/src/mbgl/text/tagged_string.cpp
@@ -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
diff --git a/src/mbgl/text/tagged_string.hpp b/src/mbgl/text/tagged_string.hpp
new file mode 100644
index 0000000000..476c2225f0
--- /dev/null
+++ b/src/mbgl/text/tagged_string.hpp
@@ -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
diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp
index c9032894a4..e16b805f6b 100644
--- a/src/mbgl/tile/geometry_tile_worker.cpp
+++ b/src/mbgl/tile/geometry_tile_worker.cpp
@@ -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& newGlyph : newGlyphs) {
- const GlyphID& glyphID = newGlyph.first;
- optional<Immutable<Glyph>>& glyph = newGlyph.second;
-
- if (pendingGlyphIDs.erase(glyphID)) {
- glyphs.emplace(glyphID, std::move(glyph));
+ 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;
+
+ 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);
diff --git a/src/mbgl/util/font_stack.cpp b/src/mbgl/util/font_stack.cpp
index 177d5e6f31..fb1b716fb6 100644
--- a/src/mbgl/util/font_stack.cpp
+++ b/src/mbgl/util/font_stack.cpp
@@ -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());
}
diff --git a/src/mbgl/util/i18n.cpp b/src/mbgl/util/i18n.cpp
index 5530796915..0fa6122673 100644
--- a/src/mbgl/util/i18n.cpp
+++ b/src/mbgl/util/i18n.cpp
@@ -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;