blob: f86357d786fb404a1edfc5e361bda57f124dff14 [file] [log] [blame]
/*
* Copyright (C) 2017 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 "minikin/LineBreaker.h"
#include <algorithm>
#include <array>
#include <memory>
#include <utility>
#include <cutils/log.h>
#include <gtest/gtest.h>
#include <unicode/locid.h>
#include <unicode/uchriter.h>
#include "FontTestUtils.h"
#include "ICUTestBase.h"
#include "LocaleListCache.h"
#include "MinikinInternal.h"
#include "UnicodeUtils.h"
#include "WordBreaker.h"
namespace minikin {
constexpr float CHAR_WIDTH = 10.0; // Mock implementation always returns 10.0 for advance.
static uint32_t getLocaleListId(const std::string& langTags) {
android::AutoMutex _l(gMinikinLock);
return LocaleListCache::getId(langTags);
}
static const LocaleList& getLocaleList(uint32_t localeListId) {
android::AutoMutex _l(gMinikinLock);
return LocaleListCache::getById(localeListId);
}
struct LocaleComparator {
bool operator()(const Locale& l, const Locale& r) const {
return l.getIdentifier() > r.getIdentifier();
}
};
// Helper function for creating vector of move-only elements.
template <class Base, class T, class... Tail>
constexpr std::vector<Base> makeVector(T&& head, Tail&&... tail) {
std::array<T, 1 + sizeof...(Tail)> ar = {{std::forward<T>(head), std::forward<Tail>(tail)...}};
return std::vector<Base>(
{std::make_move_iterator(std::begin(ar)), std::make_move_iterator(std::end(ar))});
}
typedef std::map<Locale, std::string, LocaleComparator> LocaleIteratorMap;
class MockBreakIterator : public icu::BreakIterator {
public:
MockBreakIterator(const std::vector<int32_t>& breakPoints) : mBreakPoints(breakPoints) {}
virtual ~MockBreakIterator() {}
UClassID getDynamicClassID() const override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return 0;
}
UBool operator==(const BreakIterator&) const override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return false;
}
BreakIterator* clone() const override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return nullptr;
}
CharacterIterator& getText() const override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return *static_cast<CharacterIterator*>(new UCharCharacterIterator(nullptr, 0));
}
UText* getUText(UText*, UErrorCode&) const override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return nullptr;
}
void setText(const UnicodeString&) override {
// Don't care
}
void setText(UText*, UErrorCode&) override {
// Don't care
}
void adoptText(CharacterIterator*) override {
// Don't care
}
int32_t first() override {
mCurrent = 0;
return 0;
}
int32_t last() override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return 0;
}
int32_t previous() override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return 0;
}
int32_t next() override { return following(mCurrent); }
int32_t current() const override { return mCurrent; }
int32_t following(int32_t offset) override {
auto i = std::upper_bound(mBreakPoints.begin(), mBreakPoints.end(), offset);
if (i == mBreakPoints.end()) {
mCurrent = DONE;
} else {
mCurrent = *i;
}
return mCurrent;
}
int32_t preceding(int32_t) override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return 0;
}
UBool isBoundary(int32_t offset) override {
auto i = std::find(mBreakPoints.begin(), mBreakPoints.end(), offset);
return i != mBreakPoints.end();
}
int32_t next(int32_t) override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return 0;
}
BreakIterator* createBufferClone(void*, int32_t&, UErrorCode&) override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return nullptr;
}
BreakIterator& refreshInputText(UText*, UErrorCode&) override {
LOG_ALWAYS_FATAL("Not yet implemented.");
return *this;
}
private:
int32_t mCurrent;
std::vector<int32_t> mBreakPoints;
};
// A mock implementation of ICULineBreakerPool for injecting mock break iterators.
// This class does not pool and delete immediately when the iterator is released.
class MockICULineBreakerPoolImpl : public ICULineBreakerPool {
public:
MockICULineBreakerPoolImpl(LocaleIteratorMap&& map) : mLocaleIteratorMap(std::move(map)) {}
Slot acquire(const Locale& locale) override {
auto i = mLocaleIteratorMap.find(locale);
LOG_ALWAYS_FATAL_IF(i == mLocaleIteratorMap.end(), "Iterator not found for %s",
locale.getString().c_str());
return {locale.getIdentifier(),
std::make_unique<MockBreakIterator>(buildBreakPointList(i->second))};
}
void release(Slot&& slot) override {
// Move to local variable, so that the slot will be deleted when returning this method.
Slot localSlot = std::move(slot);
}
private:
// Converts break point string representation to break point index list.
// For example, "a|bc|d" will be {0, 1, 3, 4}.
// This means '|' could not be used as an input character.
static std::vector<int> buildBreakPointList(const std::string& input) {
std::vector<int> out = {0};
int breakPos = 0;
for (const char c : input) {
if (c == '|') {
out.push_back(breakPos);
} else {
breakPos++;
}
}
out.push_back(breakPos);
return out;
}
LocaleIteratorMap mLocaleIteratorMap;
};
class TestableWordBreaker : public WordBreaker {
public:
TestableWordBreaker(ICULineBreakerPool* pool) : WordBreaker(pool) {}
};
class TestableLineBreaker : public LineBreaker {
public:
TestableLineBreaker(ICULineBreakerPool* pool, const U16StringPiece& string,
const MeasuredText& measuredText)
: LineBreaker(std::make_unique<TestableWordBreaker>(pool), string, measuredText,
BreakStrategy::Greedy, HyphenationFrequency::None,
false /* justified */) {}
};
class RectangleLineWidthDelegate : public LineWidth {
public:
RectangleLineWidthDelegate(float width) : mWidth(width) {}
virtual ~RectangleLineWidthDelegate() {}
float getAt(size_t) const override { return mWidth; }
float getMin() const override { return mWidth; }
float getLeftPaddingAt(size_t) const override { return 0; }
float getRightPaddingAt(size_t) const override { return 0; }
private:
float mWidth;
};
class LineBreakerTest : public ICUTestBase {
public:
LineBreakerTest() : ICUTestBase(), mTabStops(nullptr, 0, 0) {}
virtual ~LineBreakerTest() {}
protected:
virtual void SetUp() override {
ICUTestBase::SetUp();
mCollection = getFontCollection(SYSTEM_FONT_PATH, SYSTEM_FONT_XML);
}
virtual void TearDown() override {
mCollection.reset();
ICUTestBase::TearDown();
}
LineBreakResult doLineBreak(ICULineBreakerPool* pool, const U16StringPiece& text,
const std::vector<std::unique_ptr<minikin::Run>>& runs,
float lineWidth) {
RectangleLineWidthDelegate rectangleLineWidth(lineWidth);
MeasuredText measuredText = MeasuredText::generate(text, runs);
TestableLineBreaker b(pool, text, measuredText);
return b.computeBreaks(runs, rectangleLineWidth, mTabStops);
}
std::shared_ptr<FontCollection> mCollection;
TabStops mTabStops;
private:
static constexpr const char* SYSTEM_FONT_PATH = "/system/fonts/";
static constexpr const char* SYSTEM_FONT_XML = "/system/etc/fonts.xml";
};
static std::string buildDisplayText(const std::string& text, const std::vector<int>& breaks) {
std::string out = text;
for (auto ri = breaks.rbegin(); ri != breaks.rend(); ri++) {
out.insert(*ri, "|");
}
return out;
}
static void expectLineBreaks(const std::string& expected, const std::vector<int>& actualBreaks) {
std::vector<int> expectedBreaks;
std::string text;
int breakPos = 0;
for (char c : expected) {
if (c == '|') {
expectedBreaks.push_back(breakPos);
} else {
text.push_back(c);
breakPos++;
}
}
expectedBreaks.push_back(breakPos);
EXPECT_EQ(expectedBreaks, actualBreaks)
<< "Expected: " << buildDisplayText(text, expectedBreaks) << std::endl
<< "Actual : " << buildDisplayText(text, actualBreaks);
}
class ConstantRun : public Run {
public:
ConstantRun(const Range& range, uint32_t localeListId, float width)
: Run(range), mLocaleListId(localeListId), mWidth(width) {}
virtual bool isRtl() const override { return false; }
virtual bool canHyphenate() const override { return true; }
virtual uint32_t getLocaleListId() const { return mLocaleListId; }
virtual void getMetrics(const U16StringPiece&, float* advances, MinikinExtent*,
LayoutOverhang*) const {
std::fill(advances, advances + mRange.getLength(), mWidth);
}
virtual const MinikinPaint* getPaint() const { return &mPaint; }
virtual float measureHyphenPiece(const U16StringPiece&, const Range& range, StartHyphenEdit,
EndHyphenEdit, float* advances, LayoutOverhang*) const {
std::fill(advances, advances + range.getLength(), mWidth);
return mWidth * range.getLength();
}
private:
MinikinPaint mPaint;
uint32_t mLocaleListId;
float mWidth;
};
TEST_F(LineBreakerTest, greedyLineBreakAtWordBreakingPoint) {
const uint32_t enUSLocaleListId = getLocaleListId("en-US");
const Locale& enUSLocale = getLocaleList(enUSLocaleListId)[0];
{
// The line breaking is expected to work like this:
// Input:
// Text: a b c d e f g h i j k l m n o p q r s t u v w x y z
// WordBreaker: ^ ^ ^ ^ ^ ^ ^ ^ ^
// Output: | a b c d e |
// | f g h |
// | i j k |
// | l m n o |
// | p q r s t |
// | u |
// | v w x y z |
// Here, "|" is canvas boundary. "^" is word break point.
const std::vector<uint16_t> text = utf8ToUtf16("abcdefghijklmnopqrstuvwxyz");
LocaleIteratorMap map;
map[enUSLocale] = "ab|cde|fgh|ijk|lmn|o|pqr|st|u|vwxyz";
MockICULineBreakerPoolImpl impl(std::move(map));
LineBreakResult result =
doLineBreak(&impl, text,
makeVector<std::unique_ptr<minikin::Run>>(std::make_unique<ConstantRun>(
Range(0, text.size()), enUSLocaleListId, CHAR_WIDTH)),
5 * CHAR_WIDTH);
ASSERT_EQ(7U, result.breakPoints.size());
expectLineBreaks("abcde|fgh|ijk|lmno|pqrst|u|vwxyz", result.breakPoints);
EXPECT_EQ(std::vector<float>({
5 * CHAR_WIDTH, 3 * CHAR_WIDTH, 3 * CHAR_WIDTH, 4 * CHAR_WIDTH,
5 * CHAR_WIDTH, 1 * CHAR_WIDTH, 5 * CHAR_WIDTH,
}),
result.widths);
}
// TODO: Add more test cases, non rectangle, hyphenation, addReplacementSpan etc.
}
TEST_F(LineBreakerTest, greedyLocaleSwitchTest) {
const uint32_t enUSLocaleListId = getLocaleListId("en-US");
const Locale& enUSLocale = getLocaleList(enUSLocaleListId)[0];
const uint32_t frFRLocaleListId = getLocaleListId("fr-FR");
const Locale& frFRLocale = getLocaleList(frFRLocaleListId)[0];
{
// The line breaking is expected to work like this:
// Input:
// Text: a b c d e f g h
// US WordBreaker: ^ ^
// FR WordBreaker: ^ ^
// Locale Region : [US][ FR ]
// Output: | a b |
// | c d e f |
// | g h |
// Here, "|" is canvas boundary. "^" is word break point.
const std::vector<uint16_t> text = utf8ToUtf16("abcdefgh");
LocaleIteratorMap map;
map[enUSLocale] = "ab|cde|fgh";
map[frFRLocale] = "ab|cdef|gh";
MockICULineBreakerPoolImpl impl(std::move(map));
LineBreakResult result = doLineBreak(
&impl, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 2), enUSLocaleListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, text.size()), frFRLocaleListId,
CHAR_WIDTH)),
5 * CHAR_WIDTH);
ASSERT_EQ(3U, result.breakPoints.size());
expectLineBreaks("ab|cdef|gh", result.breakPoints);
EXPECT_EQ(std::vector<float>({
2 * CHAR_WIDTH, 4 * CHAR_WIDTH, 2 * CHAR_WIDTH,
}),
result.widths);
}
// TODO: Add more test cases, hyphenataion etc.
}
TEST_F(LineBreakerTest, greedyLocaleSwich_KeepSameLocaleTest) {
const uint32_t enUSLocaleListId = getLocaleListId("en-US");
const Locale& enUSLocale = getLocaleList(enUSLocaleListId)[0];
{
// The line breaking is expected to work like this:
// Input:
// Text: a b c d e f g h
// US WordBreaker: ^ ^
// Output: | a b c d e |
// | f g h _ _ |
// Here, "|" is canvas space. "^" means word break point and "_" means empty
const std::vector<uint16_t> text = utf8ToUtf16("abcdefgh");
LocaleIteratorMap map;
map[enUSLocale] = "ab|cde|fgh";
MockICULineBreakerPoolImpl impl(std::move(map));
LineBreakResult result = doLineBreak(
&impl, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 2), enUSLocaleListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, text.size()), enUSLocaleListId,
CHAR_WIDTH)),
5 * CHAR_WIDTH);
ASSERT_EQ(2U, result.breakPoints.size());
expectLineBreaks("abcde|fgh", result.breakPoints);
EXPECT_EQ(std::vector<float>({
5 * CHAR_WIDTH, 3 * CHAR_WIDTH,
}),
result.widths);
}
}
TEST_F(LineBreakerTest, greedyLocaleSwich_insideEmail) {
const uint32_t enUSLocaleListId = getLocaleListId("en-US");
const Locale& enUSLocale = getLocaleList(enUSLocaleListId)[0];
const uint32_t frFRLocaleListId = getLocaleListId("fr-FR");
const Locale& frFRLocale = getLocaleList(frFRLocaleListId)[0];
{
// The line breaking is expected to work like this:
// Input:
// Text: a a a U+20 b @ c - d U+20 e e e
// US WordBreaker: ^ ^
// FR WordBreaker: ^ ^
// Locale Region : [ US ][ FR ]
// Output: | a a a _ |
// | b @ c _ |
// | - d _ _ |
// | e e e _ |
//
// Here, "|" is canvas space. "^" means word break point and "_" means empty
const std::vector<uint16_t> text = utf8ToUtf16("aaa b@c-d eee");
LocaleIteratorMap map;
map[enUSLocale] = "aaa |b@c-d |eee";
map[frFRLocale] = "aaa |b@c-d |eee";
MockICULineBreakerPoolImpl impl(std::move(map));
LineBreakResult result = doLineBreak(
&impl, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 7), enUSLocaleListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(7, text.size()), frFRLocaleListId,
CHAR_WIDTH)),
4 * CHAR_WIDTH);
ASSERT_EQ(4U, result.breakPoints.size());
expectLineBreaks("aaa |b@c|-d |eee", result.breakPoints);
EXPECT_EQ(std::vector<float>({
3 * CHAR_WIDTH, // Doesn't count the trailing spaces.
3 * CHAR_WIDTH, // Doesn't count the trailing spaces.
2 * CHAR_WIDTH, 3 * CHAR_WIDTH,
}),
result.widths);
}
}
// b/68669534
TEST_F(LineBreakerTest, CrashFix_Space_Tab) {
const uint32_t enUSLocaleListId = getLocaleListId("en-US");
const Locale& enUSLocale = getLocaleList(enUSLocaleListId)[0];
const std::vector<uint16_t> text = utf8ToUtf16("a \tb");
LocaleIteratorMap map;
map[enUSLocale] = "a |\t|b";
MockICULineBreakerPoolImpl impl(std::move(map));
std::vector<std::unique_ptr<minikin::Run>> runs = makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, text.size()), enUSLocaleListId, CHAR_WIDTH));
doLineBreak(&impl, text, runs, 5 * CHAR_WIDTH); // Make sure no crash happens.
}
class LocaleTraceICULineBreakerPoolImpl : public ICULineBreakerPool {
public:
LocaleTraceICULineBreakerPoolImpl() {}
Slot acquire(const Locale& locale) override {
mRequestedLocaleLog.push_back(locale);
UErrorCode status = U_ZERO_ERROR;
return {locale.getIdentifier(),
std::unique_ptr<icu::BreakIterator>(
icu::BreakIterator::createLineInstance(icu::Locale::getRoot(), status))};
}
void release(Slot&& slot) override {
// Move to local variable, so that the slot will be deleted when returning this method.
Slot localSlot = std::move(slot);
}
const std::vector<Locale>& getPassedLocaleLog() const { return mRequestedLocaleLog; }
void reset() { mRequestedLocaleLog.clear(); }
private:
std::vector<Locale> mRequestedLocaleLog;
};
TEST_F(LineBreakerTest, setLocaleList) {
constexpr size_t CHAR_COUNT = 14;
constexpr float LINE_WIDTH = CHAR_COUNT * CHAR_WIDTH;
const std::vector<uint16_t> text(CHAR_COUNT, 'a');
LocaleTraceICULineBreakerPoolImpl localeTracer;
const Locale& enUS = getLocaleList(getLocaleListId("en-US"))[0];
const Locale& frFR = getLocaleList(getLocaleListId("fr-FR"))[0];
const uint32_t enUSId = getLocaleListId("en-US");
{
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS}), localeTracer.getPassedLocaleLog());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), enUSId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS}), localeTracer.getPassedLocaleLog());
}
{
const uint32_t localeListId = getLocaleListId("fr-FR,en-US");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
}
{
const uint32_t localeListId = getLocaleListId("fr-FR");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
}
{
const uint32_t localeListId = getLocaleListId("");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
}
{
const uint32_t localeListId = getLocaleListId("THISISABOGUSLANGUAGE");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
}
{
const uint32_t localeListId = getLocaleListId("THISISABOGUSLANGUAGE,fr-FR");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS, frFR}), localeTracer.getPassedLocaleLog());
}
{
const uint32_t localeListId = getLocaleListId("THISISABOGUSLANGUAGE,en-US");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS}), localeTracer.getPassedLocaleLog());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
EXPECT_EQ(std::vector<Locale>({enUS}), localeTracer.getPassedLocaleLog());
}
{
const uint32_t localeListId = getLocaleListId("THISISABOGUSLANGUAGE,ANOTHERBOGUSLANGUAGE");
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
// Changing to the same locale must not update locale.
localeTracer.reset();
doLineBreak(&localeTracer, text,
makeVector<std::unique_ptr<minikin::Run>>(
std::make_unique<ConstantRun>(Range(0, 1), enUSId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(1, 2), localeListId, CHAR_WIDTH),
std::make_unique<ConstantRun>(Range(2, 3), localeListId, CHAR_WIDTH)),
LINE_WIDTH);
ASSERT_EQ(2u, localeTracer.getPassedLocaleLog().size());
EXPECT_EQ(enUS, localeTracer.getPassedLocaleLog()[0]);
EXPECT_FALSE(localeTracer.getPassedLocaleLog()[1].isSupported());
}
}
} // namespace minikin