[core] Add image sections to format expression

This commit is contained in:
Alexander Shalamov
2019-11-12 22:21:12 +02:00
parent 5e3ef2e9e0
commit a01ecc92a0
8 changed files with 153 additions and 94 deletions

View File

@@ -8,12 +8,16 @@ namespace style {
namespace expression {
struct FormatExpressionSection {
FormatExpressionSection(std::unique_ptr<Expression> text_,
optional<std::unique_ptr<Expression>> fontScale_,
optional<std::unique_ptr<Expression>> textFont_,
optional<std::unique_ptr<Expression>> textColor_);
std::shared_ptr<Expression> text;
explicit FormatExpressionSection(std::unique_ptr<Expression> content_);
void setTextSectionOptions(optional<std::unique_ptr<Expression>> fontScale_,
optional<std::unique_ptr<Expression>> textFont_,
optional<std::unique_ptr<Expression>> textColor_);
// Content can be expression that evaluates to String or Image.
std::shared_ptr<Expression> content;
// Text related section options.
optional<std::shared_ptr<Expression>> fontScale;
optional<std::shared_ptr<Expression>> textFont;
optional<std::shared_ptr<Expression>> textColor;

View File

@@ -1,6 +1,7 @@
#pragma once
#include <mbgl/style/conversion.hpp>
#include <mbgl/style/expression/image.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/util/font_stack.hpp>
#include <mbgl/util/optional.hpp>
@@ -17,17 +18,19 @@ extern const char* const kFormattedSectionTextFont;
extern const char* const kFormattedSectionTextColor;
struct FormattedSection {
FormattedSection(std::string text_,
optional<double> fontScale_,
optional<FontStack> fontStack_,
optional<Color> textColor_)
: text(std::move(text_))
, fontScale(std::move(fontScale_))
, fontStack(std::move(fontStack_))
, textColor(std::move(textColor_))
{}
explicit FormattedSection(std::string text_,
optional<double> fontScale_,
optional<FontStack> fontStack_,
optional<Color> textColor_)
: text(std::move(text_)),
fontScale(std::move(fontScale_)),
fontStack(std::move(fontStack_)),
textColor(std::move(textColor_)) {}
explicit FormattedSection(Image image_) : image(std::move(image_)) {}
std::string text;
optional<Image> image;
optional<double> fontScale;
optional<FontStack> fontStack;
optional<Color> textColor;
@@ -50,10 +53,8 @@ public:
std::string toString() const;
mbgl::Value toObject() const;
bool empty() const {
return sections.empty() || sections.at(0).text.empty();
}
bool empty() const;
std::vector<FormattedSection> sections;
};

View File

@@ -42,9 +42,8 @@ bool hasTokens(const std::string& source) {
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, nullopt);
return std::make_unique<FormatExpression>(sections);
std::vector<FormatExpressionSection> sections{FormatExpressionSection(std::move(textExpression))};
return std::make_unique<FormatExpression>(std::move(sections));
}
std::unique_ptr<Expression> convertTokenStringToImageExpression(const std::string& source) {

View File

@@ -237,9 +237,8 @@ std::unique_ptr<Expression> format(const char* value) {
}
std::unique_ptr<Expression> format(std::unique_ptr<Expression> input) {
std::vector<FormatExpressionSection> sections;
sections.emplace_back(std::move(input), nullopt, nullopt, nullopt);
return std::make_unique<FormatExpression>(sections);
std::vector<FormatExpressionSection> sections{FormatExpressionSection(std::move(input))};
return std::make_unique<FormatExpression>(std::move(sections));
}
std::unique_ptr<Expression> image(const char* value) {

View File

@@ -6,21 +6,23 @@ 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_,
optional<std::unique_ptr<Expression>> textColor_)
: text(std::move(text_))
{
FormatExpressionSection::FormatExpressionSection(std::unique_ptr<Expression> content_) : content(std::move(content_)) {}
void FormatExpressionSection::setTextSectionOptions(optional<std::unique_ptr<Expression>> fontScale_,
optional<std::unique_ptr<Expression>> textFont_,
optional<std::unique_ptr<Expression>> textColor_) {
if (fontScale_) {
assert(*fontScale_);
fontScale = std::shared_ptr<Expression>(std::move(*fontScale_));
}
if (textFont_) {
assert(*textFont_);
textFont = std::shared_ptr<Expression>(std::move(*textFont_));
}
if (textColor_) {
assert(*textColor_);
textColor = std::shared_ptr<Expression>(std::move(*textColor_));
}
}
@@ -34,68 +36,69 @@ 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.");
if (argsLength < 2) {
ctx.error("Expected at least one argument.");
return ParseResult();
}
if ((argsLength - 1) % 2 != 0) {
ctx.error("Expected an even number of arguments.");
if (isObject(arrayMember(value, 1))) {
ctx.error("First argument must be an image or text section.");
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, kFormattedSectionFontScale);
ParseResult fontScale;
if (fontScaleOption) {
fontScale = ctx.parse(*fontScaleOption, 1, {type::Number});
if (!fontScale) {
return ParseResult();
}
}
const optional<Convertible> textFontOption = objectMember(options, kFormattedSectionTextFont);
ParseResult textFont;
if (textFontOption) {
textFont = ctx.parse(*textFontOption, 1, {type::Array(type::String)});
if (!textFont) {
return ParseResult();
}
}
bool nextTokenMayBeObject = false;
for (std::size_t i = 1; i <= argsLength - 1; ++i) {
auto arg = arrayMember(value, i);
const optional<Convertible> textColorOption = objectMember(options, kFormattedSectionTextColor);
ParseResult textColor;
if (textColorOption) {
textColor = ctx.parse(*textColorOption, 1, {type::Color});
if (!textColor) {
if (nextTokenMayBeObject && isObject(arg)) {
nextTokenMayBeObject = false;
const optional<Convertible> fontScaleOption = objectMember(arg, kFormattedSectionFontScale);
ParseResult fontScale;
if (fontScaleOption) {
fontScale = ctx.parse(*fontScaleOption, 1, {type::Number});
if (!fontScale) {
return ParseResult();
}
}
const optional<Convertible> textFontOption = objectMember(arg, kFormattedSectionTextFont);
ParseResult textFont;
if (textFontOption) {
textFont = ctx.parse(*textFontOption, 1, {type::Array(type::String)});
if (!textFont) {
return ParseResult();
}
}
const optional<Convertible> textColorOption = objectMember(arg, kFormattedSectionTextColor);
ParseResult textColor;
if (textColorOption) {
textColor = ctx.parse(*textColorOption, 1, {type::Color});
if (!textColor) {
return ParseResult();
}
}
sections.back().setTextSectionOptions(std::move(fontScale), std::move(textFont), std::move(textColor));
} else {
ParseResult parsedArg = ctx.parse(arg, 1, {type::Value});
if (!parsedArg) {
ctx.error("Cannot parse formatted section.");
return ParseResult();
}
}
sections.emplace_back(std::move(*text),
std::move(fontScale),
std::move(textFont),
std::move(textColor));
nextTokenMayBeObject = true;
sections.emplace_back(std::move(*parsedArg));
}
}
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);
fn(*section.content);
if (section.fontScale) {
fn(**section.fontScale);
}
@@ -117,7 +120,7 @@ bool FormatExpression::operator==(const Expression& e) const {
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) {
if (*lhsSection.content != *rhsSection.content) {
return false;
}
if ((lhsSection.fontScale && (!rhsSection.fontScale || **lhsSection.fontScale != **rhsSection.fontScale)) ||
@@ -141,7 +144,7 @@ bool FormatExpression::operator==(const Expression& e) const {
mbgl::Value FormatExpression::serialize() const {
std::vector<mbgl::Value> serialized{{ getOperator() }};
for (const auto& section : sections) {
serialized.push_back(section.text->serialize());
serialized.push_back(section.content->serialize());
std::unordered_map<std::string, mbgl::Value> options;
if (section.fontScale) {
options.emplace(kFormattedSectionFontScale, (*section.fontScale)->serialize());
@@ -160,14 +163,25 @@ mbgl::Value FormatExpression::serialize() const {
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();
auto contentResult = section.content->evaluate(params);
if (!contentResult) {
return contentResult.error();
}
optional<std::string> evaluatedText = toString(*textResult);
if (!evaluatedText) {
return EvaluationError({ "Could not coerce format expression text input to string." });
optional<std::string> evaluatedText;
if (typeOf(*contentResult) == type::Image) {
const auto& image = contentResult->get<Image>();
// Omit sections with empty image ids.
if (!image.id().empty()) {
evaluatedSections.emplace_back(image);
}
// Continue evaluation of a next section, as the image section does not have section options.
continue;
} else {
evaluatedText = toString(*contentResult);
if (!evaluatedText) {
return EvaluationError({"Could not coerce format expression text input to string."});
}
}
optional<double> evaluatedFontScale;

View File

@@ -35,6 +35,16 @@ std::string Formatted::toString() const {
return result;
}
bool Formatted::empty() const {
if (sections.empty()) {
return true;
}
return !std::any_of(sections.begin(), sections.end(), [](const FormattedSection& section) {
return !section.text.empty() || (section.image && !section.image->empty());
});
}
mbgl::Value Formatted::toObject() const {
mapbox::base::ValueObject result;
mapbox::base::ValueArray sectionValues;
@@ -58,6 +68,7 @@ mbgl::Value Formatted::toObject() const {
} else {
serializedSection.emplace("textColor", NullValue());
}
serializedSection.emplace("image", section.image ? section.image->toValue() : NullValue());
sectionValues.emplace_back(serializedSection);
}
result.emplace("sections", std::move(sectionValues));
@@ -76,14 +87,37 @@ optional<Formatted> Converter<Formatted>::operator()(const Convertible& value, E
if (isArray(value)) {
std::vector<FormattedSection> sections;
for (std::size_t i = 0; i < arrayLength(value); ++i) {
Convertible section = arrayMember(value, i);
const Convertible& section = arrayMember(value, i);
std::size_t sectionLength = arrayLength(section);
if (sectionLength < 1) {
error.message = "Section has to contain a text and optional parameters.";
error.message = "Section has to contain a text and optional parameters or an image.";
return nullopt;
}
optional<std::string> sectionText = toString(arrayMember(section, 0));
const Convertible& firstElement = arrayMember(section, 0);
if (isArray(firstElement)) {
if (arrayLength(firstElement) < 2) {
error.message = "Image section has to contain image name.";
return nullopt;
}
optional<std::string> imageOp = toString(arrayMember(firstElement, 0));
if (!imageOp || *imageOp != "image") {
error.message = "Serialized image section has to contain 'image' operator.";
return nullopt;
}
optional<std::string> imageArg = toString(arrayMember(firstElement, 1));
if (!imageArg) {
error.message = "Serialized image section agument has to be of a String type.";
return nullopt;
}
sections.emplace_back(Image(*imageArg));
continue;
}
optional<std::string> sectionText = toString(firstElement);
if (!sectionText) {
error.message = "Section has to contain a text.";
return nullopt;
@@ -93,7 +127,7 @@ optional<Formatted> Converter<Formatted>::operator()(const Convertible& value, E
optional<FontStack> textFont;
optional<Color> textColor;
if (sectionLength > 1) {
Convertible sectionParams = arrayMember(section, 1);
const Convertible& sectionParams = arrayMember(section, 1);
if (!isObject(sectionParams)) {
error.message = "Parameters have to be enclosed in an object.";
return nullopt;

View File

@@ -149,13 +149,18 @@ mbgl::Value ValueConverter<mbgl::Value>::fromExpressionValue(const Value& value)
static std::string formatOperator("format");
serialized.emplace_back(formatOperator);
for (const auto& section : formatted.sections) {
if (section.image) {
serialized.emplace_back(std::vector<mbgl::Value>{std::string("image"), section.image->id()});
continue;
}
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) {

View File

@@ -343,7 +343,8 @@ void testHasOverrides(LayoutType& layout) {
EXPECT_FALSE(MockOverrides::hasOverrides(layout.template get<TextField>()));
// Expression, overridden text-color.
FormatExpressionSection section(literal(""), nullopt, nullopt, toColor(literal("red")));
FormatExpressionSection section(literal(""));
section.setTextSectionOptions(nullopt, nullopt, toColor(literal("red")));
auto formatExprOverride = std::make_unique<FormatExpression>(std::vector<FormatExpressionSection>{section});
PropertyExpression<Formatted> propExprOverride(std::move(formatExprOverride));
layout.template get<TextField>() = PropertyValueType<Formatted>(std::move(propExprOverride));
@@ -351,7 +352,9 @@ void testHasOverrides(LayoutType& layout) {
// Nested expressions, overridden text-color.
auto formattedExpr1 = format("first paragraph");
std::vector<FormatExpressionSection> sections{ { literal("second paragraph"), nullopt, nullopt, toColor(literal("blue")) } };
FormatExpressionSection secondParagraph(literal("second paragraph"));
secondParagraph.setTextSectionOptions(nullopt, nullopt, toColor(literal("blue")));
std::vector<FormatExpressionSection> sections{{std::move(secondParagraph)}};
auto formattedExpr2 = std::make_unique<FormatExpression>(std::move(sections));
std::unordered_map<std::string, std::shared_ptr<Expression>> branches{ { "1st", std::move(formattedExpr1) },
{ "2nd", std::move(formattedExpr2) } };