[general] moving ios and android project under ./platform folder
This commit is contained in:
90
test/text/bidi.test.cpp
Normal file
90
test/text/bidi.test.cpp
Normal 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);
|
||||
}
|
||||
|
||||
27
test/text/calculate_tile_distances.test.cpp
Normal file
27
test/text/calculate_tile_distances.test.cpp
Normal 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);
|
||||
}
|
||||
419
test/text/cross_tile_symbol_index.test.cpp
Normal file
419
test/text/cross_tile_symbol_index.test.cpp
Normal 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);
|
||||
}
|
||||
51
test/text/formatted.test.cpp
Normal file
51
test/text/formatted.test.cpp
Normal 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");
|
||||
}
|
||||
130
test/text/get_anchors.test.cpp
Normal file
130
test/text/get_anchors.test.cpp
Normal 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);
|
||||
}
|
||||
392
test/text/glyph_manager.test.cpp
Normal file
392
test/text/glyph_manager.test.cpp
Normal 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' '}}
|
||||
});
|
||||
}
|
||||
23
test/text/glyph_pbf.test.cpp
Normal file
23
test/text/glyph_pbf.test.cpp
Normal 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);
|
||||
}
|
||||
45
test/text/language_tag.test.cpp
Normal file
45
test/text/language_tag.test.cpp
Normal 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");
|
||||
}
|
||||
|
||||
165
test/text/local_glyph_rasterizer.test.cpp
Normal file
165
test/text/local_glyph_rasterizer.test.cpp
Normal 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
272
test/text/quads.test.cpp
Normal 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
107
test/text/shaping.test.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
44
test/text/tagged_string.test.cpp
Normal file
44
test/text/tagged_string.test.cpp
Normal 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');
|
||||
}
|
||||
Reference in New Issue
Block a user