363 lines
16 KiB
C++
363 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "minikin/FontCollection.h"
|
|
#include "minikin/LayoutPieces.h"
|
|
|
|
#include "FontTestUtils.h"
|
|
#include "LayoutSplitter.h"
|
|
#include "UnicodeUtils.h"
|
|
|
|
namespace minikin {
|
|
namespace {
|
|
|
|
std::pair<std::vector<uint16_t>, Range> parseTestString(const std::string& text) {
|
|
auto utf16 = utf8ToUtf16(text);
|
|
Range range;
|
|
std::vector<uint16_t> outText;
|
|
for (uint16_t c : utf16) {
|
|
switch (c) {
|
|
case '(':
|
|
range.setStart(outText.size());
|
|
break;
|
|
case ')':
|
|
range.setEnd(outText.size());
|
|
break;
|
|
default:
|
|
outText.push_back(c);
|
|
}
|
|
}
|
|
return std::make_pair(outText, range);
|
|
}
|
|
|
|
std::pair<Range, Range> parseExpectString(const std::string& text) {
|
|
auto utf16 = utf8ToUtf16(text);
|
|
Range context;
|
|
Range piece;
|
|
uint32_t textPos = 0;
|
|
for (uint16_t c : utf16) {
|
|
switch (c) {
|
|
case '[':
|
|
context.setStart(textPos);
|
|
break;
|
|
case ']':
|
|
context.setEnd(textPos);
|
|
break;
|
|
case '(':
|
|
piece.setStart(textPos);
|
|
break;
|
|
case ')':
|
|
piece.setEnd(textPos);
|
|
break;
|
|
default:
|
|
textPos++;
|
|
}
|
|
}
|
|
return std::make_pair(context, piece);
|
|
}
|
|
|
|
std::string buildDebugString(const U16StringPiece& textBuf, const Range& context,
|
|
const Range& piece) {
|
|
std::vector<uint16_t> out;
|
|
out.reserve(textBuf.size() + 4);
|
|
for (uint32_t i = 0; i < textBuf.size() + 1; ++i) {
|
|
if (i == context.getStart()) {
|
|
out.push_back('[');
|
|
}
|
|
if (i == piece.getStart()) {
|
|
out.push_back('(');
|
|
}
|
|
if (i == piece.getEnd()) {
|
|
out.push_back(')');
|
|
}
|
|
if (i == context.getEnd()) {
|
|
out.push_back(']');
|
|
}
|
|
if (i != textBuf.size()) {
|
|
out.push_back(textBuf[i]);
|
|
}
|
|
}
|
|
return utf16ToUtf8(out);
|
|
}
|
|
|
|
TEST(LayoutSplitterTest, LTR_Latin) {
|
|
struct TestCase {
|
|
std::string testStr;
|
|
std::vector<std::string> expects;
|
|
} testCases[] = {
|
|
{"(This is an example text.)",
|
|
{
|
|
"[(This)] is an example text.", "This[( )]is an example text.",
|
|
"This [(is)] an example text.", "This is[( )]an example text.",
|
|
"This is [(an)] example text.", "This is an[( )]example text.",
|
|
"This is an [(example)] text.", "This is an example[( )]text.",
|
|
"This is an example [(text.)]",
|
|
}},
|
|
{"This( is an example )text.",
|
|
{
|
|
"This[( )]is an example text.", "This [(is)] an example text.",
|
|
"This is[( )]an example text.", "This is [(an)] example text.",
|
|
"This is an[( )]example text.", "This is an [(example)] text.",
|
|
"This is an example[( )]text.",
|
|
}},
|
|
{"This (is an example) text.",
|
|
{
|
|
"This [(is)] an example text.", "This is[( )]an example text.",
|
|
"This is [(an)] example text.", "This is an[( )]example text.",
|
|
"This is an [(example)] text.",
|
|
}},
|
|
{"Th(is is an example te)xt.",
|
|
{
|
|
"[Th(is)] is an example text.", "This[( )]is an example text.",
|
|
"This [(is)] an example text.", "This is[( )]an example text.",
|
|
"This is [(an)] example text.", "This is an[( )]example text.",
|
|
"This is an [(example)] text.", "This is an example[( )]text.",
|
|
"This is an example [(te)xt.]",
|
|
}},
|
|
{"This is an ex(amp)le text.",
|
|
{
|
|
"This is an [ex(amp)le] text.",
|
|
}},
|
|
{"There are (three spaces.)",
|
|
{
|
|
"There are [(three)] spaces.", "There are three[( )] spaces.",
|
|
"There are three [( )] spaces.", "There are three [( )]spaces.",
|
|
"There are three [(spaces.)]",
|
|
}},
|
|
};
|
|
|
|
for (const auto& testCase : testCases) {
|
|
auto[text, range] = parseTestString(testCase.testStr);
|
|
uint32_t expectationIndex = 0;
|
|
for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
|
|
ASSERT_NE(expectationIndex, testCase.expects.size());
|
|
const std::string expectString = testCase.expects[expectationIndex++];
|
|
auto[exContext, exPiece] = parseExpectString(expectString);
|
|
EXPECT_EQ(acContext, exContext)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
EXPECT_EQ(acPiece, exPiece)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
}
|
|
EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
|
|
}
|
|
}
|
|
|
|
TEST(LayoutSplitterTest, RTL_Latin) {
|
|
struct TestCase {
|
|
std::string testStr;
|
|
std::vector<std::string> expects;
|
|
} testCases[] = {
|
|
{"(This is an example text.)",
|
|
{
|
|
"This is an example [(text.)]", "This is an example[( )]text.",
|
|
"This is an [(example)] text.", "This is an[( )]example text.",
|
|
"This is [(an)] example text.", "This is[( )]an example text.",
|
|
"This [(is)] an example text.", "This[( )]is an example text.",
|
|
"[(This)] is an example text.",
|
|
}},
|
|
{"This( is an example )text.",
|
|
{
|
|
"This is an example[( )]text.", "This is an [(example)] text.",
|
|
"This is an[( )]example text.", "This is [(an)] example text.",
|
|
"This is[( )]an example text.", "This [(is)] an example text.",
|
|
"This[( )]is an example text.",
|
|
}},
|
|
{"This (is an example) text.",
|
|
{
|
|
"This is an [(example)] text.", "This is an[( )]example text.",
|
|
"This is [(an)] example text.", "This is[( )]an example text.",
|
|
"This [(is)] an example text.",
|
|
}},
|
|
{"Th(is is an example te)xt.",
|
|
{
|
|
"This is an example [(te)xt.]", "This is an example[( )]text.",
|
|
"This is an [(example)] text.", "This is an[( )]example text.",
|
|
"This is [(an)] example text.", "This is[( )]an example text.",
|
|
"This [(is)] an example text.", "This[( )]is an example text.",
|
|
"[Th(is)] is an example text.",
|
|
}},
|
|
{"This is an ex(amp)le text.",
|
|
{
|
|
"This is an [ex(amp)le] text.",
|
|
}},
|
|
{"There are (three spaces.)",
|
|
{
|
|
"There are three [(spaces.)]", "There are three [( )]spaces.",
|
|
"There are three [( )] spaces.", "There are three[( )] spaces.",
|
|
"There are [(three)] spaces.",
|
|
}},
|
|
};
|
|
|
|
for (const auto& testCase : testCases) {
|
|
auto[text, range] = parseTestString(testCase.testStr);
|
|
uint32_t expectationIndex = 0;
|
|
for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
|
|
ASSERT_NE(expectationIndex, testCase.expects.size());
|
|
const std::string expectString = testCase.expects[expectationIndex++];
|
|
auto[exContext, exPiece] = parseExpectString(expectString);
|
|
EXPECT_EQ(acContext, exContext)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
EXPECT_EQ(acPiece, exPiece)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
}
|
|
EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
|
|
}
|
|
}
|
|
|
|
TEST(LayoutSplitterTest, LTR_CJK) {
|
|
struct TestCase {
|
|
std::string testStr;
|
|
std::vector<std::string> expects;
|
|
} testCases[] = {
|
|
{// All Kanji text
|
|
"(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
|
|
{
|
|
"[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
|
|
"\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
|
|
"\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
|
|
"\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
|
|
"\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
|
|
"\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
|
|
"\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
|
|
"\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
|
|
}},
|
|
{// Japanese text like as follows
|
|
// [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
|
|
"(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
|
|
{
|
|
"[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
|
|
"\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
|
|
"\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
|
|
"\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
|
|
}},
|
|
{// Japanese text like as follows
|
|
// [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
|
|
"\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
|
|
{
|
|
"\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
|
|
"\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
|
|
"\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
|
|
}},
|
|
};
|
|
|
|
for (const auto& testCase : testCases) {
|
|
auto[text, range] = parseTestString(testCase.testStr);
|
|
uint32_t expectationIndex = 0;
|
|
for (auto[acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
|
|
ASSERT_NE(expectationIndex, testCase.expects.size());
|
|
const std::string expectString = testCase.expects[expectationIndex++];
|
|
auto[exContext, exPiece] = parseExpectString(expectString);
|
|
EXPECT_EQ(acContext, exContext)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
EXPECT_EQ(acPiece, exPiece)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
}
|
|
EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
|
|
}
|
|
}
|
|
|
|
TEST(LayoutSplitterTest, RTL_CJK) {
|
|
struct TestCase {
|
|
std::string testStr;
|
|
std::vector<std::string> expects;
|
|
} testCases[] = {
|
|
{// All Kanji text
|
|
"(\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776)",
|
|
{
|
|
"\u6614\u8005\u8358\u5468\u5922\u70BA\u80E1[(\u8776)]",
|
|
"\u6614\u8005\u8358\u5468\u5922\u70BA[(\u80E1)]\u8776",
|
|
"\u6614\u8005\u8358\u5468\u5922[(\u70BA)]\u80E1\u8776",
|
|
"\u6614\u8005\u8358\u5468[(\u5922)]\u70BA\u80E1\u8776",
|
|
"\u6614\u8005\u8358[(\u5468)]\u5922\u70BA\u80E1\u8776",
|
|
"\u6614\u8005[(\u8358)]\u5468\u5922\u70BA\u80E1\u8776",
|
|
"\u6614[(\u8005)]\u8358\u5468\u5922\u70BA\u80E1\u8776",
|
|
"[(\u6614)]\u8005\u8358\u5468\u5922\u70BA\u80E1\u8776",
|
|
}},
|
|
{// Japanese text like as follows
|
|
// [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
|
|
"(\u672C\u65E5\u306F\u6674\u5929\u306A\u308A\u3002)",
|
|
{
|
|
"\u672C\u65E5\u306F\u6674[(\u5929\u306A\u308A\u3002)]",
|
|
"\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
|
|
"\u672C[(\u65E5\u306F)]\u6674\u5929\u306A\u308A\u3002",
|
|
"[(\u672C)]\u65E5\u306F\u6674\u5929\u306A\u308A\u3002",
|
|
}},
|
|
{// Japanese text like as follows
|
|
// [Kanji][Kanji][Kana][Kanji][Kanji][Kana][Kana][Kana]
|
|
"\u672C\u65E5(\u306F\u6674\u5929\u306A)\u308A\u3002",
|
|
{
|
|
"\u672C\u65E5\u306F\u6674[(\u5929\u306A)\u308A\u3002]",
|
|
"\u672C\u65E5\u306F[(\u6674)]\u5929\u306A\u308A\u3002",
|
|
"\u672C[\u65E5(\u306F)]\u6674\u5929\u306A\u308A\u3002",
|
|
}},
|
|
};
|
|
|
|
for (const auto& testCase : testCases) {
|
|
auto[text, range] = parseTestString(testCase.testStr);
|
|
uint32_t expectationIndex = 0;
|
|
for (auto[acContext, acPiece] : LayoutSplitter(text, range, true /* isRtl */)) {
|
|
ASSERT_NE(expectationIndex, testCase.expects.size());
|
|
const std::string expectString = testCase.expects[expectationIndex++];
|
|
auto[exContext, exPiece] = parseExpectString(expectString);
|
|
EXPECT_EQ(acContext, exContext)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
EXPECT_EQ(acPiece, exPiece)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
}
|
|
EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
|
|
}
|
|
}
|
|
|
|
TEST(LayoutSplitterTest, BidiCtrl) {
|
|
struct TestCase {
|
|
std::string testStr;
|
|
std::vector<std::string> expects;
|
|
} testCases[] = {
|
|
{// Repeated Bidi sequence
|
|
"(a\u2066\u2069\u202A\u202E\u200E\u200Fb)",
|
|
{
|
|
"[(a)]\u2066\u2069\u202A\u202E\u200E\u200Fb",
|
|
"a[(\u2066)]\u2069\u202A\u202E\u200E\u200Fb",
|
|
"a\u2066[(\u2069)]\u202A\u202E\u200E\u200Fb",
|
|
"a\u2066\u2069[(\u202A)]\u202E\u200E\u200Fb",
|
|
"a\u2066\u2069\u202A[(\u202E)]\u200E\u200Fb",
|
|
"a\u2066\u2069\u202A\u202E[(\u200E)]\u200Fb",
|
|
"a\u2066\u2069\u202A\u202E\u200E[(\u200F)]b",
|
|
"a\u2066\u2069\u202A\u202E\u200E\u200F[(b)]",
|
|
}},
|
|
};
|
|
|
|
for (const auto& testCase : testCases) {
|
|
auto [text, range] = parseTestString(testCase.testStr);
|
|
uint32_t expectationIndex = 0;
|
|
for (auto [acContext, acPiece] : LayoutSplitter(text, range, false /* isRtl */)) {
|
|
ASSERT_NE(expectationIndex, testCase.expects.size());
|
|
const std::string expectString = testCase.expects[expectationIndex++];
|
|
auto [exContext, exPiece] = parseExpectString(expectString);
|
|
EXPECT_EQ(acContext, exContext)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
EXPECT_EQ(acPiece, exPiece)
|
|
<< expectString << " vs " << buildDebugString(text, acContext, acPiece);
|
|
}
|
|
EXPECT_EQ(expectationIndex, testCase.expects.size()) << "Expectations Remains";
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace minikin
|