Files
MapLibreParticleLab/test/text/shaping.test.cpp

852 lines
30 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include <mln/test/util.hpp>
#include <mln/text/bidi.hpp>
#include <mln/text/tagged_string.hpp>
#include <mln/text/shaping.hpp>
#include <mln/util/constants.hpp>
#include <mln/util/i18n.hpp>
#include <optional>
using namespace mln;
using namespace util;
namespace {
void addStubGlyph(GlyphMap& glyphs,
GlyphPositions& glyphPositions,
const FontStackHash fontStackHash,
const char16_t codePoint,
const GlyphIDType type) {
const GlyphID glyphID(codePoint, type);
GlyphMetrics metrics;
metrics.width = 10;
metrics.height = 18;
metrics.left = 1;
metrics.top = -8;
metrics.advance = 12;
Glyph glyph;
glyph.id = glyphID;
glyph.metrics = metrics;
glyphs[fontStackHash][glyphID] = Immutable<Glyph>(makeMutable<Glyph>(std::move(glyph)));
GlyphPosition position;
position.metrics = metrics;
glyphPositions[fontStackHash][glyphID] = position;
}
void addStubGlyphs(const TaggedString& string, GlyphMap& glyphs, GlyphPositions& glyphPositions) {
for (std::size_t i = 0; i < string.length(); ++i) {
const auto codePoint = string.getCharCodeAt(i);
const auto& section = string.getSection(i);
if (section.imageID) continue;
addStubGlyph(glyphs, glyphPositions, section.fontStackHash, codePoint, section.type);
if (section.type == GlyphIDType::FontPBF) {
if (const auto verticalCodePoint = util::i18n::verticalizePunctuation(codePoint)) {
addStubGlyph(glyphs, glyphPositions, section.fontStackHash, verticalCodePoint, section.type);
}
}
}
}
Shaping shapeTaggedText(const TaggedString& string,
const WritingModeType writingMode = WritingModeType::Vertical,
const bool allowVerticalPlacement = false,
ImagePositions imagePositions = {}) {
GlyphMap glyphs;
GlyphPositions glyphPositions;
addStubGlyphs(string, glyphs, glyphPositions);
BiDi bidi;
return getShaping(string,
4096.0f,
ONE_EM,
style::SymbolAnchorType::TopLeft,
style::TextJustifyType::Left,
0.0f,
{{0.0f, 0.0f}},
writingMode,
bidi,
glyphs,
glyphPositions,
imagePositions,
ONE_EM,
ONE_EM,
allowVerticalPlacement);
}
Shaping shapeLineLabel(const std::u16string& text,
const WritingModeType writingMode = WritingModeType::Vertical,
const bool allowVerticalPlacement = false) {
const FontStack fontStack{{"font-stack"}};
return shapeTaggedText(TaggedString(text, SectionOptions(1.0, fontStack, GlyphIDType::FontPBF, 0)),
writingMode,
allowVerticalPlacement);
}
using GlyphOrientation = std::pair<char16_t, std::string>;
std::vector<GlyphOrientation> getGlyphOrientations(const Shaping& shaping) {
std::vector<GlyphOrientation> result;
for (const auto& line : shaping.positionedLines) {
for (const auto& glyph : line.positionedGlyphs) {
result.emplace_back(glyph.glyph.complex.code, glyph.vertical ? "upright" : "along-line");
}
}
return result;
}
} // namespace
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, GlyphIDType::FontPBF, 0);
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);
}
}
// Regression cases ported from maplibre-gl-js#8205 for maplibre-native#4565.
TEST(Shaping, VerticalDigitsBetweenCJKCharacters) {
const auto shapedLineLabel = shapeLineLabel(u"반포대로21길");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'반', "upright"},
{u'포', "upright"},
{u'대', "upright"},
{u'로', "upright"},
{u'2', "upright"},
{u'1', "upright"},
{u'길', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalTrailingDigits) {
const auto shapedLineLabel = shapeLineLabel(u"身什戰33");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
{u'3', "upright"},
{u'3', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalDigitsOfAnyLength) {
const auto shapedLineLabel = shapeLineLabel(u"国道1234号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'1', "upright"},
{u'2', "upright"},
{u'3', "upright"},
{u'4', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalWhitespaceSeparatedDigit) {
const auto shapedLineLabel = shapeLineLabel(u"身什戰 1");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
{u' ', "along-line"},
{u'1', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalSingleUppercaseLetter) {
const auto shapedLineLabel = shapeLineLabel(u"国道A号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'A', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalDecomposedLatinLetter) {
const auto shapedLineLabel = shapeLineLabel(u"国道e\u0301号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'e', "along-line"},
{u'\u0301', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalLowercaseGreekLetter) {
const auto shapedLineLabel = shapeLineLabel(u"国道α号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'α', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalNonLatinUppercaseCode) {
const auto shapedLineLabel = shapeLineLabel(u"国道\u041C1号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'\u041C', "upright"},
{u'1', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalComplexScript) {
const auto shapedLineLabel = shapeLineLabel(u"国道ب号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'ب', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalProlongedSoundMark) {
const auto shapedLineLabel = shapeLineLabel(u"札幌タワー");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'札', "upright"},
{u'幌', "upright"},
{u'タ', "upright"},
{u'ワ', "upright"},
{u'ー', "along-line"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalWaveDashBetweenDigits) {
const auto shapedLineLabel = shapeLineLabel(u"身什戰1〜2");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
{u'1', "upright"},
{u'〜', "along-line"},
{u'2', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalLowercaseLatinWord) {
const auto shapedLineLabel = shapeLineLabel(u"two 身什戰");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u't', "along-line"},
{u'w', "along-line"},
{u'o', "along-line"},
{u' ', "along-line"},
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalMixedAlphanumericCode) {
const auto shapedLineLabel = shapeLineLabel(u"身什戰A1");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
{u'A', "upright"},
{u'1', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalLatinWordAdjoiningCJK) {
const auto shapedLineLabel = shapeLineLabel(u"銀座Ginza通り");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'銀', "upright"},
{u'座', "upright"},
{u'G', "along-line"},
{u'i', "along-line"},
{u'n', "along-line"},
{u'z', "along-line"},
{u'a', "along-line"},
{u'通', "upright"},
{u'り', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalLongUppercaseWord) {
const auto shapedLineLabel = shapeLineLabel(u"ヴィラ ISHIKAWA");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'ヴ', "upright"},
{u'ィ', "upright"},
{u'ラ', "upright"},
{u' ', "along-line"},
{u'I', "along-line"},
{u'S', "along-line"},
{u'H', "along-line"},
{u'I', "along-line"},
{u'K', "along-line"},
{u'A', "along-line"},
{u'W', "along-line"},
{u'A', "along-line"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalShortUppercaseCode) {
const auto shapedLineLabel = shapeLineLabel(u"JR山手線");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'J', "upright"},
{u'R', "upright"},
{u'山', "upright"},
{u'手', "upright"},
{u'線', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalPunctuationBetweenDigits) {
const auto shapedLineLabel = shapeLineLabel(u"国道1-2号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'1', "upright"},
{u'︲', "upright"},
{u'2', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalPunctuationInRotatedRun) {
const auto shapedLineLabel = shapeLineLabel(u"国道3.5km");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'3', "along-line"},
{u'.', "along-line"},
{u'5', "along-line"},
{u'k', "along-line"},
{u'm', "along-line"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalMissingGlyphPreservesIndices) {
const FontStack fontStack{{"font-stack"}};
const auto fontStackHash = FontStackHasher()(fontStack);
const TaggedString text(u"what 国21号", SectionOptions(1.0, fontStack, GlyphIDType::FontPBF, 0));
GlyphMap glyphs;
GlyphPositions glyphPositions;
addStubGlyphs(text, glyphs, glyphPositions);
// Missing "w" must not shift the character index used for the remaining glyphs.
glyphs.at(fontStackHash).erase(u'w');
glyphPositions.at(fontStackHash).erase(u'w');
BiDi bidi;
const auto shapedLineLabel = getShaping(text,
4096.0f,
ONE_EM,
style::SymbolAnchorType::TopLeft,
style::TextJustifyType::Left,
0.0f,
{{0.0f, 0.0f}},
WritingModeType::Vertical,
bidi,
glyphs,
glyphPositions,
{},
ONE_EM,
ONE_EM,
false);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'h', "along-line"},
{u'a', "along-line"},
{u't', "along-line"},
{u' ', "along-line"},
{u'国', "upright"},
{u'2', "upright"},
{u'1', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, HorizontalGlyphOrientation) {
const auto shapedLineLabel = shapeLineLabel(u"身什戰33", WritingModeType::Horizontal);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'身', "along-line"},
{u'什', "along-line"},
{u'戰', "along-line"},
{u'3', "along-line"},
{u'3', "along-line"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, AllowedVerticalGlyphOrientation) {
const auto shapedLineLabel = shapeLineLabel(u"two 身什戰", WritingModeType::Vertical, true);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u't', "upright"},
{u'w', "upright"},
{u'o', "upright"},
{u' ', "along-line"},
{u'身', "upright"},
{u'什', "upright"},
{u'戰', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalThreeCharacterUppercaseCode) {
const auto shapedLineLabel = shapeLineLabel(u"国道ABC号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'A', "upright"},
{u'B', "upright"},
{u'C', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalFourCharacterUppercaseWord) {
const auto shapedLineLabel = shapeLineLabel(u"国道ABCD号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'A', "along-line"},
{u'B', "along-line"},
{u'C', "along-line"},
{u'D', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalBengaliDigits) {
const auto shapedLineLabel = shapeLineLabel(u"国道১২号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'১', "upright"},
{u'২', "upright"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalArabicIndicDigits) {
const auto shapedLineLabel = shapeLineLabel(u"国道١٢号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'١', "along-line"},
{u'٢', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalDigitWithCombiningMark) {
const auto shapedLineLabel = shapeLineLabel(u"国道1\u0301号");
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'国', "upright"},
{u'道', "upright"},
{u'1', "along-line"},
{u'\u0301', "along-line"},
{u'号', "upright"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalGlyphOrientationRespectsFormattedSections) {
const FontStack fontStack{{"font-stack"}};
TaggedString text;
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
text.addTextSection(u"CD", 0.5, fontStack, GlyphIDType::FontPBF);
const auto shapedLineLabel = shapeTaggedText(text);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'A', "along-line"},
{u'B', "along-line"},
{u'C', "along-line"},
{u'D', "along-line"},
};
EXPECT_EQ(orientations, expected);
}
TEST(Shaping, VerticalGlyphOrientationDelimitsInlineImages) {
const FontStack fontStack{{"font-stack"}};
TaggedString text;
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
text.addImageSection("image");
text.addTextSection(u"CD", 1.0, fontStack, GlyphIDType::FontPBF);
const style::Image::Impl image("image", PremultipliedImage({12, 24}), 1.0f);
ImagePositions imagePositions;
imagePositions.emplace("image", ImagePosition(Rect<uint16_t>(0, 0, 14, 26), image));
const auto shapedLineLabel = shapeTaggedText(text, WritingModeType::Vertical, false, std::move(imagePositions));
ASSERT_EQ(shapedLineLabel.positionedLines.size(), 1u);
ASSERT_EQ(shapedLineLabel.positionedLines.front().positionedGlyphs.size(), 5u);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'A', "upright"},
{u'B', "upright"},
{u'\uE000', "along-line"}, // Inline image placeholder.
{u'C', "upright"},
{u'D', "upright"},
};
EXPECT_EQ(orientations, expected);
EXPECT_EQ(shapedLineLabel.positionedLines.front().positionedGlyphs[2].imageID, std::optional<std::string>("image"));
EXPECT_TRUE(shapedLineLabel.iconsInText);
}
TEST(Shaping, VerticalGlyphOrientationDelimitsNativeGlyphSections) {
const FontStack fontStack{{"font-stack"}};
const auto nativeType = genNewGlyphIDType("shaping-test-native", fontStack, {{0, 0xffff}});
TaggedString text;
text.addTextSection(u"AB", 1.0, fontStack, GlyphIDType::FontPBF);
text.addTextSection(u"C", 1.0, fontStack, nativeType);
text.addTextSection(u"D", 1.0, fontStack, GlyphIDType::FontPBF);
const auto shapedLineLabel = shapeTaggedText(text);
const auto orientations = getGlyphOrientations(shapedLineLabel);
const std::vector<GlyphOrientation> expected = {
{u'A', "upright"},
{u'B', "upright"},
{u'C', "along-line"},
{u'D', "upright"},
};
EXPECT_EQ(orientations, expected);
}
void setupShapedText(Shaping& shapedText, float textSize) {
const auto glyph = PositionedGlyph(32,
0.0f,
0.0f,
false,
0,
1.0,
/*texRect*/ {},
/*metrics*/ {},
/*imageID*/ std::nullopt);
shapedText.right = textSize;
shapedText.bottom = textSize;
shapedText.positionedLines.emplace_back();
shapedText.positionedLines.back().positionedGlyphs.emplace_back(glyph);
}
void testApplyTextFit(const Rect<uint16_t>& rectangle,
const style::ImageContent& content,
const std::optional<style::TextFit> textFitWidth,
const std::optional<style::TextFit> textFitHeight,
const Shaping& shapedText,
float fontScale,
float expectedRight,
float expectedBottom) {
ImagePosition image = {
rectangle,
style::Image::Impl("test",
PremultipliedImage({static_cast<uint32_t>(rectangle.w), static_cast<uint32_t>(rectangle.h)}),
1.0f,
false,
{},
{},
content,
textFitWidth,
textFitHeight)};
auto shapedIcon = PositionedIcon::shapeIcon(image, {0, 0}, style::SymbolAnchorType::TopLeft);
shapedIcon.fitIconToText(shapedText, style::IconTextFitType::Both, {0, 0, 0, 0}, {0, 0}, fontScale);
const auto& icon = shapedIcon.applyTextFit();
ASSERT_EQ(icon.top(), 0);
ASSERT_EQ(icon.left(), 0);
ASSERT_EQ(icon.right(), expectedRight);
ASSERT_EQ(icon.bottom(), expectedBottom);
}
TEST(Shaping, BreakBeforeLeftParenthesis) {
GlyphPosition glyphPosition;
glyphPosition.metrics.width = 18;
glyphPosition.metrics.height = 18;
glyphPosition.metrics.left = 2;
glyphPosition.metrics.top = -8;
glyphPosition.metrics.advance = 21;
BiDi bidi;
const std::vector<std::string> fontStack{{"font-stack"}};
const SectionOptions sectionOptions(1.0f, fontStack, GlyphIDType::FontPBF, 0);
GlyphMap glyphs;
GlyphPositions glyphPositions;
for (const char16_t id : {u'o', u'a', u'(', u')', u' '}) {
Glyph glyph;
glyph.id = id;
glyph.metrics = glyphPosition.metrics;
glyphs[FontStackHasher()(fontStack)].emplace(id, Immutable<Glyph>(makeMutable<Glyph>(std::move(glyph))));
glyphPositions[FontStackHasher()(fontStack)].emplace(id, glyphPosition);
}
ImagePositions imagePositions;
const auto glyphsPerLine = [&](const std::u16string& text) {
const Shaping shaping = getShaping(TaggedString(text, sectionOptions),
5 * ONE_EM,
ONE_EM, // lineHeight
style::SymbolAnchorType::Center,
style::TextJustifyType::Center,
0, // spacing
{{0.0f, 0.0f}}, // translate
WritingModeType::Horizontal,
bidi,
glyphs,
glyphPositions,
imagePositions,
16.0f, // layoutTextSize
16.0f, // layoutTextSizeAtBucketZoomLevel
/*allowVerticalPlacement*/ false);
std::vector<std::size_t> counts;
for (const auto& line : shaping.positionedLines) {
counts.push_back(line.positionedGlyphs.size());
}
return counts;
};
// The break goes before the parenthesis, so it opens the second line instead of closing the first.
EXPECT_EQ(glyphsPerLine(u"oooooooo(aaaaaaaaa)"), (std::vector<std::size_t>{8, 11}));
EXPECT_EQ(glyphsPerLine(u"oooooooo (aaaaaaaaa)"), (std::vector<std::size_t>{8, 11}));
// A trailing parenthesis never becomes a line of its own.
EXPECT_EQ(glyphsPerLine(u"oooooooo("), (std::vector<std::size_t>{9}));
}
TEST(Shaping, applyTextFit) {
float textSize = 4;
float fontScale = 4;
float expectedImageSize = textSize * fontScale;
Shaping shapedText;
setupShapedText(shapedText, textSize);
{
// applyTextFitHorizontal
// This set of tests against applyTextFit starts with a 100x20 image with a 5,5,95,15 content box
// that has been fitted to a 4*4 text with scale 4, resulting in a 16*16 image.
const auto horizontalRectangle = Rect<uint16_t>(0, 0, 100, 20);
const style::ImageContent horizontalContent = {.left = 5, .top = 5, .right = 95, .bottom = 15};
{
// applyTextFit: not specified
// No change should happen
testApplyTextFit(horizontalRectangle,
horizontalContent,
std::nullopt,
std::nullopt,
shapedText,
fontScale,
expectedImageSize,
expectedImageSize);
}
{
// applyTextFit: both stretchOrShrink
// No change should happen
testApplyTextFit(horizontalRectangle,
horizontalContent,
style::TextFit::stretchOrShrink,
style::TextFit::stretchOrShrink,
shapedText,
fontScale,
expectedImageSize,
expectedImageSize);
}
{
// applyTextFit: stretchOnly, proportional
// Since textFitWidth is stretchOnly, it should be returned to
// the aspect ratio of the content rectangle (9:1) aspect ratio so 144x16.
testApplyTextFit(horizontalRectangle,
horizontalContent,
style::TextFit::stretchOnly,
style::TextFit::proportional,
shapedText,
fontScale,
expectedImageSize * 9,
expectedImageSize);
}
}
{
// applyTextFitVertical
// This set of tests against applyTextFit starts with a 20x100 image with a 5,5,15,95 content box
// that has been fitted to a 4*4 text with scale 4, resulting in a 16*16 image.
const auto verticalRectangle = Rect<uint16_t>(0, 0, 20, 100);
const style::ImageContent verticalContent = {.left = 5, .top = 5, .right = 15, .bottom = 95};
{
// applyTextFit: stretchOnly, proportional
// Since textFitWidth is stretchOnly, it should be returned to
// the aspect ratio of the content rectangle (9:1) aspect ratio so 144x16.
testApplyTextFit(verticalRectangle,
verticalContent,
style::TextFit::proportional,
style::TextFit::stretchOnly,
shapedText,
fontScale,
expectedImageSize,
expectedImageSize * 9);
}
}
}