[general] moving ios and android project under ./platform folder

This commit is contained in:
Petr Pokorny
2020-12-20 12:38:56 +01:00
parent 8e759a51e4
commit 41f0be34e7
9877 changed files with 141 additions and 2889 deletions

90
test/text/bidi.test.cpp Normal file
View File

@@ -0,0 +1,90 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/bidi.hpp>
using namespace mbgl;
/*
These tests mirror the unit tests in mapbox-gl-rtl-text's arabic.test.js
The arabic text in results may appear to be backwards
This is because whatever you're viewing the text with is
applying the bidirectional algorithm a second time.
Although they may look the same as input in your viewer, the
characters in the test results are "presentation forms" of
the characters.
To closely inspect the inputs and outputs, use a binary/hex editor.
*/
TEST(BiDi, ArabicShaping) {
EXPECT_EQ(applyArabicShaping(u"اليَمَن‎‎"), u"ﺍﻟﻴﹷﻤﹷﻦ‎‎");
}
TEST(BiDi, Tashkeel) {
EXPECT_EQ(applyArabicShaping(u"سلام۳۹"), u"ﺳﻼﻡ۳۹");
}
TEST(BiDi, MixedShaping) {
EXPECT_EQ(applyArabicShaping(u"مكتبة الإسكندرية‎‎ Maktabat al-Iskandarīyah"),
u"ﻣﻜﺘﺒﺔ ﺍﻹﺳﻜﻨﺪﺭﻳﺔ‎‎ Maktabat al-Iskandarīyah");
}
TEST(BiDi, ReverseArabic) {
BiDi bidi;
EXPECT_EQ(bidi.processText(applyArabicShaping(u"سلام۳۹"), {}),
std::vector<std::u16string>{ u"۳۹ﻡﻼﺳ" });
}
TEST(BiDi, ReverseMixed) {
BiDi bidi;
EXPECT_EQ(bidi.processText(applyArabicShaping(u"مكتبة الإسكندرية‎‎ Maktabat al-Iskandarīyah"), {}),
std::vector<std::u16string>{ u" Maktabat al-Iskandarīyahﺔﻳﺭﺪﻨﻜﺳﻹﺍ ﺔﺒﺘﻜﻣ" });
}
TEST(BiDi, WithLineBreaks) {
BiDi bidi;
std::vector<std::u16string> expected;
expected.emplace_back(u" ﺔﻳﺭﺪﻨﻜﺳﻹﺍ ﺔﺒﺘﻜﻣ");
expected.emplace_back(u"Maktabat al-");
expected.emplace_back(u"Iskandarīyah");
EXPECT_EQ(bidi.processText(applyArabicShaping(u"مكتبة الإسكندرية‎‎ Maktabat al-Iskandarīyah"), { 18, 30 }),
expected);
}
TEST(BiDi, StyledText) {
// This test uses line breaks that intentionally split/reorder/interleave styled blocks
// that are contiguous in the input
BiDi bidi;
std::vector<StyledText> expected;
StyledText input(
applyArabicShaping(u"مكتبة الإسكندرية‎‎ Maktabat al-Iskandarīyah"),
std::vector<uint8_t>{ 0,0,0,0,0,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,4,5,5,5,5,6,6,6,6,6,6,6,6,6,6,7,7,7 }
);
expected.emplace_back(StyledText(
u"ﺔﺒﺘﻜﻣ",
std::vector<uint8_t>{ 0,0,0,0,0 }
));
EXPECT_EQ(expected.rbegin()->first.size(), expected.rbegin()->second.size());
expected.emplace_back(StyledText(
u" ‎‎ﺔﻳﺭﺪﻨﻜﺳﻹﺍ ",
std::vector<uint8_t>{ 2,2,2,2,2,2,2,1,1,1,1,1,1 }
));
EXPECT_EQ(expected.rbegin()->first.size(), expected.rbegin()->second.size());
expected.emplace_back(StyledText(
u"Maktabat al-",
std::vector<uint8_t>{ 2,3,3,3,3,3,4,5,5,5,5,6 }
));
EXPECT_EQ(expected.rbegin()->first.size(), expected.rbegin()->second.size());
expected.emplace_back(StyledText(
u"Iskandarīyah",
std::vector<uint8_t>{ 6,6,6,6,6,6,6,6,6,7,7,7 }
));
EXPECT_EQ(expected.rbegin()->first.size(), expected.rbegin()->second.size());
EXPECT_EQ(bidi.processStyledText(input, { 5, 18, 30 }), expected);
}

View File

@@ -0,0 +1,27 @@
#include <mbgl/test/util.hpp>
#include <mbgl/geometry/anchor.hpp>
#include <mbgl/layout/symbol_layout.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
using namespace mbgl;
TEST(calculateTileDistances, Point) {
const GeometryCoordinates line = {Point<int16_t>{1, 1}};
const auto distances = SymbolLayout::calculateTileDistances(line, Anchor(1.0f, 1.0f, .0f, 0u));
EXPECT_EQ(distances.size(), 1u);
EXPECT_EQ(distances[0], .0f);
}
TEST(calculateTileDistances, Line) {
const GeometryCoordinates line = {
Point<int16_t>{1, 1}, Point<int16_t>{1, 2}, Point<int16_t>{1, 3}, Point<int16_t>{1, 4}};
const auto distances = SymbolLayout::calculateTileDistances(line, Anchor(1.0f, 3.0f, .0f, 2u));
EXPECT_EQ(distances, std::vector<float>({2.0f, 1.0f, 0.0f, 1.0f}));
}
TEST(calculateTileDistances, EmptySegment) {
const GeometryCoordinates line = {};
const auto distances = SymbolLayout::calculateTileDistances(line, Anchor(1.0f, 1.0f, .0f));
EXPECT_EQ(distances.size(), 0u);
}

View File

@@ -0,0 +1,419 @@
#include <mbgl/map/transform_state.hpp>
#include <mbgl/renderer/buckets/symbol_bucket.hpp>
#include <mbgl/test/util.hpp>
#include <mbgl/text/cross_tile_symbol_index.hpp>
using namespace mbgl;
SymbolInstance makeSymbolInstance(float x, float y, std::u16string key) {
GeometryCoordinates line;
ImageMap imageMap;
const ShapedTextOrientations shaping{};
style::SymbolLayoutProperties::Evaluated layout_;
IndexedSubfeature subfeature(0, "", "", 0);
Anchor anchor(x, y, 0, 0);
std::array<float, 2> textOffset{{0.0f, 0.0f}};
std::array<float, 2> iconOffset{{0.0f, 0.0f}};
std::array<float, 2> variableTextOffset{{0.0f, 0.0f}};
style::SymbolPlacementType placementType = style::SymbolPlacementType::Point;
auto sharedData = std::make_shared<SymbolInstanceSharedData>(std::move(line),
shaping,
nullopt,
nullopt,
layout_,
placementType,
textOffset,
imageMap,
0,
SymbolContent::IconSDF,
false,
false);
return SymbolInstance(anchor, std::move(sharedData), shaping, nullopt, nullopt, 0, 0, placementType, textOffset, 0, 0, iconOffset, subfeature, 0, 0, key, 0.0f, 0.0f, 0.0f, variableTextOffset, false);
}
TEST(CrossTileSymbolLayerIndex, addBucket) {
uint32_t maxCrossTileID = 0;
uint32_t maxBucketInstanceId = 0;
CrossTileSymbolLayerIndex index(maxCrossTileID);
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout =
makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>();
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
std::string bucketLeaderID = "test";
OverscaledTileID mainID(6, 0, 6, 8, 8);
std::vector<SymbolInstance> mainInstances;
std::vector<SortKeyRange> mainRanges;
mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit"));
mainInstances.push_back(makeSymbolInstance(2000, 2000, u"Toronto"));
SymbolBucket mainBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(mainInstances),
std::move(mainRanges),
1.0f,
false,
{},
false /*iconsInText*/};
mainBucket.bucketInstanceId = ++maxBucketInstanceId;
index.addBucket(mainID, mat4{}, mainBucket);
// Assigned new IDs
ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u);
ASSERT_EQ(mainBucket.symbolInstances.at(1).crossTileID, 2u);
OverscaledTileID childID(7, 0, 7, 16, 16);
std::vector<SymbolInstance> childInstances;
std::vector<SortKeyRange> childRanges;
childInstances.push_back(makeSymbolInstance(2000, 2000, u"Detroit"));
childInstances.push_back(makeSymbolInstance(2000, 2000, u"Windsor"));
childInstances.push_back(makeSymbolInstance(3000, 3000, u"Toronto"));
childInstances.push_back(makeSymbolInstance(4001, 4001, u"Toronto"));
SymbolBucket childBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(childInstances),
std::move(childRanges),
1.0f,
false,
{},
false /*iconsInText*/};
childBucket.bucketInstanceId = ++maxBucketInstanceId;
index.addBucket(childID, mat4{}, childBucket);
// matched parent tile
ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 1u);
// does not match because of different key
ASSERT_EQ(childBucket.symbolInstances.at(1).crossTileID, 3u);
// does not match because of different location
ASSERT_EQ(childBucket.symbolInstances.at(2).crossTileID, 4u);
// matches with a slightly different location
ASSERT_EQ(childBucket.symbolInstances.at(3).crossTileID, 2u);
OverscaledTileID parentID(5, 0, 5, 4, 4);
std::vector<SymbolInstance> parentInstances;
std::vector<SortKeyRange> parentRanges;
parentInstances.push_back(makeSymbolInstance(500, 500, u"Detroit"));
SymbolBucket parentBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(parentInstances),
std::move(parentRanges),
1.0f,
false,
{},
false /*iconsInText*/};
parentBucket.bucketInstanceId = ++maxBucketInstanceId;
index.addBucket(parentID, mat4{}, parentBucket);
// matched child tile
ASSERT_EQ(parentBucket.symbolInstances.at(0).crossTileID, 1u);
std::unordered_set<uint32_t> currentIDs;
currentIDs.insert(mainBucket.bucketInstanceId);
index.removeStaleBuckets(currentIDs);
// grandchild
OverscaledTileID grandchildID(8, 0, 8, 32, 32);
std::vector<SymbolInstance> grandchildInstances;
std::vector<SortKeyRange> grandchildRanges;
grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Detroit"));
grandchildInstances.push_back(makeSymbolInstance(4000, 4000, u"Windsor"));
SymbolBucket grandchildBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(grandchildInstances),
std::move(grandchildRanges),
1.0f,
false,
{},
false /*iconsInText*/};
grandchildBucket.bucketInstanceId = ++maxBucketInstanceId;
index.addBucket(grandchildID, mat4{}, grandchildBucket);
// Matches the symbol in `mainBucket`
ASSERT_EQ(grandchildBucket.symbolInstances.at(0).crossTileID, 1u);
// Does not match the previous value for Windsor because that tile was removed
ASSERT_EQ(grandchildBucket.symbolInstances.at(1).crossTileID, 5u);
}
TEST(CrossTileSymbolLayerIndex, resetIDs) {
uint32_t maxCrossTileID = 0;
uint32_t maxBucketInstanceId = 0;
CrossTileSymbolLayerIndex index(maxCrossTileID);
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout =
makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>();
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
std::string bucketLeaderID = "test";
OverscaledTileID mainID(6, 0, 6, 8, 8);
std::vector<SymbolInstance> mainInstances;
std::vector<SortKeyRange> mainRanges;
mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Detroit"));
SymbolBucket mainBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(mainInstances),
std::move(mainRanges),
1.0f,
false,
{},
false /*iconsInText*/};
mainBucket.bucketInstanceId = ++maxBucketInstanceId;
OverscaledTileID childID(7, 0, 7, 16, 16);
std::vector<SymbolInstance> childInstances;
std::vector<SortKeyRange> childRanges;
childInstances.push_back(makeSymbolInstance(2000, 2000, u"Detroit"));
SymbolBucket childBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(childInstances),
std::move(childRanges),
1.0f,
false,
{},
false /*iconsInText*/};
childBucket.bucketInstanceId = ++maxBucketInstanceId;
// assigns a new id
index.addBucket(mainID, mat4{}, mainBucket);
ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u);
// removes the tile
std::unordered_set<uint32_t> currentIDs;
index.removeStaleBuckets(currentIDs);
// assigns a new id
index.addBucket(childID, mat4{}, childBucket);
ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 2u);
// overwrites the old id to match the already-added tile
index.addBucket(mainID, mat4{}, mainBucket);
ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 2u);
}
TEST(CrossTileSymbolLayerIndex, noDuplicatesWithinZoomLevel) {
uint32_t maxCrossTileID = 0;
uint32_t maxBucketInstanceId = 0;
CrossTileSymbolLayerIndex index(maxCrossTileID);
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout =
makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>();
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
std::string bucketLeaderID = "test";
OverscaledTileID mainID(6, 0, 6, 8, 8);
std::vector<SymbolInstance> mainInstances;
std::vector<SortKeyRange> mainRanges;
mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A
mainInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B
SymbolBucket mainBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(mainInstances),
std::move(mainRanges),
1.0f,
false,
{},
false /*iconsInText*/};
mainBucket.bucketInstanceId = ++maxBucketInstanceId;
OverscaledTileID childID(7, 0, 7, 16, 16);
std::vector<SymbolInstance> childInstances;
std::vector<SortKeyRange> childRanges;
childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // A'
childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // B'
childInstances.push_back(makeSymbolInstance(2000, 2000, u"")); // C'
SymbolBucket childBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(childInstances),
std::move(childRanges),
1.0f,
false,
{},
false /*iconsInText*/};
childBucket.bucketInstanceId = ++maxBucketInstanceId;
// assigns new ids
index.addBucket(mainID, mat4{}, mainBucket);
ASSERT_EQ(mainBucket.symbolInstances.at(0).crossTileID, 1u);
ASSERT_EQ(mainBucket.symbolInstances.at(1).crossTileID, 2u);
// copies parent ids without duplicate ids in this tile
index.addBucket(childID, mat4{}, childBucket);
ASSERT_EQ(childBucket.symbolInstances.at(0).crossTileID, 1u); // A' copies from A
ASSERT_EQ(childBucket.symbolInstances.at(1).crossTileID, 2u); // B' copies from B
ASSERT_EQ(childBucket.symbolInstances.at(2).crossTileID, 3u); // C' gets new ID
}
TEST(CrossTileSymbolLayerIndex, bucketReplacement) {
uint32_t maxCrossTileID = 0;
uint32_t maxBucketInstanceId = 0;
CrossTileSymbolLayerIndex index(maxCrossTileID);
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout =
makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>();
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
std::string bucketLeaderID = "test";
OverscaledTileID tileID(6, 0, 6, 8, 8);
std::vector<SymbolInstance> firstInstances;
std::vector<SortKeyRange> firstRanges;
firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A
firstInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B
SymbolBucket firstBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(firstInstances),
std::move(firstRanges),
1.0f,
false,
{},
false /*iconsInText*/};
firstBucket.bucketInstanceId = ++maxBucketInstanceId;
std::vector<SymbolInstance> secondInstances;
std::vector<SortKeyRange> secondRanges;
secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // A'
secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // B'
secondInstances.push_back(makeSymbolInstance(1000, 1000, u"")); // C'
SymbolBucket secondBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(secondInstances),
std::move(secondRanges),
1.0f,
false,
{},
false /*iconsInText*/};
secondBucket.bucketInstanceId = ++maxBucketInstanceId;
// assigns new ids
index.addBucket(tileID, mat4{}, firstBucket);
ASSERT_EQ(firstBucket.symbolInstances.at(0).crossTileID, 1u);
ASSERT_EQ(firstBucket.symbolInstances.at(1).crossTileID, 2u);
// copies parent ids without duplicate ids in this tile
index.addBucket(tileID, mat4{}, secondBucket);
ASSERT_EQ(secondBucket.symbolInstances.at(0).crossTileID, 1u); // A' copies from A
ASSERT_EQ(secondBucket.symbolInstances.at(1).crossTileID, 2u); // B' copies from B
ASSERT_EQ(secondBucket.symbolInstances.at(2).crossTileID, 3u); // C' gets new ID
}
namespace {
void populatePosMatrix(mat4& posMatrix, const OverscaledTileID& tileId, double lat, double lon, double zoom) {
TransformState transformState;
transformState.setSize({512, 512});
transformState.setLatLngZoom(LatLng(lat, lon), zoom);
transformState.matrixFor(posMatrix, tileId.toUnwrapped());
matrix::multiply(posMatrix, transformState.getProjectionMatrix(), posMatrix);
}
} // namespace
TEST(CrossTileSymbolLayerIndex, offscreenSymbols) {
uint32_t maxCrossTileID = 0;
CrossTileSymbolLayerIndex index(maxCrossTileID);
Immutable<style::SymbolLayoutProperties::PossiblyEvaluated> layout =
makeMutable<style::SymbolLayoutProperties::PossiblyEvaluated>();
bool iconsNeedLinear = false;
bool sortFeaturesByY = false;
std::string bucketLeaderID = "test";
OverscaledTileID tileId(7, 0, 6, 18, 24);
std::vector<SymbolInstance> mainInstances;
mainInstances.push_back(makeSymbolInstance(1000, 1000, u"Washington"));
mainInstances.push_back(makeSymbolInstance(2000, 2000, u"Richmond"));
std::vector<SortKeyRange> mainRanges;
SymbolBucket symbolBucket{layout,
{},
16.0f,
1.0f,
0,
iconsNeedLinear,
sortFeaturesByY,
bucketLeaderID,
std::move(mainInstances),
std::move(mainRanges),
1.0f,
false,
{},
false /*iconsInText*/};
mat4 posMatrix;
populatePosMatrix(posMatrix, tileId, 60.0, 25.0, 7.0);
index.addBucket(tileId, posMatrix, symbolBucket);
EXPECT_EQ(symbolBucket.symbolInstances.at(0).crossTileID, SymbolInstance::invalidCrossTileID());
EXPECT_EQ(symbolBucket.symbolInstances.at(1).crossTileID, SymbolInstance::invalidCrossTileID());
populatePosMatrix(posMatrix, tileId, 39.0, -76.0, 7.0);
index.addBucket(tileId, posMatrix, symbolBucket);
EXPECT_EQ(symbolBucket.symbolInstances.at(0).crossTileID, 1u);
EXPECT_EQ(symbolBucket.symbolInstances.at(1).crossTileID, 2u);
}

View File

@@ -0,0 +1,51 @@
#include <mbgl/style/expression/formatted.hpp>
#include <mbgl/test/util.hpp>
#include <mbgl/util/optional.hpp>
using namespace mbgl;
using namespace mbgl::style::expression;
TEST(Formatted, Equality) {
Formatted text{"Formatted"};
auto emptyImage = FormattedSection{style::expression::Image("Formatted")};
Formatted image{std::vector<FormattedSection>{emptyImage}};
EXPECT_FALSE(text == image);
EXPECT_EQ(text, text);
EXPECT_EQ(image, image);
}
TEST(Formatted, Empty) {
Formatted emptyFormatted{""};
EXPECT_TRUE(emptyFormatted.empty());
auto emptyText = FormattedSection{"", nullopt, nullopt, nullopt};
auto emptyImage = FormattedSection{style::expression::Image()};
Formatted multipleEmptySections{std::vector<FormattedSection>{emptyText, emptyText, emptyText}};
EXPECT_TRUE(multipleEmptySections.empty());
Formatted multipleEmptySectionsWithImage{std::vector<FormattedSection>{emptyText, emptyImage, emptyText}};
EXPECT_TRUE(multipleEmptySectionsWithImage.empty());
auto text = FormattedSection{"Formatted", nullopt, nullopt, nullopt};
auto image = FormattedSection{style::expression::Image("Image")};
Formatted multipleSections{std::vector<FormattedSection>{emptyText, text, emptyText}};
EXPECT_FALSE(multipleSections.empty());
Formatted multipleSectionsWithImage{std::vector<FormattedSection>{emptyText, image, text}};
EXPECT_FALSE(multipleSectionsWithImage.empty());
}
TEST(Formatted, ToString) {
Formatted emptyFormatted{""};
EXPECT_EQ(emptyFormatted.toString(), "");
auto text = FormattedSection{"Formatted", nullopt, nullopt, nullopt};
Formatted multipleSections{std::vector<FormattedSection>{text, text}};
EXPECT_EQ(multipleSections.toString(), "FormattedFormatted");
auto image = FormattedSection{style::expression::Image("Image")};
Formatted multipleEmptySectionsWithImage{std::vector<FormattedSection>{text, image}};
EXPECT_EQ(multipleEmptySectionsWithImage.toString(), "Formatted");
}

View File

@@ -0,0 +1,130 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/get_anchors.hpp>
using namespace mbgl;
namespace mbgl {
constexpr inline bool operator==(const Anchor& rhs, const Anchor& lhs) {
return rhs.point == lhs.point && rhs.angle == lhs.angle && rhs.segment == lhs.segment;
}
} // namespace mbgl
namespace {
const auto makeLine = [](std::size_t shift = 0u) {
GeometryCoordinates line;
for (std::size_t i = shift; i < 10u + shift; ++i) {
line.push_back({1, static_cast<int16_t>(i)});
}
return line;
};
const GeometryCoordinates continuedLine = makeLine();
const GeometryCoordinates nonContinuedLine = makeLine(1u);
const float smallSpacing = 2.0f;
const float bigSpacing = 3.0f;
const float textLeft = -1.0f;
const float textRight = 1.0f;
const float iconLeft = -0.5f;
const float iconRight = 0.5f;
const float glyphSize = 0.1f;
const float boxScale = 1.0f;
const float overscaling = 1.0f;
} // namespace
TEST(getAnchors, NonContinuedLineShortLabels) {
const Anchors anchors = getAnchors(
nonContinuedLine, bigSpacing, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale, overscaling);
const Anchors expected_anchors = {Anchor(1.0f, 2.0f, 1.570796371f, 1u),
Anchor(1.0f, 5.0f, 1.570796371f, 4u),
Anchor(1.0f, 8.0f, 1.570796371f, 7u)};
EXPECT_EQ(anchors, expected_anchors);
}
TEST(getAnchors, NonContinuedLineLongLabels) {
const Anchors anchors = getAnchors(nonContinuedLine,
smallSpacing,
M_PI,
textLeft,
textRight,
iconLeft,
iconRight,
glyphSize,
boxScale,
overscaling);
const Anchors expected_anchors = {Anchor(1.0f, 2.0f, 1.570796371f, 1u),
Anchor(1.0f, 5.0f, 1.570796371f, 3u),
Anchor(1.0f, 7.0f, 1.570796371f, 6u)};
EXPECT_EQ(anchors, expected_anchors);
}
TEST(getAnchors, ContinuedLineShortLabels) {
const Anchors anchors = getAnchors(
continuedLine, bigSpacing, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale, overscaling);
const Anchors expected_anchors = {Anchor(1.0f, 2.0f, 1.570796371f, 1u),
Anchor(1.0f, 5.0f, 1.570796371f, 4u),
Anchor(1.0f, 8.0f, 1.570796371f, 7u)};
EXPECT_EQ(anchors, expected_anchors);
}
TEST(getAnchors, ContinuedLineLongLabels) {
const Anchors anchors = getAnchors(
continuedLine, smallSpacing, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale, overscaling);
const Anchors expected_anchors = {Anchor(1.0f, 1.0f, 1.570796371f, 1u),
Anchor(1.0f, 4.0f, 1.570796371f, 3u),
Anchor(1.0f, 6.0f, 1.570796371f, 6u)};
EXPECT_EQ(anchors, expected_anchors);
}
TEST(getAnchors, OverscaledAnchorsInParent) {
const Anchors anchors = getAnchors(
nonContinuedLine, bigSpacing, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale, overscaling);
const Anchors childAnchors = getAnchors(nonContinuedLine,
bigSpacing / 2,
M_PI,
textLeft,
textRight,
iconLeft,
iconRight,
glyphSize,
0.5f /*boxScale*/,
2.0f /*overscaling*/);
for (const auto& anchor : anchors) {
EXPECT_TRUE(std::find(childAnchors.begin(), childAnchors.end(), anchor) != childAnchors.end());
}
}
TEST(getAnchors, UseMidpointForShortLine) {
const GeometryCoordinates shortLine = {Point<int16_t>{1, 1}, Point<int16_t>{1, 3}};
const Anchors anchors = getAnchors(
shortLine, smallSpacing, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale, overscaling);
const Anchors expected_anchors = {Anchor(1.0f, 2.0f, 1.570796371f, 0u)};
EXPECT_EQ(anchors, expected_anchors);
}
TEST(getAnchors, GetCenterAnchor) {
const GeometryCoordinates line = {
Point<int16_t>{1, 1}, Point<int16_t>{1, 3}, Point<int16_t>{3, 6}, Point<int16_t>{4, 7}};
const auto anchor = getCenterAnchor(line, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale);
EXPECT_TRUE(anchor);
EXPECT_EQ(*anchor, Anchor(2.0f, 4.0f, .982793748f, 1u));
}
TEST(getAnchors, GetCenterAnchorOutsideTileBounds) {
const GeometryCoordinates line = {Point<int16_t>{-10, -10}, Point<int16_t>{5, 5}};
const auto anchor = getCenterAnchor(line, M_PI, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale);
EXPECT_TRUE(anchor);
EXPECT_EQ(*anchor, Anchor(-3.0f, -3.0f, .785398185f, 0u));
}
TEST(getAnchors, GetCenterAnchorFailMaxAngle) {
const GeometryCoordinates line = {Point<int16_t>{1, 1}, Point<int16_t>{1, 3}, Point<int16_t>{3, 3}};
const auto anchor = getCenterAnchor(line, M_PI / 4, textLeft, textRight, iconLeft, iconRight, glyphSize, boxScale);
EXPECT_FALSE(anchor);
}

View File

@@ -0,0 +1,392 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
#include <mbgl/text/glyph_manager.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/i18n.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/logging.hpp>
using namespace mbgl;
// Alpha channel rendering of '中'
static constexpr const uint8_t stubBitmap[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 95, 82, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 45, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 255, 227, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 22, 18, 18, 18, 18, 18, 15, 55, 255, 255, 42, 14, 18, 18, 18, 18, 18, 22, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 167, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 178, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 255, 120, 8, 15, 15, 15, 11, 51, 255, 233, 32, 11, 15, 15, 15, 8, 93, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 255, 100, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 84, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 255, 107, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 84, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 255, 107, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 84, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 255, 100, 0, 0, 0, 0, 0, 32, 255, 227, 10, 0, 0, 0, 0, 0, 78, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84, 255, 170, 101, 101, 101, 101, 101, 129, 255, 255, 133, 100, 101, 101, 101, 96, 191, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 105, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 178, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1, 156, 255, 172, 110, 116, 116, 116, 115, 140, 255, 242, 127, 116, 116, 116, 116, 110, 163, 255, 169, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145, 255, 108, 0, 0, 0, 0, 0, 32, 255, 227, 10, 0, 0, 0, 0, 0, 89, 255, 194, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 27, 16, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 14, 27, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37, 255, 233, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 73, 205, 167, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
// SDF transformation of '中'
static constexpr const uint8_t sdfBitmap[] = {0, 0, 0, 0, 0, 0, 0, 0, 19, 48, 76, 101, 119, 127, 127, 127, 126, 118, 100, 75, 48, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 19, 28, 31, 31, 31, 31, 59, 90, 119, 145, 158, 159, 159, 156, 144, 118, 89, 59, 31, 31, 31, 31, 28, 19, 5, 0, 0, 0, 10, 31, 48, 59, 63, 63, 63, 63, 63, 95, 127, 158, 182, 187, 186, 176, 156, 126, 94, 63, 63, 63, 63, 63, 59, 48, 31, 10, 0, 5, 31, 55, 75, 89, 94, 95, 95, 95, 95, 95, 127, 158, 181, 223, 204, 177, 157, 126, 95, 95, 95, 95, 95, 95, 89, 75, 55, 31, 10, 19, 48, 75, 100, 118, 126, 126, 126, 126, 126, 126, 127, 157, 180, 223, 204, 177, 157, 126, 126, 126, 126, 126, 126, 126, 119, 100, 75, 55, 31, 28, 59, 89, 118, 144, 156, 157, 157, 157, 157, 157, 157, 157, 180, 223, 204, 177, 156, 157, 157, 157, 157, 157, 157, 156, 144, 119, 100, 75, 48, 31, 63, 94, 126, 156, 176, 178, 178, 178, 178, 178, 178, 177, 182, 223, 223, 181, 177, 178, 178, 178, 178, 178, 178, 177, 156, 144, 118, 89, 59, 32, 64, 96, 128, 159, 193, 223, 223, 223, 223, 223, 223, 223, 223, 236, 236, 223, 223, 223, 223, 223, 223, 223, 223, 196, 176, 156, 126, 94, 63, 32, 64, 96, 128, 159, 194, 223, 223, 223, 223, 223, 223, 223, 223, 236, 226, 223, 223, 223, 223, 223, 223, 223, 224, 198, 176, 156, 126, 94, 63, 32, 64, 96, 127, 159, 187, 223, 190, 176, 177, 177, 177, 177, 182, 223, 204, 179, 177, 177, 177, 177, 176, 187, 223, 198, 176, 156, 126, 94, 63, 32, 64, 95, 127, 159, 186, 223, 188, 159, 156, 156, 156, 157, 180, 223, 204, 177, 157, 156, 156, 156, 159, 186, 223, 198, 176, 156, 126, 94, 63, 32, 64, 95, 127, 159, 186, 223, 189, 159, 127, 126, 127, 157, 180, 223, 204, 177, 157, 126, 126, 127, 159, 186, 223, 198, 176, 156, 126, 94, 63, 32, 64, 95, 127, 159, 186, 223, 189, 159, 127, 127, 127, 157, 180, 223, 204, 177, 157, 127, 127, 127, 159, 186, 223, 198, 176, 156, 126, 94, 63, 32, 64, 95, 127, 159, 186, 223, 188, 159, 159, 159, 159, 159, 179, 223, 204, 177, 159, 159, 159, 159, 159, 185, 223, 198, 176, 156, 126, 94, 63, 48, 75, 100, 127, 159, 186, 223, 197, 188, 188, 188, 188, 188, 191, 223, 223, 192, 188, 188, 188, 188, 187, 199, 224, 198, 176, 156, 126, 94, 63, 59, 89, 118, 143, 159, 188, 223, 224, 223, 223, 223, 223, 223, 223, 236, 226, 223, 223, 223, 223, 223, 223, 223, 224, 198, 176, 156, 126, 94, 63, 63, 94, 126, 156, 175, 195, 223, 197, 189, 190, 190, 190, 190, 193, 223, 206, 191, 190, 190, 190, 190, 189, 196, 223, 196, 176, 156, 126, 94, 63, 59, 89, 118, 143, 159, 193, 223, 189, 159, 159, 159, 159, 159, 179, 223, 204, 177, 159, 159, 159, 159, 159, 186, 223, 200, 176, 156, 126, 94, 63, 48, 75, 100, 126, 156, 176, 179, 177, 156, 127, 127, 127, 157, 180, 223, 204, 177, 157, 127, 127, 127, 156, 177, 179, 178, 157, 144, 118, 89, 59, 31, 59, 89, 118, 144, 156, 157, 156, 144, 119, 96, 127, 157, 180, 223, 204, 177, 157, 126, 96, 119, 144, 156, 157, 157, 144, 119, 100, 75, 48, 19, 48, 75, 100, 118, 126, 126, 126, 119, 100, 95, 127, 157, 180, 223, 204, 177, 157, 126, 95, 100, 119, 126, 126, 126, 119, 100, 76, 55, 31, 5, 31, 55, 75, 89, 94, 95, 95, 89, 75, 95, 127, 157, 180, 223, 204, 177, 157, 126, 95, 75, 89, 95, 95, 95, 90, 76, 55, 31, 10, 0, 10, 31, 48, 59, 63, 63, 63, 59, 63, 95, 127, 157, 180, 223, 204, 177, 157, 126, 95, 63, 59, 63, 63, 63, 59, 48, 31, 10, 0, 0, 0, 5, 19, 28, 31, 31, 31, 31, 63, 95, 127, 157, 180, 223, 204, 177, 157, 126, 95, 63, 31, 31, 31, 31, 28, 19, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 64, 95, 127, 159, 184, 201, 196, 176, 156, 126, 94, 63, 31, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 60, 90, 120, 146, 159, 159, 159, 156, 144, 118, 89, 59, 28, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 49, 76, 101, 120, 127, 128, 128, 126, 118, 100, 75, 48, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 32, 56, 76, 90, 95, 96, 96, 94, 89, 75, 55, 31, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 32, 49, 60, 64, 64, 64, 63, 59, 48, 31, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 19, 29, 32, 32, 32, 31, 28, 19, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
static constexpr const size_t stubBitmapLength = 900;
class StubLocalGlyphRasterizer : public LocalGlyphRasterizer {
public:
bool canRasterizeGlyph(const FontStack&, GlyphID glyphID) override {
return util::i18n::allowsFixedWidthGlyphGeneration(glyphID);
}
Glyph rasterizeGlyph(const FontStack&, GlyphID glyphID) override {
Glyph stub;
stub.id = glyphID;
stub.metrics.width = 24;
stub.metrics.height = 24;
stub.metrics.left = 0;
stub.metrics.top = -8;
stub.metrics.advance = 24;
stub.bitmap = AlphaImage(Size(30, 30), stubBitmap, stubBitmapLength);
return stub;
}
};
class StubGlyphManagerObserver : public GlyphManagerObserver {
public:
void onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) override {
if (glyphsLoaded) glyphsLoaded(fontStack, glyphRange);
}
void onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) override {
if (glyphsError) glyphsError(fontStack, glyphRange, error);
}
std::function<void (const FontStack&, const GlyphRange&)> glyphsLoaded;
std::function<void (const FontStack&, const GlyphRange&, std::exception_ptr)> glyphsError;
};
class StubGlyphRequestor : public GlyphRequestor {
public:
void onGlyphsAvailable(GlyphMap glyphs) override {
if (glyphsAvailable) glyphsAvailable(std::move(glyphs));
}
std::function<void (GlyphMap)> glyphsAvailable;
};
class GlyphManagerTest {
public:
util::RunLoop loop;
StubFileSource fileSource;
StubGlyphManagerObserver observer;
StubGlyphRequestor requestor;
GlyphManager glyphManager{ std::make_unique<StubLocalGlyphRasterizer>() };
void run(const std::string& url, GlyphDependencies dependencies) {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
glyphManager.setURL(url);
glyphManager.setObserver(&observer);
glyphManager.getGlyphs(requestor, std::move(dependencies), fileSource);
loop.run();
}
void end() {
loop.stop();
}
};
TEST(GlyphManager, LoadingSuccess) {
GlyphManagerTest test;
test.fileSource.glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.observer.glyphsError = [&] (const FontStack&, const GlyphRange&, std::exception_ptr) {
FAIL();
test.end();
};
test.observer.glyphsLoaded = [&] (const FontStack& fontStack, const GlyphRange& range) {
ASSERT_EQ(fontStack, FontStack {{"Test Stack"}});
ASSERT_EQ(range, GlyphRange(0, 255));
};
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
ASSERT_EQ(testPositions.size(), 3u);
ASSERT_EQ(testPositions.count(u'a'), 1u);
ASSERT_EQ(testPositions.count(u'å'), 1u);
ASSERT_EQ(testPositions.count(u' '), 1u);
ASSERT_TRUE(bool(testPositions.at(u' ')));
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'a', u'å', u' '}}
});
}
TEST(GlyphManager, LoadingFail) {
GlyphManagerTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
Response response;
response.error = std::make_unique<Response::Error>(
Response::Error::Reason::Other,
"Failed by the test case");
return response;
};
test.observer.glyphsError = [&] (const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) {
EXPECT_EQ(fontStack, FontStack({"Test Stack"}));
EXPECT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "Failed by the test case");
test.end();
};
test.requestor.glyphsAvailable = [&] (GlyphMap) {
FAIL();
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'a', u'å'}}
});
}
TEST(GlyphManager, LoadingCorrupted) {
GlyphManagerTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
Response response;
response.data = std::make_unique<std::string>("CORRUPTED");
return response;
};
test.observer.glyphsError = [&] (const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) {
EXPECT_EQ(fontStack, FontStack({"Test Stack"}));
EXPECT_EQ(glyphRange, GlyphRange(0, 255));
EXPECT_TRUE(error != nullptr);
EXPECT_EQ(util::toString(error), "unknown pbf field type exception");
test.end();
};
test.requestor.glyphsAvailable = [&] (GlyphMap) {
FAIL();
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'a', u'å'}}
});
}
TEST(GlyphManager, LoadingCancel) {
GlyphManagerTest test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
test.end();
return optional<Response>();
};
test.observer.glyphsLoaded = [&] (const FontStack&, const GlyphRange&) {
FAIL() << "Should never be called";
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'a', u'å'}}
});
}
TEST(GlyphManager, LoadLocalCJKGlyph) {
GlyphManagerTest test;
int glyphResponses = 0;
test.fileSource.glyphsResponse = [&] (const Resource&) {
glyphResponses++;
return optional<Response>();
};
test.observer.glyphsLoaded = [&] (const FontStack&, const GlyphRange&) {
glyphResponses++;
};
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
EXPECT_EQ(glyphResponses, 0); // Local generation should prevent requesting any glyphs
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
ASSERT_EQ(testPositions.size(), 1u);
ASSERT_EQ(testPositions.count(u'中'), 1u);
Immutable<Glyph> glyph = *testPositions.at(u'中');
EXPECT_EQ(glyph->id, u'中');
EXPECT_EQ(glyph->metrics.width, 24ul);
EXPECT_EQ(glyph->metrics.height, 24ul);
EXPECT_EQ(glyph->metrics.left, 0);
EXPECT_EQ(glyph->metrics.top, -8);
EXPECT_EQ(glyph->metrics.advance, 24ul);
EXPECT_EQ(glyph->bitmap.size, Size(30, 30));
size_t pixelCount = glyph->bitmap.size.width * glyph->bitmap.size.height;
for (size_t i = 0; i < pixelCount; i++) {
EXPECT_EQ(glyph->bitmap.data[i], sdfBitmap[i]);
}
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'中'}}
});
}
TEST(GlyphManager, LoadLocalCJKGlyphAfterLoadingRangeFromURL) {
GlyphManagerTest test;
int firstGlyphResponse = false;
test.fileSource.glyphsResponse = [&] (const Resource&) {
firstGlyphResponse = true;
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs-12244-12543.pbf"));
return response;
};
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
if (firstGlyphResponse == true) {
firstGlyphResponse = false;
ASSERT_EQ(testPositions.size(), 1u);
ASSERT_EQ(testPositions.count(u'々'), 1u);
//Katakana letter te, should be locally rasterized
// instead of using the glyph recieved from the range
// for the ideagraphic mark
test.glyphManager.getGlyphs(test.requestor,
GlyphDependencies {
{{{"Test Stack"}}, {u'テ'}} // 0x30c6
},
test.fileSource);
} else {
ASSERT_EQ(testPositions.size(), 1u);
ASSERT_EQ(testPositions.count(u'テ'), 1u);
Immutable<Glyph> glyph = *testPositions.at(u'テ');
EXPECT_EQ(glyph->id, u'テ');
EXPECT_EQ(glyph->metrics.width, 24ul);
EXPECT_EQ(glyph->metrics.height, 24ul);
EXPECT_EQ(glyph->metrics.left, 0);
EXPECT_EQ(glyph->metrics.top, -8);
EXPECT_EQ(glyph->metrics.advance, 24ul);
EXPECT_EQ(glyph->bitmap.size, Size(30, 30));
test.end();
}
};
test.run(
"test/fixtures/resources/glyphs-12244-12543.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'々'}} //0x3005
});
}
TEST(GlyphManager, LoadingInvalid) {
GlyphManagerTest test;
test.fileSource.glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/fake_glyphs-0-255.pbf"));
return response;
};
test.observer.glyphsError = [&] (const FontStack&, const GlyphRange&, std::exception_ptr) {
FAIL();
test.end();
};
test.observer.glyphsLoaded = [&] (const FontStack& fontStack, const GlyphRange& range) {
ASSERT_EQ(fontStack, FontStack {{"Test Stack"}});
ASSERT_EQ(range, GlyphRange(0, 255));
};
test.requestor.glyphsAvailable = [&] (GlyphMap glyphs) {
const auto& testPositions = glyphs.at(FontStackHasher()({{"Test Stack"}}));
ASSERT_EQ(testPositions.size(), 2u);
ASSERT_FALSE(bool(testPositions.at(u'A')));
ASSERT_TRUE(bool(testPositions.at(u'E')));
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'A', u'E'}}
});
}
TEST(GlyphManager, ImmediateFileSource) {
class GlyphManagerTestSynchronous {
public:
util::RunLoop loop;
StubFileSource fileSource = { StubFileSource::ResponseType::Synchronous };
StubGlyphManagerObserver observer;
StubGlyphRequestor requestor;
GlyphManager glyphManager;
void run(const std::string& url, GlyphDependencies dependencies) {
// Squelch logging.
Log::setObserver(std::make_unique<Log::NullObserver>());
glyphManager.setURL(url);
glyphManager.setObserver(&observer);
glyphManager.getGlyphs(requestor, std::move(dependencies), fileSource);
loop.run();
}
void end() {
loop.stop();
}
};
GlyphManagerTestSynchronous test;
test.fileSource.glyphsResponse = [&] (const Resource&) {
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.observer.glyphsError = [&] (const FontStack&, const GlyphRange&, std::exception_ptr) {
FAIL();
test.end();
};
test.requestor.glyphsAvailable = [&] (GlyphMap) {
test.end();
};
test.run(
"test/fixtures/resources/glyphs.pbf",
GlyphDependencies {
{{{"Test Stack"}}, {u'a', u'å', u' '}}
});
}

View File

@@ -0,0 +1,23 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/glyph_pbf.hpp>
#include <mbgl/util/io.hpp>
using namespace mbgl;
TEST(GlyphPBF, Parsing) {
// The fake glyphs contain a number of invalid glyphs, which should be skipped by the parser.
auto sdfs = parseGlyphPBF(GlyphRange { 0, 255 }, util::read_file("test/fixtures/resources/fake_glyphs-0-255.pbf"));
EXPECT_TRUE(sdfs.size() == 1);
auto& sdf = sdfs[0];
EXPECT_EQ(69u, sdf.id);
AlphaImage expected({7, 7});
expected.fill('x');
EXPECT_EQ(expected, sdf.bitmap);
EXPECT_EQ(1u, sdf.metrics.width);
EXPECT_EQ(1u, sdf.metrics.height);
EXPECT_EQ(20, sdf.metrics.left);
EXPECT_EQ(2, sdf.metrics.top);
EXPECT_EQ(8u, sdf.metrics.advance);
}

View File

@@ -0,0 +1,45 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/language_tag.hpp>
using namespace mbgl;
TEST(LanguageTag, Parsing) {
LanguageTag bareLanguage = LanguageTag::fromBCP47("de");
EXPECT_EQ(*(bareLanguage.language), "de");
EXPECT_FALSE(bareLanguage.script);
EXPECT_FALSE(bareLanguage.region);
LanguageTag languageAndScript = LanguageTag::fromBCP47("de-hans");
EXPECT_EQ(*(languageAndScript.language), "de");
EXPECT_EQ(*(languageAndScript.script), "hans");
EXPECT_FALSE(languageAndScript.region);
LanguageTag languageAndRegion = LanguageTag::fromBCP47("de-US");
EXPECT_EQ(*(languageAndRegion.language), "de");
EXPECT_FALSE(languageAndRegion.script);
EXPECT_EQ(*(languageAndRegion.region), "US");
LanguageTag kitchenSink = LanguageTag::fromBCP47("de-ext-two-Hans-US-variant-x-private");
EXPECT_EQ(*(kitchenSink.language), "de");
EXPECT_EQ(*(kitchenSink.script), "Hans");
EXPECT_EQ(*(kitchenSink.region), "US");
}
TEST(LanguageTag, Generation) {
LanguageTag deHansUS({"de"}, {"hans"}, {"US"});
EXPECT_EQ(deHansUS.toBCP47(), "de-hans-US");
LanguageTag deUS({"de"}, {}, {"US"});
EXPECT_EQ(deUS.toBCP47(), "de-US");
LanguageTag noLanguage({}, {"hans"}, {"US"});
EXPECT_EQ(noLanguage.toBCP47(), "");
LanguageTag deHans({"de"}, {"hans"}, {});
EXPECT_EQ(deHans.toBCP47(), "de-hans");
LanguageTag de({"de"}, {}, {});
EXPECT_EQ(de.toBCP47(), "de");
}

View File

@@ -0,0 +1,165 @@
#include <mbgl/test/util.hpp>
#include <mbgl/test/stub_file_source.hpp>
#include <mbgl/test/map_adapter.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/color.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/style/style.hpp>
/*
LoadLocalCJKGlyph in glyph_manager.test.cpp exercises the platform-independent
part of LocalGlyphRasterizer. This test actually exercises platform-dependent
font loading code for whatever platform it runs on. Different platforms have
different default fonts, so adding a new platform requires new "expected"
fixtures.
At the time of writing, we don't run `mbgl-test` on iOS or Android, so the only
supported test platform is macOS. Supporting Android would require adding a new
test case (probably using the "Droid" font family). iOS should theoretically
work -- the "PingFang" font family used below is expected to be available on
all iOS devices, and we use a relatively high image diff tolerance (0.05) to
account for small changes between the many possible variants of the PingFang
family.
*/
using namespace mbgl;
namespace {
class LocalGlyphRasterizerTest {
public:
LocalGlyphRasterizerTest(const optional<std::string> fontFamily)
: frontend(1, gfx::HeadlessBackend::SwapBehaviour::NoFlush, gfx::ContextMode::Unique, fontFamily) {}
util::RunLoop loop;
std::shared_ptr<StubFileSource> fileSource = std::make_shared<StubFileSource>();
HeadlessFrontend frontend;
MapAdapter map { frontend, MapObserver::nullObserver(), fileSource,
MapOptions().withMapMode(MapMode::Static).withSize(frontend.getSize())};
void checkRendering(const char * name, double imageMatchPixelsThreshold = 0.015, double pixelMatchThreshold = 0.1) {
test::checkImage(std::string("test/fixtures/local_glyphs/") + name,
frontend.render(map).image,
imageMatchPixelsThreshold,
pixelMatchThreshold);
}
};
} // end namespace
// Enabling Qt requires adding a CJK-compatible font in the CI image.
#if defined(__APPLE__)
TEST(LocalGlyphRasterizer, PingFang) {
LocalGlyphRasterizerTest test(std::string("PingFang TC"));
test.fileSource->glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
#if defined(__APPLE__) && !defined(__QT__)
test.checkRendering("ping_fang", 0.0161);
#elif defined(__QT__)
test.checkRendering("ping_fang_qt");
#endif // defined(__APPLE__)
}
#if !defined(__QT__)
TEST(LocalGlyphRasterizer, PingFangSemibold) {
LocalGlyphRasterizerTest test(std::string("PingFang TC Semibold"));
test.fileSource->glyphsResponse = [&](const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("ping_fang_semibold", 0.0161);
}
#endif // !defined(__QT__)
#endif // defined(__APPLE__)
#if defined(__linux__) && defined(__QT__)
TEST(LocalGlyphRasterizer, NotoSansCJK) {
LocalGlyphRasterizerTest test(std::string("Noto Sans CJK KR Regular"));
test.fileSource->glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("noto_sans_cjk_kr_regular_qt");
}
#endif // defined(__linux__) && defined(__QT__)
TEST(LocalGlyphRasterizer, NoLocal) {
// Expectation: without any local fonts set, and without any CJK glyphs provided,
// the output should just contain basic latin characters.
LocalGlyphRasterizerTest test({});
test.fileSource->glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
test.checkRendering("no_local", 0.001, 0.1);
}
TEST(LocalGlyphRasterizer, NoLocalWithContentInsets) {
// Expectations: content insets imply rendering to offsetted viewport center.
// Rendered text should be on the same offset and keep the same size as
// with no offset.
LocalGlyphRasterizerTest test({});
test.fileSource->glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
auto viewSize = test.frontend.getSize();
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
// Expected image was created using center offset as in the line below - content insets,
// with no pitch defined, should produce the same output.
// test.map.moveBy({ viewSize.width * 0.25, 0 });
test.map.jumpTo(CameraOptions().withPadding(EdgeInsets { 0, viewSize.width * 0.25 * 2, 0, 0 }));
test.checkRendering("no_local_with_content_insets", 0.001, 0.1);
}
TEST(LocalGlyphRasterizer, NoLocalWithContentInsetsAndPitch) {
// Expectations: content insets imply rendering to offsetted viewport center.
// Rendered text should be on the same offset and keep the same size as
// with no offset.
LocalGlyphRasterizerTest test({});
test.fileSource->glyphsResponse = [&] (const Resource& resource) {
EXPECT_EQ(Resource::Kind::Glyphs, resource.kind);
Response response;
response.data = std::make_shared<std::string>(util::read_file("test/fixtures/resources/glyphs.pbf"));
return response;
};
auto viewSize = test.frontend.getSize();
test.map.getStyle().loadJSON(util::read_file("test/fixtures/local_glyphs/mixed.json"));
// Expected image was verified using no-padding render, offsetted to right
// using bitmap editor.
test.map.jumpTo(CameraOptions().withPitch(45).withPadding(EdgeInsets { 0, viewSize.width * 0.4, 0, 0 }));
test.checkRendering("no_local_with_content_insets_and_pitch", 0.001, 0.1);
}

272
test/text/quads.test.cpp Normal file
View File

@@ -0,0 +1,272 @@
#include <mbgl/geometry/anchor.hpp>
#include <mbgl/layout/symbol_instance.hpp>
#include <mbgl/style/image_impl.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <mbgl/test/util.hpp>
#include <mbgl/text/glyph.hpp>
#include <mbgl/text/quads.hpp>
#include <mbgl/text/shaping.hpp>
using namespace mbgl;
using namespace mbgl::style;
TEST(getIconQuads, normal) {
SymbolLayoutProperties::Evaluated layout;
Anchor anchor(2.0, 3.0, 0.0, 0);
ImagePosition image = {
mapbox::Bin(-1, 15, 11, 0, 0, 0, 0),
style::Image::Impl("test", PremultipliedImage({1,1}), 1.0)
};
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-6.5f, -4.5f}}, SymbolAnchorType::Center);
GeometryCoordinates line;
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -14);
EXPECT_FLOAT_EQ(quad.tl.y, -10);
EXPECT_FLOAT_EQ(quad.tr.x, 1);
EXPECT_FLOAT_EQ(quad.tr.y, -10);
EXPECT_FLOAT_EQ(quad.bl.x, -14);
EXPECT_FLOAT_EQ(quad.bl.y, 1);
EXPECT_FLOAT_EQ(quad.br.x, 1);
EXPECT_FLOAT_EQ(quad.br.y, 1);
}
TEST(getIconQuads, style) {
Anchor anchor(0.0, 0.0, 0.0, 0);
const ImagePosition image = {mapbox::Bin(-1, 20, 20, 0, 0, 0, 0),
style::Image::Impl("test", PremultipliedImage({1, 1}), 1.0)};
GeometryCoordinates line;
Shaping shapedText;
shapedText.top = -10.0f;
shapedText.bottom = 30.0f;
shapedText.left = -60.0f;
shapedText.right = 20.0f;
// shapedText.positionedGlyphs.emplace_back(PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0));
shapedText.positionedLines.emplace_back();
shapedText.positionedLines.back().positionedGlyphs.emplace_back(
PositionedGlyph(32, 0.0f, 0.0f, false, 0, 1.0, /*texRect*/ {}, /*metrics*/ {}, /*imageID*/ nullopt));
// none
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
EXPECT_FLOAT_EQ(-18.5f, shapedIcon.top());
EXPECT_FLOAT_EQ(-0.5f, shapedIcon.right());
EXPECT_FLOAT_EQ(-0.5f, shapedIcon.bottom());
EXPECT_FLOAT_EQ(-18.5f, shapedIcon.left());
SymbolLayoutProperties::Evaluated layout;
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -19.5);
EXPECT_FLOAT_EQ(quad.tl.y, -19.5);
EXPECT_FLOAT_EQ(quad.tr.x, 0.5);
EXPECT_FLOAT_EQ(quad.tr.y, -19.5);
EXPECT_FLOAT_EQ(quad.bl.x, -19.5);
EXPECT_FLOAT_EQ(quad.bl.y, 0.5);
EXPECT_FLOAT_EQ(quad.br.x, 0.5);
EXPECT_FLOAT_EQ(quad.br.y, 0.5);
}
// width
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -64.4444427);
EXPECT_FLOAT_EQ(quad.tl.y, 0);
EXPECT_FLOAT_EQ(quad.tr.x, 24.4444427);
EXPECT_FLOAT_EQ(quad.tr.y, 0);
EXPECT_FLOAT_EQ(quad.bl.x, -64.4444427);
EXPECT_FLOAT_EQ(quad.bl.y, 20);
EXPECT_FLOAT_EQ(quad.br.x, 24.4444427);
EXPECT_FLOAT_EQ(quad.br.y, 20);
}
// width x textSize
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -32.2222214);
EXPECT_FLOAT_EQ(quad.tl.y, -5);
EXPECT_FLOAT_EQ(quad.tr.x, 12.2222214);
EXPECT_FLOAT_EQ(quad.tr.y, -5);
EXPECT_FLOAT_EQ(quad.bl.x, -32.2222214);
EXPECT_FLOAT_EQ(quad.bl.y, 15);
EXPECT_FLOAT_EQ(quad.br.x, 12.2222214);
EXPECT_FLOAT_EQ(quad.br.y, 15);
}
// width x textSize + padding
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Width, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -43.3333321);
EXPECT_FLOAT_EQ(quad.tl.y, -5);
EXPECT_FLOAT_EQ(quad.tr.x, 23.3333321);
EXPECT_FLOAT_EQ(quad.tr.y, -5);
EXPECT_FLOAT_EQ(quad.bl.x, -43.3333321);
EXPECT_FLOAT_EQ(quad.bl.y, 15);
EXPECT_FLOAT_EQ(quad.br.x, 23.3333321);
EXPECT_FLOAT_EQ(quad.br.y, 15);
}
// height
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -30);
EXPECT_FLOAT_EQ(quad.tl.y, -12.2222214);
EXPECT_FLOAT_EQ(quad.tr.x, -10);
EXPECT_FLOAT_EQ(quad.tr.y, -12.2222214);
EXPECT_FLOAT_EQ(quad.bl.x, -30);
EXPECT_FLOAT_EQ(quad.bl.y, 32.2222214);
EXPECT_FLOAT_EQ(quad.br.x, -10);
EXPECT_FLOAT_EQ(quad.br.y, 32.2222214);
}
// height x textSize
{
SymbolLayoutProperties::Evaluated layout;
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -20);
EXPECT_FLOAT_EQ(quad.tl.y, -6.11111069);
EXPECT_FLOAT_EQ(quad.tr.x, 0);
EXPECT_FLOAT_EQ(quad.tr.y, -6.11111069);
EXPECT_FLOAT_EQ(quad.bl.x, -20);
EXPECT_FLOAT_EQ(quad.bl.y, 16.1111107);
EXPECT_FLOAT_EQ(quad.br.x, 0);
EXPECT_FLOAT_EQ(quad.br.y, 16.1111107);
}
// height x textSize + padding
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Height, {{5, 10, 5, 20}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -20);
EXPECT_FLOAT_EQ(quad.tl.y, -11.666666);
EXPECT_FLOAT_EQ(quad.tr.x, 0);
EXPECT_FLOAT_EQ(quad.tr.y, -11.666666);
EXPECT_FLOAT_EQ(quad.bl.x, -20);
EXPECT_FLOAT_EQ(quad.bl.y, 21.666666);
EXPECT_FLOAT_EQ(quad.br.x, 0);
EXPECT_FLOAT_EQ(quad.br.y, 21.666666);
}
// both
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 24.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -64.4444427);
EXPECT_FLOAT_EQ(quad.tl.y, -12.2222214);
EXPECT_FLOAT_EQ(quad.tr.x, 24.4444427);
EXPECT_FLOAT_EQ(quad.tr.y, -12.2222214);
EXPECT_FLOAT_EQ(quad.bl.x, -64.4444427);
EXPECT_FLOAT_EQ(quad.bl.y, 32.2222214);
EXPECT_FLOAT_EQ(quad.br.x, 24.4444427);
EXPECT_FLOAT_EQ(quad.br.y, 32.2222214);
}
// both x textSize
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 0, 0, 0}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -32.2222214);
EXPECT_FLOAT_EQ(quad.tl.y, -6.11111069);
EXPECT_FLOAT_EQ(quad.tr.x, 12.2222214);
EXPECT_FLOAT_EQ(quad.tr.y, -6.11111069);
EXPECT_FLOAT_EQ(quad.bl.x, -32.2222214);
EXPECT_FLOAT_EQ(quad.bl.y, 16.1111107);
EXPECT_FLOAT_EQ(quad.br.x, 12.2222214);
EXPECT_FLOAT_EQ(quad.br.y, 16.1111107);
}
// both x textSize + padding
{
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{5, 10, 5, 10}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -43.3333321);
EXPECT_FLOAT_EQ(quad.tl.y, -11.666666);
EXPECT_FLOAT_EQ(quad.tr.x, 23.3333321);
EXPECT_FLOAT_EQ(quad.tr.y, -11.666666);
EXPECT_FLOAT_EQ(quad.bl.x, -43.3333321);
EXPECT_FLOAT_EQ(quad.bl.y, 21.666666);
EXPECT_FLOAT_EQ(quad.br.x, 23.3333321);
EXPECT_FLOAT_EQ(quad.br.y, 21.666666);
}
// both x textSize + padding t/r/b/l
{
SymbolLayoutProperties::Evaluated layout;
layout.get<TextSize>() = 12.0f;
auto shapedIcon = PositionedIcon::shapeIcon(image, {{-9.5f, -9.5f}}, SymbolAnchorType::Center);
shapedIcon.fitIconToText(shapedText, IconTextFitType::Both, {{0, 5, 10, 15}}, {{0, 0}}, 12.0f / 24.0f);
SymbolQuads quads = getIconQuads(shapedIcon, 0, SymbolContent::IconRGBA, false);
ASSERT_EQ(quads.size(), 1);
const auto& quad = quads[0];
EXPECT_FLOAT_EQ(quad.tl.x, -48.3333321);
EXPECT_FLOAT_EQ(quad.tl.y, -6.66666603);
EXPECT_FLOAT_EQ(quad.tr.x, 18.3333321);
EXPECT_FLOAT_EQ(quad.tr.y, -6.66666603);
EXPECT_FLOAT_EQ(quad.bl.x, -48.3333321);
EXPECT_FLOAT_EQ(quad.bl.y, 26.666666);
EXPECT_FLOAT_EQ(quad.br.x, 18.3333321);
EXPECT_FLOAT_EQ(quad.br.y, 26.666666);
}
}

107
test/text/shaping.test.cpp Normal file
View File

@@ -0,0 +1,107 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/bidi.hpp>
#include <mbgl/text/tagged_string.hpp>
#include <mbgl/text/shaping.hpp>
#include <mbgl/util/constants.hpp>
using namespace mbgl;
using namespace util;
TEST(Shaping, ZWSP) {
GlyphPosition glyphPosition;
glyphPosition.metrics.width = 18;
glyphPosition.metrics.height = 18;
glyphPosition.metrics.left = 2;
glyphPosition.metrics.top = -8;
glyphPosition.metrics.advance = 21;
Glyph glyph;
glyph.id = u'中';
glyph.metrics = glyphPosition.metrics;
BiDi bidi;
auto immutableGlyph = Immutable<Glyph>(makeMutable<Glyph>(std::move(glyph)));
const std::vector<std::string> fontStack{{"font-stack"}};
const SectionOptions sectionOptions(1.0f, fontStack);
const float layoutTextSize = 16.0f;
const float layoutTextSizeAtBucketZoomLevel = 16.0f;
GlyphMap glyphs = {
{ FontStackHasher()(fontStack), {{u'中', std::move(immutableGlyph)}} }
};
GlyphPositions glyphPositions = {{FontStackHasher()(fontStack), {{u'中', std::move(glyphPosition)}}}};
ImagePositions imagePositions;
const auto testGetShaping = [&](const TaggedString& string, unsigned maxWidthInChars) {
return getShaping(string,
maxWidthInChars * ONE_EM,
ONE_EM, // lineHeight
style::SymbolAnchorType::Center,
style::TextJustifyType::Center,
0, // spacing
{{0.0f, 0.0f}}, // translate
WritingModeType::Horizontal,
bidi,
glyphs,
glyphPositions,
imagePositions,
layoutTextSize,
layoutTextSizeAtBucketZoomLevel,
/*allowVerticalPlacement*/ false);
};
// 3 lines
// 中中中中中中
// 中中中中中中
// 中中
{
TaggedString string(u"中中\u200b中中\u200b中中\u200b中中中中中中\u200b中中", sectionOptions);
auto shaping = testGetShaping(string, 5);
ASSERT_EQ(shaping.positionedLines.size(), 3);
ASSERT_EQ(shaping.top, -36);
ASSERT_EQ(shaping.bottom, 36);
ASSERT_EQ(shaping.left, -63);
ASSERT_EQ(shaping.right, 63);
ASSERT_EQ(shaping.writingMode, WritingModeType::Horizontal);
}
// 2 lines
// 中中
// 中
{
TaggedString string(u"中中\u200b中", sectionOptions);
auto shaping = testGetShaping(string, 1);
ASSERT_EQ(shaping.positionedLines.size(), 2);
ASSERT_EQ(shaping.top, -24);
ASSERT_EQ(shaping.bottom, 24);
ASSERT_EQ(shaping.left, -21);
ASSERT_EQ(shaping.right, 21);
ASSERT_EQ(shaping.writingMode, WritingModeType::Horizontal);
}
// 1 line
// 中中
{
TaggedString string(u"中中\u200b", sectionOptions);
auto shaping = testGetShaping(string, 2);
ASSERT_EQ(shaping.positionedLines.size(), 1);
ASSERT_EQ(shaping.top, -12);
ASSERT_EQ(shaping.bottom, 12);
ASSERT_EQ(shaping.left, -21);
ASSERT_EQ(shaping.right, 21);
ASSERT_EQ(shaping.writingMode, WritingModeType::Horizontal);
}
// 5 'new' lines.
{
TaggedString string(u"\u200b\u200b\u200b\u200b\u200b", sectionOptions);
auto shaping = testGetShaping(string, 1);
ASSERT_EQ(shaping.positionedLines.size(), 5);
ASSERT_EQ(shaping.top, -60);
ASSERT_EQ(shaping.bottom, 60);
ASSERT_EQ(shaping.left, 0);
ASSERT_EQ(shaping.right, 0);
ASSERT_EQ(shaping.writingMode, WritingModeType::Horizontal);
}
}

View File

@@ -0,0 +1,44 @@
#include <mbgl/test/util.hpp>
#include <mbgl/text/tagged_string.hpp>
using namespace mbgl;
TEST(TaggedString, Trim) {
TaggedString basic(u" \t\ntrim that and not this \n\t", SectionOptions(1.0f, {}));
basic.trim();
EXPECT_EQ(basic.rawText(), u"trim that and not this");
TaggedString twoSections;
twoSections.addTextSection(u" \t\ntrim that", 1.5f, {});
twoSections.addTextSection(u" and not this \n\t", 0.5f, {});
twoSections.trim();
EXPECT_EQ(twoSections.rawText(), u"trim that and not this");
TaggedString empty(u"\n\t\v \r \t\n", SectionOptions(1.0f, {}));
empty.trim();
EXPECT_EQ(empty.rawText(), u"");
TaggedString noTrim(u"no trim!", SectionOptions(1.0f, {}));
noTrim.trim();
EXPECT_EQ(noTrim.rawText(), u"no trim!");
}
TEST(TaggedString, ImageSections) {
TaggedString string;
string.addImageSection("image_name");
EXPECT_EQ(string.rawText(), u"\uE000");
EXPECT_TRUE(string.getSection(0).imageID);
EXPECT_EQ(*string.getSection(0).imageID, "image_name");
TaggedString maxSections;
for (std::size_t i = 0; i < 6401; ++i) {
maxSections.addImageSection(util::toString(i));
}
EXPECT_EQ(maxSections.getSections().size(), 6400u);
EXPECT_EQ(maxSections.getCharCodeAt(0), u'\uE000');
EXPECT_EQ(maxSections.getCharCodeAt(6399), u'\uF8FF');
}