summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeigo Nonaka <nona@google.com>2018-02-26 17:16:07 -0800
committerSeigo Nonaka <nona@google.com>2018-02-28 15:22:36 -0800
commit56f8ea51818c682991c1e700e07f93e26167ed89 (patch)
tree79879b6455c83c7a1c206732b585cf205c653263
parent3998e6fc7fb9cdd27e056253fa2af6de221da926 (diff)
downloadminikin-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.h9
-rw-r--r--include/minikin/Range.h5
-rw-r--r--libs/minikin/Android.bp1
-rw-r--r--libs/minikin/BidiUtils.cpp123
-rw-r--r--libs/minikin/BidiUtils.h90
-rw-r--r--libs/minikin/Layout.cpp243
-rw-r--r--tests/unittest/Android.bp1
-rw-r--r--tests/unittest/BidiUtilsTest.cpp536
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