diff options
author | Seigo Nonaka <nona@google.com> | 2018-02-26 17:16:07 -0800 |
---|---|---|
committer | Seigo Nonaka <nona@google.com> | 2018-02-28 15:22:36 -0800 |
commit | 56f8ea51818c682991c1e700e07f93e26167ed89 (patch) | |
tree | 79879b6455c83c7a1c206732b585cf205c653263 | |
parent | 3998e6fc7fb9cdd27e056253fa2af6de221da926 (diff) | |
download | minikin-56f8ea51818c682991c1e700e07f93e26167ed89.tar.gz |
Move BidiText to its own header with tests
Also revised the doLayoutRunCached arguments to use string pieces.
StaticLayout creation time:
RandomText Balanced Hyphenation : 18,073,107 -> 18,016,001: (-0.3%)
RandomText Balanced NoHyphenation : 7,450,524 -> 7,443,239: (-0.1%)
RandomText Greedy Hyphenation : 7,400,186 -> 7,395,553: (-0.1%)
RandomText Greedy NoHyphenation : 7,399,679 -> 7,388,850: (-0.1%)
PrecomputedText Balanced Hyphenation : 689,868 -> 694,994: (+0.7%)
PrecomputedText Balanced NoHyphenation: 516,777 -> 521,159: (+0.8%)
PrecomputedText Greedy Hyphenation : 466,565 -> 467,685: (+0.2%)
PrecomputedText Greedy NoHyphenation : 466,130 -> 469,467: (+0.7%)
PrecomputedText creation time:
NoStyled Hyphenation : 18,068,965 -> 17,992,733: (-0.4%)
NoStyled Hyphenation WidthOnly : 18,028,284 -> 18,055,468: (+0.2%)
NoStyled NoHyphenation : 7,454,650 -> 7,487,982: (+0.4%)
NoStyled NoHyphenation WidthOnly : 7,445,369 -> 7,454,787: (+0.1%)
Styled Hyphenation : 15,385,852 -> 15,357,579: (-0.2%)
Styled Hyphenation WidthOnly : 15,390,795 -> 15,381,445: (-0.1%)
Styled NoHyphenation : 14,975,306 -> 14,939,929: (-0.2%)
Styled NoHyphenation WidthOnly : 14,983,997 -> 14,979,650: (-0.0%)
StaticLayout draw time:
PrecomputedText NoStyled : 536,392 -> 541,213: (+0.9%)
PrecomputedText NoStyled WithoutCache : 528,682 -> 524,672: (-0.8%)
PrecomputedText Styled : 806,088 -> 820,548: (+1.8%)
PrecomputedText Styled WithoutCache : 856,605 -> 844,277: (-1.4%)
RandomText NoStyled : 535,535 -> 547,398: (+2.2%)
RandomText NoStyled WithoutCache : 6,819,228 -> 6,807,230: (-0.2%)
RandomText Styled : 2,929,458 -> 2,930,573: (+0.0%)
RandomText Styled WithoutCache : 3,360,559 -> 3,384,135: (+0.7%)
Bug: 65024629
Test: minikin_tests
Test: atest CtsWidgetTestCases:EditTextTest
CtsWidgetTestCases:TextViewFadingEdgeTest
FrameworksCoreTests:TextViewFallbackLineSpacingTest
FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
CtsTextTestCases FrameworksCoreTests:android.text
Change-Id: I5080db4c42be496771a4042464b48395bd1567eb
-rw-r--r-- | include/minikin/Layout.h | 9 | ||||
-rw-r--r-- | include/minikin/Range.h | 5 | ||||
-rw-r--r-- | libs/minikin/Android.bp | 1 | ||||
-rw-r--r-- | libs/minikin/BidiUtils.cpp | 123 | ||||
-rw-r--r-- | libs/minikin/BidiUtils.h | 90 | ||||
-rw-r--r-- | libs/minikin/Layout.cpp | 243 | ||||
-rw-r--r-- | tests/unittest/Android.bp | 1 | ||||
-rw-r--r-- | tests/unittest/BidiUtilsTest.cpp | 536 |
8 files changed, 794 insertions, 214 deletions
diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h index 4f22918..b3ce629 100644 --- a/include/minikin/Layout.h +++ b/include/minikin/Layout.h @@ -155,11 +155,10 @@ private: // Lay out a single bidi run // When layout is not null, layout info will be stored in the object. // When advances is not null, measurement results will be stored in the array. - static float doLayoutRunCached(const uint16_t* buf, size_t runStart, size_t runLength, - size_t bufSize, bool isRtl, LayoutContext* ctx, size_t dstStart, - StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, - Layout* layout, float* advances, MinikinExtent* extents, - LayoutPieces* lpOut); + static float doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl, + LayoutContext* ctx, size_t dstStart, StartHyphenEdit startHyphen, + EndHyphenEdit endHyphen, Layout* layout, float* advances, + MinikinExtent* extents, LayoutPieces* lpOut); // Lay out a single word static float doLayoutWord(const uint16_t* buf, size_t start, size_t count, size_t bufSize, diff --git a/include/minikin/Range.h b/include/minikin/Range.h index 2e94237..2efde7e 100644 --- a/include/minikin/Range.h +++ b/include/minikin/Range.h @@ -30,6 +30,7 @@ public: // start must be smaller than or equal to end otherwise the behavior is undefined. Range(uint32_t start, uint32_t end) : mStart(start), mEnd(end) {} + Range() : Range(NOWHERE, NOWHERE) {} Range(const Range&) = default; Range& operator=(const Range&) = default; @@ -79,6 +80,10 @@ public: return Range({std::min(left.mStart, right.mStart), std::max(left.mEnd, right.mEnd)}); } + inline bool operator==(const Range& o) const { return mStart == o.mStart && mEnd == o.mEnd; } + + inline bool operator!=(const Range& o) const { return !(*this == o); } + private: // Helper class for "for (uint32_t i : range)" style for-loop. class RangeIterator { diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp index 3c3410c..4793850 100644 --- a/libs/minikin/Android.bp +++ b/libs/minikin/Android.bp @@ -29,6 +29,7 @@ cc_library { target: { android: { srcs: [ + "BidiUtils.cpp", "CmapCoverage.cpp", "Emoji.cpp", "FontCollection.cpp", diff --git a/libs/minikin/BidiUtils.cpp b/libs/minikin/BidiUtils.cpp new file mode 100644 index 0000000..c397837 --- /dev/null +++ b/libs/minikin/BidiUtils.cpp @@ -0,0 +1,123 @@ +/* + * 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. + */ + +#define LOG_TAG "Minikin" + +#include "BidiUtils.h" + +#include <algorithm> + +#include <unicode/ubidi.h> +#include <unicode/utf16.h> + +#include "minikin/Emoji.h" + +#include "MinikinInternal.h" + +namespace minikin { + +static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) { + switch (bidi) { + case Bidi::LTR: + return 0x00; + case Bidi::RTL: + return 0x01; + case Bidi::DEFAULT_LTR: + return UBIDI_DEFAULT_LTR; + case Bidi::DEFAULT_RTL: + return UBIDI_DEFAULT_RTL; + case Bidi::FORCE_LTR: + case Bidi::FORCE_RTL: + MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel."); + return 0x00; + default: + MINIKIN_NOT_REACHED("Unknown Bidi value."); + return 0x00; + } +} + +BidiText::RunInfo BidiText::getRunInfoAt(uint32_t runOffset) const { + MINIKIN_ASSERT(runOffset < mRunCount, "Out of range access. %d/%d", runOffset, mRunCount); + if (mRunCount == 1) { + // Single run. No need to iteract with UBiDi. + return {mRange, mIsRtl}; + } + + int32_t startRun = -1; + int32_t lengthRun = -1; + const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), runOffset, &startRun, &lengthRun); + if (startRun == -1 || lengthRun == -1) { + ALOGE("invalid visual run"); + return {Range::invalidRange(), false}; + } + const uint32_t runStart = std::max(static_cast<uint32_t>(startRun), mRange.getStart()); + const uint32_t runEnd = std::min(static_cast<uint32_t>(startRun + lengthRun), mRange.getEnd()); + if (runEnd <= runStart) { + // skip the empty run. + return {Range::invalidRange(), false}; + } + return {Range(runStart, runEnd), (runDir == UBIDI_RTL)}; +} + +BidiText::BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags) + : mRange(range), mIsRtl(isRtl(bidiFlags)), mRunCount(1 /* by default, single run */) { + if (isOverride(bidiFlags)) { + // force single run. + return; + } + + mBidi.reset(ubidi_open()); + if (!mBidi) { + ALOGE("error creating bidi object"); + return; + } + UErrorCode status = U_ZERO_ERROR; + // Set callbacks to override bidi classes of new emoji + ubidi_setClassCallback(mBidi.get(), emojiBidiOverride, nullptr, nullptr, nullptr, &status); + if (!U_SUCCESS(status)) { + ALOGE("error setting bidi callback function, status = %d", status); + return; + } + + const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags); + ubidi_setPara(mBidi.get(), reinterpret_cast<const UChar*>(textBuf.data()), textBuf.size(), + bidiReq, nullptr, &status); + if (!U_SUCCESS(status)) { + ALOGE("error calling ubidi_setPara, status = %d", status); + return; + } + // RTL paragraphs get an odd level, while LTR paragraphs get an even level, + const bool paraIsRTL = ubidi_getParaLevel(mBidi.get()) & 0x01; + const ssize_t rc = ubidi_countRuns(mBidi.get(), &status); + if (!U_SUCCESS(status) || rc < 0) { + ALOGW("error counting bidi runs, status = %d", status); + return; + } + if (rc == 0) { + mIsRtl = paraIsRTL; + return; + } + if (rc == 1) { + // If the paragraph is a single run, override the paragraph dirction with the run + // (actually the whole text) direction. + const UBiDiDirection runDir = ubidi_getVisualRun(mBidi.get(), 0, nullptr, nullptr); + mIsRtl = (runDir == UBIDI_RTL); + return; + } + mRunCount = rc; +} + +} // namespace minikin diff --git a/libs/minikin/BidiUtils.h b/libs/minikin/BidiUtils.h new file mode 100644 index 0000000..14678d8 --- /dev/null +++ b/libs/minikin/BidiUtils.h @@ -0,0 +1,90 @@ +/* + * 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. + */ + +#ifndef MINIKIN_BIDI_UTILS_H +#define MINIKIN_BIDI_UTILS_H + +#define LOG_TAG "Minikin" + +#include "minikin/Layout.h" + +#include <memory> + +#include <unicode/ubidi.h> + +#include "minikin/Macros.h" +#include "minikin/U16StringPiece.h" + +namespace minikin { + +struct UBiDiDeleter { + void operator()(UBiDi* v) { ubidi_close(v); } +}; + +using UBiDiUniquePtr = std::unique_ptr<UBiDi, UBiDiDeleter>; + +// A helper class for iterating the bidi run transitions. +class BidiText { +public: + struct RunInfo { + Range range; + bool isRtl; + }; + + BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags); + + RunInfo getRunInfoAt(uint32_t runOffset) const; + + class iterator { + public: + inline bool operator==(const iterator& o) const { + return mRunOffset == o.mRunOffset && mParent == o.mParent; + } + + inline bool operator!=(const iterator& o) const { return !(*this == o); } + + inline RunInfo operator*() const { return mParent->getRunInfoAt(mRunOffset); } + + inline iterator& operator++() { + mRunOffset++; + return *this; + } + + private: + friend class BidiText; + + iterator(const BidiText* parent, uint32_t runOffset) + : mParent(parent), mRunOffset(runOffset) {} + + const BidiText* mParent; + uint32_t mRunOffset; + }; + + inline iterator begin() const { return iterator(this, 0); } + inline iterator end() const { return iterator(this, mRunCount); } + +private: + UBiDiUniquePtr mBidi; // Maybe null for single run. + const Range mRange; // The range in the original buffer. Used for range check. + bool mIsRtl; // The paragraph direction. + uint32_t mRunCount; // The number of the bidi run in this text. + + MINIKIN_PREVENT_COPY_AND_ASSIGN(BidiText); +}; + +} // namespace minikin + +#endif // MINIKIN_BIDI_UTILS_H diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp index 83b4ecb..9327b14 100644 --- a/libs/minikin/Layout.cpp +++ b/libs/minikin/Layout.cpp @@ -36,6 +36,7 @@ #include "minikin/HbUtils.h" #include "minikin/Macros.h" +#include "BidiUtils.h" #include "LayoutUtils.h" #include "LocaleListCache.h" #include "MinikinInternal.h" @@ -116,26 +117,6 @@ hb_font_funcs_t* getFontFuncsForEmoji() { } // namespace -static inline UBiDiLevel bidiToUBidiLevel(Bidi bidi) { - switch (bidi) { - case Bidi::LTR: - return 0x00; - case Bidi::RTL: - return 0x01; - case Bidi::DEFAULT_LTR: - return UBIDI_DEFAULT_LTR; - case Bidi::DEFAULT_RTL: - return UBIDI_DEFAULT_RTL; - case Bidi::FORCE_LTR: - case Bidi::FORCE_RTL: - MINIKIN_NOT_REACHED("FORCE_LTR/FORCE_RTL can not be converted to UBiDiLevel."); - return 0x00; - default: - MINIKIN_NOT_REACHED("Unknown Bidi value."); - return 0x00; - } -} - struct LayoutContext { LayoutContext(const MinikinPaint& paint) : paint(paint) {} const MinikinPaint& paint; @@ -465,164 +446,6 @@ static bool isScriptOkForLetterspacing(hb_script_t script) { script == HB_SCRIPT_TIRHUTA || script == HB_SCRIPT_OGHAM); } -class BidiText { -public: - class Iter { - public: - struct RunInfo { - int32_t mRunStart; - int32_t mRunLength; - bool mIsRtl; - }; - - Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, bool isRtl); - - bool operator!=(const Iter& other) const { - return mIsEnd != other.mIsEnd || mNextRunIndex != other.mNextRunIndex || - mBidi != other.mBidi; - } - - const RunInfo& operator*() const { return mRunInfo; } - - const Iter& operator++() { - updateRunInfo(); - return *this; - } - - private: - UBiDi* const mBidi; - bool mIsEnd; - size_t mNextRunIndex; - const size_t mRunCount; - const int32_t mStart; - const int32_t mEnd; - RunInfo mRunInfo; - - void updateRunInfo(); - }; - - BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, Bidi bidiFlags); - BidiText(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags) - : BidiText(textBuf.data(), range.getStart(), range.getLength(), textBuf.size(), - bidiFlags){}; - - ~BidiText() { - if (mBidi) { - ubidi_close(mBidi); - } - } - - Iter begin() const { return Iter(mBidi, mStart, mEnd, 0, mRunCount, mIsRtl); } - - Iter end() const { return Iter(mBidi, mStart, mEnd, mRunCount, mRunCount, mIsRtl); } - -private: - const size_t mStart; - const size_t mEnd; - const size_t mBufSize; - UBiDi* mBidi; - size_t mRunCount; - bool mIsRtl; - - BidiText(const BidiText&) = delete; - void operator=(const BidiText&) = delete; -}; - -BidiText::Iter::Iter(UBiDi* bidi, size_t start, size_t end, size_t runIndex, size_t runCount, - bool isRtl) - : mBidi(bidi), - mIsEnd(runIndex == runCount), - mNextRunIndex(runIndex), - mRunCount(runCount), - mStart(start), - mEnd(end), - mRunInfo() { - if (mRunCount == 1) { - mRunInfo.mRunStart = start; - mRunInfo.mRunLength = end - start; - mRunInfo.mIsRtl = isRtl; - mNextRunIndex = mRunCount; - return; - } - updateRunInfo(); -} - -void BidiText::Iter::updateRunInfo() { - if (mNextRunIndex == mRunCount) { - // All runs have been iterated. - mIsEnd = true; - return; - } - int32_t startRun = -1; - int32_t lengthRun = -1; - const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, mNextRunIndex, &startRun, &lengthRun); - mNextRunIndex++; - if (startRun == -1 || lengthRun == -1) { - ALOGE("invalid visual run"); - // skip the invalid run. - updateRunInfo(); - return; - } - const int32_t runEnd = std::min(startRun + lengthRun, mEnd); - mRunInfo.mRunStart = std::max(startRun, mStart); - mRunInfo.mRunLength = runEnd - mRunInfo.mRunStart; - if (mRunInfo.mRunLength <= 0) { - // skip the empty run. - updateRunInfo(); - return; - } - mRunInfo.mIsRtl = (runDir == UBIDI_RTL); -} - -BidiText::BidiText(const uint16_t* buf, size_t start, size_t count, size_t bufSize, Bidi bidiFlags) - : mStart(start), - mEnd(start + count), - mBufSize(bufSize), - mBidi(NULL), - mRunCount(1), - mIsRtl(isRtl(bidiFlags)) { - if (isOverride(bidiFlags)) { - // force single run. - return; - } - mBidi = ubidi_open(); - if (!mBidi) { - ALOGE("error creating bidi object"); - return; - } - UErrorCode status = U_ZERO_ERROR; - // Set callbacks to override bidi classes of new emoji - ubidi_setClassCallback(mBidi, emojiBidiOverride, nullptr, nullptr, nullptr, &status); - if (!U_SUCCESS(status)) { - ALOGE("error setting bidi callback function, status = %d", status); - return; - } - - const UBiDiLevel bidiReq = bidiToUBidiLevel(bidiFlags); - - ubidi_setPara(mBidi, reinterpret_cast<const UChar*>(buf), mBufSize, bidiReq, NULL, &status); - if (!U_SUCCESS(status)) { - ALOGE("error calling ubidi_setPara, status = %d", status); - return; - } - // RTL paragraphs get an odd level, while LTR paragraphs get an even level, - const bool paraIsRTL = ubidi_getParaLevel(mBidi) & 0x01; - const ssize_t rc = ubidi_countRuns(mBidi, &status); - if (!U_SUCCESS(status) || rc < 0) { - ALOGW("error counting bidi runs, status = %d", status); - } - if (!U_SUCCESS(status) || rc <= 0) { - mIsRtl = paraIsRTL; - return; - } - if (rc == 1) { - const UBiDiDirection runDir = ubidi_getVisualRun(mBidi, 0, nullptr, nullptr); - mIsRtl = (runDir == UBIDI_RTL); - return; - } - mRunCount = rc; -} - void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bidiFlags, const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen) { @@ -633,10 +456,9 @@ void Layout::doLayout(const U16StringPiece& textBuf, const Range& range, Bidi bi mAdvances.resize(count, 0); mExtents.resize(count); - for (const BidiText::Iter::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) { - doLayoutRunCached(textBuf.data(), runInfo.mRunStart, runInfo.mRunLength, textBuf.size(), - runInfo.mIsRtl, &ctx, range.getStart(), startHyphen, endHyphen, this, - nullptr, nullptr, nullptr); + for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) { + doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, &ctx, range.getStart(), + startHyphen, endHyphen, this, nullptr, nullptr, nullptr); } } @@ -647,15 +469,14 @@ void Layout::addToLayoutPieces(const U16StringPiece& textBuf, const Range& range LayoutContext ctx(paint); float advance = 0; - for (const BidiText::Iter::RunInfo& runInfo : BidiText(textBuf, range, bidiFlag)) { - advance += doLayoutRunCached(textBuf.data(), runInfo.mRunStart, runInfo.mRunLength, - textBuf.size(), runInfo.mIsRtl, &ctx, + for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlag)) { + advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, &ctx, 0, // Destination start. Not used. StartHyphenEdit::NO_EDIT, // Hyphen edit, not used. EndHyphenEdit::NO_EDIT, // Hyphen edit, not used. - nullptr, // output layout. Not used - nullptr, // advances. Not used - nullptr, // extents. Not used. + nullptr, // output layout. Not used + nullptr, // advances. Not used + nullptr, // extents. Not used. out); } } @@ -666,50 +487,54 @@ float Layout::measureText(const U16StringPiece& textBuf, const Range& range, Bid LayoutContext ctx(paint); float advance = 0; - for (const BidiText::Iter::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) { - const size_t offset = range.toRangeOffset(runInfo.mRunStart); + for (const BidiText::RunInfo& runInfo : BidiText(textBuf, range, bidiFlags)) { + const size_t offset = range.toRangeOffset(runInfo.range.getStart()); float* advancesForRun = advances ? advances + offset : nullptr; MinikinExtent* extentsForRun = extents ? extents + offset : nullptr; - advance += doLayoutRunCached(textBuf.data(), runInfo.mRunStart, runInfo.mRunLength, - textBuf.size(), runInfo.mIsRtl, &ctx, 0, startHyphen, + advance += doLayoutRunCached(textBuf, runInfo.range, runInfo.isRtl, &ctx, 0, startHyphen, endHyphen, NULL, advancesForRun, extentsForRun, nullptr); } return advance; } -float Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, size_t bufSize, - bool isRtl, LayoutContext* ctx, size_t dstStart, - StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, - Layout* layout, float* advances, MinikinExtent* extents, - LayoutPieces* lpOut) { +float Layout::doLayoutRunCached(const U16StringPiece& textBuf, const Range& range, bool isRtl, + LayoutContext* ctx, size_t dstStart, StartHyphenEdit startHyphen, + EndHyphenEdit endHyphen, Layout* layout, float* advances, + MinikinExtent* extents, LayoutPieces* lpOut) { + if (!range.isValid()) { + return 0.0f; // ICU failed to retrieve the bidi run? + } + const uint16_t* buf = textBuf.data(); + const uint32_t bufSize = textBuf.size(); + const uint32_t start = range.getStart(); + const uint32_t end = range.getEnd(); float advance = 0; if (!isRtl) { // left to right - size_t wordstart = + uint32_t wordstart = start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize); - size_t wordend; - for (size_t iter = start; iter < start + count; iter = wordend) { + uint32_t wordend; + for (size_t iter = start; iter < end; iter = wordend) { wordend = getNextWordBreakForCache(buf, iter, bufSize); - const size_t wordcount = std::min(start + count, wordend) - iter; - const size_t offset = iter - start; + const uint32_t wordcount = std::min(end, wordend) - iter; + const uint32_t offset = iter - start; advance += doLayoutWord(buf + wordstart, iter - wordstart, wordcount, wordend - wordstart, isRtl, ctx, iter - dstStart, // Only apply hyphen to the first or last word in the string. iter == start ? startHyphen : StartHyphenEdit::NO_EDIT, - wordend >= start + count ? endHyphen : EndHyphenEdit::NO_EDIT, - layout, advances ? advances + offset : nullptr, + wordend >= end ? endHyphen : EndHyphenEdit::NO_EDIT, layout, + advances ? advances + offset : nullptr, extents ? extents + offset : nullptr, lpOut); wordstart = wordend; } } else { // right to left - size_t wordstart; - size_t end = start + count; - size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize); + uint32_t wordstart; + uint32_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize); for (size_t iter = end; iter > start; iter = wordstart) { wordstart = getPrevWordBreakForCache(buf, iter, bufSize); - size_t bufStart = std::max(start, wordstart); - const size_t offset = bufStart - start; + uint32_t bufStart = std::max(start, wordstart); + const uint32_t offset = bufStart - start; advance += doLayoutWord(buf + wordstart, bufStart - wordstart, iter - bufStart, wordend - wordstart, isRtl, ctx, bufStart - dstStart, // Only apply hyphen to the first (rightmost) or last (leftmost) diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp index 7475b94..5e62871 100644 --- a/tests/unittest/Android.bp +++ b/tests/unittest/Android.bp @@ -39,6 +39,7 @@ cc_test { ], srcs: [ + "BidiUtilsTest.cpp", "CmapCoverageTest.cpp", "EmojiTest.cpp", "FontCollectionTest.cpp", diff --git a/tests/unittest/BidiUtilsTest.cpp b/tests/unittest/BidiUtilsTest.cpp new file mode 100644 index 0000000..f743a66 --- /dev/null +++ b/tests/unittest/BidiUtilsTest.cpp @@ -0,0 +1,536 @@ +/* + * 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 "BidiUtils.h" + +#include <gtest/gtest.h> + +#include "minikin/Range.h" + +#include "UnicodeUtils.h" + +namespace minikin { + +const char LTR_1[] = "Hello, World"; +const char RTL_1[] = "\u0627\u0644\u0633\u0644\u0627\u0645\u0020\u0639\u0644\u064A\u0643\u0645"; +const char LTR_2[] = "Hello, Android"; +const char RTL_2[] = "\u0639\u0644\u064A\u0643\u0645\u0020\u0627\u0644\u0633\u0644\u0627\u0645"; + +TEST(BidiUtilsTest, AllLTRCharText) { + auto text = utf8ToUtf16(LTR_1); + uint32_t ltrLength = text.size(); + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, ltrLength), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +TEST(BidiUtilsTest, AllRTLCharText) { + auto text = utf8ToUtf16(RTL_1); + uint32_t rtlLength = text.size(); + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + BidiText bidiText(text, Range(0, rtlLength), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +TEST(BidiUtilsTest, LTR_RTL_CharText) { + auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1); + uint32_t ltrLength = utf8ToUtf16(LTR_1).size(); + uint32_t rtlLength = utf8ToUtf16(RTL_1).size(); + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : R2 R1 L1 L2 + BidiText bidiText(text, Range(0, text.size()), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltrLength, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : L1 L2 R1 R2 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 + // Visual Run : R2 R1 L2 L1 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +TEST(BidiUtilsTest, RTL_LTR_CharText) { + auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1); + uint32_t ltrLength = utf8ToUtf16(LTR_1).size(); + uint32_t rtlLength = utf8ToUtf16(RTL_1).size(); + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : R2 R1 L1 L2 + BidiText bidiText(text, Range(0, text.size()), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtlLength, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : R1 R2 L1 L2 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 + // Visual Run : L2 L1 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltrLength + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +TEST(BidiUtilsTest, LTR_RTL_LTR_CharText) { + auto text = utf8ToUtf16(std::string(LTR_1) + RTL_1 + LTR_2); + uint32_t ltr1Length = utf8ToUtf16(LTR_1).size(); + uint32_t ltr2Length = utf8ToUtf16(LTR_2).size(); + uint32_t rtlLength = utf8ToUtf16(RTL_1).size(); + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L1 L2 R2 R1 L3 L4 + BidiText bidiText(text, Range(0, text.size()), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L3 L4 R2 R1 L1 2L + BidiText bidiText(text, Range(0, text.size()), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length + rtlLength, text.size()), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L1 L2 R2 R1 L3 L4 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L1 L2 R2 R1 L3 L4 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length, ltr1Length + rtlLength), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(ltr1Length + rtlLength, ltr1Length + rtlLength + ltr2Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L1 L2 R2 R1 L3 L4 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: L1 L2 R1 R2 L3 L4 + // Visual Run : L1 L2 R2 R1 L3 L4 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, ltr1Length + rtlLength + ltr2Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +TEST(BidiUtilsTest, RTL_LTR_RTL_CharText) { + auto text = utf8ToUtf16(std::string(RTL_1) + LTR_1 + RTL_2); + uint32_t ltrLength = utf8ToUtf16(LTR_1).size(); + uint32_t rtl1Length = utf8ToUtf16(RTL_1).size(); + uint32_t rtl2Length = utf8ToUtf16(RTL_2).size(); + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R2 R1 L1 L2 R4 R3 + BidiText bidiText(text, Range(0, text.size()), Bidi::LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R4 R3 L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R4 R3 L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R4 R3 L1 L2 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::DEFAULT_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length + ltrLength, text.size()), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(rtl1Length, ltrLength + rtl1Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R1 R2 L1 L2 R3 R4 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_LTR); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range); + EXPECT_FALSE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } + { + // Logical Run: R1 R2 L1 L2 R3 R4 + // Visual Run : R4 R3 L2 L1 R2 R1 + BidiText bidiText(text, Range(0, text.size()), Bidi::FORCE_RTL); + auto it = bidiText.begin(); + EXPECT_NE(bidiText.end(), it); + EXPECT_EQ(Range(0, rtl1Length + ltrLength + rtl2Length), (*it).range); + EXPECT_TRUE((*it).isRtl); + ++it; + EXPECT_EQ(bidiText.end(), it); + } +} + +} // namespace minikin |