diff --git a/engine/src/flutter/libs/minikin/Android.mk b/engine/src/flutter/libs/minikin/Android.mk index e2b588c19c7..765a3ddfd75 100644 --- a/engine/src/flutter/libs/minikin/Android.mk +++ b/engine/src/flutter/libs/minikin/Android.mk @@ -24,6 +24,7 @@ minikin_src_files := \ GraphemeBreak.cpp \ Hyphenator.cpp \ Layout.cpp \ + LayoutUtils.cpp \ LineBreaker.cpp \ Measurement.cpp \ MinikinInternal.cpp \ diff --git a/engine/src/flutter/libs/minikin/Layout.cpp b/engine/src/flutter/libs/minikin/Layout.cpp index 1b1ad1f296a..b995a144cf1 100644 --- a/engine/src/flutter/libs/minikin/Layout.cpp +++ b/engine/src/flutter/libs/minikin/Layout.cpp @@ -33,6 +33,7 @@ #include #include +#include "LayoutUtils.h" #include "MinikinInternal.h" #include #include @@ -468,56 +469,6 @@ static hb_script_t getScriptRun(const uint16_t* chars, size_t len, ssize_t* iter return current_script; } -/** - * For the purpose of layout, a word break is a boundary with no - * kerning or complex script processing. This is necessarily a - * heuristic, but should be accurate most of the time. - */ -static bool isWordBreak(int c) { - if (c == ' ' || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) { - // spaces - return true; - } - if ((c >= 0x3400 && c <= 0x9fff)) { - // CJK ideographs (and yijing hexagram symbols) - return true; - } - // Note: kana is not included, as sophisticated fonts may kern kana - return false; -} - -/** - * Return offset of previous word break. It is either < offset or == 0. - */ -static size_t getPrevWordBreak(const uint16_t* chars, size_t offset) { - if (offset == 0) return 0; - if (isWordBreak(chars[offset - 1])) { - return offset - 1; - } - for (size_t i = offset - 1; i > 0; i--) { - if (isWordBreak(chars[i - 1])) { - return i; - } - } - return 0; -} - -/** - * Return offset of next word break. It is either > offset or == len. - */ -static size_t getNextWordBreak(const uint16_t* chars, size_t offset, size_t len) { - if (offset >= len) return len; - if (isWordBreak(chars[offset])) { - return offset + 1; - } - for (size_t i = offset + 1; i < len; i++) { - if (isWordBreak(chars[i])) { - return i; - } - } - return len; -} - /** * Disable certain scripts (mostly those with cursive connection) from having letterspacing * applied. See https://github.com/behdad/harfbuzz/issues/64 for more details. @@ -615,10 +566,11 @@ void Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, HyphenEdit hyphen = ctx->paint.hyphenEdit; if (!isRtl) { // left to right - size_t wordstart = start == bufSize ? start : getPrevWordBreak(buf, start + 1); + size_t wordstart = + start == bufSize ? start : getPrevWordBreakForCache(buf, start + 1, bufSize); size_t wordend; for (size_t iter = start; iter < start + count; iter = wordend) { - wordend = getNextWordBreak(buf, iter, bufSize); + wordend = getNextWordBreakForCache(buf, iter, bufSize); // Only apply hyphen to the last word in the string. ctx->paint.hyphenEdit = wordend >= start + count ? hyphen : HyphenEdit(); size_t wordcount = std::min(start + count, wordend) - iter; @@ -630,9 +582,9 @@ void Layout::doLayoutRunCached(const uint16_t* buf, size_t start, size_t count, // right to left size_t wordstart; size_t end = start + count; - size_t wordend = end == 0 ? 0 : getNextWordBreak(buf, end - 1, bufSize); + size_t wordend = end == 0 ? 0 : getNextWordBreakForCache(buf, end - 1, bufSize); for (size_t iter = end; iter > start; iter = wordstart) { - wordstart = getPrevWordBreak(buf, iter); + wordstart = getPrevWordBreakForCache(buf, iter, bufSize); // Only apply hyphen to the last (leftmost) word in the string. ctx->paint.hyphenEdit = iter == end ? hyphen : HyphenEdit(); size_t bufStart = std::max(start, wordstart); diff --git a/engine/src/flutter/libs/minikin/LayoutUtils.cpp b/engine/src/flutter/libs/minikin/LayoutUtils.cpp new file mode 100644 index 00000000000..418268286c6 --- /dev/null +++ b/engine/src/flutter/libs/minikin/LayoutUtils.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2015 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 "LayoutUtils.h" + +/** + * For the purpose of layout, a word break is a boundary with no + * kerning or complex script processing. This is necessarily a + * heuristic, but should be accurate most of the time. + */ +static bool isWordBreakAfter(int c) { + if (c == ' ' || (c >= 0x2000 && c <= 0x200a) || c == 0x3000) { + // spaces + return true; + } + // Note: kana is not included, as sophisticated fonts may kern kana + return false; +} + +static bool isWordBreakBefore(int c) { + // CJK ideographs (and yijing hexagram symbols) + return isWordBreakAfter(c) || (c >= 0x3400 && c <= 0x9fff); +} + +/** + * Return offset of previous word break. It is either < offset or == 0. + */ +size_t getPrevWordBreakForCache( + const uint16_t* chars, size_t offset, size_t len) { + if (offset == 0) return 0; + if (offset > len) offset = len; + if (isWordBreakBefore(chars[offset - 1])) { + return offset - 1; + } + for (size_t i = offset - 1; i > 0; i--) { + if (isWordBreakBefore(chars[i]) || isWordBreakAfter(chars[i - 1])) { + return i; + } + } + return 0; +} + +/** + * Return offset of next word break. It is either > offset or == len. + */ +size_t getNextWordBreakForCache( + const uint16_t* chars, size_t offset, size_t len) { + if (offset >= len) return len; + if (isWordBreakAfter(chars[offset])) { + return offset + 1; + } + for (size_t i = offset + 1; i < len; i++) { + // No need to check isWordBreakAfter(chars[i - 1]) since it is checked + // in previous iteration. Note that isWordBreakBefore returns true + // whenever isWordBreakAfter returns true. + if (isWordBreakBefore(chars[i])) { + return i; + } + } + return len; +} diff --git a/engine/src/flutter/libs/minikin/LayoutUtils.h b/engine/src/flutter/libs/minikin/LayoutUtils.h new file mode 100644 index 00000000000..83ddd0a255d --- /dev/null +++ b/engine/src/flutter/libs/minikin/LayoutUtils.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 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_LAYOUT_UTILS_H +#define MINIKIN_LAYOUT_UTILS_H + +#include + +/** + * Return offset of previous word break. It is either < offset or == 0. + * + * For the purpose of layout, a word break is a boundary with no + * kerning or complex script processing. This is necessarily a + * heuristic, but should be accurate most of the time. + */ +size_t getPrevWordBreakForCache( + const uint16_t* chars, size_t offset, size_t len); + +/** + * Return offset of next word break. It is either > offset or == len. + * + * For the purpose of layout, a word break is a boundary with no + * kerning or complex script processing. This is necessarily a + * heuristic, but should be accurate most of the time. + */ +size_t getNextWordBreakForCache( + const uint16_t* chars, size_t offset, size_t len); + +#endif // MINIKIN_LAYOUT_UTILS_H diff --git a/engine/src/flutter/tests/Android.mk b/engine/src/flutter/tests/Android.mk index ebce0eace3b..00847e3b2a7 100644 --- a/engine/src/flutter/tests/Android.mk +++ b/engine/src/flutter/tests/Android.mk @@ -36,6 +36,9 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_SRC_FILES += \ GraphemeBreakTests.cpp \ + LayoutUtilsTest.cpp \ UnicodeUtils.cpp +LOCAL_C_INCLUDES := $(LOCAL_PATH)/../libs/minikin/ + include $(BUILD_NATIVE_TEST) diff --git a/engine/src/flutter/tests/LayoutUtilsTest.cpp b/engine/src/flutter/tests/LayoutUtilsTest.cpp new file mode 100644 index 00000000000..f4fbb181076 --- /dev/null +++ b/engine/src/flutter/tests/LayoutUtilsTest.cpp @@ -0,0 +1,510 @@ +/* + * Copyright (C) 2015 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 +#include + +#include "LayoutUtils.h" + +namespace { + +void ExpectNextWordBreakForCache(size_t offset_in, const char* query_str) { + const size_t BUF_SIZE = 256U; + uint16_t buf[BUF_SIZE]; + size_t expected_breakpoint = 0U; + size_t size = 0U; + + ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint); + EXPECT_EQ(expected_breakpoint, + getNextWordBreakForCache(buf, offset_in, size)) + << "Expected position is [" << query_str << "] from offset " << offset_in; +} + +void ExpectPrevWordBreakForCache(size_t offset_in, const char* query_str) { + const size_t BUF_SIZE = 256U; + uint16_t buf[BUF_SIZE]; + size_t expected_breakpoint = 0U; + size_t size = 0U; + + ParseUnicode(buf, BUF_SIZE, query_str, &size, &expected_breakpoint); + EXPECT_EQ(expected_breakpoint, + getPrevWordBreakForCache(buf, offset_in, size)) + << "Expected position is [" << query_str << "] from offset " << offset_in; +} + +TEST(WordBreakTest, goNextWordBreakTest) { + ExpectNextWordBreakForCache(0, "|"); + + // Continue for spaces. + ExpectNextWordBreakForCache(0, "'a' 'b' 'c' 'd' |"); + ExpectNextWordBreakForCache(1, "'a' 'b' 'c' 'd' |"); + ExpectNextWordBreakForCache(2, "'a' 'b' 'c' 'd' |"); + ExpectNextWordBreakForCache(3, "'a' 'b' 'c' 'd' |"); + ExpectNextWordBreakForCache(4, "'a' 'b' 'c' 'd' |"); + ExpectNextWordBreakForCache(1000, "'a' 'b' 'c' 'd' |"); + + // Space makes word break. + ExpectNextWordBreakForCache(0, "'a' 'b' | U+0020 'c' 'd'"); + ExpectNextWordBreakForCache(1, "'a' 'b' | U+0020 'c' 'd'"); + ExpectNextWordBreakForCache(2, "'a' 'b' U+0020 | 'c' 'd'"); + ExpectNextWordBreakForCache(3, "'a' 'b' U+0020 'c' 'd' |"); + ExpectNextWordBreakForCache(4, "'a' 'b' U+0020 'c' 'd' |"); + ExpectNextWordBreakForCache(5, "'a' 'b' U+0020 'c' 'd' |"); + ExpectNextWordBreakForCache(1000, "'a' 'b' U+0020 'c' 'd' |"); + + ExpectNextWordBreakForCache(0, "'a' 'b' | U+2000 'c' 'd'"); + ExpectNextWordBreakForCache(1, "'a' 'b' | U+2000 'c' 'd'"); + ExpectNextWordBreakForCache(2, "'a' 'b' U+2000 | 'c' 'd'"); + ExpectNextWordBreakForCache(3, "'a' 'b' U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(4, "'a' 'b' U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(5, "'a' 'b' U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(1000, "'a' 'b' U+2000 'c' 'd' |"); + + ExpectNextWordBreakForCache(0, "'a' 'b' | U+2000 U+2000 'c' 'd'"); + ExpectNextWordBreakForCache(1, "'a' 'b' | U+2000 U+2000 'c' 'd'"); + ExpectNextWordBreakForCache(2, "'a' 'b' U+2000 | U+2000 'c' 'd'"); + ExpectNextWordBreakForCache(3, "'a' 'b' U+2000 U+2000 | 'c' 'd'"); + ExpectNextWordBreakForCache(4, "'a' 'b' U+2000 U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(5, "'a' 'b' U+2000 U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(6, "'a' 'b' U+2000 U+2000 'c' 'd' |"); + ExpectNextWordBreakForCache(1000, "'a' 'b' U+2000 U+2000 'c' 'd' |"); + + // CJK ideographs makes word break. + ExpectNextWordBreakForCache(0, "U+4E00 | U+4E00 U+4E00 U+4E00 U+4E00"); + ExpectNextWordBreakForCache(1, "U+4E00 U+4E00 | U+4E00 U+4E00 U+4E00"); + ExpectNextWordBreakForCache(2, "U+4E00 U+4E00 U+4E00 | U+4E00 U+4E00"); + ExpectNextWordBreakForCache(3, "U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00"); + ExpectNextWordBreakForCache(4, "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |"); + ExpectNextWordBreakForCache(5, "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |"); + ExpectNextWordBreakForCache(1000, + "U+4E00 U+4E00 U+4E00 U+4E00 U+4E00 |"); + + ExpectNextWordBreakForCache(0, "U+4E00 | U+4E8C U+4E09 U+56DB U+4E94"); + ExpectNextWordBreakForCache(1, "U+4E00 U+4E8C | U+4E09 U+56DB U+4E94"); + ExpectNextWordBreakForCache(2, "U+4E00 U+4E8C U+4E09 | U+56DB U+4E94"); + ExpectNextWordBreakForCache(3, "U+4E00 U+4E8C U+4E09 U+56DB | U+4E94"); + ExpectNextWordBreakForCache(4, "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |"); + ExpectNextWordBreakForCache(5, "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |"); + ExpectNextWordBreakForCache(1000, + "U+4E00 U+4E8C U+4E09 U+56DB U+4E94 |"); + + ExpectNextWordBreakForCache(0, "U+4E00 'a' 'b' | U+2000 'c' U+4E00"); + ExpectNextWordBreakForCache(1, "U+4E00 'a' 'b' | U+2000 'c' U+4E00"); + ExpectNextWordBreakForCache(2, "U+4E00 'a' 'b' | U+2000 'c' U+4E00"); + ExpectNextWordBreakForCache(3, "U+4E00 'a' 'b' U+2000 | 'c' U+4E00"); + ExpectNextWordBreakForCache(4, "U+4E00 'a' 'b' U+2000 'c' | U+4E00"); + ExpectNextWordBreakForCache(5, "U+4E00 'a' 'b' U+2000 'c' U+4E00 |"); + ExpectNextWordBreakForCache(1000, "U+4E00 'a' 'b' U+2000 'c' U+4E00 |"); + + // Continue if trailing characters is Unicode combining characters. + ExpectNextWordBreakForCache(0, "U+4E00 U+0332 | U+4E00"); + ExpectNextWordBreakForCache(1, "U+4E00 U+0332 | U+4E00"); + ExpectNextWordBreakForCache(2, "U+4E00 U+0332 U+4E00 |"); + ExpectNextWordBreakForCache(3, "U+4E00 U+0332 U+4E00 |"); + ExpectNextWordBreakForCache(1000, "U+4E00 U+0332 U+4E00 |"); + + // Surrogate pairs. + ExpectNextWordBreakForCache(0, "U+1F60D U+1F618 |"); + ExpectNextWordBreakForCache(1, "U+1F60D U+1F618 |"); + ExpectNextWordBreakForCache(2, "U+1F60D U+1F618 |"); + ExpectNextWordBreakForCache(3, "U+1F60D U+1F618 |"); + ExpectNextWordBreakForCache(4, "U+1F60D U+1F618 |"); + ExpectNextWordBreakForCache(1000, "U+1F60D U+1F618 |"); + + // Broken surrogate pairs. + // U+D84D is leading surrogate but there is no trailing surrogate for it. + ExpectNextWordBreakForCache(0, "U+D84D U+1F618 |"); + ExpectNextWordBreakForCache(1, "U+D84D U+1F618 |"); + ExpectNextWordBreakForCache(2, "U+D84D U+1F618 |"); + ExpectNextWordBreakForCache(3, "U+D84D U+1F618 |"); + ExpectNextWordBreakForCache(1000, "U+D84D U+1F618 |"); + + ExpectNextWordBreakForCache(0, "U+1F618 U+D84D |"); + ExpectNextWordBreakForCache(1, "U+1F618 U+D84D |"); + ExpectNextWordBreakForCache(2, "U+1F618 U+D84D |"); + ExpectNextWordBreakForCache(3, "U+1F618 U+D84D |"); + ExpectNextWordBreakForCache(1000, "U+1F618 U+D84D |"); + + // U+DE0D is trailing surrogate but there is no leading surrogate for it. + ExpectNextWordBreakForCache(0, "U+DE0D U+1F618 |"); + ExpectNextWordBreakForCache(1, "U+DE0D U+1F618 |"); + ExpectNextWordBreakForCache(2, "U+DE0D U+1F618 |"); + ExpectNextWordBreakForCache(3, "U+DE0D U+1F618 |"); + ExpectNextWordBreakForCache(1000, "U+DE0D U+1F618 |"); + + ExpectNextWordBreakForCache(0, "U+1F618 U+DE0D |"); + ExpectNextWordBreakForCache(1, "U+1F618 U+DE0D |"); + ExpectNextWordBreakForCache(2, "U+1F618 U+DE0D |"); + ExpectNextWordBreakForCache(3, "U+1F618 U+DE0D |"); + ExpectNextWordBreakForCache(1000, "U+1F618 U+DE0D |"); + + // Regional indicator pair. U+1F1FA U+1F1F8 is US national flag. + ExpectNextWordBreakForCache(0, "U+1F1FA U+1F1F8 |"); + ExpectNextWordBreakForCache(1, "U+1F1FA U+1F1F8 |"); + ExpectNextWordBreakForCache(2, "U+1F1FA U+1F1F8 |"); + ExpectNextWordBreakForCache(1000, "U+1F1FA U+1F1F8 |"); + + // Tone marks. + // CJK ideographic char + Tone mark + CJK ideographic char + ExpectNextWordBreakForCache(0, "U+4444 U+302D | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+302D | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+302D U+4444 |"); + ExpectNextWordBreakForCache(3, "U+4444 U+302D U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+302D U+4444 |"); + + // Variation Selectors. + // CJK Ideographic char + Variation Selector(VS1) + CJK Ideographic char + ExpectNextWordBreakForCache(0, "U+845B U+FE00 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+FE00 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+FE00 U+845B |"); + ExpectNextWordBreakForCache(3, "U+845B U+FE00 U+845B |"); + ExpectNextWordBreakForCache(1000, "U+845B U+FE00 U+845B |"); + + // CJK Ideographic char + Variation Selector(VS17) + CJK Ideographic char + ExpectNextWordBreakForCache(0, "U+845B U+E0100 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+E0100 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+E0100 | U+845B"); + ExpectNextWordBreakForCache(3, "U+845B U+E0100 U+845B |"); + ExpectNextWordBreakForCache(4, "U+845B U+E0100 U+845B |"); + ExpectNextWordBreakForCache(5, "U+845B U+E0100 U+845B |"); + ExpectNextWordBreakForCache(1000, "U+845B U+E0100 U+845B |"); + + // CJK ideographic char + Tone mark + Variation Character(VS1) + ExpectNextWordBreakForCache(0, "U+4444 U+302D U+FE00 | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+302D U+FE00 | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+302D U+FE00 | U+4444"); + ExpectNextWordBreakForCache(3, "U+4444 U+302D U+FE00 U+4444 |"); + ExpectNextWordBreakForCache(4, "U+4444 U+302D U+FE00 U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+302D U+FE00 U+4444 |"); + + // CJK ideographic char + Tone mark + Variation Character(VS17) + ExpectNextWordBreakForCache(0, "U+4444 U+302D U+E0100 | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+302D U+E0100 | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+302D U+E0100 | U+4444"); + ExpectNextWordBreakForCache(3, "U+4444 U+302D U+E0100 | U+4444"); + ExpectNextWordBreakForCache(4, "U+4444 U+302D U+E0100 U+4444 |"); + ExpectNextWordBreakForCache(5, "U+4444 U+302D U+E0100 U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+302D U+E0100 U+4444 |"); + + // CJK ideographic char + Variation Character(VS1) + Tone mark + ExpectNextWordBreakForCache(0, "U+4444 U+FE00 U+302D | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+FE00 U+302D | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+FE00 U+302D | U+4444"); + ExpectNextWordBreakForCache(3, "U+4444 U+FE00 U+302D U+4444 |"); + ExpectNextWordBreakForCache(4, "U+4444 U+FE00 U+302D U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+FE00 U+302D U+4444 |"); + + // CJK ideographic char + Variation Character(VS17) + Tone mark + ExpectNextWordBreakForCache(0, "U+4444 U+E0100 U+302D | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+E0100 U+302D | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+E0100 U+302D | U+4444"); + ExpectNextWordBreakForCache(3, "U+4444 U+E0100 U+302D | U+4444"); + ExpectNextWordBreakForCache(4, "U+4444 U+E0100 U+302D U+4444 |"); + ExpectNextWordBreakForCache(5, "U+4444 U+E0100 U+302D U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+E0100 U+302D U+4444 |"); + + // Following test cases are unusual usage of variation selectors and tone + // marks for caching up the further behavior changes, e.g. index of bounds + // or crashes. Please feel free to update the test expectations if the + // behavior change makes sense to you. + + // Isolated Tone marks and Variation Selectors + ExpectNextWordBreakForCache(0, "U+FE00 |"); + ExpectNextWordBreakForCache(1, "U+FE00 |"); + ExpectNextWordBreakForCache(1000, "U+FE00 |"); + ExpectNextWordBreakForCache(0, "U+E0100 |"); + ExpectNextWordBreakForCache(1000, "U+E0100 |"); + ExpectNextWordBreakForCache(0, "U+302D |"); + ExpectNextWordBreakForCache(1000, "U+302D |"); + + // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS1) + ExpectNextWordBreakForCache(0, "U+845B U+FE00 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+FE00 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+FE00 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(3, "U+845B U+FE00 U+FE00 U+845B |"); + ExpectNextWordBreakForCache(4, "U+845B U+FE00 U+FE00 U+845B |"); + ExpectNextWordBreakForCache(1000, "U+845B U+FE00 U+FE00 U+845B |"); + + // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS17) + ExpectNextWordBreakForCache(0, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(3, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(4, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(5, "U+845B U+E0100 U+E0100 U+845B |"); + ExpectNextWordBreakForCache(6, "U+845B U+E0100 U+E0100 U+845B |"); + ExpectNextWordBreakForCache(1000, + "U+845B U+E0100 U+E0100 U+845B |"); + + // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17) + ExpectNextWordBreakForCache(0, "U+845B U+FE00 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+FE00 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+FE00 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(3, "U+845B U+FE00 U+E0100 | U+845B"); + ExpectNextWordBreakForCache(4, "U+845B U+FE00 U+E0100 U+845B |"); + ExpectNextWordBreakForCache(5, "U+845B U+FE00 U+E0100 U+845B |"); + ExpectNextWordBreakForCache(1000, "U+845B U+FE00 U+E0100 U+845B |"); + + // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS1) + ExpectNextWordBreakForCache(0, "U+845B U+E0100 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(1, "U+845B U+E0100 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(2, "U+845B U+E0100 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(3, "U+845B U+E0100 U+FE00 | U+845B"); + ExpectNextWordBreakForCache(4, "U+845B U+E0100 U+FE00 U+845B |"); + ExpectNextWordBreakForCache(5, "U+845B U+E0100 U+FE00 U+845B |"); + ExpectNextWordBreakForCache(1000, "U+845B U+E0100 U+FE00 U+845B |"); + + // Tone mark. + Tone mark + ExpectNextWordBreakForCache(0, "U+4444 U+302D U+302D | U+4444"); + ExpectNextWordBreakForCache(1, "U+4444 U+302D U+302D | U+4444"); + ExpectNextWordBreakForCache(2, "U+4444 U+302D U+302D | U+4444"); + ExpectNextWordBreakForCache(3, "U+4444 U+302D U+302D U+4444 |"); + ExpectNextWordBreakForCache(4, "U+4444 U+302D U+302D U+4444 |"); + ExpectNextWordBreakForCache(1000, "U+4444 U+302D U+302D U+4444 |"); +} + +TEST(WordBreakTest, goPrevWordBreakTest) { + ExpectPrevWordBreakForCache(0, "|"); + + // Continue for spaces. + ExpectPrevWordBreakForCache(0, "| 'a' 'b' 'c' 'd'"); + ExpectPrevWordBreakForCache(1, "| 'a' 'b' 'c' 'd'"); + ExpectPrevWordBreakForCache(2, "| 'a' 'b' 'c' 'd'"); + ExpectPrevWordBreakForCache(3, "| 'a' 'b' 'c' 'd'"); + ExpectPrevWordBreakForCache(4, "| 'a' 'b' 'c' 'd'"); + ExpectPrevWordBreakForCache(1000, "| 'a' 'b' 'c' 'd'"); + + // Space makes word break. + ExpectPrevWordBreakForCache(0, "| 'a' 'b' U+0020 'c' 'd'"); + ExpectPrevWordBreakForCache(1, "| 'a' 'b' U+0020 'c' 'd'"); + ExpectPrevWordBreakForCache(2, "| 'a' 'b' U+0020 'c' 'd'"); + ExpectPrevWordBreakForCache(3, "'a' 'b' | U+0020 'c' 'd'"); + ExpectPrevWordBreakForCache(4, "'a' 'b' U+0020 | 'c' 'd'"); + ExpectPrevWordBreakForCache(5, "'a' 'b' U+0020 | 'c' 'd'"); + ExpectPrevWordBreakForCache(1000, "'a' 'b' U+0020 | 'c' 'd'"); + + ExpectPrevWordBreakForCache(0, "| 'a' 'b' U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(1, "| 'a' 'b' U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(2, "| 'a' 'b' U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(3, "'a' 'b' | U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(4, "'a' 'b' U+2000 | 'c' 'd'"); + ExpectPrevWordBreakForCache(5, "'a' 'b' U+2000 | 'c' 'd'"); + ExpectPrevWordBreakForCache(1000, "'a' 'b' U+2000 | 'c' 'd'"); + + ExpectPrevWordBreakForCache(0, "| 'a' 'b' U+2000 U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(1, "| 'a' 'b' U+2000 U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(2, "| 'a' 'b' U+2000 U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(3, "'a' 'b' | U+2000 U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(4, "'a' 'b' U+2000 | U+2000 'c' 'd'"); + ExpectPrevWordBreakForCache(5, "'a' 'b' U+2000 U+2000 | 'c' 'd'"); + ExpectPrevWordBreakForCache(6, "'a' 'b' U+2000 U+2000 | 'c' 'd'"); + ExpectPrevWordBreakForCache(1000, "'a' 'b' U+2000 U+2000 | 'c' 'd'"); + + // CJK ideographs makes word break. + ExpectPrevWordBreakForCache(0, "| U+4E00 U+4E00 U+4E00 U+4E00 U+4E00"); + ExpectPrevWordBreakForCache(1, "| U+4E00 U+4E00 U+4E00 U+4E00 U+4E00"); + ExpectPrevWordBreakForCache(2, "U+4E00 | U+4E00 U+4E00 U+4E00 U+4E00"); + ExpectPrevWordBreakForCache(3, "U+4E00 U+4E00 | U+4E00 U+4E00 U+4E00"); + ExpectPrevWordBreakForCache(4, "U+4E00 U+4E00 U+4E00 | U+4E00 U+4E00"); + ExpectPrevWordBreakForCache(5, "U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00"); + ExpectPrevWordBreakForCache(1000, "U+4E00 U+4E00 U+4E00 U+4E00 | U+4E00"); + + ExpectPrevWordBreakForCache(0, "| U+4E00 U+4E8C U+4E09 U+56DB U+4E94"); + ExpectPrevWordBreakForCache(1, "| U+4E00 U+4E8C U+4E09 U+56DB U+4E94"); + ExpectPrevWordBreakForCache(2, "U+4E00 | U+4E8C U+4E09 U+56DB U+4E94"); + ExpectPrevWordBreakForCache(3, "U+4E00 U+4E8C | U+4E09 U+56DB U+4E94"); + ExpectPrevWordBreakForCache(4, "U+4E00 U+4E8C U+4E09 | U+56DB U+4E94"); + ExpectPrevWordBreakForCache(5, "U+4E00 U+4E8C U+4E09 U+56DB | U+4E94"); + ExpectPrevWordBreakForCache(1000, "U+4E00 U+4E8C U+4E09 U+56DB | U+4E94"); + + // Mixed case. + ExpectPrevWordBreakForCache(0, "| U+4E00 'a' 'b' U+2000 'c' U+4E00"); + ExpectPrevWordBreakForCache(1, "| U+4E00 'a' 'b' U+2000 'c' U+4E00"); + ExpectPrevWordBreakForCache(2, "| U+4E00 'a' 'b' U+2000 'c' U+4E00"); + ExpectPrevWordBreakForCache(3, "| U+4E00 'a' 'b' U+2000 'c' U+4E00"); + ExpectPrevWordBreakForCache(4, "U+4E00 'a' 'b' | U+2000 'c' U+4E00"); + ExpectPrevWordBreakForCache(5, "U+4E00 'a' 'b' U+2000 | 'c' U+4E00"); + ExpectPrevWordBreakForCache(6, "U+4E00 'a' 'b' U+2000 'c' | U+4E00"); + ExpectPrevWordBreakForCache(1000, "U+4E00 'a' 'b' U+2000 'c' | U+4E00"); + + // Continue if trailing characters is Unicode combining characters. + ExpectPrevWordBreakForCache(0, "| U+4E00 U+0332 U+4E00"); + ExpectPrevWordBreakForCache(1, "| U+4E00 U+0332 U+4E00"); + ExpectPrevWordBreakForCache(2, "| U+4E00 U+0332 U+4E00"); + ExpectPrevWordBreakForCache(3, "U+4E00 U+0332 | U+4E00"); + ExpectPrevWordBreakForCache(1000, "U+4E00 U+0332 | U+4E00"); + + // Surrogate pairs. + ExpectPrevWordBreakForCache(0, "| U+1F60D U+1F618"); + ExpectPrevWordBreakForCache(1, "| U+1F60D U+1F618"); + ExpectPrevWordBreakForCache(2, "| U+1F60D U+1F618"); + ExpectPrevWordBreakForCache(3, "| U+1F60D U+1F618"); + ExpectPrevWordBreakForCache(4, "| U+1F60D U+1F618"); + ExpectPrevWordBreakForCache(1000, "| U+1F60D U+1F618"); + + // Broken surrogate pairs. + // U+D84D is leading surrogate but there is no trailing surrogate for it. + ExpectPrevWordBreakForCache(0, "| U+D84D U+1F618"); + ExpectPrevWordBreakForCache(1, "| U+D84D U+1F618"); + ExpectPrevWordBreakForCache(2, "| U+D84D U+1F618"); + ExpectPrevWordBreakForCache(3, "| U+D84D U+1F618"); + ExpectPrevWordBreakForCache(1000, "| U+D84D U+1F618"); + + ExpectPrevWordBreakForCache(0, "| U+1F618 U+D84D"); + ExpectPrevWordBreakForCache(1, "| U+1F618 U+D84D"); + ExpectPrevWordBreakForCache(2, "| U+1F618 U+D84D"); + ExpectPrevWordBreakForCache(3, "| U+1F618 U+D84D"); + ExpectPrevWordBreakForCache(1000, "| U+1F618 U+D84D"); + + // U+DE0D is trailing surrogate but there is no leading surrogate for it. + ExpectPrevWordBreakForCache(0, "| U+DE0D U+1F618"); + ExpectPrevWordBreakForCache(1, "| U+DE0D U+1F618"); + ExpectPrevWordBreakForCache(2, "| U+DE0D U+1F618"); + ExpectPrevWordBreakForCache(3, "| U+DE0D U+1F618"); + ExpectPrevWordBreakForCache(1000, "| U+DE0D U+1F618"); + + ExpectPrevWordBreakForCache(0, "| U+1F618 U+DE0D"); + ExpectPrevWordBreakForCache(1, "| U+1F618 U+DE0D"); + ExpectPrevWordBreakForCache(2, "| U+1F618 U+DE0D"); + ExpectPrevWordBreakForCache(3, "| U+1F618 U+DE0D"); + ExpectPrevWordBreakForCache(1000, "| U+1F618 U+DE0D"); + + // Regional indicator pair. U+1F1FA U+1F1F8 is US national flag. + ExpectPrevWordBreakForCache(0, "| U+1F1FA U+1F1F8"); + ExpectPrevWordBreakForCache(1, "| U+1F1FA U+1F1F8"); + ExpectPrevWordBreakForCache(2, "| U+1F1FA U+1F1F8"); + ExpectPrevWordBreakForCache(1000, "| U+1F1FA U+1F1F8"); + + // Tone marks. + // CJK ideographic char + Tone mark + CJK ideographic char + ExpectPrevWordBreakForCache(0, "| U+4444 U+302D U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+302D U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+302D U+4444"); + ExpectPrevWordBreakForCache(3, "U+4444 U+302D | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+302D | U+4444"); + + // Variation Selectors. + // CJK Ideographic char + Variation Selector(VS1) + CJK Ideographic char + ExpectPrevWordBreakForCache(0, "| U+845B U+FE00 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+FE00 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+FE00 U+845B"); + ExpectPrevWordBreakForCache(3, "U+845B U+FE00 | U+845B"); + ExpectPrevWordBreakForCache(1000, "U+845B U+FE00 | U+845B"); + + // CJK Ideographic char + Variation Selector(VS17) + CJK Ideographic char + ExpectPrevWordBreakForCache(0, "| U+845B U+E0100 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+E0100 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+E0100 U+845B"); + ExpectPrevWordBreakForCache(3, "| U+845B U+E0100 U+845B"); + ExpectPrevWordBreakForCache(4, "U+845B U+E0100 | U+845B"); + ExpectPrevWordBreakForCache(5, "U+845B U+E0100 | U+845B"); + ExpectPrevWordBreakForCache(1000, "U+845B U+E0100 | U+845B"); + + // CJK ideographic char + Tone mark + Variation Character(VS1) + ExpectPrevWordBreakForCache(0, "| U+4444 U+302D U+FE00 U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+302D U+FE00 U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+302D U+FE00 U+4444"); + ExpectPrevWordBreakForCache(3, "| U+4444 U+302D U+FE00 U+4444"); + ExpectPrevWordBreakForCache(4, "U+4444 U+302D U+FE00 | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+302D U+FE00 | U+4444"); + + // CJK ideographic char + Tone mark + Variation Character(VS17) + ExpectPrevWordBreakForCache(0, "| U+4444 U+302D U+E0100 U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+302D U+E0100 U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+302D U+E0100 U+4444"); + ExpectPrevWordBreakForCache(3, "| U+4444 U+302D U+E0100 U+4444"); + ExpectPrevWordBreakForCache(4, "| U+4444 U+302D U+E0100 U+4444"); + ExpectPrevWordBreakForCache(5, "U+4444 U+302D U+E0100 | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+302D U+E0100 | U+4444"); + + // CJK ideographic char + Variation Character(VS1) + Tone mark + ExpectPrevWordBreakForCache(0, "| U+4444 U+FE00 U+302D U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+FE00 U+302D U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+FE00 U+302D U+4444"); + ExpectPrevWordBreakForCache(3, "| U+4444 U+FE00 U+302D U+4444"); + ExpectPrevWordBreakForCache(4, "U+4444 U+FE00 U+302D | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+FE00 U+302D | U+4444"); + + // CJK ideographic char + Variation Character(VS17) + Tone mark + ExpectPrevWordBreakForCache(0, "| U+4444 U+E0100 U+302D U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+E0100 U+302D U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+E0100 U+302D U+4444"); + ExpectPrevWordBreakForCache(3, "| U+4444 U+E0100 U+302D U+4444"); + ExpectPrevWordBreakForCache(4, "| U+4444 U+E0100 U+302D U+4444"); + ExpectPrevWordBreakForCache(5, "U+4444 U+E0100 U+302D | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+E0100 U+302D | U+4444"); + + // Following test cases are unusual usage of variation selectors and tone + // marks for caching up the further behavior changes, e.g. index of bounds + // or crashes. Please feel free to update the test expectations if the + // behavior change makes sense to you. + + // Isolated Tone marks and Variation Selectors + ExpectPrevWordBreakForCache(0, "| U+FE00"); + ExpectPrevWordBreakForCache(1, "| U+FE00"); + ExpectPrevWordBreakForCache(1000, "| U+FE00"); + ExpectPrevWordBreakForCache(0, "| U+E0100"); + ExpectPrevWordBreakForCache(1000, "| U+E0100"); + ExpectPrevWordBreakForCache(0, "| U+302D"); + ExpectPrevWordBreakForCache(1000, "| U+302D"); + + // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS1) + ExpectPrevWordBreakForCache(0, "| U+845B U+FE00 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+FE00 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+FE00 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(3, "| U+845B U+FE00 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(4, "U+845B U+FE00 U+FE00 | U+845B"); + ExpectPrevWordBreakForCache(1000, "U+845B U+FE00 U+FE00 | U+845B"); + + // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS17) + ExpectPrevWordBreakForCache(0, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(3, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(4, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(5, "| U+845B U+E0100 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(6, "U+845B U+E0100 U+E0100 | U+845B"); + ExpectPrevWordBreakForCache(1000, + "U+845B U+E0100 U+E0100 | U+845B"); + + // CJK Ideographic char + Variation Selector(VS1) + Variation Selector(VS17) + ExpectPrevWordBreakForCache(0, "| U+845B U+FE00 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+FE00 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+FE00 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(3, "| U+845B U+FE00 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(4, "| U+845B U+FE00 U+E0100 U+845B"); + ExpectPrevWordBreakForCache(5, "U+845B U+FE00 U+E0100 | U+845B"); + ExpectPrevWordBreakForCache(1000, "U+845B U+FE00 U+E0100 | U+845B"); + + // CJK Ideographic char + Variation Selector(VS17) + Variation Selector(VS1) + ExpectPrevWordBreakForCache(0, "| U+845B U+E0100 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(1, "| U+845B U+E0100 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(2, "| U+845B U+E0100 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(3, "| U+845B U+E0100 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(4, "| U+845B U+E0100 U+FE00 U+845B"); + ExpectPrevWordBreakForCache(5, "U+845B U+E0100 U+FE00 | U+845B"); + ExpectPrevWordBreakForCache(1000, "U+845B U+E0100 U+FE00 | U+845B"); + + // Tone mark. + Tone mark + ExpectPrevWordBreakForCache(0, "| U+4444 U+302D U+302D U+4444"); + ExpectPrevWordBreakForCache(1, "| U+4444 U+302D U+302D U+4444"); + ExpectPrevWordBreakForCache(2, "| U+4444 U+302D U+302D U+4444"); + ExpectPrevWordBreakForCache(3, "| U+4444 U+302D U+302D U+4444"); + ExpectPrevWordBreakForCache(4, "U+4444 U+302D U+302D | U+4444"); + ExpectPrevWordBreakForCache(1000, "U+4444 U+302D U+302D | U+4444"); +} + +} // namespace