blob: 9b88de04012dd34b99eeb34550f4d4c4a38dfbf5 [file] [log] [blame]
/*
* 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