diff --git a/libs/minikin/Android.mk b/libs/minikin/Android.mk index 873f279f0c9..e2b588c19c7 100644 --- a/libs/minikin/Android.mk +++ b/libs/minikin/Android.mk @@ -16,7 +16,7 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LOCAL_SRC_FILES := \ +minikin_src_files := \ AnalyzeStyle.cpp \ CmapCoverage.cpp \ FontCollection.cpp \ @@ -31,20 +31,34 @@ LOCAL_SRC_FILES := \ MinikinFontFreeType.cpp \ SparseBitSet.cpp -LOCAL_MODULE := libminikin - -LOCAL_C_INCLUDES += \ +minikin_c_includes := \ external/harfbuzz_ng/src \ external/freetype/include \ frameworks/minikin/include -LOCAL_SHARED_LIBRARIES := \ +minikin_shared_libraries := \ libharfbuzz_ng \ libft2 \ liblog \ - libpng \ libz \ libicuuc \ libutils +LOCAL_MODULE := libminikin +LOCAL_EXPORT_C_INCLUDE_DIRS := frameworks/minikin/include +LOCAL_SRC_FILES := $(minikin_src_files) +LOCAL_C_INCLUDES := $(minikin_c_includes) +LOCAL_SHARED_LIBRARIES := $(minikin_shared_libraries) + include $(BUILD_SHARED_LIBRARY) + +include $(CLEAR_VARS) + +LOCAL_MODULE := libminikin +LOCAL_MODULE_TAGS := optional +LOCAL_EXPORT_C_INCLUDE_DIRS := frameworks/minikin/include +LOCAL_SRC_FILES := $(minikin_src_files) +LOCAL_C_INCLUDES := $(minikin_c_includes) +LOCAL_SHARED_LIBRARIES := $(minikin_shared_libraries) + +include $(BUILD_STATIC_LIBRARY) diff --git a/libs/minikin/GraphemeBreak.cpp b/libs/minikin/GraphemeBreak.cpp index 21da180b105..eca74b178ce 100644 --- a/libs/minikin/GraphemeBreak.cpp +++ b/libs/minikin/GraphemeBreak.cpp @@ -71,13 +71,13 @@ bool GraphemeBreak::isGraphemeBreak(const uint16_t* buf, size_t start, size_t co // implementing a tailored version of extended grapheme clusters. // The GB rules refer to section 3.1.1, Grapheme Cluster Boundary Rules. - // Rule GB1, sot /; Rule GB2, / eot + // Rule GB1, sot ÷; Rule GB2, ÷ eot if (offset <= start || offset >= start + count) { return true; } if (U16_IS_TRAIL(buf[offset])) { - // Don't break a surrogate pair - return false; + // Don't break a surrogate pair, but a lonely trailing surrogate pair is a break + return !U16_IS_LEAD(buf[offset - 1]); } uint32_t c1 = 0; uint32_t c2 = 0; @@ -90,11 +90,11 @@ bool GraphemeBreak::isGraphemeBreak(const uint16_t* buf, size_t start, size_t co if (p1 == U_GCB_CR && p2 == U_GCB_LF) { return false; } - // Rule GB4, (Control | CR | LF) / + // Rule GB4, (Control | CR | LF) ÷ if (p1 == U_GCB_CONTROL || p1 == U_GCB_CR || p1 == U_GCB_LF) { return true; } - // Rule GB5, / (Control | CR | LF) + // Rule GB5, ÷ (Control | CR | LF) if (p2 == U_GCB_CONTROL || p2 == U_GCB_CR || p2 == U_GCB_LF) { return true; } @@ -107,7 +107,7 @@ bool GraphemeBreak::isGraphemeBreak(const uint16_t* buf, size_t start, size_t co return false; } // Rule GB8, ( LVT | T ) x T - if ((p1 == U_GCB_L || p1 == U_GCB_T) && p2 == U_GCB_T) { + if ((p1 == U_GCB_LVT || p1 == U_GCB_T) && p2 == U_GCB_T) { return false; } // Rule GB8a, Regional_Indicator x Regional_Indicator @@ -139,7 +139,7 @@ bool GraphemeBreak::isGraphemeBreak(const uint16_t* buf, size_t start, size_t co && u_getIntPropertyValue(c2, UCHAR_GENERAL_CATEGORY) == U_OTHER_LETTER) { return false; } - // Rule GB10, Any / Any + // Rule GB10, Any ÷ Any return true; } diff --git a/tests/Android.mk b/tests/Android.mk new file mode 100644 index 00000000000..ebce0eace3b --- /dev/null +++ b/tests/Android.mk @@ -0,0 +1,41 @@ +# 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. + +# see how_to_run.txt for instructions on running these tests + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := minikin_tests +LOCAL_MODULE_TAGS := tests + +LOCAL_STATIC_LIBRARIES := libminikin + +# Shared libraries which are dependencies of minikin; these are not automatically +# pulled in by the build system (and thus sadly must be repeated). + +LOCAL_SHARED_LIBRARIES := \ + libharfbuzz_ng \ + libft2 \ + liblog \ + libz \ + libicuuc \ + libutils + +LOCAL_SRC_FILES += \ + GraphemeBreakTests.cpp \ + UnicodeUtils.cpp + +include $(BUILD_NATIVE_TEST) diff --git a/tests/GraphemeBreakTests.cpp b/tests/GraphemeBreakTests.cpp new file mode 100644 index 00000000000..6eda4da9fdb --- /dev/null +++ b/tests/GraphemeBreakTests.cpp @@ -0,0 +1,130 @@ +/* + * 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 + +using namespace android; + +bool IsBreak(const char* src) { + const size_t BUF_SIZE = 256; + uint16_t buf[BUF_SIZE]; + size_t offset; + size_t size; + ParseUnicode(buf, BUF_SIZE, src, &size, &offset); + return GraphemeBreak::isGraphemeBreak(buf, 0, size, offset); +} + +TEST(GraphemeBreak, utf16) { + EXPECT_FALSE(IsBreak("U+D83C | U+DC31")); // emoji, U+1F431 + + // tests for invalid UTF-16 + EXPECT_TRUE(IsBreak("U+D800 | U+D800")); // two leading surrogates + EXPECT_TRUE(IsBreak("U+DC00 | U+DC00")); // two trailing surrogates + EXPECT_TRUE(IsBreak("'a' | U+D800")); // lonely leading surrogate + EXPECT_TRUE(IsBreak("U+DC00 | 'a'")); // lonely trailing surrogate + EXPECT_TRUE(IsBreak("U+D800 | 'a'")); // leading surrogate followed by non-surrogate + EXPECT_TRUE(IsBreak("'a' | U+DC00")); // non-surrogate followed by trailing surrogate +} + +TEST(GraphemeBreak, rules) { + // Rule GB1, sot ÷; Rule GB2, ÷ eot + EXPECT_TRUE(IsBreak("| 'a'")); + EXPECT_TRUE(IsBreak("'a' |")); + + // Rule GB3, CR x LF + EXPECT_FALSE(IsBreak("U+000D | U+000A")); // CR x LF + + // Rule GB4, (Control | CR | LF) ÷ + EXPECT_TRUE(IsBreak("'a' | U+2028")); // Line separator + EXPECT_TRUE(IsBreak("'a' | U+000D")); // LF + EXPECT_TRUE(IsBreak("'a' | U+000A")); // CR + + // Rule GB5, ÷ (Control | CR | LF) + EXPECT_TRUE(IsBreak("U+2028 | 'a'")); // Line separator + EXPECT_TRUE(IsBreak("U+000D | 'a'")); // LF + EXPECT_TRUE(IsBreak("U+000A | 'a'")); // CR + + // Rule GB6, L x ( L | V | LV | LVT ) + EXPECT_FALSE(IsBreak("U+1100 | U+1100")); // L x L + EXPECT_FALSE(IsBreak("U+1100 | U+1161")); // L x V + EXPECT_FALSE(IsBreak("U+1100 | U+AC00")); // L x LV + EXPECT_FALSE(IsBreak("U+1100 | U+AC01")); // L x LVT + + // Rule GB7, ( LV | V ) x ( V | T ) + EXPECT_FALSE(IsBreak("U+AC00 | U+1161")); // LV x V + EXPECT_FALSE(IsBreak("U+1161 | U+1161")); // V x V + EXPECT_FALSE(IsBreak("U+AC00 | U+11A8")); // LV x T + EXPECT_FALSE(IsBreak("U+1161 | U+11A8")); // V x T + + // Rule GB8, ( LVT | T ) x T + EXPECT_FALSE(IsBreak("U+AC01 | U+11A8")); // LVT x T + EXPECT_FALSE(IsBreak("U+11A8 | U+11A8")); // T x T + + // Other hangul pairs not counted above _are_ breaks (GB10) + EXPECT_TRUE(IsBreak("U+AC00 | U+1100")); // LV x L + EXPECT_TRUE(IsBreak("U+AC01 | U+1100")); // LVT x L + EXPECT_TRUE(IsBreak("U+11A8 | U+1100")); // T x L + EXPECT_TRUE(IsBreak("U+11A8 | U+AC00")); // T x LV + EXPECT_TRUE(IsBreak("U+11A8 | U+AC01")); // T x LVT + + // Rule GB8a, Regional_Indicator x Regional_Indicator + EXPECT_FALSE(IsBreak("U+1F1FA | U+1F1F8")); + + // Rule GB9, x Extend + EXPECT_FALSE(IsBreak("'a' | U+0301")); // combining accent + // Rule GB9a, x SpacingMark + EXPECT_FALSE(IsBreak("U+0915 | U+093E")); // KA, AA (spacing mark) + // Rule GB9b, Prepend x + // see tailoring test for prepend, as current ICU doesn't have any characters in the class + + // Rule GB10, Any ÷ Any + EXPECT_TRUE(IsBreak("'a' | 'b'")); + EXPECT_TRUE(IsBreak("'f' | 'i'")); // probable ligature + EXPECT_TRUE(IsBreak("U+0644 | U+0627")); // probable ligature, lam + alef + EXPECT_TRUE(IsBreak("U+4E00 | U+4E00")); // CJK ideographs + EXPECT_TRUE(IsBreak("'a' | U+1F1FA U+1F1F8")); // Regional indicator pair (flag) + EXPECT_TRUE(IsBreak("U+1F1FA U+1F1F8 | 'a'")); // Regional indicator pair (flag) +} + +TEST(GraphemeBreak, tailoring) { + // control characters that we interpret as "extend" + EXPECT_FALSE(IsBreak("'a' | U+00AD")); // soft hyphen + EXPECT_FALSE(IsBreak("'a' | U+200B")); // zwsp + EXPECT_FALSE(IsBreak("'a' | U+200E")); // lrm + EXPECT_FALSE(IsBreak("'a' | U+202A")); // lre + EXPECT_FALSE(IsBreak("'a' | U+E0041")); // tag character + + // UTC-approved characters for the Prepend class + EXPECT_FALSE(IsBreak("U+06DD | U+0661")); // arabic subtending mark + digit one + + EXPECT_TRUE(IsBreak("U+0E01 | U+0E33")); // Thai sara am + + // virama is not a grapheme break, but "pure killer" is + EXPECT_FALSE(IsBreak("U+0915 | U+094D U+0915")); // Devanagari ka+virama+ka + EXPECT_FALSE(IsBreak("U+0915 U+094D | U+0915")); // Devanagari ka+virama+ka + EXPECT_FALSE(IsBreak("U+0E01 | U+0E3A U+0E01")); // thai phinthu = pure killer + EXPECT_TRUE(IsBreak("U+0E01 U+0E3A | U+0E01")); // thai phinthu = pure killer +} + +TEST(GraphemeBreak, offsets) { + uint16_t string[] = { 0x0041, 0x06DD, 0x0045, 0x0301, 0x0049, 0x0301 }; + EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(string, 2, 3, 2)); + EXPECT_FALSE(GraphemeBreak::isGraphemeBreak(string, 2, 3, 3)); + EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(string, 2, 3, 4)); + EXPECT_TRUE(GraphemeBreak::isGraphemeBreak(string, 2, 3, 5)); +} diff --git a/tests/UnicodeUtils.cpp b/tests/UnicodeUtils.cpp new file mode 100644 index 00000000000..501fc9f4808 --- /dev/null +++ b/tests/UnicodeUtils.cpp @@ -0,0 +1,96 @@ +/* + * 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 + +// src is of the form "U+1F431 | 'h' 'i'". Position of "|" gets saved to offset if non-null. +// Size is returned in an out parameter because gtest needs a void return for ASSERT to work. +void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size, + size_t* offset) { + size_t input_ix = 0; + size_t output_ix = 0; + bool seen_offset = false; + + while (src[input_ix] != 0) { + switch (src[input_ix]) { + case '\'': + // single ASCII char + ASSERT_LT(src[input_ix], 0x80); + input_ix++; + ASSERT_NE(src[input_ix], 0); + ASSERT_LT(output_ix, buf_size); + buf[output_ix++] = (uint16_t)src[input_ix++]; + ASSERT_EQ(src[input_ix], '\''); + input_ix++; + break; + case 'u': + case 'U': { + // Unicode codepoint in hex syntax + input_ix++; + ASSERT_EQ(src[input_ix], '+'); + input_ix++; + char* endptr = (char*)src + input_ix; + unsigned long int codepoint = strtoul(src + input_ix, &endptr, 16); + size_t num_hex_digits = endptr - (src + input_ix); + ASSERT_GE(num_hex_digits, 4u); // also triggers on invalid number syntax, digits = 0 + ASSERT_LE(num_hex_digits, 6u); + ASSERT_LE(codepoint, 0x10FFFFu); + input_ix += num_hex_digits; + if (U16_LENGTH(codepoint) == 1) { + ASSERT_LE(output_ix + 1, buf_size); + buf[output_ix++] = codepoint; + } else { + // UTF-16 encoding + ASSERT_LE(output_ix + 2, buf_size); + buf[output_ix++] = U16_LEAD(codepoint); + buf[output_ix++] = U16_TRAIL(codepoint); + } + break; + } + case ' ': + input_ix++; + break; + case '|': + ASSERT_FALSE(seen_offset); + ASSERT_NE(offset, nullptr); + *offset = output_ix; + seen_offset = true; + input_ix++; + break; + default: + FAIL(); // unexpected character + } + } + ASSERT_NE(result_size, nullptr); + *result_size = output_ix; + ASSERT_TRUE(seen_offset || offset == nullptr); +} + +TEST(UnicodeUtils, parse) { + const size_t BUF_SIZE = 256; + uint16_t buf[BUF_SIZE]; + size_t offset; + size_t size; + ParseUnicode(buf, BUF_SIZE, "U+000D U+1F431 | 'a'", &size, &offset); + EXPECT_EQ(size, 4u); + EXPECT_EQ(offset, 3u); + EXPECT_EQ(buf[0], 0x000D); + EXPECT_EQ(buf[1], 0xD83D); + EXPECT_EQ(buf[2], 0xDC31); + EXPECT_EQ(buf[3], 'a'); +} diff --git a/tests/UnicodeUtils.h b/tests/UnicodeUtils.h new file mode 100644 index 00000000000..4f1b06a2fc1 --- /dev/null +++ b/tests/UnicodeUtils.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + + void ParseUnicode(uint16_t* buf, size_t buf_size, const char* src, size_t* result_size, + size_t* offset); diff --git a/tests/how_to_run.txt b/tests/how_to_run.txt new file mode 100644 index 00000000000..a6b79528551 --- /dev/null +++ b/tests/how_to_run.txt @@ -0,0 +1,4 @@ +mmm -j8 frameworks/minikin/tests && +adb push $OUT/data/nativetest/minikin_tests/minikin_tests \ + /data/nativetest/minikin_tests/minikin_tests && +adb shell /data/nativetest/minikin_tests/minikin_tests