diff options
58 files changed, 1963 insertions, 694 deletions
@@ -2,6 +2,16 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +cc_defaults { + name: "libminikin_defaults", + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + "-Wthread-safety", + ], +} + cc_library_headers { name: "libminikin_headers", host_supported: true, @@ -0,0 +1,3 @@ +set noparent + +file:platform/frameworks/base:/core/java/android/text/OWNERS diff --git a/app/Android.bp b/app/Android.bp index 9d6c28a..939d58c 100644 --- a/app/Android.bp +++ b/app/Android.bp @@ -33,5 +33,5 @@ cc_binary_host { srcs: ["HyphTool.cpp"], - cflags: ["-Wall", "-Werror"], + defaults: ["libminikin_defaults"], } diff --git a/fuzz/hyphenator_fuzzer/Android.bp b/fuzz/hyphenator_fuzzer/Android.bp new file mode 100644 index 0000000..0b69dc3 --- /dev/null +++ b/fuzz/hyphenator_fuzzer/Android.bp @@ -0,0 +1,44 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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. + * + ***************************************************************************** + */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_fuzz { + name: "hyphenator_fuzzer", + srcs: [ + "hyphenator_fuzzer.cpp", + ], + static_libs: [ + "libminikin", + "libminikin-tests-util", + ], + shared_libs: [ + "liblog", + "libharfbuzz_ng", + "libft2", + "libicu", + "libutils", + ], + header_libs: [ + "libminikin_headers", + "libminikin-headers-for-tests", + ], + dictionary: "hyphenator.dict", +} diff --git a/fuzz/hyphenator_fuzzer/hyphenator.dict b/fuzz/hyphenator_fuzzer/hyphenator.dict new file mode 100644 index 0000000..6bcb2b4 --- /dev/null +++ b/fuzz/hyphenator_fuzzer/hyphenator.dict @@ -0,0 +1,67 @@ +# Copyright (C) 2022 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. +# +################################################################################ +"U+063B" +"U+FE8x" +"U+06F7" +"U+06CB" +"U+06C7" +"pl" +"ca" +"sl" +"pl" +"ca" +"as" +"be" +"bn" +"bg" +"cu" +"cy" +"da" +"de" +"de-1996" +"de-ch-1901" +"en-GB" +"en-gb" +"en-US" +"en-us" +"es" +"et" +"eu" +"Ethi" +"und-ethi" +"fr" +"ga" +"gu" +"hi" +"hr" +"hu" +"hy" +"kn" +"la" +"ml" +"mn" +"mn-cyrl" +"mr" +"nb" +"nn" +"or" +"pa" +"pt" +"sl" +"ta" +"te" +"tk" +"ca" diff --git a/fuzz/hyphenator_fuzzer/hyphenator_fuzzer.cpp b/fuzz/hyphenator_fuzzer/hyphenator_fuzzer.cpp new file mode 100644 index 0000000..1fa4d95 --- /dev/null +++ b/fuzz/hyphenator_fuzzer/hyphenator_fuzzer.cpp @@ -0,0 +1,137 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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 <fuzzer/FuzzedDataProvider.h> +#include <minikin/Hyphenator.h> + +#include <iostream> +#include <string> + +#include "HyphenatorMap.h" +#include "Locale.h" +#include "LocaleListCache.h" +#include "MinikinInternal.h" +#include "UnicodeUtils.h" +#include "minikin/LocaleList.h" +#include "minikin/U16StringPiece.h" + +using namespace minikin; + +const EndHyphenEdit EndHyphenEdits[] = { + EndHyphenEdit::NO_EDIT, + EndHyphenEdit::REPLACE_WITH_HYPHEN, + EndHyphenEdit::INSERT_HYPHEN, + EndHyphenEdit::INSERT_ARMENIAN_HYPHEN, + EndHyphenEdit::INSERT_MAQAF, + EndHyphenEdit::INSERT_UCAS_HYPHEN, + EndHyphenEdit::INSERT_ZWJ_AND_HYPHEN, +}; + +const StartHyphenEdit StartHyphenEdits[] = { + StartHyphenEdit::NO_EDIT, + StartHyphenEdit::INSERT_HYPHEN, + StartHyphenEdit::INSERT_ZWJ, +}; + +const HyphenationType HyphenationTypes[] = { + HyphenationType::DONT_BREAK, + HyphenationType::BREAK_AND_INSERT_HYPHEN, + HyphenationType::BREAK_AND_INSERT_ARMENIAN_HYPHEN, + HyphenationType::BREAK_AND_INSERT_MAQAF, + HyphenationType::BREAK_AND_INSERT_UCAS_HYPHEN, + HyphenationType::BREAK_AND_DONT_INSERT_HYPHEN, + HyphenationType::BREAK_AND_REPLACE_WITH_HYPHEN, + HyphenationType::BREAK_AND_INSERT_HYPHEN_AT_NEXT_LINE, + HyphenationType::BREAK_AND_INSERT_HYPHEN_AND_ZWJ, +}; + +uint16_t specialChars[] = { + 0x000A, 0x000D, 0x0009, 0x002D, 0x00A0, 0x00AD, + 0x00B7, 0x058A, 0x05BE, 0x1400, 0x200D, 0x2010, +}; + +const uint16_t MAX_STR_LEN = 256; + +// Function to generate StringPiece from a vector by pushing random valued elements using fdp +U16StringPiece generateStringPiece(FuzzedDataProvider* fdp) { + uint16_t size = fdp->ConsumeIntegralInRange<uint16_t>(0, (fdp->remaining_bytes() / 3)); + + std::vector<uint16_t> v; + for (uint16_t i = 0; i < size; ++i) { + // To randomize the insertion of special characters + if (fdp->ConsumeBool()) { + v.push_back(fdp->PickValueInArray(specialChars)); + } else { + v.push_back(fdp->ConsumeIntegral<uint16_t>()); + } + } + + return U16StringPiece(v); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + + uint8_t minPrefix = fdp.ConsumeIntegral<size_t>(); + uint8_t minSuffix = fdp.ConsumeIntegral<size_t>(); + std::string locale = fdp.ConsumeRandomLengthString(MAX_STR_LEN); + std::vector<uint8_t> patternData(fdp.ConsumeIntegralInRange<uint32_t>(0, 256)); + + Hyphenator* hyphenator = Hyphenator::loadBinary(&patternData[0], minPrefix, minSuffix, locale); + + // To randomize the API calls + while (fdp.remaining_bytes() > 0) { + auto func = fdp.PickValueInArray<const std::function<void()>>({ + [&]() { addHyphenator(locale, hyphenator); }, + [&]() { + auto fromLocaleString = fdp.ConsumeRandomLengthString(MAX_STR_LEN); + auto toLocaleString = fdp.ConsumeRandomLengthString(MAX_STR_LEN); + addHyphenatorAlias(fromLocaleString, toLocaleString); + }, + [&]() { + packHyphenEdit(fdp.PickValueInArray(StartHyphenEdits), + fdp.PickValueInArray(EndHyphenEdits)); + }, + [&]() { + auto textBuf = generateStringPiece(&fdp); + std::vector<HyphenationType> result; + result.push_back(fdp.PickValueInArray(HyphenationTypes)); + hyphenator->hyphenate(textBuf, &result); + }, + // Get the list of locales and invoke the API for each one of them + [&]() { + uint32_t id = registerLocaleList(fdp.ConsumeRandomLengthString(MAX_STR_LEN)); + const LocaleList& locales = LocaleListCache::getById(id); + for (size_t i = 0; i < locales.size(); ++i) { + HyphenatorMap::lookup(locales[i]); + } + }, + [&]() { getHyphenString(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); }, + [&]() { getHyphenString(startHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); }, + [&]() { isInsertion(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); }, + [&]() { isInsertion(startHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); }, + [&]() { editForThisLine(fdp.PickValueInArray(HyphenationTypes)); }, + [&]() { editForNextLine(fdp.PickValueInArray(HyphenationTypes)); }, + [&]() { isReplacement(endHyphenEdit(fdp.ConsumeIntegral<uint8_t>())); }, + }); + + func(); + } + + return 0; +} diff --git a/fuzz/locale_fuzzer/Android.bp b/fuzz/locale_fuzzer/Android.bp new file mode 100644 index 0000000..5367687 --- /dev/null +++ b/fuzz/locale_fuzzer/Android.bp @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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. + * + ***************************************************************************** + */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + + cc_fuzz { + name: "locale_fuzzer", + srcs: [ + "locale_fuzzer.cpp", + ], + static_libs: [ + "libminikin", + "libminikin-tests-util", + ], + shared_libs: [ + "liblog", + "libharfbuzz_ng", + "libft2", + "libicu", + "libutils", + "libz", + ], + header_libs: [ + "libminikin_headers", + "libminikin-headers-for-tests", + ], + dictionary: "locale.dict", +} diff --git a/fuzz/locale_fuzzer/locale.dict b/fuzz/locale_fuzzer/locale.dict new file mode 100644 index 0000000..66654de --- /dev/null +++ b/fuzz/locale_fuzzer/locale.dict @@ -0,0 +1,83 @@ +# Copyright (C) 2022 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. +# +################################################################################ +"as" +"be" +"bn" +"bg" +"cu" +"cy" +"da" +"de" +"de-1996" +"de-ch-1901" +"en-GB" +"en-gb" +"en-US" +"en-us" +"es" +"et" +"eu" +"Ethi" +"und-ethi" +"fr" +"ga" +"gu" +"hi" +"hr" +"hu" +"hy" +"kn" +"la" +"ml" +"mn" +"mn-cyrl" +"mr" +"nb" +"nn" +"or" +"pa" +"pt" +"sl" +"ta" +"te" +"tk" +"en-Latn" +"Latn" +"Jpan" +"zh-Hant" +"Hant" +"ko-Kore" +"Kore" +"ko-Hang" +"zh-Hans" +"Bopo" +"Hang" +"Hant" +"zh-Hant" +"Hans" +"zh-Hans" +"ja-Hira" +"ja-Kana" +"ja-Hrkt" +"ja-Hani" +"Hrkt" +"Hira" +"Kana" +"zh-Hani" +"Hanb" +"ko-Hani" +"Hani" +"zh-Hanb" diff --git a/fuzz/locale_fuzzer/locale_fuzzer.cpp b/fuzz/locale_fuzzer/locale_fuzzer.cpp new file mode 100644 index 0000000..8ae5140 --- /dev/null +++ b/fuzz/locale_fuzzer/locale_fuzzer.cpp @@ -0,0 +1,53 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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 <fuzzer/FuzzedDataProvider.h> + +#include "LocaleListCache.h" +#include "minikin/LocaleList.h" + +using namespace minikin; + +const SubtagBits subtangBits[] = {SubtagBits::EMPTY, SubtagBits::LANGUAGE, SubtagBits::SCRIPT, + SubtagBits::REGION, SubtagBits::VARIANT, SubtagBits::EMOJI, + SubtagBits::ALL}; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + + uint32_t id = registerLocaleList(fdp.ConsumeRandomLengthString()); + uint32_t localeListId = fdp.ConsumeIntegralInRange<uint32_t>(0, id); + const LocaleList& locales = LocaleListCache::getById(localeListId); + std::string langTag = getLocaleString(localeListId); + + for (size_t i = 0; i < locales.size(); i++) { + locales[i].getPartialLocale(fdp.PickValueInArray(subtangBits)); + locales[i].supportsScript(fdp.ConsumeIntegral<uint32_t>()); + locales[i].calcScoreFor(locales); + } + + BufferWriter fakeWriter(nullptr); + LocaleListCache::writeTo(&fakeWriter, LocaleListCache::getId(langTag)); + std::vector<uint8_t> buffer(fakeWriter.size()); + BufferWriter writer(buffer.data()); + LocaleListCache::writeTo(&writer, LocaleListCache::getId(langTag)); + BufferReader reader(buffer.data()); + LocaleListCache::readFrom(&reader); + + return 0; +} diff --git a/fuzz/measurement_fuzzer/Android.bp b/fuzz/measurement_fuzzer/Android.bp new file mode 100644 index 0000000..133c172 --- /dev/null +++ b/fuzz/measurement_fuzzer/Android.bp @@ -0,0 +1,45 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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. + * + ***************************************************************************** + */ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_fuzz { + name: "measurement_fuzzer", + srcs: [ + "measurement_fuzzer.cpp", + ], + static_libs: [ + "libminikin", + "libminikin-tests-util", + "libxml2", + ], + shared_libs: [ + "liblog", + "libharfbuzz_ng", + "libft2", + "libicu", + "libutils", + "libz", + ], + header_libs: [ + "libminikin_headers", + "libminikin-headers-for-tests", + ], +} diff --git a/fuzz/measurement_fuzzer/measurement_fuzzer.cpp b/fuzz/measurement_fuzzer/measurement_fuzzer.cpp new file mode 100644 index 0000000..872f4c6 --- /dev/null +++ b/fuzz/measurement_fuzzer/measurement_fuzzer.cpp @@ -0,0 +1,49 @@ +/****************************************************************************** + * + * Copyright (C) 2022 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 <fuzzer/FuzzedDataProvider.h> + +#include "minikin/Measurement.h" +using namespace minikin; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fdp(data, size); + float advance = fdp.ConsumeFloatingPoint<float>(); + int sizeof_size = (int)sizeof(size_t); + int sizeof_uint16 = (int)sizeof(uint16_t); + int sizeof_float = (int)sizeof(float); + int remaining = fdp.remaining_bytes(); + int limit = (int)((remaining - 3 * sizeof_size) / sizeof_uint16); + limit = (limit < 0) ? 0 : limit; + int buf_size = fdp.ConsumeIntegralInRange<int>(0, limit); + if (buf_size == 0) return 0; + uint16_t buf[buf_size]; + for (int i = 0; i < buf_size; i++) buf[i] = fdp.ConsumeIntegral<uint16_t>(); + size_t start = fdp.ConsumeIntegralInRange<size_t>(0, buf_size - 1); + size_t count = fdp.ConsumeIntegralInRange<size_t>(0, buf_size - 1 - start); + size_t offset = fdp.ConsumeIntegralInRange<size_t>(start, start + count); + remaining = fdp.remaining_bytes(); + if (remaining / sizeof_float < count) return 0; + if (offset == start + count) return 0; + int advances_size = count; + float advances[advances_size]; + for (int i = 0; i < advances_size; i++) advances[i] = fdp.ConsumeFloatingPoint<float>(); + float advance_run = getRunAdvance(advances, buf, start, count, offset); + size_t advance_offset = getOffsetForAdvance(advances, buf, start, count, advance); + return 0; +} diff --git a/include/minikin/Buffer.h b/include/minikin/Buffer.h index 87ba3fd..a4d0104 100644 --- a/include/minikin/Buffer.h +++ b/include/minikin/Buffer.h @@ -27,55 +27,88 @@ namespace minikin { // This is a helper class to read data from a memory buffer. // This class does not copy memory, and may return pointers to parts of the memory buffer. // Thus the memory buffer should outlive objects created using this class. +// +// Note on alignment: +// Some CPU archs (e.g. arm32) do not allow misaligned memory access. +// Therefore, BufferReader and BufferWriter automatically insert paddings +// to align data records. +// For the padding to be deterministic, the following conditions must be met: +// (1) Alignment and size of each data record must be fixed regardless of +// CPU arch. +// (2) Alignment for each data record must be a power of 2 (2^n) and +// must be less than or equal to kMaxAlignment. +// (3) The head address of the buffer must be aligned at kMaxAlignment. +// +// The condition (2) and (3) ensures that 'headAddress % align == 0' +// and the padding is determined only by the current position. +// I.e. mCurrent % align == (mCurrent - headAddress) % align. class BufferReader { public: - BufferReader(const void* buffer) : BufferReader(buffer, 0) {} + static constexpr size_t kMaxAlignment = 8; + + explicit BufferReader(const void* buffer) : BufferReader(buffer, 0) {} BufferReader(const void* buffer, uint32_t pos) - : mData(reinterpret_cast<const uint8_t*>(buffer)), mPos(pos) {} - - template <typename T> - static uint32_t align(uint32_t pos) { - // This should be true for all types, unless custom alignment attributes are set. - static_assert(sizeof(T) % alignof(T) == 0, "sizeof(T) must be a multiple of alignof(T)"); - // We align to sizeof(T) instead of alignof(T), because the buffer may be shared between - // 32-bit processes and 64-bit processes. alignof(T) may change between the two. - // We assume that T is a type whose size is fixed (e.g. uint32_t). - return (pos + sizeof(T) - 1) / sizeof(T) * sizeof(T); + : mCurrent(reinterpret_cast<const uint8_t*>(buffer) + pos) {} + + // align() adds padding if necessary so that the returned pointer is aligned + // at 'align' template parameter (i.e. align<T, _align>(p) % _align == 0). + // + // By default we align to sizeof(T) instead of alignof(T), because the + // buffer may be shared between 32-bit processes and 64-bit processes. + // The value of alignof(T) may change between the two. + // + // If T is a large struct or class, you would need to specify 'align' + // template parameter manually. + template <typename T, size_t align = sizeof(T)> + static const uint8_t* align(const uint8_t* p) { + static_assert(align <= kMaxAlignment); + static_assert(__builtin_popcount(align) == 1, "align must be a power of 2"); + constexpr size_t mask = align - 1; + intptr_t i = reinterpret_cast<intptr_t>(p); + intptr_t aligned = (i + mask) & ~mask; + return reinterpret_cast<const uint8_t*>(aligned); } - template <typename T> + template <typename T, size_t align = sizeof(T)> const T& read() { - static_assert(std::is_pod<T>::value, "T must be a POD"); - mPos = BufferReader::align<T>(mPos); - const T* data = reinterpret_cast<const T*>(mData + mPos); - mPos += sizeof(T); + const T* data = map<T, align>(sizeof(T)); return *data; } - template <typename T> + template <typename T, size_t align = sizeof(T)> + const T* map(uint32_t size) { + static_assert(std::is_pod<T>::value, "T must be a POD"); + mCurrent = BufferReader::align<T, align>(mCurrent); + const T* data = reinterpret_cast<const T*>(mCurrent); + mCurrent += size; + return data; + } + + template <typename T, size_t align = sizeof(T)> void skip() { static_assert(std::is_pod<T>::value, "T must be a POD"); - mPos = BufferReader::align<T>(mPos); - mPos += sizeof(T); + mCurrent = BufferReader::align<T, align>(mCurrent); + mCurrent += sizeof(T); } // Return a pointer to an array and its number of elements. - template <typename T> + template <typename T, size_t align = sizeof(T)> std::pair<const T*, uint32_t> readArray() { static_assert(std::is_pod<T>::value, "T must be a POD"); + static_assert(sizeof(T) % align == 0); uint32_t size = read<uint32_t>(); - mPos = BufferReader::align<T>(mPos); - const T* data = reinterpret_cast<const T*>(mData + mPos); - mPos += size * sizeof(T); + mCurrent = BufferReader::align<T, align>(mCurrent); + const T* data = reinterpret_cast<const T*>(mCurrent); + mCurrent += size * sizeof(T); return std::make_pair(data, size); } - template <typename T> + template <typename T, size_t align = sizeof(T)> void skipArray() { static_assert(std::is_pod<T>::value, "T must be a POD"); uint32_t size = read<uint32_t>(); - mPos = BufferReader::align<T>(mPos); - mPos += size * sizeof(T); + mCurrent = BufferReader::align<T, align>(mCurrent); + mCurrent += size * sizeof(T); } std::string_view readString() { @@ -85,20 +118,32 @@ public: void skipString() { skipArray<char>(); } - const void* data() const { return mData; } - size_t pos() const { return mPos; } + const void* current() const { return mCurrent; } private: - const uint8_t* mData; - size_t mPos; + const uint8_t* mCurrent; }; // This is a helper class to write data to a memory buffer. +// +// BufferWriter does NOT allocate the memory. +// The typical usage is to use BufferWriter twice; in the first pass, write +// data with a fake BufferWriter (BufferWriter(nullptr)) to calculate the buffer +// size. In the second pass, allocate a memory buffer and use a real +// BufferWriter to write the data. +// Pseudo code: +// BufferWriter fakeWriter(nullptr); +// myData.writeTo(&fakeWriter); +// void* buffer = malloc(fakeWriter.size()); +// BufferWriter realWriter(buffer); +// myData.writeTo(&realWriter); class BufferWriter { public: // Create a buffer writer. Passing nullptr creates a fake writer, // which can be used to measure the buffer size needed. - BufferWriter(void* buffer) : mData(reinterpret_cast<uint8_t*>(buffer)), mPos(0) {} + explicit BufferWriter(void* buffer) : BufferWriter(buffer, 0) {} + BufferWriter(void* buffer, uint32_t pos) + : mData(reinterpret_cast<uint8_t*>(buffer)), mPos(pos) {} BufferWriter(BufferWriter&&) = default; BufferWriter& operator=(BufferWriter&&) = default; @@ -107,25 +152,35 @@ public: // Please always specify T explicitly using <>. std::common_type_t<T> resolves to T, but // disables template argument deduction. // TODO: use std::type_identity_t when C++20 is available. - template <typename T> + template <typename T, size_t align = sizeof(T)> void write(const std::common_type_t<T>& data) { - static_assert(std::is_pod<T>::value, "T must be a POD"); - mPos = BufferReader::align<T>(mPos); - if (mData != nullptr) { - memcpy(mData + mPos, &data, sizeof(T)); + T* buf = reserve<T, align>(sizeof(T)); + if (buf != nullptr) { + memcpy(buf, &data, sizeof(T)); } - mPos += sizeof(T); + } + + // Reserve a region and return a pointer to the reserved region. + // The reserved region is not initialized. + template <typename T, size_t align = sizeof(T)> + T* reserve(uint32_t size) { + static_assert(std::is_pod<T>::value, "T must be a POD"); + mPos = BufferWriter::align<T, align>(mPos); + uint32_t pos = mPos; + mPos += size; + return mData == nullptr ? nullptr : reinterpret_cast<T*>(mData + pos); } // Write an array of type T. // Please always specify T explicitly using <>. std::common_type_t<T> resolves to T, but // disables template argument deduction. // TODO: use std::type_identity_t when C++20 is available. - template <typename T> + template <typename T, size_t align = sizeof(T)> void writeArray(const std::common_type_t<T>* data, uint32_t size) { static_assert(std::is_pod<T>::value, "T must be a POD"); + static_assert(sizeof(T) % align == 0); write<uint32_t>(size); - mPos = BufferReader::align<T>(mPos); + mPos = BufferWriter::align<T, align>(mPos); if (mData != nullptr) { memcpy(mData + mPos, data, size * sizeof(T)); } @@ -141,6 +196,11 @@ private: uint8_t* mData; size_t mPos; + template <typename T, size_t align> + size_t align(size_t pos) const { + return BufferReader::align<T, align>(mData + pos) - mData; + } + // Forbid copy and assign. BufferWriter(const BufferWriter&) = delete; void operator=(const BufferWriter&) = delete; diff --git a/include/minikin/CmapCoverage.h b/include/minikin/CmapCoverage.h index 583593d..898e63b 100644 --- a/include/minikin/CmapCoverage.h +++ b/include/minikin/CmapCoverage.h @@ -27,7 +27,7 @@ namespace minikin { class CmapCoverage { public: static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size, - std::vector<std::unique_ptr<SparseBitSet>>* out); + std::vector<SparseBitSet>* out); }; } // namespace minikin diff --git a/include/minikin/Font.h b/include/minikin/Font.h index 67feecf..f144bea 100644 --- a/include/minikin/Font.h +++ b/include/minikin/Font.h @@ -17,8 +17,10 @@ #ifndef MINIKIN_FONT_H #define MINIKIN_FONT_H +#include <gtest/gtest_prod.h> + +#include <atomic> #include <memory> -#include <mutex> #include <unordered_set> #include "minikin/Buffer.h" @@ -110,29 +112,12 @@ public: bool mIsSlantSet = false; }; - // Type for functions to load MinikinFont lazily. - using TypefaceLoader = std::shared_ptr<MinikinFont>(BufferReader reader); - // Type for functions to read MinikinFont metadata and return - // TypefaceLoader. - using TypefaceReader = TypefaceLoader*(BufferReader* reader); - // Type for functions to write MinikinFont metadata. - using TypefaceWriter = void(BufferWriter* writer, const MinikinFont* typeface); - - template <TypefaceReader typefaceReader> - static std::shared_ptr<Font> readFrom(BufferReader* reader, uint32_t localeListId) { - FontStyle style = FontStyle(reader); - BufferReader typefaceMetadataReader = *reader; - TypefaceLoader* typefaceLoader = typefaceReader(reader); - return std::shared_ptr<Font>( - new Font(style, typefaceMetadataReader, typefaceLoader, localeListId)); - } - - template <TypefaceWriter typefaceWriter> - void writeTo(BufferWriter* writer) const { - mStyle.writeTo(writer); - typefaceWriter(writer, typeface().get()); - } + explicit Font(BufferReader* reader); + void writeTo(BufferWriter* writer) const; + Font(Font&& o) noexcept; + Font& operator=(Font&& o) noexcept; + ~Font(); // This locale list is just for API compatibility. This is not used in font selection or family // fallback. uint32_t getLocaleListId() const { return mLocaleListId; } @@ -144,46 +129,47 @@ public: std::unordered_set<AxisTag> getSupportedAxes() const; private: + // ExternalRefs holds references to objects provided by external libraries. + // Because creating these external objects is costly, + // ExternalRefs is lazily created if Font was created by readFrom(). + class ExternalRefs { + public: + ExternalRefs(std::shared_ptr<MinikinFont>&& typeface, HbFontUniquePtr&& baseFont) + : mTypeface(std::move(typeface)), mBaseFont(std::move(baseFont)) {} + + std::shared_ptr<MinikinFont> mTypeface; + HbFontUniquePtr mBaseFont; + }; + // Use Builder instead. Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont, uint32_t localeListId) - : mTypeface(std::move(typeface)), + : mExternalRefsHolder(new ExternalRefs(std::move(typeface), std::move(baseFont))), mStyle(style), - mBaseFont(std::move(baseFont)), - mTypefaceLoader(nullptr), - mTypefaceMetadataReader(nullptr), - mLocaleListId(localeListId) {} - Font(FontStyle style, BufferReader typefaceMetadataReader, TypefaceLoader* typefaceLoader, - uint32_t localeListId) - : mStyle(style), - mTypefaceLoader(typefaceLoader), - mTypefaceMetadataReader(typefaceMetadataReader), - mLocaleListId(localeListId) {} + mLocaleListId(localeListId), + mTypefaceMetadataReader(nullptr) {} + + void resetExternalRefs(ExternalRefs* refs); - void initTypefaceLocked() const EXCLUSIVE_LOCKS_REQUIRED(mTypefaceMutex); + const ExternalRefs* getExternalRefs() const; static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface); static FontStyle analyzeStyle(const HbFontUniquePtr& font); // Lazy-initialized if created by readFrom(). - mutable std::shared_ptr<MinikinFont> mTypeface GUARDED_BY(mTypefaceMutex); + mutable std::atomic<ExternalRefs*> mExternalRefsHolder; FontStyle mStyle; - // Lazy-initialized if created by readFrom(). - mutable HbFontUniquePtr mBaseFont GUARDED_BY(mTypefaceMutex); + uint32_t mLocaleListId; - mutable std::mutex mTypefaceMutex; - // Non-null if created by readFrom(). - TypefaceLoader* mTypefaceLoader; // Non-null if created by readFrom(). BufferReader mTypefaceMetadataReader; - uint32_t mLocaleListId; - - // Stop copying and moving - Font(Font&& o) = delete; - Font& operator=(Font&& o) = delete; + // Stop copying. Font(const Font& o) = delete; Font& operator=(const Font& o) = delete; + + FRIEND_TEST(FontTest, MoveConstructorTest); + FRIEND_TEST(FontTest, MoveAssignmentTest); }; } // namespace minikin diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h index 98df571..1679abc 100644 --- a/include/minikin/FontCollection.h +++ b/include/minikin/FontCollection.h @@ -17,13 +17,12 @@ #ifndef MINIKIN_FONT_COLLECTION_H #define MINIKIN_FONT_COLLECTION_H +#include <gtest/gtest_prod.h> + #include <memory> #include <unordered_map> -#include <unordered_set> #include <vector> -#include <gtest/gtest_prod.h> - #include "minikin/Buffer.h" #include "minikin/Font.h" #include "minikin/FontFamily.h" @@ -37,43 +36,13 @@ constexpr uint32_t MAX_FAMILY_COUNT = 254; class FontCollection { public: - explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces); - explicit FontCollection(std::shared_ptr<FontFamily>&& typeface); - - template <Font::TypefaceReader typefaceReader> - static std::vector<std::shared_ptr<FontCollection>> readVector(BufferReader* reader) { - uint32_t allFontFamiliesCount = reader->read<uint32_t>(); - std::vector<std::shared_ptr<FontFamily>> allFontFamilies; - allFontFamilies.reserve(allFontFamiliesCount); - for (uint32_t i = 0; i < allFontFamiliesCount; i++) { - allFontFamilies.push_back(FontFamily::readFrom<typefaceReader>(reader)); - } - uint32_t fontCollectionsCount = reader->read<uint32_t>(); - std::vector<std::shared_ptr<FontCollection>> fontCollections; - fontCollections.reserve(fontCollectionsCount); - for (uint32_t i = 0; i < fontCollectionsCount; i++) { - fontCollections.emplace_back(new FontCollection(reader, allFontFamilies)); - } - return fontCollections; - } + static std::shared_ptr<FontCollection> create( + const std::vector<std::shared_ptr<FontFamily>>& typefaces); + static std::shared_ptr<FontCollection> create(std::shared_ptr<FontFamily>&& typeface); - template <Font::TypefaceWriter typefaceWriter> + static std::vector<std::shared_ptr<FontCollection>> readVector(BufferReader* reader); static void writeVector(BufferWriter* writer, - const std::vector<std::shared_ptr<FontCollection>>& fontCollections) { - std::vector<std::shared_ptr<FontFamily>> allFontFamilies; - // Note: operator== for shared_ptr compares raw pointer values. - std::unordered_map<std::shared_ptr<FontFamily>, uint32_t> fontFamilyToIndexMap; - collectAllFontFamilies(fontCollections, &allFontFamilies, &fontFamilyToIndexMap); - - writer->write<uint32_t>(allFontFamilies.size()); - for (const auto& fontFamily : allFontFamilies) { - fontFamily->writeTo<typefaceWriter>(writer); - } - writer->write<uint32_t>(fontCollections.size()); - for (const auto& fontCollection : fontCollections) { - fontCollection->writeTo(writer, fontFamilyToIndexMap); - } - } + const std::vector<std::shared_ptr<FontCollection>>& fontCollections); // Helper class for representing font family match result in packed bits. struct FamilyMatchResult { @@ -190,18 +159,32 @@ public: // nullptr if none of variations apply to this collection. std::shared_ptr<FontCollection> createCollectionWithVariation( const std::vector<FontVariation>& variations); + // Creates new FontCollection that uses the specified families as top families and + // families from this FontCollection as fallback. + std::shared_ptr<FontCollection> createCollectionWithFamilies( + std::vector<std::shared_ptr<FontFamily>>&& families) const; - const std::unordered_set<AxisTag>& getSupportedTags() const { return mSupportedAxes; } + size_t getSupportedAxesCount() const { return mSupportedAxesCount; } + AxisTag getSupportedAxisAt(size_t index) const { return mSupportedAxes[index]; } uint32_t getId() const; - const std::vector<std::shared_ptr<FontFamily>>& getFamilies() const { return mFamilies; } + size_t getFamilyCount() const { return mFamilyCount; } + + const std::shared_ptr<FontFamily>& getFamilyAt(size_t index) const { + if (mFamilyIndices != nullptr) { + index = mFamilyIndices[index]; + } + return (*mMaybeSharedFamilies)[index]; + } private: FRIEND_TEST(FontCollectionTest, bufferTest); - FontCollection(BufferReader* reader, - const std::vector<std::shared_ptr<FontFamily>>& allFontFamilies); + explicit FontCollection(const std::vector<std::shared_ptr<FontFamily>>& typefaces); + FontCollection( + BufferReader* reader, + const std::shared_ptr<std::vector<std::shared_ptr<FontFamily>>>& allFontFamilies); // Write fields of the instance, using fontFamilyToIndexMap for finding // indices for FontFamily. void writeTo(BufferWriter* writer, @@ -215,8 +198,9 @@ private: static const int kLogCharsPerPage = 8; static const int kPageMask = (1 << kLogCharsPerPage) - 1; - // mFamilyVec holds the indices of the mFamilies and mRanges holds the range of indices of - // mFamilyVec. The maximum number of pages is 0x10FF (U+10FFFF >> 8). The maximum number of + // mFamilyVec holds the indices of the family (as in getFamilyAt()) and + // mRanges holds the range of indices of mFamilyVec. + // The maximum number of pages is 0x10FF (U+10FFFF >> 8). The maximum number of // the fonts is 0xFF. Thus, technically the maximum length of mFamilyVec is 0x10EE01 // (0x10FF * 0xFF). However, in practice, 16-bit integers are enough since most fonts supports // only limited range of code points. @@ -237,6 +221,8 @@ private: uint32_t calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t localeListId, const std::shared_ptr<FontFamily>& fontFamily) const; + bool isPrimaryFamily(const std::shared_ptr<FontFamily>& fontFamily) const; + static uint32_t calcLocaleMatchingScore(uint32_t userLocaleListId, const FontFamily& fontFamily); @@ -250,7 +236,18 @@ private: // This vector has pointers to the all font family instances in this collection. // This vector can't be empty. - std::vector<std::shared_ptr<FontFamily>> mFamilies; + // This vector may be shared with other font collections. + // (1) When shared, this vector is a union of all font family instances + // shared by multiple font collections. + // mFamilyIndices will be non-null in this case. + // The i-th family in this collection will be + // mMaybeSharedFamilies[mFamilyIndices[i]]. + // (2) When not shared, mFamilyIndices will be null and + // the i-th family in this collection will be mMaybeSharedFamilies[i]. + // Use getFamilyAt(i) to access the i-th font in this family. + std::shared_ptr<std::vector<std::shared_ptr<FontFamily>>> mMaybeSharedFamilies; + uint32_t mFamilyCount; + const uint32_t* mFamilyIndices; // Following two vectors are pre-calculated tables for resolving coverage faster. // For example, to iterate over all fonts which support Unicode code point U+XXYYZZ, @@ -267,7 +264,9 @@ private: std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec; // Set of supported axes in this collection. - std::unordered_set<AxisTag> mSupportedAxes; + uint32_t mSupportedAxesCount; + // mSupportedAxes is sorted. + std::unique_ptr<AxisTag[]> mSupportedAxes; // Owns allocated memory if this class is created from font families, otherwise these are // nullptr. diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h index 6169193..6161f5c 100644 --- a/include/minikin/FontFamily.h +++ b/include/minikin/FontFamily.h @@ -19,7 +19,6 @@ #include <memory> #include <string> -#include <unordered_set> #include <vector> #include "minikin/FamilyVariant.h" @@ -33,32 +32,19 @@ namespace minikin { class FontFamily { public: - explicit FontFamily(std::vector<std::shared_ptr<Font>>&& fonts); - FontFamily(FamilyVariant variant, std::vector<std::shared_ptr<Font>>&& fonts); - FontFamily(uint32_t localeListId, FamilyVariant variant, - std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback); - - template <Font::TypefaceReader typefaceReader> - static std::shared_ptr<FontFamily> readFrom(BufferReader* reader) { - uint32_t localeListId = readLocaleListInternal(reader); - uint32_t fontsCount = reader->read<uint32_t>(); - std::vector<std::shared_ptr<Font>> fonts; - fonts.reserve(fontsCount); - for (uint32_t i = 0; i < fontsCount; i++) { - fonts.emplace_back(Font::readFrom<typefaceReader>(reader, localeListId)); - } - return readFromInternal(reader, std::move(fonts), localeListId); - } - - template <Font::TypefaceWriter typefaceWriter> - void writeTo(BufferWriter* writer) const { - writeLocaleListInternal(writer); - writer->write<uint32_t>(mFonts.size()); - for (const std::shared_ptr<Font>& font : mFonts) { - font->writeTo<typefaceWriter>(writer); - } - writeToInternal(writer); - } + static std::shared_ptr<FontFamily> create(std::vector<std::shared_ptr<Font>>&& fonts); + static std::shared_ptr<FontFamily> create(FamilyVariant variant, + std::vector<std::shared_ptr<Font>>&& fonts); + static std::shared_ptr<FontFamily> create(uint32_t localeListId, FamilyVariant variant, + std::vector<std::shared_ptr<Font>>&& fonts, + bool isCustomFallback, bool isDefaultFallback); + + FontFamily(FontFamily&&) = default; + FontFamily& operator=(FontFamily&&) = default; + + static std::vector<std::shared_ptr<FontFamily>> readVector(BufferReader* reader); + static void writeVector(BufferWriter* writer, + const std::vector<std::shared_ptr<FontFamily>>& families); FakedFont getClosestMatch(FontStyle style) const; @@ -66,13 +52,15 @@ public: FamilyVariant variant() const { return mVariant; } // API's for enumerating the fonts in a family. These don't guarantee any particular order - size_t getNumFonts() const { return mFonts.size(); } + size_t getNumFonts() const { return mFontsCount; } const Font* getFont(size_t index) const { return mFonts[index].get(); } const std::shared_ptr<Font>& getFontRef(size_t index) const { return mFonts[index]; } FontStyle getStyle(size_t index) const { return mFonts[index]->style(); } bool isColorEmojiFamily() const { return mIsColorEmoji; } - const std::unordered_set<AxisTag>& supportedAxes() const { return mSupportedAxes; } + size_t getSupportedAxesCount() const { return mSupportedAxesCount; } + AxisTag getSupportedAxisAt(size_t index) const { return mSupportedAxes[index]; } bool isCustomFallback() const { return mIsCustomFallback; } + bool isDefaultFallback() const { return mIsDefaultFallback; } // Get Unicode coverage. const SparseBitSet& getCoverage() const { return mCoverage; } @@ -82,7 +70,7 @@ public: bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const; // Returns true if this font family has a variaion sequence table (cmap format 14 subtable). - bool hasVSTable() const { return !mCmapFmt14Coverage.empty(); } + bool hasVSTable() const { return mCmapFmt14CoverageCount != 0; } // Creates new FontFamily based on this family while applying font variations. Returns nullptr // if none of variations apply to this family. @@ -91,29 +79,30 @@ public: private: FontFamily(uint32_t localeListId, FamilyVariant variant, - std::vector<std::shared_ptr<Font>>&& fonts, - std::unordered_set<AxisTag>&& supportedAxes, bool isColorEmoji, - bool isCustomFallback, SparseBitSet&& coverage, - std::vector<std::unique_ptr<SparseBitSet>>&& cmapFmt14Coverage); - - static uint32_t readLocaleListInternal(BufferReader* reader); - static std::shared_ptr<FontFamily> readFromInternal(BufferReader* reader, - std::vector<std::shared_ptr<Font>>&& fonts, - uint32_t localeListId); - void writeLocaleListInternal(BufferWriter* writer) const; - void writeToInternal(BufferWriter* writer) const; + std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback, + bool isDefaultFallback); + explicit FontFamily(BufferReader* reader, const std::shared_ptr<std::vector<Font>>& fonts); - void computeCoverage(); + void writeTo(BufferWriter* writer, uint32_t* fontIndex) const; - uint32_t mLocaleListId; - FamilyVariant mVariant; - std::vector<std::shared_ptr<Font>> mFonts; - std::unordered_set<AxisTag> mSupportedAxes; - bool mIsColorEmoji; - bool mIsCustomFallback; + void computeCoverage(); + // Note: to minimize padding, small member fields are grouped at the end. + std::unique_ptr<std::shared_ptr<Font>[]> mFonts; + // mSupportedAxes is sorted. + std::unique_ptr<AxisTag[]> mSupportedAxes; SparseBitSet mCoverage; - std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage; + std::unique_ptr<SparseBitSet[]> mCmapFmt14Coverage; + uint32_t mLocaleListId; // 4 bytes + uint32_t mFontsCount; // 4 bytes + // OpenType supports up to 2^16-1 (uint16) axes. + // https://docs.microsoft.com/en-us/typography/opentype/spec/fvar + uint16_t mSupportedAxesCount; // 2 bytes + uint16_t mCmapFmt14CoverageCount; // 2 bytes + FamilyVariant mVariant; // 1 byte + bool mIsColorEmoji; // 1 byte + bool mIsCustomFallback; // 1 byte + bool mIsDefaultFallback; // 1 byte MINIKIN_PREVENT_COPY_AND_ASSIGN(FontFamily); }; diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h index 76f3701..ef0b6a5 100644 --- a/include/minikin/Measurement.h +++ b/include/minikin/Measurement.h @@ -28,6 +28,8 @@ namespace minikin { float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, size_t offset); +void distributeAdvances(float* advances, const uint16_t* buf, size_t start, size_t count); + size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, float advance); diff --git a/include/minikin/MinikinFontFactory.h b/include/minikin/MinikinFontFactory.h new file mode 100644 index 0000000..b8c3368 --- /dev/null +++ b/include/minikin/MinikinFontFactory.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2022 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_MINIKIN_FONT_FACTORY_H +#define MINIKIN_MINIKIN_FONT_FACTORY_H + +#include "minikin/Buffer.h" +#include "minikin/MinikinFont.h" + +namespace minikin { + +// A class to serialize / deserialize MinikinFont instance into / from memory buffer. +class MinikinFontFactory { +public: + MinikinFontFactory() {} + + virtual ~MinikinFontFactory() = 0; + + // Create MinikinFont instance from the buffer. + virtual std::shared_ptr<MinikinFont> create(BufferReader reader) const = 0; + + // Skip a MinikinFont region in the buffer and advance the reader to the + // next position. + virtual void skip(BufferReader* reader) const = 0; + + // Serialize MinikinFont into the buffer. + virtual void write(BufferWriter* writer, const MinikinFont* minikinFont) const = 0; + + // Return the singleton MinikinFontFactory instance. + // setInstance() must be called before any MinikinFont instance is + // serialized or deserialized. + static const MinikinFontFactory& getInstance(); + + // Set the factory instance. + // The factory must be a singleton and cannot be changed during the process lifetime. + // It is safe to call this method multiple times with the same instance. + // This method itself is not thread safe. The call to this method, as well + // as deserialized MinikinFont objects, must be synchronized by the caller. + static void setInstance(const MinikinFontFactory* factory); +}; + +} // namespace minikin + +#endif // MINIKIN_MINIKIN_FONT_FACTORY_H diff --git a/include/minikin/SparseBitSet.h b/include/minikin/SparseBitSet.h index 3034243..33047e8 100644 --- a/include/minikin/SparseBitSet.h +++ b/include/minikin/SparseBitSet.h @@ -19,6 +19,7 @@ #include <minikin/Buffer.h> #include <sys/types.h> + #include <cstdint> #include <memory> @@ -34,7 +35,7 @@ namespace minikin { class SparseBitSet { public: // Create an empty bit set. - SparseBitSet() : mMaxVal(0) {} + SparseBitSet() : mData(nullptr) {} // Initialize the set to a new value, represented by ranges. For // simplicity, these ranges are arranged as pairs of values, @@ -52,14 +53,16 @@ public: // Determine whether the value is included in the set bool get(uint32_t ch) const { - if (ch >= mMaxVal) return false; - const uint32_t* bitmap = &mBitmaps[mIndices[ch >> kLogValuesPerPage]]; + if (ch >= length()) return false; + const uint32_t* bitmap = mData->bitmaps() + mData->indices()[ch >> kLogValuesPerPage]; uint32_t index = ch & kPageMask; return (bitmap[index >> kLogBitsPerEl] & (kElFirst >> (index & kElMask))) != 0; } // One more than the maximum value in the set, or zero if empty - uint32_t length() const { return mMaxVal; } + uint32_t length() const { return mData != nullptr ? mData->mMaxVal : 0; } + + bool empty() const { return mData == nullptr || mData->mMaxVal == 0; } // The next set bit starting at fromIndex, inclusive, or kNotFound // if none exists. @@ -86,20 +89,53 @@ private: static uint32_t calcNumPages(const uint32_t* ranges, size_t nRanges); static int CountLeadingZeros(element x); - uint32_t mMaxVal; - uint32_t mIndicesCount; - const uint16_t* mIndices; - uint32_t mBitmapsCount; - const element* mBitmaps; - uint16_t mZeroPageIndex; - - // Owns allocated memory if this class is created from ranges, otherwise these are nullptr. - std::unique_ptr<uint16_t[]> mOwnedIndices; - std::unique_ptr<element[]> mOwnedBitmaps; + // MappableData represents memory block holding SparseBitSet's fields. + // 'packed' is used so that the object layout won't change between + // 32-bit and 64-bit processes. + // 'aligned(4)' is only for optimization. + struct __attribute__((packed, aligned(4))) MappableData { + uint32_t mMaxVal; + uint32_t mIndicesCount; + uint32_t mBitmapsCount; + uint16_t mZeroPageIndex; + // Whether the memory is mapped (BufferReader::map()) or allocated + // (malloc()). + uint16_t mIsMapped; + // mArray packs two arrays: + // element mBitmaps[mBitmapsCount]; + // uint16_t mIndices[mIndicesCount]; + __attribute__((aligned(4))) uint32_t mArray[]; + const element* bitmaps() const { return mArray; } + element* bitmaps() { return mArray; } + const uint16_t* indices() const { + return reinterpret_cast<const uint16_t*>(mArray + mBitmapsCount); + } + uint16_t* indices() { return reinterpret_cast<uint16_t*>(mArray + mBitmapsCount); } + size_t size() const { return calcSize(mIndicesCount, mBitmapsCount); } + static size_t calcSize(uint32_t indicesCount, uint32_t bitmapsCount) { + static_assert(std::is_same<element, uint32_t>::value); + static_assert(sizeof(uint32_t) == 4); + static_assert(sizeof(uint16_t) == 2); + // Round-up indicesCount / 2 + size_t arrayCount = bitmapsCount + (indicesCount + 1) / 2; + return offsetof(MappableData, mArray) + sizeof(uint32_t) * arrayCount; + } + static MappableData* allocate(uint32_t indicesCount, uint32_t bitmapsCount); + }; + + // MappableDataDeleter does NOT call free() if the data is on a memory map. + class MappableDataDeleter { + public: + void operator()(const MappableData* data) const { + if (data != nullptr && !data->mIsMapped) free((void*)data); + } + }; + + std::unique_ptr<const MappableData, MappableDataDeleter> mData; // Forbid copy and assign. SparseBitSet(const SparseBitSet&) = delete; - void operator=(const SparseBitSet&) = delete; + SparseBitSet& operator=(const SparseBitSet&) = delete; }; } // namespace minikin diff --git a/include/minikin/SystemFonts.h b/include/minikin/SystemFonts.h index cf4ab75..4ef2705 100644 --- a/include/minikin/SystemFonts.h +++ b/include/minikin/SystemFonts.h @@ -50,11 +50,6 @@ public: } // This obtains a mutex inside, so do not call this method inside callback. - static void getFontMap( - std::function<void(const std::vector<std::shared_ptr<FontCollection>>&)> func) { - return getInstance().getFontMapInternal(func); - } - static void getFontSet(std::function<void(const std::vector<std::shared_ptr<Font>>&)> func) { return getInstance().getFontSetInternal(func); } @@ -81,12 +76,6 @@ protected: mCollections.emplace_back(std::move(collections)); } - void getFontMapInternal( - std::function<void(const std::vector<std::shared_ptr<FontCollection>>&)> func) { - std::lock_guard<std::mutex> lock(mMutex); - func(mCollections); - } - void getFontSetInternal(std::function<void(const std::vector<std::shared_ptr<Font>>&)> func) { std::lock_guard<std::mutex> lock(mMutex); if (!mFonts) { diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp index c0c4035..5c7abf2 100644 --- a/libs/minikin/Android.bp +++ b/libs/minikin/Android.bp @@ -50,16 +50,14 @@ cc_library { "LocaleListCache.cpp", "MeasuredText.cpp", "Measurement.cpp", + "MinikinFontFactory.cpp", "MinikinInternal.cpp", "OptimalLineBreaker.cpp", "SparseBitSet.cpp", "SystemFonts.cpp", "WordBreaker.cpp", ], - cflags: [ - "-Wall", - "-Werror", - ], + defaults: ["libminikin_defaults"], sanitize: { misc_undefined: [ "signed-integer-overflow", @@ -67,12 +65,6 @@ cc_library { "bounds", ], }, - cppflags: [ - "-Werror", - "-Wall", - "-Wextra", - "-Wthread-safety", - ], product_variables: { debuggable: { // Enable assertion on eng and userdebug build. @@ -93,10 +85,10 @@ cc_library { target: { android: { shared_libs: [ - "libandroidicu", + "libicu", ], export_shared_lib_headers: [ - "libandroidicu", + "libicu", ], }, host: { diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp index 8d04ce8..fd1a7f1 100644 --- a/libs/minikin/CmapCoverage.cpp +++ b/libs/minikin/CmapCoverage.cpp @@ -368,8 +368,7 @@ static bool getVSCoverage(std::vector<uint32_t>* out_ranges, const uint8_t* data return true; } -static void getCoverageFormat14(std::vector<std::unique_ptr<SparseBitSet>>* out, - const uint8_t* data, size_t size, +static void getCoverageFormat14(std::vector<SparseBitSet>* out, const uint8_t* data, size_t size, const SparseBitSet& baseCoverage) { constexpr size_t kHeaderSize = 10; constexpr size_t kRecordSize = 11; @@ -417,14 +416,14 @@ static void getCoverageFormat14(std::vector<std::unique_ptr<SparseBitSet>>* out, if (out->size() < vsIndex + 1) { out->resize(vsIndex + 1); } - (*out)[vsIndex].reset(new SparseBitSet(ranges.data(), ranges.size() >> 1)); + (*out)[vsIndex] = SparseBitSet(ranges.data(), ranges.size() >> 1); } out->shrink_to_fit(); } SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size, - std::vector<std::unique_ptr<SparseBitSet>>* out) { + std::vector<SparseBitSet>* out) { constexpr size_t kHeaderSize = 4; constexpr size_t kNumTablesOffset = 2; constexpr size_t kTableSize = 8; diff --git a/libs/minikin/Font.cpp b/libs/minikin/Font.cpp index c2e74b7..bf7450a 100644 --- a/libs/minikin/Font.cpp +++ b/libs/minikin/Font.cpp @@ -18,17 +18,18 @@ #include "minikin/Font.h" -#include <vector> - #include <hb-ot.h> #include <hb.h> #include <log/log.h> -#include "minikin/HbUtils.h" -#include "minikin/MinikinFont.h" +#include <vector> #include "FontUtils.h" +#include "LocaleListCache.h" #include "MinikinInternal.h" +#include "minikin/HbUtils.h" +#include "minikin/MinikinFont.h" +#include "minikin/MinikinFontFactory.h" namespace minikin { @@ -51,25 +52,80 @@ std::shared_ptr<Font> Font::Builder::build() { std::move(font), mLocaleListId)); } +Font::Font(BufferReader* reader) : mExternalRefsHolder(nullptr), mTypefaceMetadataReader(nullptr) { + mStyle = FontStyle(reader); + mLocaleListId = LocaleListCache::readFrom(reader); + mTypefaceMetadataReader = *reader; + MinikinFontFactory::getInstance().skip(reader); +} + +void Font::writeTo(BufferWriter* writer) const { + mStyle.writeTo(writer); + LocaleListCache::writeTo(writer, mLocaleListId); + MinikinFontFactory::getInstance().write(writer, typeface().get()); +} + +Font::Font(Font&& o) noexcept + : mStyle(o.mStyle), + mLocaleListId(o.mLocaleListId), + mTypefaceMetadataReader(o.mTypefaceMetadataReader) { + mExternalRefsHolder.store(o.mExternalRefsHolder.exchange(nullptr)); +} + +Font& Font::operator=(Font&& o) noexcept { + resetExternalRefs(o.mExternalRefsHolder.exchange(nullptr)); + mStyle = o.mStyle; + mLocaleListId = o.mLocaleListId; + mTypefaceMetadataReader = o.mTypefaceMetadataReader; + return *this; +} + +Font::~Font() { + resetExternalRefs(nullptr); +} + +void Font::resetExternalRefs(ExternalRefs* refs) { + ExternalRefs* oldRefs = mExternalRefsHolder.exchange(refs); + if (oldRefs != nullptr) { + delete oldRefs; + } +} + const std::shared_ptr<MinikinFont>& Font::typeface() const { - std::lock_guard lock(mTypefaceMutex); - if (mTypeface) return mTypeface; - initTypefaceLocked(); - return mTypeface; + return getExternalRefs()->mTypeface; } const HbFontUniquePtr& Font::baseFont() const { - std::lock_guard lock(mTypefaceMutex); - if (mBaseFont) return mBaseFont; - initTypefaceLocked(); - mBaseFont = prepareFont(mTypeface); - return mBaseFont; + return getExternalRefs()->mBaseFont; } -void Font::initTypefaceLocked() const { - if (mTypeface) return; - MINIKIN_ASSERT(mTypefaceLoader, "mTypefaceLoader should not be empty when mTypeface is null"); - mTypeface = mTypefaceLoader(mTypefaceMetadataReader); +const Font::ExternalRefs* Font::getExternalRefs() const { + // Thread safety note: getExternalRefs() is thread-safe. + // getExternalRefs() returns the first ExternalRefs set to mExternalRefsHolder. + // When multiple threads called getExternalRefs() at the same time and + // mExternalRefsHolder is not set, multiple ExternalRefs may be created, + // but only one ExternalRefs will be set to mExternalRefsHolder and + // others will be deleted. + Font::ExternalRefs* externalRefs = mExternalRefsHolder.load(); + if (externalRefs) return externalRefs; + // mExternalRefsHolder is null. Try creating an ExternalRefs. + std::shared_ptr<MinikinFont> typeface = + MinikinFontFactory::getInstance().create(mTypefaceMetadataReader); + HbFontUniquePtr font = prepareFont(typeface); + Font::ExternalRefs* newExternalRefs = + new Font::ExternalRefs(std::move(typeface), std::move(font)); + // Set the new ExternalRefs to mExternalRefsHolder if it is still null. + Font::ExternalRefs* expected = nullptr; + if (mExternalRefsHolder.compare_exchange_strong(expected, newExternalRefs)) { + return newExternalRefs; + } else { + // Another thread has already created and set an ExternalRefs. + // Delete ours and use theirs instead. + delete newExternalRefs; + // compare_exchange_strong writes the stored value into 'expected' + // when comparison fails. + return expected; + } } // static diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 38fc022..0c1a173 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -22,6 +22,7 @@ #include <unicode/unorm2.h> #include <algorithm> +#include <unordered_set> #include "Locale.h" #include "LocaleListCache.h" @@ -121,13 +122,22 @@ uint32_t getGlyphScore(U16StringPiece text, uint32_t start, uint32_t end, } // namespace -FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface) : mMaxChar(0) { +// static +std::shared_ptr<FontCollection> FontCollection::create(std::shared_ptr<FontFamily>&& typeface) { std::vector<std::shared_ptr<FontFamily>> typefaces; typefaces.push_back(typeface); - init(typefaces); + return create(typefaces); +} + +// static +std::shared_ptr<FontCollection> FontCollection::create( + const vector<std::shared_ptr<FontFamily>>& typefaces) { + // TODO(b/174672300): Revert back to make_shared. + return std::shared_ptr<FontCollection>(new FontCollection(typefaces)); } -FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) : mMaxChar(0) { +FontCollection::FontCollection(const vector<std::shared_ptr<FontFamily>>& typefaces) + : mMaxChar(0), mSupportedAxes(nullptr) { init(typefaces); } @@ -136,26 +146,38 @@ void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) vector<uint32_t> lastChar; size_t nTypefaces = typefaces.size(); const FontStyle defaultStyle; + auto families = std::make_shared<vector<std::shared_ptr<FontFamily>>>(); + std::unordered_set<AxisTag> supportedAxesSet; for (size_t i = 0; i < nTypefaces; i++) { const std::shared_ptr<FontFamily>& family = typefaces[i]; if (family->getClosestMatch(defaultStyle).font == nullptr) { continue; } const SparseBitSet& coverage = family->getCoverage(); - mFamilies.push_back(family); // emplace_back would be better + families->emplace_back(family); if (family->hasVSTable()) { mVSFamilyVec.push_back(family); } mMaxChar = max(mMaxChar, coverage.length()); lastChar.push_back(coverage.nextSetBit(0)); - const std::unordered_set<AxisTag>& supportedAxes = family->supportedAxes(); - mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end()); + for (size_t i = 0; i < family->getSupportedAxesCount(); i++) { + supportedAxesSet.insert(family->getSupportedAxisAt(i)); + } } - nTypefaces = mFamilies.size(); - MINIKIN_ASSERT(nTypefaces > 0, "Font collection must have at least one valid typeface"); - MINIKIN_ASSERT(nTypefaces <= MAX_FAMILY_COUNT, + // mMaybeSharedFamilies is not shared. + mMaybeSharedFamilies = families; + mFamilyCount = families->size(); + mFamilyIndices = nullptr; + MINIKIN_ASSERT(mFamilyCount > 0, "Font collection must have at least one valid typeface"); + MINIKIN_ASSERT(mFamilyCount <= MAX_FAMILY_COUNT, "Font collection may only have up to %d font families.", MAX_FAMILY_COUNT); + // Although OpenType supports up to 2^16-1 axes per font, + // mSupportedAxesCount may exceed 2^16-1 as we have multiple fonts. + mSupportedAxesCount = static_cast<uint32_t>(supportedAxesSet.size()); + if (mSupportedAxesCount > 0) { + mSupportedAxes = sortedArrayFromSet(supportedAxesSet); + } size_t nPages = (mMaxChar + kPageMask) >> kLogCharsPerPage; // TODO: Use variation selector map for mRanges construction. // A font can have a glyph for a base code point and variation selector pair but no glyph for @@ -167,9 +189,9 @@ void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) for (size_t i = 0; i < nPages; i++) { Range* range = &mOwnedRanges[i]; range->start = mOwnedFamilyVec.size(); - for (size_t j = 0; j < nTypefaces; j++) { + for (size_t j = 0; j < getFamilyCount(); j++) { if (lastChar[j] < (i + 1) << kLogCharsPerPage) { - const std::shared_ptr<FontFamily>& family = mFamilies[j]; + const std::shared_ptr<FontFamily>& family = getFamilyAt(j); mOwnedFamilyVec.push_back(static_cast<uint8_t>(j)); uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage); lastChar[j] = nextChar; @@ -184,52 +206,78 @@ void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) mFamilyVecCount = mOwnedFamilyVec.size(); } -FontCollection::FontCollection(BufferReader* reader, - const std::vector<std::shared_ptr<FontFamily>>& families) { +FontCollection::FontCollection( + BufferReader* reader, + const std::shared_ptr<std::vector<std::shared_ptr<FontFamily>>>& families) + : mSupportedAxes(nullptr) { mId = gNextCollectionId++; mMaxChar = reader->read<uint32_t>(); - uint32_t familiesCount = reader->read<uint32_t>(); - mFamilies.reserve(familiesCount); - for (uint32_t i = 0; i < familiesCount; i++) { - uint32_t index = reader->read<uint32_t>(); - if (index >= families.size()) { - ALOGE("Invalid FontFamily index: %zu", (size_t)index); - } else { - mFamilies.push_back(families[index]); - if (families[index]->hasVSTable()) { - mVSFamilyVec.push_back(families[index]); - } - } + mMaybeSharedFamilies = families; + std::tie(mFamilyIndices, mFamilyCount) = reader->readArray<uint32_t>(); + for (size_t i = 0; i < getFamilyCount(); i++) { + const auto& family = getFamilyAt(i); + if (family->hasVSTable()) mVSFamilyVec.emplace_back(family); } // Range is two packed uint16_t static_assert(sizeof(Range) == 4); std::tie(mRanges, mRangesCount) = reader->readArray<Range>(); std::tie(mFamilyVec, mFamilyVecCount) = reader->readArray<uint8_t>(); const auto& [axesPtr, axesCount] = reader->readArray<AxisTag>(); - mSupportedAxes.insert(axesPtr, axesPtr + axesCount); + mSupportedAxesCount = axesCount; + if (axesCount > 0) { + mSupportedAxes = std::unique_ptr<AxisTag[]>(new AxisTag[axesCount]); + std::copy(axesPtr, axesPtr + axesCount, mSupportedAxes.get()); + } } void FontCollection::writeTo(BufferWriter* writer, const std::unordered_map<std::shared_ptr<FontFamily>, uint32_t>& fontFamilyToIndexMap) const { writer->write<uint32_t>(mMaxChar); - writer->write<uint32_t>(mFamilies.size()); - for (const std::shared_ptr<FontFamily>& fontFamily : mFamilies) { + std::vector<uint32_t> indices; + indices.reserve(getFamilyCount()); + for (size_t i = 0; i < getFamilyCount(); ++i) { + const std::shared_ptr<FontFamily>& fontFamily = getFamilyAt(i); auto it = fontFamilyToIndexMap.find(fontFamily); if (it == fontFamilyToIndexMap.end()) { ALOGE("fontFamily not found in fontFamilyToIndexMap"); - writer->write<uint32_t>(-1); } else { - writer->write<uint32_t>(it->second); + indices.push_back(it->second); } } + writer->writeArray<uint32_t>(indices.data(), indices.size()); writer->writeArray<Range>(mRanges, mRangesCount); writer->writeArray<uint8_t>(mFamilyVec, mFamilyVecCount); // No need to serialize mVSFamilyVec as it can be reconstructed easily from mFamilies. - std::vector<AxisTag> axes(mSupportedAxes.begin(), mSupportedAxes.end()); - // Sort axes to be deterministic. - std::sort(axes.begin(), axes.end()); - writer->writeArray<AxisTag>(axes.data(), axes.size()); + writer->writeArray<AxisTag>(mSupportedAxes.get(), mSupportedAxesCount); +} + +// static +std::vector<std::shared_ptr<FontCollection>> FontCollection::readVector(BufferReader* reader) { + auto allFontFamilies = std::make_shared<std::vector<std::shared_ptr<FontFamily>>>( + FontFamily::readVector(reader)); + uint32_t count = reader->read<uint32_t>(); + std::vector<std::shared_ptr<FontCollection>> fontCollections; + fontCollections.reserve(count); + for (uint32_t i = 0; i < count; i++) { + fontCollections.emplace_back(new FontCollection(reader, allFontFamilies)); + } + return fontCollections; +} + +// static +void FontCollection::writeVector( + BufferWriter* writer, const std::vector<std::shared_ptr<FontCollection>>& fontCollections) { + std::vector<std::shared_ptr<FontFamily>> allFontFamilies; + // Note: operator== for shared_ptr compares raw pointer values. + std::unordered_map<std::shared_ptr<FontFamily>, uint32_t> fontFamilyToIndexMap; + collectAllFontFamilies(fontCollections, &allFontFamilies, &fontFamilyToIndexMap); + + FontFamily::writeVector(writer, allFontFamilies); + writer->write<uint32_t>(fontCollections.size()); + for (const auto& fontCollection : fontCollections) { + fontCollection->writeTo(writer, fontFamilyToIndexMap); + } } // static @@ -238,7 +286,8 @@ void FontCollection::collectAllFontFamilies( std::vector<std::shared_ptr<FontFamily>>* outAllFontFamilies, std::unordered_map<std::shared_ptr<FontFamily>, uint32_t>* outFontFamilyToIndexMap) { for (const auto& fontCollection : fontCollections) { - for (const std::shared_ptr<FontFamily>& fontFamily : fontCollection->mFamilies) { + for (size_t i = 0; i < fontCollection->getFamilyCount(); ++i) { + const std::shared_ptr<FontFamily>& fontFamily = fontCollection->getFamilyAt(i); bool inserted = outFontFamilyToIndexMap->emplace(fontFamily, outAllFontFamilies->size()).second; if (inserted) { @@ -286,6 +335,22 @@ uint32_t FontCollection::calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant return coverageScore << 29 | localeScore << 1 | variantScore; } +// Returns true if +// - the fontFamily is a developer specified custom fallback. +// - no custom fallback is provided and the fontFamily is a default fallback. +bool FontCollection::isPrimaryFamily(const std::shared_ptr<FontFamily>& fontFamily) const { + // If the font family is provided by developers, it is primary. + if (fontFamily->isCustomFallback()) { + return true; + } + + if (getFamilyAt(0)->isCustomFallback()) { + return false; + } else { + return fontFamily->isDefaultFallback(); + } +} + // Calculates a font score based on variation sequence coverage. // - Returns kUnsupportedFontScore if the font doesn't support the variation sequence or its base // character. @@ -304,7 +369,7 @@ uint32_t FontCollection::calcCoverageScore(uint32_t ch, uint32_t vs, uint32_t lo return kUnsupportedFontScore; } - if ((vs == 0 || hasVSGlyph) && (mFamilies[0] == fontFamily || fontFamily->isCustomFallback())) { + if ((vs == 0 || hasVSGlyph) && isPrimaryFamily(fontFamily)) { // If the first font family supports the given character or variation sequence, always use // it. return kFirstFontScore; @@ -404,7 +469,7 @@ FontCollection::FamilyMatchResult FontCollection::getFamilyForChar(uint32_t ch, Range range = mRanges[ch >> kLogCharsPerPage]; if (vs != 0) { - range = {0, static_cast<uint16_t>(mFamilies.size())}; + range = {0, static_cast<uint16_t>(getFamilyCount())}; } uint32_t bestScore = kUnsupportedFontScore; @@ -412,7 +477,7 @@ FontCollection::FamilyMatchResult FontCollection::getFamilyForChar(uint32_t ch, for (size_t i = range.start; i < range.end; i++) { const uint8_t familyIndex = vs == 0 ? mFamilyVec[i] : i; - const std::shared_ptr<FontFamily>& family = mFamilies[familyIndex]; + const std::shared_ptr<FontFamily>& family = getFamilyAt(familyIndex); const uint32_t score = calcFamilyScore(ch, vs, variant, localeListId, family); if (score == kFirstFontScore) { // If the first font family supports the given character or variation sequence, always @@ -505,8 +570,9 @@ bool FontCollection::hasVariationSelector(uint32_t baseCodepoint, // sequences, since Unicode is adding variation sequences more frequently now and may even move // towards allowing text and emoji variation selectors on any character. if (variationSelector == TEXT_STYLE_VS) { - for (size_t i = 0; i < mFamilies.size(); ++i) { - if (!mFamilies[i]->isColorEmojiFamily() && mFamilies[i]->hasGlyph(baseCodepoint, 0)) { + for (size_t i = 0; i < getFamilyCount(); ++i) { + const std::shared_ptr<FontFamily>& family = getFamilyAt(i); + if (!family->isColorEmojiFamily() && family->hasGlyph(baseCodepoint, 0)) { return true; } } @@ -586,12 +652,12 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo } else if (!lastFamilyIndices.empty() && (isStickyAllowlisted(ch) || isCombining(ch))) { // Continue using existing font as long as it has coverage and is whitelisted. - const std::shared_ptr<FontFamily>& lastFamily = mFamilies[lastFamilyIndices[0]]; + const std::shared_ptr<FontFamily>& lastFamily = getFamilyAt(lastFamilyIndices[0]); if (lastFamily->isColorEmojiFamily()) { // If the last family is color emoji font, find the longest family. shouldContinueRun = false; for (uint8_t ix : lastFamilyIndices) { - shouldContinueRun |= mFamilies[ix]->getCoverage().get(ch); + shouldContinueRun |= getFamilyAt(ix)->getCoverage().get(ch); } } else { shouldContinueRun = lastFamily->getCoverage().get(ch); @@ -605,7 +671,7 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo if (utf16Pos == 0 || lastFamilyIndices.empty()) { breakRun = true; } else { - const std::shared_ptr<FontFamily>& lastFamily = mFamilies[lastFamilyIndices[0]]; + const std::shared_ptr<FontFamily>& lastFamily = getFamilyAt(lastFamilyIndices[0]); if (lastFamily->isColorEmojiFamily()) { FamilyMatchResult intersection = FamilyMatchResult::intersect(familyIndices, lastFamilyIndices); @@ -637,7 +703,7 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo if (utf16Pos != 0 && (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh)))) { for (uint8_t ix : familyIndices) { - if (mFamilies[ix]->getCoverage().get(prevCh)) { + if (getFamilyAt(ix)->getCoverage().get(prevCh)) { const size_t prevChLength = U16_LENGTH(prevCh); if (run != nullptr) { run->end -= prevChLength; @@ -696,10 +762,10 @@ FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontS uint8_t bestIndex = 0; uint32_t bestScore = 0xFFFFFFFF; - const std::shared_ptr<FontFamily>& family = mFamilies[run.familyMatch[0]]; + const std::shared_ptr<FontFamily>& family = getFamilyAt(run.familyMatch[0]); if (family->isColorEmojiFamily() && run.familyMatch.size() > 1) { for (size_t i = 0; i < run.familyMatch.size(); ++i) { - const std::shared_ptr<FontFamily>& family = mFamilies[run.familyMatch[i]]; + const std::shared_ptr<FontFamily>& family = getFamilyAt(run.familyMatch[i]); const HbFontUniquePtr& font = family->getFont(0)->baseFont(); uint32_t score = getGlyphScore(text, run.start, run.end, font); @@ -711,22 +777,23 @@ FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontS } else { bestIndex = run.familyMatch[0]; } - return mFamilies[bestIndex]->getClosestMatch(style); + return getFamilyAt(bestIndex)->getClosestMatch(style); } FakedFont FontCollection::baseFontFaked(FontStyle style) { - return mFamilies[0]->getClosestMatch(style); + return getFamilyAt(0)->getClosestMatch(style); } std::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation( const std::vector<FontVariation>& variations) { - if (variations.empty() || mSupportedAxes.empty()) { + if (variations.empty() || mSupportedAxesCount == 0) { return nullptr; } bool hasSupportedAxis = false; for (const FontVariation& variation : variations) { - if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) { + if (std::binary_search(mSupportedAxes.get(), mSupportedAxes.get() + mSupportedAxesCount, + variation.axisTag)) { hasSupportedAxis = true; break; } @@ -737,7 +804,8 @@ std::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation( } std::vector<std::shared_ptr<FontFamily>> families; - for (const std::shared_ptr<FontFamily>& family : mFamilies) { + for (size_t i = 0; i < getFamilyCount(); ++i) { + const std::shared_ptr<FontFamily>& family = getFamilyAt(i); std::shared_ptr<FontFamily> newFamily = family->createFamilyWithVariation(variations); if (newFamily) { families.push_back(newFamily); @@ -749,6 +817,15 @@ std::shared_ptr<FontCollection> FontCollection::createCollectionWithVariation( return std::shared_ptr<FontCollection>(new FontCollection(families)); } +std::shared_ptr<FontCollection> FontCollection::createCollectionWithFamilies( + std::vector<std::shared_ptr<FontFamily>>&& families) const { + families.reserve(families.size() + getFamilyCount()); + for (size_t i = 0; i < getFamilyCount(); i++) { + families.push_back(getFamilyAt(i)); + } + return FontCollection::create(families); +} + uint32_t FontCollection::getId() const { return mId; } diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index f1fd00a..836674c 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -18,118 +18,190 @@ #include "minikin/FontFamily.h" -#include <algorithm> -#include <vector> - #include <log/log.h> -#include "minikin/CmapCoverage.h" -#include "minikin/FamilyVariant.h" -#include "minikin/HbUtils.h" -#include "minikin/MinikinFont.h" +#include <algorithm> +#include <unordered_set> +#include <vector> #include "FontUtils.h" #include "Locale.h" #include "LocaleListCache.h" #include "MinikinInternal.h" +#include "minikin/CmapCoverage.h" +#include "minikin/FamilyVariant.h" +#include "minikin/HbUtils.h" +#include "minikin/MinikinFont.h" namespace minikin { -FontFamily::FontFamily(std::vector<std::shared_ptr<Font>>&& fonts) - : FontFamily(FamilyVariant::DEFAULT, std::move(fonts)) {} +// static +std::shared_ptr<FontFamily> FontFamily::create(std::vector<std::shared_ptr<Font>>&& fonts) { + return create(FamilyVariant::DEFAULT, std::move(fonts)); +} -FontFamily::FontFamily(FamilyVariant variant, std::vector<std::shared_ptr<Font>>&& fonts) - : FontFamily(kEmptyLocaleListId, variant, std::move(fonts), false /* isCustomFallback */) {} +// static +std::shared_ptr<FontFamily> FontFamily::create(FamilyVariant variant, + std::vector<std::shared_ptr<Font>>&& fonts) { + return create(kEmptyLocaleListId, variant, std::move(fonts), false /* isCustomFallback */, + false /* isDefaultFallback */); +} + +// static +std::shared_ptr<FontFamily> FontFamily::create(uint32_t localeListId, FamilyVariant variant, + std::vector<std::shared_ptr<Font>>&& fonts, + bool isCustomFallback, bool isDefaultFallback) { + // TODO(b/174672300): Revert back to make_shared. + return std::shared_ptr<FontFamily>(new FontFamily(localeListId, variant, std::move(fonts), + isCustomFallback, isDefaultFallback)); +} FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, - std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback) - : mLocaleListId(localeListId), + std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback, + bool isDefaultFallback) + : mFonts(std::make_unique<std::shared_ptr<Font>[]>(fonts.size())), + // computeCoverage may update supported axes and coverages later. + mSupportedAxes(nullptr), + mCoverage(), + mCmapFmt14Coverage(nullptr), + mLocaleListId(localeListId), + mFontsCount(static_cast<uint32_t>(fonts.size())), + mSupportedAxesCount(0), + mCmapFmt14CoverageCount(0), mVariant(variant), - mFonts(std::move(fonts)), mIsColorEmoji(LocaleListCache::getById(localeListId).getEmojiStyle() == EmojiStyle::EMOJI), - mIsCustomFallback(isCustomFallback) { - MINIKIN_ASSERT(!mFonts.empty(), "FontFamily must contain at least one font."); + mIsCustomFallback(isCustomFallback), + mIsDefaultFallback(isDefaultFallback) { + MINIKIN_ASSERT(!fonts.empty(), "FontFamily must contain at least one font."); + MINIKIN_ASSERT(fonts.size() <= std::numeric_limits<uint32_t>::max(), + "Number of fonts must be less than 2^32."); + for (size_t i = 0; i < mFontsCount; i++) { + mFonts[i] = std::move(fonts[i]); + } computeCoverage(); } -FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, - std::vector<std::shared_ptr<Font>>&& fonts, - std::unordered_set<AxisTag>&& supportedAxes, bool isColorEmoji, - bool isCustomFallback, SparseBitSet&& coverage, - std::vector<std::unique_ptr<SparseBitSet>>&& cmapFmt14Coverage) - : mLocaleListId(localeListId), - mVariant(variant), - mFonts(std::move(fonts)), - mSupportedAxes(std::move(supportedAxes)), - mIsColorEmoji(isColorEmoji), - mIsCustomFallback(isCustomFallback), - mCoverage(std::move(coverage)), - mCmapFmt14Coverage(std::move(cmapFmt14Coverage)) {} - -// Read fields other than mFonts, mLocaleList. -// static -std::shared_ptr<FontFamily> FontFamily::readFromInternal(BufferReader* reader, - std::vector<std::shared_ptr<Font>>&& fonts, - uint32_t localeListId) { +FontFamily::FontFamily(BufferReader* reader, const std::shared_ptr<std::vector<Font>>& allFonts) + : mSupportedAxes(nullptr), mCmapFmt14Coverage(nullptr) { + mLocaleListId = LocaleListCache::readFrom(reader); + mFontsCount = reader->read<uint32_t>(); + mFonts = std::make_unique<std::shared_ptr<Font>[]>(mFontsCount); + for (size_t i = 0; i < mFontsCount; i++) { + uint32_t fontIndex = reader->read<uint32_t>(); + // Use aliasing constructor to save memory. + // See the comments on FontFamily::readVector for details. + mFonts[i] = std::shared_ptr<Font>(allFonts, &(*allFonts)[fontIndex]); + } // FamilyVariant is uint8_t static_assert(sizeof(FamilyVariant) == 1); - FamilyVariant variant = reader->read<FamilyVariant>(); + mVariant = reader->read<FamilyVariant>(); // AxisTag is uint32_t static_assert(sizeof(AxisTag) == 4); const auto& [axesPtr, axesCount] = reader->readArray<AxisTag>(); - std::unordered_set<AxisTag> supportedAxes(axesPtr, axesPtr + axesCount); - bool isColorEmoji = static_cast<bool>(reader->read<uint8_t>()); - bool isCustomFallback = static_cast<bool>(reader->read<uint8_t>()); - SparseBitSet coverage(reader); + mSupportedAxesCount = axesCount; + if (axesCount > 0) { + mSupportedAxes = std::unique_ptr<AxisTag[]>(new AxisTag[axesCount]); + std::copy(axesPtr, axesPtr + axesCount, mSupportedAxes.get()); + } + mIsColorEmoji = static_cast<bool>(reader->read<uint8_t>()); + mIsCustomFallback = static_cast<bool>(reader->read<uint8_t>()); + mIsDefaultFallback = static_cast<bool>(reader->read<uint8_t>()); + mCoverage = SparseBitSet(reader); // Read mCmapFmt14Coverage. As it can have null entries, it is stored in the buffer as a sparse // array (size, non-null entry count, array of (index, entry)). - uint32_t cmapFmt14CoverageSize = reader->read<uint32_t>(); - std::vector<std::unique_ptr<SparseBitSet>> cmapFmt14Coverage(cmapFmt14CoverageSize); - uint32_t cmapFmt14CoverageEntryCount = reader->read<uint32_t>(); - for (uint32_t i = 0; i < cmapFmt14CoverageEntryCount; i++) { - uint32_t index = reader->read<uint32_t>(); - cmapFmt14Coverage[index] = std::make_unique<SparseBitSet>(reader); - } - return std::shared_ptr<FontFamily>(new FontFamily( - localeListId, variant, std::move(fonts), std::move(supportedAxes), isColorEmoji, - isCustomFallback, std::move(coverage), std::move(cmapFmt14Coverage))); -} - -// static -uint32_t FontFamily::readLocaleListInternal(BufferReader* reader) { - return LocaleListCache::readFrom(reader); + mCmapFmt14CoverageCount = reader->read<uint32_t>(); + if (mCmapFmt14CoverageCount > 0) { + mCmapFmt14Coverage = std::make_unique<SparseBitSet[]>(mCmapFmt14CoverageCount); + uint32_t cmapFmt14CoverageEntryCount = reader->read<uint32_t>(); + for (uint32_t i = 0; i < cmapFmt14CoverageEntryCount; i++) { + uint32_t index = reader->read<uint32_t>(); + mCmapFmt14Coverage[index] = SparseBitSet(reader); + } + } } -// Write fields other than mFonts. -void FontFamily::writeToInternal(BufferWriter* writer) const { +void FontFamily::writeTo(BufferWriter* writer, uint32_t* fontIndex) const { + LocaleListCache::writeTo(writer, mLocaleListId); + writer->write<uint32_t>(mFontsCount); + for (size_t i = 0; i < mFontsCount; i++) { + writer->write<uint32_t>(*fontIndex); + (*fontIndex)++; + } writer->write<FamilyVariant>(mVariant); - std::vector<AxisTag> axes(mSupportedAxes.begin(), mSupportedAxes.end()); - // Sort axes to be deterministic. - std::sort(axes.begin(), axes.end()); - writer->writeArray<AxisTag>(axes.data(), axes.size()); + writer->writeArray<AxisTag>(mSupportedAxes.get(), mSupportedAxesCount); writer->write<uint8_t>(mIsColorEmoji); writer->write<uint8_t>(mIsCustomFallback); + writer->write<uint8_t>(mIsDefaultFallback); mCoverage.writeTo(writer); // Write mCmapFmt14Coverage as a sparse array (size, non-null entry count, // array of (index, entry)) - writer->write<uint32_t>(mCmapFmt14Coverage.size()); - uint32_t cmapFmt14CoverageEntryCount = 0; - for (const std::unique_ptr<SparseBitSet>& coverage : mCmapFmt14Coverage) { - if (coverage != nullptr) cmapFmt14CoverageEntryCount++; - } - writer->write<uint32_t>(cmapFmt14CoverageEntryCount); - for (size_t i = 0; i < mCmapFmt14Coverage.size(); i++) { - if (mCmapFmt14Coverage[i] != nullptr) { - writer->write<uint32_t>(i); - mCmapFmt14Coverage[i]->writeTo(writer); + writer->write<uint32_t>(mCmapFmt14CoverageCount); + // Skip writing the sparse entries if the size is zero + if (mCmapFmt14CoverageCount > 0) { + uint32_t cmapFmt14CoverageEntryCount = 0; + for (size_t i = 0; i < mCmapFmt14CoverageCount; i++) { + if (!mCmapFmt14Coverage[i].empty()) cmapFmt14CoverageEntryCount++; + } + writer->write<uint32_t>(cmapFmt14CoverageEntryCount); + for (size_t i = 0; i < mCmapFmt14CoverageCount; i++) { + if (!mCmapFmt14Coverage[i].empty()) { + writer->write<uint32_t>(i); + mCmapFmt14Coverage[i].writeTo(writer); + } } } } -void FontFamily::writeLocaleListInternal(BufferWriter* writer) const { - LocaleListCache::writeTo(writer, mLocaleListId); +// static +std::vector<std::shared_ptr<FontFamily>> FontFamily::readVector(BufferReader* reader) { + // To save memory used for reference counting objects, we store + // Font / FontFamily in shared_ptr<vector<Font / FontFamily>>, and use + // shared_ptr's aliasing constructor to create shared_ptr<Font / FontFamily> + // that share the reference counting objects with + // the shared_ptr<vector<Font / FontFamily>>. + // We can do this because we know that all Font and FontFamily + // instances based on the same BufferReader share the same life span. + uint32_t fontsCount = reader->read<uint32_t>(); + std::shared_ptr<std::vector<Font>> fonts = std::make_shared<std::vector<Font>>(); + fonts->reserve(fontsCount); + for (uint32_t i = 0; i < fontsCount; i++) { + fonts->emplace_back(reader); + } + uint32_t count = reader->read<uint32_t>(); + std::shared_ptr<std::vector<FontFamily>> families = std::make_shared<std::vector<FontFamily>>(); + families->reserve(count); + std::vector<std::shared_ptr<FontFamily>> pointers; + pointers.reserve(count); + for (uint32_t i = 0; i < count; i++) { + // TODO(b/174672300): Revert back to emplace_back. + families->push_back(FontFamily(reader, fonts)); + // Use aliasing constructor. + pointers.emplace_back(families, &families->back()); + } + return pointers; } + +// static +void FontFamily::writeVector(BufferWriter* writer, + const std::vector<std::shared_ptr<FontFamily>>& families) { + std::vector<std::shared_ptr<Font>> fonts; + for (const auto& fontFamily : families) { + for (uint32_t i = 0; i < fontFamily->getNumFonts(); i++) { + fonts.emplace_back(fontFamily->getFontRef(i)); + } + } + writer->write<uint32_t>(fonts.size()); + for (const auto& font : fonts) { + font->writeTo(writer); + } + uint32_t fontIndex = 0; + writer->write<uint32_t>(families.size()); + for (const auto& fontFamily : families) { + fontFamily->writeTo(writer, &fontIndex); + } +} + // Compute a matching metric between two styles - 0 is an exact match static int computeMatch(FontStyle style1, FontStyle style2) { if (style1 == style2) return 0; @@ -154,7 +226,7 @@ FakedFont FontFamily::getClosestMatch(FontStyle style) const { int bestIndex = 0; Font* bestFont = mFonts[bestIndex].get(); int bestMatch = computeMatch(bestFont->style(), style); - for (size_t i = 1; i < mFonts.size(); i++) { + for (size_t i = 1; i < mFontsCount; i++) { Font* font = mFonts[i].get(); int match = computeMatch(font->style(), style); if (i == 0 || match < bestMatch) { @@ -174,11 +246,31 @@ void FontFamily::computeCoverage() { return; } - mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mCmapFmt14Coverage); + std::vector<SparseBitSet> cmapFmt14Coverage; + mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &cmapFmt14Coverage); + static_assert(INVALID_VS_INDEX <= std::numeric_limits<uint16_t>::max()); + // cmapFmt14Coverage maps VS index to coverage. + // cmapFmt14Coverage's size cannot exceed INVALID_VS_INDEX. + MINIKIN_ASSERT(cmapFmt14Coverage.size() <= INVALID_VS_INDEX, + "cmapFmt14Coverage's size must not exceed INVALID_VS_INDEX."); + mCmapFmt14CoverageCount = static_cast<uint16_t>(cmapFmt14Coverage.size()); + if (mCmapFmt14CoverageCount > 0) { + mCmapFmt14Coverage = std::make_unique<SparseBitSet[]>(mCmapFmt14CoverageCount); + for (size_t i = 0; i < mCmapFmt14CoverageCount; i++) { + mCmapFmt14Coverage[i] = std::move(cmapFmt14Coverage[i]); + } + } - for (size_t i = 0; i < mFonts.size(); ++i) { + std::unordered_set<AxisTag> supportedAxesSet; + for (size_t i = 0; i < mFontsCount; ++i) { std::unordered_set<AxisTag> supportedAxes = mFonts[i]->getSupportedAxes(); - mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end()); + supportedAxesSet.insert(supportedAxes.begin(), supportedAxes.end()); + } + MINIKIN_ASSERT(supportedAxesSet.size() <= std::numeric_limits<uint32_t>::max(), + "Number of supported axes must be less than 2^16."); + mSupportedAxesCount = static_cast<uint16_t>(supportedAxesSet.size()); + if (mSupportedAxesCount > 0) { + mSupportedAxes = sortedArrayFromSet(supportedAxesSet); } } @@ -187,35 +279,36 @@ bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) const return mCoverage.get(codepoint); } - if (mCmapFmt14Coverage.empty()) { + if (mCmapFmt14CoverageCount == 0) { return false; } const uint16_t vsIndex = getVsIndex(variationSelector); - if (vsIndex >= mCmapFmt14Coverage.size()) { + if (vsIndex >= mCmapFmt14CoverageCount) { // Even if vsIndex is INVALID_VS_INDEX, we reach here since INVALID_VS_INDEX is defined to // be at the maximum end of the range. return false; } - const std::unique_ptr<SparseBitSet>& bitset = mCmapFmt14Coverage[vsIndex]; - if (bitset.get() == nullptr) { + const SparseBitSet& bitset = mCmapFmt14Coverage[vsIndex]; + if (bitset.empty()) { return false; } - return bitset->get(codepoint); + return bitset.get(codepoint); } std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( const std::vector<FontVariation>& variations) const { - if (variations.empty() || mSupportedAxes.empty()) { + if (variations.empty() || mSupportedAxesCount == 0) { return nullptr; } bool hasSupportedAxis = false; for (const FontVariation& variation : variations) { - if (mSupportedAxes.find(variation.axisTag) != mSupportedAxes.end()) { + if (std::binary_search(mSupportedAxes.get(), mSupportedAxes.get() + mSupportedAxesCount, + variation.axisTag)) { hasSupportedAxis = true; break; } @@ -226,7 +319,8 @@ std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( } std::vector<std::shared_ptr<Font>> fonts; - for (const auto& font : mFonts) { + for (size_t i = 0; i < mFontsCount; i++) { + const std::shared_ptr<Font>& font = mFonts[i]; bool supportedVariations = false; std::unordered_set<AxisTag> supportedAxes = font->getSupportedAxes(); if (!supportedAxes.empty()) { @@ -248,8 +342,7 @@ std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( } } - return std::shared_ptr<FontFamily>( - new FontFamily(mLocaleListId, mVariant, std::move(fonts), mIsCustomFallback)); + return create(mLocaleListId, mVariant, std::move(fonts), mIsCustomFallback, mIsDefaultFallback); } } // namespace minikin diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp index 093dba8..968ab6f 100644 --- a/libs/minikin/Measurement.cpp +++ b/libs/minikin/Measurement.cpp @@ -24,11 +24,33 @@ #include "minikin/BoundsCache.h" #include "minikin/GraphemeBreak.h" +namespace { +bool isAsciiOrBidiControlCharacter(uint16_t c) { + return (0x0000 <= c && c <= 0x001F) // ASCII control characters + || c == 0x061C || c == 0x200E || c == 0x200F // BiDi control characters + || (0x202A <= c && c <= 0x202E) || (0x2066 <= c && c <= 0x2069); +} + +} // namespace + namespace minikin { // These could be considered helper methods of layout, but need only be loosely coupled, so // are separate. +/** + * Return the unsigned advance of the given offset from the run start. + * + * @param advances the computed advances of the characters in buf. The advance of + * the i-th character in buf is stored at index (i - layoutStart) in this array. + * @param buf the text stored in utf-16 format. + * @param layoutStart the start index of the character that is laid out. + * @param start the start index of the run. + * @param count the number of the characters in this run. + * @param offset the target offset to compute the index. It should be in the + * range of [start, start + count). + * @return the unsigned advance from the run start to the given offset. + */ static float getRunAdvance(const float* advances, const uint16_t* buf, size_t layoutStart, size_t start, size_t count, size_t offset) { float advance = 0.0f; @@ -42,13 +64,17 @@ static float getRunAdvance(const float* advances, const uint16_t* buf, size_t la clusterWidth = charAdvance; } } - if (offset < start + count && advances[offset - layoutStart] == 0.0f) { + if (offset < start + count && !isAsciiOrBidiControlCharacter(buf[offset]) && + advances[offset - layoutStart] == 0.0f) { // In the middle of a cluster, distribute width of cluster so that each grapheme cluster // gets an equal share. // TODO: get caret information out of font when that's available size_t nextCluster; for (nextCluster = offset + 1; nextCluster < start + count; nextCluster++) { - if (advances[nextCluster - layoutStart] != 0.0f) break; + if (advances[nextCluster - layoutStart] != 0.0f || + isAsciiOrBidiControlCharacter(buf[nextCluster])) { + break; + } } int numGraphemeClusters = 0; int numGraphemeClustersAfter = 0; @@ -69,6 +95,50 @@ static float getRunAdvance(const float* advances, const uint16_t* buf, size_t la return advance; } +/** + * Helper method that distribute the advance to ligature characters. + * When ligature is applied, the first character in the ligature is assigned with the entire width. + * This method will evenly distribute the advance to each grapheme in the ligature. + * + * @param advances the computed advances of the characters in buf. The advance of + * the i-th character in buf is stored at index (i - start) in this array. This + * method will update this array so that advances is distributed evenly for + * ligature characters. + * @param buf the text stored in utf-16 format. + * @param start the start index of the run. + * @param count the number of the characters in this run. + */ +void distributeAdvances(float* advances, const uint16_t* buf, size_t start, size_t count) { + size_t clusterStart = start; + while (clusterStart < start + count) { + float clusterAdvance = advances[clusterStart - start]; + size_t clusterEnd; + for (clusterEnd = clusterStart + 1; clusterEnd < start + count; clusterEnd++) { + if (advances[clusterEnd - start] != 0.0f || + isAsciiOrBidiControlCharacter(buf[clusterEnd])) { + break; + } + } + size_t numGraphemeClusters = 0; + for (size_t i = clusterStart; i < clusterEnd; i++) { + if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) { + numGraphemeClusters++; + } + } + // When there are more than one grapheme in this cluster, ligature is applied. + // And we will distribute the width to each grapheme. + if (numGraphemeClusters > 1) { + for (size_t i = clusterStart; i < clusterEnd; ++i) { + if (GraphemeBreak::isGraphemeBreak(advances, buf, start, count, i)) { + // Only distribute the advance to the first character of the cluster. + advances[i - start] = clusterAdvance / numGraphemeClusters; + } + } + } + clusterStart = clusterEnd; + } +} + float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, size_t offset) { return getRunAdvance(advances, buf, start, start, count, offset); diff --git a/libs/minikin/MinikinFontFactory.cpp b/libs/minikin/MinikinFontFactory.cpp new file mode 100644 index 0000000..77f13d8 --- /dev/null +++ b/libs/minikin/MinikinFontFactory.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 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 "minikin/MinikinFontFactory.h" + +#include <log/log.h> + +#include "MinikinInternal.h" + +namespace minikin { + +namespace { +static const MinikinFontFactory* gMinikinFontFactory = nullptr; +} + +MinikinFontFactory::~MinikinFontFactory() {} + +// static +const MinikinFontFactory& MinikinFontFactory::getInstance() { + MINIKIN_ASSERT(gMinikinFontFactory != nullptr, "setInstance should have been called."); + return *gMinikinFontFactory; +} + +// static +void MinikinFontFactory::setInstance(const MinikinFontFactory* factory) { + MINIKIN_ASSERT(gMinikinFontFactory == nullptr || gMinikinFontFactory == factory, + "MinikinFontFactory cannot be changed after it is set."); + gMinikinFontFactory = factory; +} + +} // namespace minikin diff --git a/libs/minikin/MinikinInternal.h b/libs/minikin/MinikinInternal.h index d90f099..22e1b75 100644 --- a/libs/minikin/MinikinInternal.h +++ b/libs/minikin/MinikinInternal.h @@ -23,6 +23,11 @@ #include <utils/Log.h> #include <utils/Mutex.h> +#include <algorithm> +#include <memory> +#include <unordered_set> + +#include "minikin/FontVariation.h" #include "minikin/HbUtils.h" #include "minikin/MinikinFont.h" @@ -75,6 +80,14 @@ private: HbBlobUniquePtr mBlob; }; +template <typename T> +std::unique_ptr<T[]> sortedArrayFromSet(const std::unordered_set<T>& set) { + std::unique_ptr<T[]> array(new T[set.size()]); + std::copy(set.begin(), set.end(), array.get()); + std::sort(array.get(), array.get() + set.size()); + return array; +} + } // namespace minikin #endif // MINIKIN_INTERNAL_H diff --git a/libs/minikin/SparseBitSet.cpp b/libs/minikin/SparseBitSet.cpp index 41151f1..a2814f7 100644 --- a/libs/minikin/SparseBitSet.cpp +++ b/libs/minikin/SparseBitSet.cpp @@ -54,16 +54,16 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { if (maxVal >= kMaximumCapacity) { return; } - mMaxVal = maxVal; - mIndicesCount = (mMaxVal + kPageMask) >> kLogValuesPerPage; - // Avoid zero-filling mOwnedIndices. - mOwnedIndices.reset(new uint16_t[mIndicesCount]); - mIndices = mOwnedIndices.get(); + uint32_t indicesCount = (maxVal + kPageMask) >> kLogValuesPerPage; uint32_t nPages = calcNumPages(ranges, nRanges); - mBitmapsCount = nPages << (kLogValuesPerPage - kLogBitsPerEl); - mOwnedBitmaps = std::make_unique<element[]>(mBitmapsCount); - mBitmaps = mOwnedBitmaps.get(); - mZeroPageIndex = noZeroPage; + uint32_t bitmapsCount = nPages << (kLogValuesPerPage - kLogBitsPerEl); + MappableData* data = MappableData::allocate(indicesCount, bitmapsCount); + mData.reset(data); + data->mMaxVal = maxVal; + uint16_t* indices = data->indices(); + element* bitmaps = data->bitmaps(); + memset(bitmaps, 0, sizeof(uint32_t) * bitmapsCount); + data->mZeroPageIndex = noZeroPage; uint32_t nonzeroPageEnd = 0; uint32_t currentPage = 0; for (size_t i = 0; i < nRanges; i++) { @@ -74,54 +74,56 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { uint32_t endPage = (end - 1) >> kLogValuesPerPage; if (startPage >= nonzeroPageEnd) { if (startPage > nonzeroPageEnd) { - if (mZeroPageIndex == noZeroPage) { - mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + if (data->mZeroPageIndex == noZeroPage) { + data->mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } for (uint32_t j = nonzeroPageEnd; j < startPage; j++) { - mOwnedIndices[j] = mZeroPageIndex; + indices[j] = data->mZeroPageIndex; } } - mOwnedIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + indices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) + ((start & kPageMask) >> kLogBitsPerEl); size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl; if (nElements == 1) { - mOwnedBitmaps[index] |= + bitmaps[index] |= (kElAllOnes >> (start & kElMask)) & (kElAllOnes << ((~end + 1) & kElMask)); } else { - mOwnedBitmaps[index] |= kElAllOnes >> (start & kElMask); + bitmaps[index] |= kElAllOnes >> (start & kElMask); for (size_t j = 1; j < nElements - 1; j++) { - mOwnedBitmaps[index + j] = kElAllOnes; + bitmaps[index + j] = kElAllOnes; } - mOwnedBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); + bitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); } for (size_t j = startPage + 1; j < endPage + 1; j++) { - mOwnedIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + indices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } nonzeroPageEnd = endPage + 1; } } void SparseBitSet::initFromBuffer(BufferReader* reader) { - mMaxVal = reader->read<uint32_t>(); - // mIndices and mBitmaps are not initialized when mMaxVal == 0 - if (mMaxVal == 0) return; - std::tie(mIndices, mIndicesCount) = reader->readArray<uint16_t>(); - // element is uint32_t - static_assert(sizeof(element) == 4); - std::tie(mBitmaps, mBitmapsCount) = reader->readArray<element>(); - mZeroPageIndex = reader->read<uint16_t>(); + uint32_t size = reader->read<uint32_t>(); + if (size == 0) return; + mData.reset(reader->map<MappableData, alignof(MappableData)>(size)); } void SparseBitSet::writeTo(BufferWriter* writer) const { - writer->write<uint32_t>(mMaxVal); - // mIndices and mBitmaps are not initialized when mMaxVal == 0 - if (mMaxVal == 0) return; - writer->writeArray<uint16_t>(mIndices, mIndicesCount); - writer->writeArray<element>(mBitmaps, mBitmapsCount); - writer->write<uint16_t>(mZeroPageIndex); + if (mData == nullptr) { + // Write 0 for empty SparseBitSet. + writer->write<uint32_t>(0); + return; + } + size_t size = mData->size(); + writer->write<uint32_t>(size); + static_assert(alignof(MappableData) == 4); + MappableData* out = writer->reserve<MappableData, alignof(MappableData)>(size); + if (out != nullptr) { + memcpy(out, mData.get(), size); + out->mIsMapped = 1; + } } int SparseBitSet::CountLeadingZeros(element x) { @@ -130,11 +132,11 @@ int SparseBitSet::CountLeadingZeros(element x) { } uint32_t SparseBitSet::nextSetBit(uint32_t fromIndex) const { - if (fromIndex >= mMaxVal) { + if (mData == nullptr || fromIndex >= mData->mMaxVal) { return kNotFound; } uint32_t fromPage = fromIndex >> kLogValuesPerPage; - const element* bitmap = &mBitmaps[mIndices[fromPage]]; + const element* bitmap = mData->bitmaps() + mData->indices()[fromPage]; uint32_t offset = (fromIndex & kPageMask) >> kLogBitsPerEl; element e = bitmap[offset] & (kElAllOnes >> (fromIndex & kElMask)); if (e != 0) { @@ -146,13 +148,13 @@ uint32_t SparseBitSet::nextSetBit(uint32_t fromIndex) const { return (fromIndex & ~kPageMask) + (j << kLogBitsPerEl) + CountLeadingZeros(e); } } - uint32_t maxPage = (mMaxVal + kPageMask) >> kLogValuesPerPage; + uint32_t maxPage = (mData->mMaxVal + kPageMask) >> kLogValuesPerPage; for (uint32_t page = fromPage + 1; page < maxPage; page++) { - uint16_t index = mIndices[page]; - if (index == mZeroPageIndex) { + uint16_t index = mData->indices()[page]; + if (index == mData->mZeroPageIndex) { continue; } - bitmap = &mBitmaps[index]; + bitmap = mData->bitmaps() + index; for (uint32_t j = 0; j < (1 << (kLogValuesPerPage - kLogBitsPerEl)); j++) { e = bitmap[j]; if (e != 0) { @@ -163,4 +165,15 @@ uint32_t SparseBitSet::nextSetBit(uint32_t fromIndex) const { return kNotFound; } +// static +SparseBitSet::MappableData* SparseBitSet::MappableData::allocate(uint32_t indicesCount, + uint32_t bitmapsCount) { + MappableData* data = reinterpret_cast<MappableData*>( + malloc(MappableData::calcSize(indicesCount, bitmapsCount))); + data->mIndicesCount = indicesCount; + data->mBitmapsCount = bitmapsCount; + data->mIsMapped = 0; + return data; +} + } // namespace minikin diff --git a/libs/minikin/SystemFonts.cpp b/libs/minikin/SystemFonts.cpp index 9c8fa66..b263d66 100644 --- a/libs/minikin/SystemFonts.cpp +++ b/libs/minikin/SystemFonts.cpp @@ -40,7 +40,8 @@ void SystemFonts::buildFontSetLocked() { std::unordered_set<FontFamily*> uniqueFamilies; for (const auto& collection : mCollections) { - for (const auto& family : collection->getFamilies()) { + for (size_t i = 0; i < collection->getFamilyCount(); ++i) { + const auto& family = collection->getFamilyAt(i); uniqueFamilies.insert(family.get()); } } diff --git a/libs/minikin/WordBreaker.cpp b/libs/minikin/WordBreaker.cpp index fd0dea9..ae79d3c 100644 --- a/libs/minikin/WordBreaker.cpp +++ b/libs/minikin/WordBreaker.cpp @@ -75,17 +75,21 @@ void ICULineBreakerPoolImpl::release(ICULineBreakerPool::Slot&& slot) { mPool.push_front(std::move(slot)); } -WordBreaker::WordBreaker() : mPool(&ICULineBreakerPoolImpl::getInstance()) {} +WordBreaker::WordBreaker() + : mPool(&ICULineBreakerPoolImpl::getInstance()), mUText(nullptr, &utext_close) {} -WordBreaker::WordBreaker(ICULineBreakerPool* pool) : mPool(pool) {} +WordBreaker::WordBreaker(ICULineBreakerPool* pool) : mPool(pool), mUText(nullptr, &utext_close) {} ssize_t WordBreaker::followingWithLocale(const Locale& locale, LineBreakStyle lbStyle, LineBreakWordStyle lbWordStyle, size_t from) { + if (!mUText) { + return mCurrent; + } mIcuBreaker = mPool->acquire(locale, lbStyle, lbWordStyle); UErrorCode status = U_ZERO_ERROR; MINIKIN_ASSERT(mText != nullptr, "setText must be called first"); // TODO: handle failure status - ubrk_setUText(mIcuBreaker.breaker.get(), &mUText, &status); + ubrk_setUText(mIcuBreaker.breaker.get(), mUText.get(), &status); if (mInEmailOrUrl) { // Note: // Don't reset mCurrent, mLast, or mScanOffset for keeping email/URL context. @@ -108,7 +112,7 @@ void WordBreaker::setText(const uint16_t* data, size_t size) { mScanOffset = 0; mInEmailOrUrl = false; UErrorCode status = U_ZERO_ERROR; - utext_openUChars(&mUText, reinterpret_cast<const UChar*>(data), size, &status); + mUText.reset(utext_openUChars(nullptr, reinterpret_cast<const UChar*>(data), size, &status)); } ssize_t WordBreaker::current() const { @@ -317,8 +321,7 @@ int WordBreaker::breakBadness() const { void WordBreaker::finish() { mText = nullptr; - // Note: calling utext_close multiply is safe - utext_close(&mUText); + mUText.reset(); mPool->release(std::move(mIcuBreaker)); } diff --git a/libs/minikin/WordBreaker.h b/libs/minikin/WordBreaker.h index 45bcd40..c4af635 100644 --- a/libs/minikin/WordBreaker.h +++ b/libs/minikin/WordBreaker.h @@ -26,6 +26,7 @@ #include <unicode/ubrk.h> #include <list> +#include <memory> #include <mutex> #include "Locale.h" @@ -146,7 +147,7 @@ private: ICULineBreakerPool::Slot mIcuBreaker; - UText mUText = UTEXT_INITIALIZER; + std::unique_ptr<UText, decltype(&utext_close)> mUText; const uint16_t* mText = nullptr; size_t mTextSize; ssize_t mLast; diff --git a/tests/perftests/Android.bp b/tests/perftests/Android.bp index 19ed8eb..dcf5b98 100644 --- a/tests/perftests/Android.bp +++ b/tests/perftests/Android.bp @@ -21,11 +21,7 @@ package { cc_benchmark { name: "minikin_perftests", test_suites: ["device-tests"], - cppflags: [ - "-Werror", - "-Wall", - "-Wextra", - ], + defaults: ["libminikin_defaults"], srcs: [ "FontCollection.cpp", "FontLanguage.cpp", @@ -46,7 +42,7 @@ cc_benchmark { shared_libs: [ "libft2", "libharfbuzz_ng", - "libandroidicu", + "libicu", "liblog", ], diff --git a/tests/perftests/FontCollection.cpp b/tests/perftests/FontCollection.cpp index 15d1c5e..b0be292 100644 --- a/tests/perftests/FontCollection.cpp +++ b/tests/perftests/FontCollection.cpp @@ -36,15 +36,14 @@ static void BM_FontCollection_construct(benchmark::State& state) { std::vector<std::shared_ptr<FontFamily>> families = getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML); while (state.KeepRunning()) { - std::make_shared<FontCollection>(families); + FontCollection::create(families); } } BENCHMARK(BM_FontCollection_construct); static void BM_FontCollection_hasVariationSelector(benchmark::State& state) { - auto collection = - std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML)); + auto collection = FontCollection::create(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML)); uint32_t baseCp = state.range(0); uint32_t vsCp = state.range(1); @@ -80,8 +79,7 @@ struct ItemizeTestCases { }; static void BM_FontCollection_itemize(benchmark::State& state) { - auto collection = - std::make_shared<FontCollection>(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML)); + auto collection = FontCollection::create(getFontFamilies(SYSTEM_FONT_PATH, SYSTEM_FONT_XML)); size_t testIndex = state.range(0); state.SetLabel("Itemize: " + ITEMIZE_TEST_CASES[testIndex].labelText); diff --git a/tests/stresstest/Android.bp b/tests/stresstest/Android.bp index 784dc6a..d963a09 100644 --- a/tests/stresstest/Android.bp +++ b/tests/stresstest/Android.bp @@ -36,7 +36,7 @@ cc_test { "libft2", "libharfbuzz_ng", - "libandroidicu", + "libicu", "liblog", "libutils", "libz", @@ -47,9 +47,5 @@ cc_test { "MultithreadTest.cpp", ], - cflags: [ - "-Werror", - "-Wall", - "-Wextra", - ], + defaults: ["libminikin_defaults"], } diff --git a/tests/stresstest/FontFamilyTest.cpp b/tests/stresstest/FontFamilyTest.cpp index 7a9813e..f5632a2 100644 --- a/tests/stresstest/FontFamilyTest.cpp +++ b/tests/stresstest/FontFamilyTest.cpp @@ -38,7 +38,7 @@ TEST_P(FontFamilyHarfBuzzCompatibilityTest, CoverageTest) { auto font = std::make_shared<FreeTypeMinikinFontForTest>(fontPath); std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(font).build()); - std::shared_ptr<FontFamily> family = std::make_shared<FontFamily>(std::move(fonts)); + std::shared_ptr<FontFamily> family = FontFamily::create(std::move(fonts)); hb_font_t* hbFont = family->getFont(0)->baseFont().get(); diff --git a/tests/stresstest/MultithreadTest.cpp b/tests/stresstest/MultithreadTest.cpp index 560b517..d32d0b4 100644 --- a/tests/stresstest/MultithreadTest.cpp +++ b/tests/stresstest/MultithreadTest.cpp @@ -21,17 +21,19 @@ #include <random> #include <thread> +#include <android-base/thread_annotations.h> #include <cutils/log.h> #include <gtest/gtest.h> #include "minikin/FontCollection.h" #include "minikin/Macros.h" #include "minikin/MinikinPaint.h" - #include "FontTestUtils.h" #include "MinikinInternal.h" #include "PathUtils.h" +using android::base::ScopedLockAssertion; + namespace minikin { constexpr int LAYOUT_COUNT_PER_COLLECTION = 500; @@ -63,7 +65,7 @@ static void thread_main(int tid) { { // Wait until all threads are created. std::unique_lock<std::mutex> lock(gMutex); - gCv.wait(lock, [] { return gReady; }); + gCv.wait(lock, []() EXCLUSIVE_LOCKS_REQUIRED(gMutex) { return gReady; }); } std::mt19937 mt(tid); @@ -90,7 +92,7 @@ TEST(MultithreadTest, ThreadSafeStressTest) { std::vector<std::thread> threads; { - std::unique_lock<std::mutex> lock(gMutex); + ScopedLockAssertion lock(gMutex); threads.reserve(NUM_THREADS); for (int i = 0; i < NUM_THREADS; ++i) { threads.emplace_back(&thread_main, i); diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp index d36c52f..9800416 100644 --- a/tests/unittest/Android.bp +++ b/tests/unittest/Android.bp @@ -36,7 +36,7 @@ cc_test { shared_libs: [ "libft2", "libharfbuzz_ng", - "libandroidicu", + "libicu", "liblog", "libutils", "libz", @@ -79,9 +79,5 @@ cc_test { "WordBreakerTests.cpp", ], - cflags: [ - "-Werror", - "-Wall", - "-Wextra", - ], + defaults: ["libminikin_defaults"], } diff --git a/tests/unittest/BufferTest.cpp b/tests/unittest/BufferTest.cpp index 8b1db33..2e589b2 100644 --- a/tests/unittest/BufferTest.cpp +++ b/tests/unittest/BufferTest.cpp @@ -33,6 +33,11 @@ public: // padding (3), array size (4), uint32_t (4) * 2 uint32_t uint32Array[] = {0x98765432, 0x98765433}; writer->writeArray<uint32_t>(uint32Array, 2); + uint16_t* uint16Array = writer->reserve<uint16_t>(2 * sizeof(uint16_t)); + if (uint16Array != nullptr) { + uint16Array[0] = 0x1234u; + uint16Array[1] = 0x5678u; + } } }; @@ -40,7 +45,7 @@ TEST(BufferTest, testMeasureWriteRead) { TestObject testObject; BufferWriter fakeWriter(nullptr); testObject.writeTo(&fakeWriter); - ASSERT_EQ(fakeWriter.size(), 20u); + ASSERT_EQ(fakeWriter.size(), 24u); std::vector<uint8_t> buffer(fakeWriter.size()); BufferWriter writer(buffer.data()); @@ -48,26 +53,29 @@ TEST(BufferTest, testMeasureWriteRead) { ASSERT_EQ(writer.size(), buffer.size()); BufferReader reader(buffer.data()); - ASSERT_EQ(reader.data(), buffer.data()); - ASSERT_EQ(reader.pos(), 0u); + ASSERT_EQ(reader.current(), buffer.data()); ASSERT_EQ(reader.read<uint8_t>(), 0xABu); - ASSERT_EQ(reader.pos(), 1u); + ASSERT_EQ(reader.current(), buffer.data() + 1u); ASSERT_EQ(reader.read<uint16_t>(), 0xCDEFu); - ASSERT_EQ(reader.pos(), 4u); + ASSERT_EQ(reader.current(), buffer.data() + 4u); ASSERT_EQ(reader.read<uint8_t>(), 0x01u); - ASSERT_EQ(reader.pos(), 5u); + ASSERT_EQ(reader.current(), buffer.data() + 5u); auto [uint32Array, size] = reader.readArray<uint32_t>(); ASSERT_EQ(size, 2u); ASSERT_EQ(uint32Array[0], 0x98765432u); ASSERT_EQ(uint32Array[1], 0x98765433u); - ASSERT_EQ(reader.pos(), 20u); + ASSERT_EQ(reader.current(), buffer.data() + 20u); + const uint16_t* uint16Array = reader.map<uint16_t>(4); + ASSERT_EQ(uint16Array[0], 0x1234u); + ASSERT_EQ(uint16Array[1], 0x5678u); + ASSERT_EQ(reader.current(), buffer.data() + 24u); } TEST(BufferTest, testSkip) { TestObject testObject; BufferWriter fakeWriter(nullptr); testObject.writeTo(&fakeWriter); - ASSERT_EQ(fakeWriter.size(), 20u); + ASSERT_EQ(fakeWriter.size(), 24u); std::vector<uint8_t> buffer(fakeWriter.size()); BufferWriter writer(buffer.data()); @@ -75,16 +83,18 @@ TEST(BufferTest, testSkip) { ASSERT_EQ(writer.size(), buffer.size()); BufferReader reader(buffer.data()); - ASSERT_EQ(reader.data(), buffer.data()); - ASSERT_EQ(reader.pos(), 0u); + ASSERT_EQ(reader.current(), buffer.data()); reader.skip<uint8_t>(); - ASSERT_EQ(reader.pos(), 1u); + ASSERT_EQ(reader.current(), buffer.data() + 1u); reader.read<uint16_t>(); - ASSERT_EQ(reader.pos(), 4u); + ASSERT_EQ(reader.current(), buffer.data() + 4u); reader.skip<uint8_t>(); - ASSERT_EQ(reader.pos(), 5u); + ASSERT_EQ(reader.current(), buffer.data() + 5u); reader.skipArray<uint32_t>(); - ASSERT_EQ(reader.pos(), 20u); + ASSERT_EQ(reader.current(), buffer.data() + 20u); + // No skip function for mapped data. + reader.map<uint16_t>(4); + ASSERT_EQ(reader.current(), buffer.data() + 24u); } } // namespace minikin diff --git a/tests/unittest/CmapCoverageTest.cpp b/tests/unittest/CmapCoverageTest.cpp index 9dba583..f871c19 100644 --- a/tests/unittest/CmapCoverageTest.cpp +++ b/tests/unittest/CmapCoverageTest.cpp @@ -285,7 +285,7 @@ private: }; TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("Reading beyond buffer size - Too small cmap size"); std::vector<uint8_t> cmap = @@ -337,7 +337,7 @@ TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { } TEST(CmapCoverageTest, SingleFormat4) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; struct TestCast { std::string testTitle; uint16_t platformId; @@ -360,7 +360,7 @@ TEST(CmapCoverageTest, SingleFormat4) { } TEST(CmapCoverageTest, SingleFormat12) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; struct TestCast { std::string testTitle; @@ -384,7 +384,7 @@ TEST(CmapCoverageTest, SingleFormat12) { } TEST(CmapCoverageTest, Format12_beyondTheUnicodeLimit) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("Starting range is out of Unicode code point. Should be ignored."); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap( @@ -410,7 +410,7 @@ TEST(CmapCoverageTest, Format12_beyondTheUnicodeLimit) { } TEST(CmapCoverageTest, notSupportedEncodings) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; struct TestCast { std::string testTitle; @@ -451,7 +451,7 @@ TEST(CmapCoverageTest, notSupportedEncodings) { } TEST(CmapCoverageTest, brokenFormat4Table) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("Too small table cmap size"); std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'})); @@ -505,7 +505,7 @@ TEST(CmapCoverageTest, brokenFormat4Table) { } TEST(CmapCoverageTest, duplicatedCmap4EntryTest) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'b', 'b', 'b'})); CmapBuilder builder(1); builder.appendTable(0, 0, table); @@ -518,7 +518,7 @@ TEST(CmapCoverageTest, duplicatedCmap4EntryTest) { } TEST(CmapCoverageTest, brokenFormat12Table) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("Too small cmap size"); std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); @@ -594,7 +594,7 @@ TEST(CmapCoverageTest, TableSelection_Priority) { std::vector<uint8_t> format4 = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); std::vector<uint8_t> format12 = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("(platform, encoding) = (3, 10) is the highest priority."); @@ -646,7 +646,7 @@ TEST(CmapCoverageTest, TableSelection_Priority) { TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat4Table) { std::vector<uint8_t> validTable = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'})); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; { SCOPED_TRACE("Unsupported format"); CmapBuilder builder(2); @@ -692,7 +692,7 @@ TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat4Table) { } TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) { - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; std::vector<uint8_t> validTable = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); { SCOPED_TRACE("Unsupported format"); @@ -751,28 +751,28 @@ TEST(CmapCoverageTest, TableSelection_VSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); ASSERT_FALSE(vsTables.empty()); const uint16_t vs15Index = getVsIndex(0xFE0E); ASSERT_LT(vs15Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs15Index]); - EXPECT_TRUE(vsTables[vs15Index]->get('a')); - EXPECT_TRUE(vsTables[vs15Index]->get('b')); + ASSERT_FALSE(vsTables[vs15Index].empty()); + EXPECT_TRUE(vsTables[vs15Index].get('a')); + EXPECT_TRUE(vsTables[vs15Index].get('b')); const uint16_t vs16Index = getVsIndex(0xFE0F); ASSERT_LT(vs16Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs16Index]); - EXPECT_TRUE(vsTables[vs16Index]->get('a')); - EXPECT_TRUE(vsTables[vs16Index]->get('b')); + ASSERT_FALSE(vsTables[vs16Index].empty()); + EXPECT_TRUE(vsTables[vs16Index].get('a')); + EXPECT_TRUE(vsTables[vs16Index].get('b')); const uint16_t vs17Index = getVsIndex(0xE0100); ASSERT_LT(vs17Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs17Index]); - EXPECT_TRUE(vsTables[vs17Index]->get('a')); - EXPECT_TRUE(vsTables[vs17Index]->get('b')); + ASSERT_FALSE(vsTables[vs17Index].empty()); + EXPECT_TRUE(vsTables[vs17Index].get('a')); + EXPECT_TRUE(vsTables[vs17Index].get('b')); } TEST(CmapCoverageTest, TableSelection_InterSection) { @@ -799,66 +799,66 @@ TEST(CmapCoverageTest, TableSelection_InterSection) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); ASSERT_FALSE(vsTables.empty()); const uint16_t vs15Index = getVsIndex(0xFE0E); ASSERT_LT(vs15Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs15Index]); - EXPECT_TRUE(vsTables[vs15Index]->get('a')); - EXPECT_TRUE(vsTables[vs15Index]->get('b')); - EXPECT_TRUE(vsTables[vs15Index]->get('c')); - EXPECT_TRUE(vsTables[vs15Index]->get('d')); - EXPECT_TRUE(vsTables[vs15Index]->get('e')); + ASSERT_FALSE(vsTables[vs15Index].empty()); + EXPECT_TRUE(vsTables[vs15Index].get('a')); + EXPECT_TRUE(vsTables[vs15Index].get('b')); + EXPECT_TRUE(vsTables[vs15Index].get('c')); + EXPECT_TRUE(vsTables[vs15Index].get('d')); + EXPECT_TRUE(vsTables[vs15Index].get('e')); const uint16_t vs16Index = getVsIndex(0xFE0F); ASSERT_LT(vs16Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs16Index]); - EXPECT_TRUE(vsTables[vs16Index]->get('a')); - EXPECT_TRUE(vsTables[vs16Index]->get('b')); - EXPECT_TRUE(vsTables[vs16Index]->get('c')); - EXPECT_TRUE(vsTables[vs16Index]->get('d')); - EXPECT_TRUE(vsTables[vs16Index]->get('e')); + ASSERT_FALSE(vsTables[vs16Index].empty()); + EXPECT_TRUE(vsTables[vs16Index].get('a')); + EXPECT_TRUE(vsTables[vs16Index].get('b')); + EXPECT_TRUE(vsTables[vs16Index].get('c')); + EXPECT_TRUE(vsTables[vs16Index].get('d')); + EXPECT_TRUE(vsTables[vs16Index].get('e')); const uint16_t vs17Index = getVsIndex(0xE0100); ASSERT_LT(vs17Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs17Index]); - EXPECT_TRUE(vsTables[vs17Index]->get('a')); - EXPECT_TRUE(vsTables[vs17Index]->get('b')); - EXPECT_TRUE(vsTables[vs17Index]->get('c')); - EXPECT_TRUE(vsTables[vs17Index]->get('d')); + ASSERT_FALSE(vsTables[vs17Index].empty()); + EXPECT_TRUE(vsTables[vs17Index].get('a')); + EXPECT_TRUE(vsTables[vs17Index].get('b')); + EXPECT_TRUE(vsTables[vs17Index].get('c')); + EXPECT_TRUE(vsTables[vs17Index].get('d')); const uint16_t vs18Index = getVsIndex(0xE0101); ASSERT_LT(vs18Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs18Index]); - EXPECT_TRUE(vsTables[vs18Index]->get('a')); - EXPECT_TRUE(vsTables[vs18Index]->get('b')); - EXPECT_TRUE(vsTables[vs18Index]->get('c')); - EXPECT_TRUE(vsTables[vs18Index]->get('d')); + ASSERT_FALSE(vsTables[vs18Index].empty()); + EXPECT_TRUE(vsTables[vs18Index].get('a')); + EXPECT_TRUE(vsTables[vs18Index].get('b')); + EXPECT_TRUE(vsTables[vs18Index].get('c')); + EXPECT_TRUE(vsTables[vs18Index].get('d')); const uint16_t vs19Index = getVsIndex(0xE0102); ASSERT_LT(vs19Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs19Index]); - EXPECT_TRUE(vsTables[vs19Index]->get('a')); - EXPECT_TRUE(vsTables[vs19Index]->get('b')); - EXPECT_TRUE(vsTables[vs19Index]->get('c')); - EXPECT_TRUE(vsTables[vs19Index]->get('d')); - EXPECT_TRUE(vsTables[vs19Index]->get('e')); - EXPECT_TRUE(vsTables[vs19Index]->get('f')); - EXPECT_TRUE(vsTables[vs19Index]->get('g')); - EXPECT_TRUE(vsTables[vs19Index]->get('h')); + ASSERT_FALSE(vsTables[vs19Index].empty()); + EXPECT_TRUE(vsTables[vs19Index].get('a')); + EXPECT_TRUE(vsTables[vs19Index].get('b')); + EXPECT_TRUE(vsTables[vs19Index].get('c')); + EXPECT_TRUE(vsTables[vs19Index].get('d')); + EXPECT_TRUE(vsTables[vs19Index].get('e')); + EXPECT_TRUE(vsTables[vs19Index].get('f')); + EXPECT_TRUE(vsTables[vs19Index].get('g')); + EXPECT_TRUE(vsTables[vs19Index].get('h')); const uint16_t vs20Index = getVsIndex(0xE0103); ASSERT_LT(vs20Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs20Index]); - EXPECT_TRUE(vsTables[vs20Index]->get('a')); - EXPECT_TRUE(vsTables[vs20Index]->get('b')); - EXPECT_TRUE(vsTables[vs20Index]->get('c')); - EXPECT_TRUE(vsTables[vs20Index]->get('d')); - EXPECT_TRUE(vsTables[vs20Index]->get('e')); - EXPECT_TRUE(vsTables[vs20Index]->get('f')); + ASSERT_FALSE(vsTables[vs20Index].empty()); + EXPECT_TRUE(vsTables[vs20Index].get('a')); + EXPECT_TRUE(vsTables[vs20Index].get('b')); + EXPECT_TRUE(vsTables[vs20Index].get('c')); + EXPECT_TRUE(vsTables[vs20Index].get('d')); + EXPECT_TRUE(vsTables[vs20Index].get('e')); + EXPECT_TRUE(vsTables[vs20Index].get('f')); } TEST(CmapCoverageTest, TableSelection_brokenVSTable) { @@ -872,7 +872,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), 3 /* too small size */, &vsTables); EXPECT_FALSE(coverage.get('a')); @@ -888,7 +888,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -902,7 +902,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -916,7 +916,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -931,7 +931,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -946,7 +946,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -959,7 +959,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -972,7 +972,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -985,7 +985,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -998,7 +998,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1014,7 +1014,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1030,7 +1030,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1046,7 +1046,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1062,7 +1062,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1078,7 +1078,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1094,7 +1094,7 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); ASSERT_TRUE(vsTables.empty()); } @@ -1113,17 +1113,17 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable_bestEffort) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); const uint16_t vs16Index = getVsIndex(0xFE0F); ASSERT_LT(vs16Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs16Index]); - EXPECT_TRUE(vsTables[vs16Index]->get('a')); - EXPECT_TRUE(vsTables[vs16Index]->get('b')); + ASSERT_FALSE(vsTables[vs16Index].empty()); + EXPECT_TRUE(vsTables[vs16Index].get('a')); + EXPECT_TRUE(vsTables[vs16Index].get('b')); const uint16_t vs15Index = getVsIndex(0xFE0E); - EXPECT_FALSE(vsTables[vs15Index]); + EXPECT_TRUE(vsTables[vs15Index].empty()); } { SCOPED_TRACE("Invalid non default UVS offset in variation records"); @@ -1136,17 +1136,17 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable_bestEffort) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); const uint16_t vs16Index = getVsIndex(0xFE0F); ASSERT_LT(vs16Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs16Index]); - EXPECT_TRUE(vsTables[vs16Index]->get('a')); - EXPECT_TRUE(vsTables[vs16Index]->get('b')); + ASSERT_FALSE(vsTables[vs16Index].empty()); + EXPECT_TRUE(vsTables[vs16Index].get('a')); + EXPECT_TRUE(vsTables[vs16Index].get('b')); const uint16_t vs15Index = getVsIndex(0xFE0E); - EXPECT_FALSE(vsTables[vs15Index]); + EXPECT_TRUE(vsTables[vs15Index].empty()); } { SCOPED_TRACE("Unknown variation selectors."); @@ -1158,14 +1158,14 @@ TEST(CmapCoverageTest, TableSelection_brokenVSTable_bestEffort) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); const uint16_t vs16Index = getVsIndex(0xFE0F); ASSERT_LT(vs16Index, vsTables.size()); - ASSERT_TRUE(vsTables[vs16Index]); - EXPECT_TRUE(vsTables[vs16Index]->get('a')); - EXPECT_TRUE(vsTables[vs16Index]->get('b')); + ASSERT_FALSE(vsTables[vs16Index].empty()); + EXPECT_TRUE(vsTables[vs16Index].get('a')); + EXPECT_TRUE(vsTables[vs16Index].get('b')); } } @@ -1183,16 +1183,16 @@ TEST(CmapCoverageTest, TableSelection_defaultUVSPointMissingGlyph) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); const uint16_t vsIndex = getVsIndex(0xFE0F); ASSERT_LT(vsIndex, vsTables.size()); - ASSERT_TRUE(vsTables[vsIndex]); + ASSERT_FALSE(vsTables[vsIndex].empty()); for (char c = 'a'; c <= 'z'; ++c) { // Default UVS table points the variation sequence to the glyph of the base code point. // Thus, if the base code point is not supported, we should exclude them. - EXPECT_EQ(coverage.get(c), vsTables[vsIndex]->get(c)) << c; + EXPECT_EQ(coverage.get(c), vsTables[vsIndex].get(c)) << c; } } @@ -1206,11 +1206,11 @@ TEST(CmapCoverageTest, TableSelection_vsTableOnly) { builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); std::vector<uint8_t> cmap = builder.build(); - std::vector<std::unique_ptr<SparseBitSet>> vsTables; + std::vector<SparseBitSet> vsTables; SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); const uint16_t vsIndex = getVsIndex(0xFE0F); ASSERT_LT(vsIndex, vsTables.size()); - ASSERT_TRUE(vsTables[vsIndex]); - EXPECT_TRUE(vsTables[vsIndex]->get('a')); + ASSERT_FALSE(vsTables[vsIndex].empty()); + EXPECT_TRUE(vsTables[vsIndex].get('a')); } } // namespace minikin diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp index e3f8a6b..1723519 100644 --- a/tests/unittest/FontCollectionItemizeTest.cpp +++ b/tests/unittest/FontCollectionItemizeTest.cpp @@ -741,7 +741,7 @@ TEST(FontCollectionItemizeTest, itemize_vs_sequence_but_no_base_char) { families.push_back(buildFontFamily(kLatinFont)); families.push_back(buildFontFamily(kVSTestFont)); - std::shared_ptr<FontCollection> collection(new FontCollection(families)); + std::shared_ptr<FontCollection> collection(FontCollection::create(families)); auto runs = itemize(collection, "U+717D U+FE02"); ASSERT_EQ(1U, runs.size()); @@ -937,9 +937,9 @@ TEST(FontCollectionItemizeTest, itemize_LocaleScore) { std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kNoGlyphFont)); std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(firstFamilyMinikinFont).build()); - auto firstFamily = - std::make_shared<FontFamily>(registerLocaleList("und"), FamilyVariant::DEFAULT, - std::move(fonts), false /* isCustomFallback */); + auto firstFamily = FontFamily::create(registerLocaleList("und"), FamilyVariant::DEFAULT, + std::move(fonts), false /* isCustomFallback */, + false /* isDefaultFallback */); families.push_back(firstFamily); // Prepare font families @@ -952,13 +952,13 @@ TEST(FontCollectionItemizeTest, itemize_LocaleScore) { std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kJAFont)); std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(minikinFont).build()); - auto family = std::make_shared<FontFamily>(registerLocaleList(testCase.fontLocales[i]), - FamilyVariant::DEFAULT, std::move(fonts), - false /* isCustomFallback */); + auto family = FontFamily::create( + registerLocaleList(testCase.fontLocales[i]), FamilyVariant::DEFAULT, + std::move(fonts), false /* isCustomFallback */, false /* isDefaultFallback */); families.push_back(family); fontLocaleIdxMap.insert(std::make_pair(minikinFont.get(), i)); } - std::shared_ptr<FontCollection> collection(new FontCollection(families)); + std::shared_ptr<FontCollection> collection(FontCollection::create(families)); // Do itemize auto runs = itemize(collection, "U+9AA8", testCase.userPreferredLocale); ASSERT_EQ(1U, runs.size()); @@ -1525,8 +1525,8 @@ TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) { std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, familyA, familyB}; std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, familyB, familyA}; - std::shared_ptr<FontCollection> collection(new FontCollection(families)); - std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies)); + std::shared_ptr<FontCollection> collection(FontCollection::create(families)); + std::shared_ptr<FontCollection> reversedCollection(FontCollection::create(reversedFamilies)); // Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first font should be // selected. @@ -1548,8 +1548,8 @@ TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) { std::vector<std::shared_ptr<FontFamily>> reversedFamilies = {dummyFamily, noCmapFormat14Family, hasCmapFormat14Family}; - std::shared_ptr<FontCollection> collection(new FontCollection(families)); - std::shared_ptr<FontCollection> reversedCollection(new FontCollection(reversedFamilies)); + std::shared_ptr<FontCollection> collection(FontCollection::create(families)); + std::shared_ptr<FontCollection> reversedCollection(FontCollection::create(reversedFamilies)); // Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't support U+5380 U+E0100. // The first font should be selected. @@ -1567,7 +1567,7 @@ TEST(FontCollectionItemizeTest, colorEmojiSelectionTest) { std::vector<std::shared_ptr<FontFamily>> families = {dummyFamily, textEmojiFamily, colorEmojiFamily}; - auto collection = std::make_shared<FontCollection>(families); + auto collection = FontCollection::create(families); // Both textEmojiFamily and colorEmojiFamily supports U+203C and U+23E9. // U+203C is text default emoji, and U+23E9 is color default emoji. auto runs = itemize(collection, "U+203C", "en-US,en-Zsym"); @@ -1619,7 +1619,7 @@ TEST(FontCollectionItemizeTest, customFallbackTest) { std::vector<std::shared_ptr<FontFamily>> families = {firstFamily, customFallbackFamily, languageFamily}; - auto collection = std::make_shared<FontCollection>(families); + auto collection = FontCollection::create(families); auto runs = itemize(collection, "'a'", ""); EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font.get()); @@ -1643,7 +1643,7 @@ std::vector<ItemizeResult> itemizeEmojiAndFontPostScriptNames(const std::string& std::vector<std::shared_ptr<FontFamily>> families = {firstFamily, OverrideEmojiFamily, emojiBaseFamily}; - auto collection = std::make_shared<FontCollection>(families); + auto collection = FontCollection::create(families); auto runs = itemize(collection, txt.c_str()); std::vector<ItemizeResult> out; diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp index aa9d4a8..b4933ab 100644 --- a/tests/unittest/FontCollectionTest.cpp +++ b/tests/unittest/FontCollectionTest.cpp @@ -177,25 +177,40 @@ TEST(FontCollectionTest, createWithVariations) { } } +TEST(FontCollectionTest, createCollectionWithFamilies) { + auto fallback = buildFontCollectionFromXml(kEmojiXmlFile); + std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont); + std::shared_ptr<FontCollection> created = fallback->createCollectionWithFamilies({family}); + ASSERT_EQ(fallback->getFamilyCount() + 1, created->getFamilyCount()); + EXPECT_EQ(family, created->getFamilyAt(0)); + for (size_t i = 0; i < fallback->getFamilyCount(); i++) { + EXPECT_EQ(fallback->getFamilyAt(i), created->getFamilyAt(i + 1)); + } +} + std::vector<uint8_t> writeToBuffer( const std::vector<std::shared_ptr<FontCollection>>& collections) { BufferWriter fakeWriter(nullptr); - FontCollection::writeVector<writeFreeTypeMinikinFontForTest>(&fakeWriter, collections); + FontCollection::writeVector(&fakeWriter, collections); std::vector<uint8_t> buffer(fakeWriter.size()); BufferWriter writer(buffer.data()); - FontCollection::writeVector<writeFreeTypeMinikinFontForTest>(&writer, collections); + FontCollection::writeVector(&writer, collections); return buffer; } TEST(FontCollectionTest, bufferTest) { + FreeTypeMinikinFontForTestFactory::init(); { std::vector<std::shared_ptr<FontCollection>> original({buildFontCollection(kVsTestFont)}); std::vector<uint8_t> buffer = writeToBuffer(original); BufferReader reader(buffer.data()); - auto copied = FontCollection::readVector<readFreeTypeMinikinFontForTest>(&reader); + auto copied = FontCollection::readVector(&reader); EXPECT_EQ(1u, copied.size()); expectVSGlyphsForVsTestFont(copied[0].get()); - EXPECT_EQ(original[0]->getSupportedTags(), copied[0]->getSupportedTags()); + ASSERT_EQ(original[0]->getSupportedAxesCount(), copied[0]->getSupportedAxesCount()); + for (size_t i = 0; i < original[0]->getSupportedAxesCount(); i++) { + EXPECT_EQ(original[0]->getSupportedAxisAt(i), copied[0]->getSupportedAxisAt(i)); + } // Id will be different. EXPECT_NE(original[0]->getId(), copied[0]->getId()); std::vector<uint8_t> newBuffer = writeToBuffer(copied); @@ -204,14 +219,14 @@ TEST(FontCollectionTest, bufferTest) { { // Test that FontFamily instances are shared. std::vector<std::shared_ptr<FontFamily>> families = {buildFontFamily(kVsTestFont)}; - auto fc1 = std::make_shared<FontCollection>(families); - auto fc2 = std::make_shared<FontCollection>(families); + auto fc1 = FontCollection::create(families); + auto fc2 = FontCollection::create(families); std::vector<std::shared_ptr<FontCollection>> original({fc1, fc2}); std::vector<uint8_t> buffer = writeToBuffer(original); BufferReader reader(buffer.data()); - auto copied = FontCollection::readVector<readFreeTypeMinikinFontForTest>(&reader); + auto copied = FontCollection::readVector(&reader); EXPECT_EQ(2u, copied.size()); - EXPECT_EQ(copied[0]->mFamilies[0], copied[1]->mFamilies[0]); + EXPECT_EQ(copied[0]->getFamilyAt(0), copied[1]->getFamilyAt(0)); std::vector<uint8_t> newBuffer = writeToBuffer(copied); EXPECT_EQ(buffer, newBuffer); } @@ -223,12 +238,12 @@ TEST(FontCollectionTest, bufferTest) { {buildFontCollection(kMultiAxisFont)}); std::vector<uint8_t> buffer = writeToBuffer(original); BufferReader reader(buffer.data()); - auto copied = FontCollection::readVector<readFreeTypeMinikinFontForTest>(&reader); + auto copied = FontCollection::readVector(&reader); EXPECT_EQ(1u, copied.size()); - EXPECT_EQ(1u, - copied[0]->getSupportedTags().count(MinikinFont::MakeTag('w', 'd', 't', 'h'))); - EXPECT_EQ(1u, - copied[0]->getSupportedTags().count(MinikinFont::MakeTag('w', 'g', 'h', 't'))); + ASSERT_EQ(2u, copied[0]->getSupportedAxesCount()); + // mSupportedAxes must be sorted. + EXPECT_EQ(MinikinFont::MakeTag('w', 'd', 't', 'h'), copied[0]->getSupportedAxisAt(0)); + EXPECT_EQ(MinikinFont::MakeTag('w', 'g', 'h', 't'), copied[0]->getSupportedAxisAt(1)); std::vector<uint8_t> newBuffer = writeToBuffer(copied); EXPECT_EQ(buffer, newBuffer); } diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp index ce710f3..3230c27 100644 --- a/tests/unittest/FontFamilyTest.cpp +++ b/tests/unittest/FontFamilyTest.cpp @@ -14,17 +14,16 @@ * limitations under the License. */ -#include "minikin/FontFamily.h" - #include <gtest/gtest.h> - -#include "minikin/LocaleList.h" +#include <malloc.h> #include "BufferUtils.h" #include "FontTestUtils.h" #include "FreeTypeMinikinFontForTest.h" #include "LocaleListCache.h" #include "MinikinInternal.h" +#include "minikin/FontFamily.h" +#include "minikin/LocaleList.h" namespace minikin { @@ -801,8 +800,8 @@ TEST_F(FontFamilyTest, closestMatch) { fonts.push_back(Font::Builder(dummyFont).setStyle(familyStyle).build()); } - FontFamily family(std::move(fonts)); - FakedFont closest = family.getClosestMatch(testCase.wantedStyle); + std::shared_ptr<FontFamily> family = FontFamily::create(std::move(fonts)); + FakedFont closest = family->getClosestMatch(testCase.wantedStyle); size_t idx = dummyFonts.size(); for (size_t i = 0; i < dummyFonts.size(); i++) { @@ -821,41 +820,57 @@ TEST_F(FontFamilyTest, closestMatch) { } } -TEST_F(FontFamilyTest, bufferTest) { - { - // Font with variation selectors - std::shared_ptr<FontFamily> original = buildFontFamily(kVsTestFont); - std::vector<uint8_t> buffer = - writeToBuffer<FontFamily, writeFreeTypeMinikinFontForTest>(*original); - BufferReader reader(buffer.data()); - std::shared_ptr<FontFamily> copied = - FontFamily::readFrom<readFreeTypeMinikinFontForTest>(&reader); - ASSERT_EQ(original->localeListId(), copied->localeListId()); - ASSERT_EQ(original->variant(), copied->variant()); - ASSERT_EQ(original->getNumFonts(), copied->getNumFonts()); - ASSERT_EQ(original->supportedAxes(), copied->supportedAxes()); - ASSERT_EQ(original->isColorEmojiFamily(), copied->isColorEmojiFamily()); - ASSERT_EQ(original->isCustomFallback(), copied->isCustomFallback()); - ASSERT_EQ(original->hasVSTable(), copied->hasVSTable()); - expectVSGlyphsForVsTestFont(copied.get()); - std::vector<uint8_t> newBuffer = - writeToBuffer<FontFamily, writeFreeTypeMinikinFontForTest>(*copied); - ASSERT_EQ(buffer, newBuffer); +std::vector<uint8_t> writeToBuffer(const std::vector<std::shared_ptr<FontFamily>>& families) { + BufferWriter fakeWriter(nullptr); + FontFamily::writeVector(&fakeWriter, families); + std::vector<uint8_t> buffer(fakeWriter.size()); + BufferWriter writer(buffer.data()); + FontFamily::writeVector(&writer, families); + return buffer; +} + +void expectFontFamilyEquals(const std::shared_ptr<FontFamily>& expected, + const std::shared_ptr<FontFamily>& actual) { + ASSERT_EQ(expected->localeListId(), actual->localeListId()); + ASSERT_EQ(expected->variant(), actual->variant()); + ASSERT_EQ(expected->getNumFonts(), actual->getNumFonts()); + ASSERT_EQ(expected->getSupportedAxesCount(), actual->getSupportedAxesCount()); + for (size_t i = 0; i < expected->getSupportedAxesCount(); i++) { + ASSERT_EQ(expected->getSupportedAxisAt(i), actual->getSupportedAxisAt(i)); } + ASSERT_EQ(expected->isColorEmojiFamily(), actual->isColorEmojiFamily()); + ASSERT_EQ(expected->isCustomFallback(), actual->isCustomFallback()); + ASSERT_EQ(expected->hasVSTable(), actual->hasVSTable()); +} + +size_t getHeapSize() { + struct mallinfo info = mallinfo(); + return info.uordblks; +} + +TEST_F(FontFamilyTest, bufferTest) { + FreeTypeMinikinFontForTestFactory::init(); + size_t baseHeapSize = getHeapSize(); { - // Font with axes constexpr char kMultiAxisFont[] = "MultiAxis.ttf"; - std::shared_ptr<FontFamily> original = buildFontFamily(kMultiAxisFont); - std::vector<uint8_t> buffer = - writeToBuffer<FontFamily, writeFreeTypeMinikinFontForTest>(*original); + std::vector<std::shared_ptr<FontFamily>> original = { + // Font with variation selectors + buildFontFamily(kVsTestFont), + // Font with axes + buildFontFamily(kMultiAxisFont), + }; + std::vector<uint8_t> buffer = writeToBuffer(original); BufferReader reader(buffer.data()); - std::shared_ptr<FontFamily> copied = - FontFamily::readFrom<readFreeTypeMinikinFontForTest>(&reader); - ASSERT_EQ(original->supportedAxes(), copied->supportedAxes()); - std::vector<uint8_t> newBuffer = - writeToBuffer<FontFamily, writeFreeTypeMinikinFontForTest>(*copied); + std::vector<std::shared_ptr<FontFamily>> copied = FontFamily::readVector(&reader); + ASSERT_EQ(2u, copied.size()); + expectFontFamilyEquals(original[0], copied[0]); + expectVSGlyphsForVsTestFont(copied[0].get()); + expectFontFamilyEquals(original[1], copied[1]); + std::vector<uint8_t> newBuffer = writeToBuffer(copied); ASSERT_EQ(buffer, newBuffer); } + // Test that there is no leak after all FontFamily is destructed. + EXPECT_EQ(baseHeapSize, getHeapSize()); } } // namespace minikin diff --git a/tests/unittest/FontTest.cpp b/tests/unittest/FontTest.cpp index 68f5b51..b50ac90 100644 --- a/tests/unittest/FontTest.cpp +++ b/tests/unittest/FontTest.cpp @@ -24,19 +24,121 @@ namespace minikin { +namespace { + +size_t getHeapSize() { + struct mallinfo info = mallinfo(); + return info.uordblks; +} + +} // namespace + TEST(FontTest, BufferTest) { + FreeTypeMinikinFontForTestFactory::init(); auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); std::shared_ptr<Font> original = Font::Builder(minikinFont).build(); - std::vector<uint8_t> buffer = writeToBuffer<Font, writeFreeTypeMinikinFontForTest>(*original); + std::vector<uint8_t> buffer = writeToBuffer<Font>(*original); BufferReader reader(buffer.data()); - std::shared_ptr<Font> font = - Font::readFrom<readFreeTypeMinikinFontForTest>(&reader, kEmptyLocaleListId); - EXPECT_EQ(minikinFont->GetFontPath(), font->typeface()->GetFontPath()); - EXPECT_EQ(original->style(), font->style()); - EXPECT_NE(nullptr, font->baseFont()); - std::vector<uint8_t> newBuffer = writeToBuffer<Font, writeFreeTypeMinikinFontForTest>(*font); + Font font(&reader); + EXPECT_EQ(minikinFont->GetFontPath(), font.typeface()->GetFontPath()); + EXPECT_EQ(original->style(), font.style()); + EXPECT_EQ(original->getLocaleListId(), font.getLocaleListId()); + // baseFont() should return the same non-null instance when called twice. + const auto& baseFont = font.baseFont(); + EXPECT_NE(nullptr, baseFont); + EXPECT_EQ(baseFont, font.baseFont()); + // typeface() should return the same non-null instance when called twice. + const auto& typeface = font.typeface(); + EXPECT_NE(nullptr, typeface); + EXPECT_EQ(typeface, font.typeface()); + std::vector<uint8_t> newBuffer = writeToBuffer<Font>(font); EXPECT_EQ(buffer, newBuffer); } +TEST(FontTest, MoveConstructorTest) { + FreeTypeMinikinFontForTestFactory::init(); + // Note: by definition, only BufferReader-based Font can be moved. + auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); + std::shared_ptr<Font> original = Font::Builder(minikinFont).build(); + std::vector<uint8_t> buffer = writeToBuffer<Font>(*original); + + size_t baseHeapSize = getHeapSize(); + { + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + Font moveTo(std::move(moveFrom)); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(nullptr, moveTo.mExternalRefsHolder.load()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); + { + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + std::shared_ptr<MinikinFont> typeface = moveFrom.typeface(); + Font moveTo(std::move(moveFrom)); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(typeface, moveTo.typeface()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); +} + +TEST(FontTest, MoveAssignmentTest) { + FreeTypeMinikinFontForTestFactory::init(); + // Note: by definition, only BufferReader-based Font can be moved. + auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); + std::shared_ptr<Font> original = Font::Builder(minikinFont).build(); + std::vector<uint8_t> buffer = writeToBuffer<Font>(*original); + + size_t baseHeapSize = getHeapSize(); + { + // mExternalRefsHolder: null -> null + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + BufferReader reader2(buffer.data()); + Font moveTo(&reader2); + moveTo = std::move(moveFrom); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(nullptr, moveTo.mExternalRefsHolder.load()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); + { + // mExternalRefsHolder: non-null -> null + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + std::shared_ptr<MinikinFont> typeface = moveFrom.typeface(); + BufferReader reader2(buffer.data()); + Font moveTo(&reader2); + moveTo = std::move(moveFrom); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(typeface, moveTo.typeface()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); + { + // mExternalRefsHolder: null -> non-null + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + BufferReader reader2(buffer.data()); + Font moveTo(&reader2); + moveTo.typeface(); + moveTo = std::move(moveFrom); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(nullptr, moveTo.mExternalRefsHolder.load()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); + { + // mExternalRefsHolder: non-null -> non-null + BufferReader reader(buffer.data()); + Font moveFrom(&reader); + std::shared_ptr<MinikinFont> typeface = moveFrom.typeface(); + BufferReader reader2(buffer.data()); + Font moveTo(&reader2); + moveTo.typeface(); + moveTo = std::move(moveFrom); + EXPECT_EQ(nullptr, moveFrom.mExternalRefsHolder.load()); + EXPECT_EQ(typeface, moveTo.typeface()); + } + EXPECT_EQ(baseHeapSize, getHeapSize()); +} + } // namespace minikin diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp index 3c395f4..e51ff83 100644 --- a/tests/unittest/GreedyLineBreakerTest.cpp +++ b/tests/unittest/GreedyLineBreakerTest.cpp @@ -75,7 +75,7 @@ protected: auto family1 = buildFontFamily("Ascii.ttf"); auto family2 = buildFontFamily("CustomExtent.ttf"); std::vector<std::shared_ptr<FontFamily>> families = {family1, family2}; - auto fc = std::make_shared<FontCollection>(families); + auto fc = FontCollection::create(families); MinikinPaint paint(fc); paint.size = 10.0f; // Make 1em=10px paint.localeListId = LocaleListCache::getId(lang); @@ -98,7 +98,7 @@ TEST_F(GreedyLineBreakerTest, roundingError) { MeasuredTextBuilder builder; auto family1 = buildFontFamily("Ascii.ttf"); std::vector<std::shared_ptr<FontFamily>> families = {family1}; - auto fc = std::make_shared<FontCollection>(families); + auto fc = FontCollection::create(families); MinikinPaint paint(fc); paint.size = 56.0f; // Make 1em=56px paint.scaleX = 1; diff --git a/tests/unittest/LayoutCoreTest.cpp b/tests/unittest/LayoutCoreTest.cpp index 2ab7543..4edc556 100644 --- a/tests/unittest/LayoutCoreTest.cpp +++ b/tests/unittest/LayoutCoreTest.cpp @@ -33,30 +33,29 @@ static LayoutPiece buildLayout(const std::string& text, const MinikinPaint& pain StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); } -static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts) { - std::vector<std::shared_ptr<FontFamily>> families; - for (const auto& fontPath : fonts) { - families.push_back(buildFontFamily(fontPath)); - } - auto fc = std::make_shared<FontCollection>(families); +static LayoutPiece buildLayout(const std::string& text, std::shared_ptr<FontCollection> fc) { MinikinPaint paint(fc); paint.size = 10.0f; // make 1em = 10px return buildLayout(text, paint); } -static LayoutPiece buildLayout(const std::string& text, const std::vector<std::string>& fonts, +static LayoutPiece buildLayout(const std::string& text, std::shared_ptr<FontCollection> fc, const std::string fontFeaturesSettings) { - std::vector<std::shared_ptr<FontFamily>> families; - for (const auto& fontPath : fonts) { - families.push_back(buildFontFamily(fontPath)); - } - auto fc = std::make_shared<FontCollection>(families); MinikinPaint paint(fc); paint.size = 10.0f; // make 1em = 10px paint.fontFeatureSettings = fontFeaturesSettings; return buildLayout(text, paint); } +static std::shared_ptr<FontCollection> makeFontCollection( + std::initializer_list<std::string> fonts) { + std::vector<std::shared_ptr<FontFamily>> families; + for (const auto& fontPath : fonts) { + families.push_back(buildFontFamily(fontPath)); + } + return FontCollection::create(families); +} + TEST(LayoutPieceTest, doLayoutTest) { // The LayoutTestFont.ttf has following coverage, extent, width and bbox. // Ascender: 10em, Descender: -2em @@ -71,7 +70,8 @@ TEST(LayoutPieceTest, doLayoutTest) { // U+FFFD (invalid surrogate will be replaced to this): 7em, (0, 0) - (7, 7) // U+10331 (\uD800\uDF31): 10em, (0, 0) - (10, 10) { - auto layout = buildLayout("I", {"LayoutTestFont.ttf"}); + auto fc = makeFontCollection({"LayoutTestFont.ttf"}); + auto layout = buildLayout("I", fc); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent()); @@ -82,7 +82,8 @@ TEST(LayoutPieceTest, doLayoutTest) { EXPECT_EQ(10.0f, layout.advance()); } { - auto layout = buildLayout("II", {"LayoutTestFont.ttf"}); + auto fc = makeFontCollection({"LayoutTestFont.ttf"}); + auto layout = buildLayout("II", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); @@ -97,7 +98,8 @@ TEST(LayoutPieceTest, doLayoutTest) { EXPECT_EQ(20.0f, layout.advance()); } { - auto layout = buildLayout("IV", {"LayoutTestFont.ttf"}); + auto fc = makeFontCollection({"LayoutTestFont.ttf"}); + auto layout = buildLayout("IV", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); @@ -123,7 +125,8 @@ TEST(LayoutPieceTest, doLayoutTest_MultiFont) { // U+3048: 2em, (0, 0) - (2, 2) // U+304A: 2em, (0, 0) - (2, 2) { - auto layout = buildLayout("I\u3042", {"LayoutTestFont.ttf", "Hiragana.ttf"}); + auto fc = makeFontCollection({"LayoutTestFont.ttf", "Hiragana.ttf"}); + auto layout = buildLayout("I\u3042", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); @@ -138,7 +141,8 @@ TEST(LayoutPieceTest, doLayoutTest_MultiFont) { EXPECT_EQ(30.0f, layout.advance()); } { - auto layout = buildLayout("\u3042I", {"LayoutTestFont.ttf", "Hiragana.ttf"}); + auto fc = makeFontCollection({"LayoutTestFont.ttf", "Hiragana.ttf"}); + auto layout = buildLayout("\u3042I", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(20.0f, 0), layout.pointAt(1)); @@ -160,7 +164,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { // U+0020..U+007E: 1em, (0, 0) - (1, 1) // Also this has ligature entry for fi as "ccmp" feature, ff as "liga" feature. { - auto layout = buildLayout("fi", {"Ligature.ttf"}); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("fi", fc); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); @@ -172,7 +177,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { EXPECT_EQ(10.0f, layout.advance()); } { - auto layout = buildLayout("ff", {"Ligature.ttf"}); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("ff", fc); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); @@ -184,7 +190,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { EXPECT_EQ(10.0f, layout.advance()); } { - auto layout = buildLayout("fi", {"Ligature.ttf"}, "'liga' off"); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("fi", fc, "'liga' off"); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); @@ -196,7 +203,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { EXPECT_EQ(10.0f, layout.advance()); } { - auto layout = buildLayout("ff", {"Ligature.ttf"}, "'liga' off"); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("ff", fc, "'liga' off"); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); @@ -210,7 +218,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { EXPECT_EQ(20.0f, layout.advance()); } { - auto layout = buildLayout("fii", {"Ligature.ttf"}); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("fii", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); @@ -225,7 +234,8 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { EXPECT_EQ(20.0f, layout.advance()); } { - auto layout = buildLayout("if", {"Ligature.ttf"}); + auto fc = makeFontCollection({"Ligature.ttf"}); + auto layout = buildLayout("if", fc); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); diff --git a/tests/unittest/MeasurementTests.cpp b/tests/unittest/MeasurementTests.cpp index b5a85c7..ff36f67 100644 --- a/tests/unittest/MeasurementTests.cpp +++ b/tests/unittest/MeasurementTests.cpp @@ -31,6 +31,15 @@ float getAdvance(const float* advances, const char* src) { return getRunAdvance(advances, buf, 0, size, offset); } +void distributeAdvances(float* advances, const char* src, int count) { + const size_t BUF_SIZE = 256; + uint16_t buf[BUF_SIZE]; + size_t offset; + size_t size; + ParseUnicode(buf, BUF_SIZE, src, &size, &offset); + distributeAdvances(advances, buf, offset, count); +} + // Latin fi TEST(Measurement, getRunAdvance_fi) { const float unligated[] = {30.0, 20.0}; @@ -44,6 +53,22 @@ TEST(Measurement, getRunAdvance_fi) { EXPECT_EQ(40.0, getAdvance(ligated, "'f' 'i' |")); } +TEST(Measurement, getRunAdvance_control_characters) { + const float unligated[] = {30.0, 20.0, 0.0, 0.0}; + EXPECT_EQ(0.0, getAdvance(unligated, "| 'f' 'i' U+2066 U+202C")); + EXPECT_EQ(30.0, getAdvance(unligated, "'f' | 'i' U+2066 U+202C")); + EXPECT_EQ(50.0, getAdvance(unligated, "'f' 'i' | U+2066 U+202C")); + EXPECT_EQ(50.0, getAdvance(unligated, "'f' 'i' U+2066 | U+202C")); + EXPECT_EQ(50.0, getAdvance(unligated, "'f' 'i' U+2066 U+202C |")); + + const float liagated[] = {40.0, 0.0, 0.0, 0.0}; + EXPECT_EQ(0.0, getAdvance(liagated, "| 'f' 'i' U+2066 U+202C")); + EXPECT_EQ(20.0, getAdvance(liagated, "'f' | 'i' U+2066 U+202C")); + EXPECT_EQ(40.0, getAdvance(liagated, "'f' 'i' | U+2066 U+202C")); + EXPECT_EQ(40.0, getAdvance(liagated, "'f' 'i' U+2066 | U+202C")); + EXPECT_EQ(40.0, getAdvance(liagated, "'f' 'i' U+2066 U+202C |")); +} + // Devanagari ka+virama+ka TEST(Measurement, getRunAdvance_kka) { const float unligated[] = {30.0, 0.0, 30.0}; @@ -59,4 +84,69 @@ TEST(Measurement, getRunAdvance_kka) { EXPECT_EQ(30.0, getAdvance(ligated, "U+0915 U+094D U+0915 |")); } +TEST(Measurement, distributeAdvances_fi) { + float ligated[] = {20.0, 0.0}; + distributeAdvances(ligated, "| 'f' 'i' ", 2); + EXPECT_EQ(ligated[0], 10.0); + EXPECT_EQ(ligated[1], 10.0); +} + +TEST(Measurement, distributeAdvances_non_zero_start) { + // Note that advance[i] corresponding to (i + start)-th character. + float ligated[] = {20.0, 0.0}; + distributeAdvances(ligated, "'a' 'b' | 'f' 'i' ", 2); + EXPECT_EQ(ligated[0], 10.0); + EXPECT_EQ(ligated[1], 10.0); +} + +TEST(Measurement, distributeAdvances_non_zero_start_with_control_characters) { + // Note that advance[i] corresponding to (i + start)-th character. + float ligated[] = {20.0, 0.0, 0.0, 0.0}; + distributeAdvances(ligated, "'a' U+2066 | 'f' 'i' U+2066 U+202C", 4); + EXPECT_EQ(ligated[0], 10.0); + EXPECT_EQ(ligated[1], 10.0); + EXPECT_EQ(ligated[2], 0.0); + EXPECT_EQ(ligated[3], 0.0); +} + +TEST(Measurement, distributeAdvances_with_count) { + // Note that advance[i] corresponding to (i + start)-th character. + float ligated[] = {20.0, 0.0, 30.0, 0.0}; + distributeAdvances(ligated, "'a' 'b' | 'f' 'i' 'f' 'i' ", 2); + EXPECT_EQ(ligated[0], 10.0); + EXPECT_EQ(ligated[1], 10.0); + // Count is 2, so it won't change the rest of the array. + EXPECT_EQ(ligated[2], 30.0); + EXPECT_EQ(ligated[3], 0.0); +} + +TEST(Measurement, distributeAdvances_control_characters) { + float ligated[] = {20.0, 0.0, 0.0, 0.0}; + distributeAdvances(ligated, "| 'f' 'i' U+2066 U+202C", 4); + EXPECT_EQ(ligated[0], 10.0); + EXPECT_EQ(ligated[1], 10.0); + EXPECT_EQ(ligated[2], 0.0); + EXPECT_EQ(ligated[3], 0.0); +} + +TEST(Measurement, distributeAdvances_surrogate) { + float advances[] = {20.0, 0.0, 0.0, 0.0}; + distributeAdvances(advances, "| U+D83D U+DE00 U+2066 U+202C", 4); + EXPECT_EQ(advances[0], 20.0); + EXPECT_EQ(advances[1], 0.0); + EXPECT_EQ(advances[2], 0.0); + EXPECT_EQ(advances[3], 0.0); +} + +TEST(Measurement, distributeAdvances_surrogate_in_ligature) { + // If a ligature contains surrogates, advances is assigned to the first + // character in surrogate. + float ligated[] = {40.0, 0.0, 0.0, 0.0}; + distributeAdvances(ligated, "| U+D83D U+DE00 U+D83D U+DE01", 4); + EXPECT_EQ(ligated[0], 20.0); + EXPECT_EQ(ligated[1], 0.0); + EXPECT_EQ(ligated[2], 20.0); + EXPECT_EQ(ligated[3], 0.0); +} + } // namespace minikin diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp index a5f8625..18619e3 100644 --- a/tests/unittest/OptimalLineBreakerTest.cpp +++ b/tests/unittest/OptimalLineBreakerTest.cpp @@ -71,7 +71,7 @@ protected: auto family1 = buildFontFamily("Ascii.ttf"); auto family2 = buildFontFamily("CustomExtent.ttf"); std::vector<std::shared_ptr<FontFamily>> families = {family1, family2}; - auto fc = std::make_shared<FontCollection>(families); + auto fc = FontCollection::create(families); MinikinPaint paint(fc); paint.size = 10.0f; // Make 1em=10px paint.localeListId = LocaleListCache::getId(lang); @@ -1847,7 +1847,7 @@ TEST_F(OptimalLineBreakerTest, roundingError) { MeasuredTextBuilder builder; auto family1 = buildFontFamily("Ascii.ttf"); std::vector<std::shared_ptr<FontFamily>> families = {family1}; - auto fc = std::make_shared<FontCollection>(families); + auto fc = FontCollection::create(families); MinikinPaint paint(fc); paint.size = 56.0f; // Make 1em=56px paint.scaleX = 1; diff --git a/tests/unittest/SparseBitSetTest.cpp b/tests/unittest/SparseBitSetTest.cpp index 8c67964..d79ebac 100644 --- a/tests/unittest/SparseBitSetTest.cpp +++ b/tests/unittest/SparseBitSetTest.cpp @@ -79,4 +79,8 @@ TEST(SparseBitSetTest, emptyBitSetBufferTest) { ASSERT_EQ(buffer, newBuffer); } +TEST(SparseBitSetTest, sizeTest) { + ASSERT_EQ(sizeof(void*), sizeof(SparseBitSet)); +} + } // namespace minikin diff --git a/tests/unittest/SystemFontsTest.cpp b/tests/unittest/SystemFontsTest.cpp index f1b0109..1bff31f 100644 --- a/tests/unittest/SystemFontsTest.cpp +++ b/tests/unittest/SystemFontsTest.cpp @@ -95,8 +95,8 @@ TEST(SystemFontTest, getAvailableFont_dedupFonts) { auto fc1Families = std::vector<std::shared_ptr<FontFamily>>{asciiFamily, boldItalicFamily}; auto fc2Families = std::vector<std::shared_ptr<FontFamily>>{boldFamily, boldItalicFamily}; - auto fc1 = std::make_shared<FontCollection>(std::move(fc1Families)); - auto fc2 = std::make_shared<FontCollection>(std::move(fc2Families)); + auto fc1 = FontCollection::create(std::move(fc1Families)); + auto fc2 = FontCollection::create(std::move(fc2Families)); systemFonts.addFontMap(std::move(fc1)); systemFonts.addFontMap(std::move(fc2)); diff --git a/tests/unittest/WordBreakerTests.cpp b/tests/unittest/WordBreakerTests.cpp index 4b5fc35..0c20a80 100644 --- a/tests/unittest/WordBreakerTests.cpp +++ b/tests/unittest/WordBreakerTests.cpp @@ -19,8 +19,6 @@ #include <cstdio> #include <gtest/gtest.h> -#include <unicode/uclean.h> -#include <unicode/udata.h> #include "UnicodeUtils.h" diff --git a/tests/util/Android.bp b/tests/util/Android.bp index 1d3d8cf..d8c153d 100644 --- a/tests/util/Android.bp +++ b/tests/util/Android.bp @@ -11,7 +11,7 @@ cc_library_static { "PathUtils.cpp", "UnicodeUtils.cpp", ], - cflags: ["-Wall", "-Werror"], + defaults: ["libminikin_defaults"], export_include_dirs: ["."], shared_libs: ["libxml2", "libft2"], static_libs: ["libminikin"], diff --git a/tests/util/BufferUtils.h b/tests/util/BufferUtils.h index 355e74e..015a939 100644 --- a/tests/util/BufferUtils.h +++ b/tests/util/BufferUtils.h @@ -46,14 +46,6 @@ std::vector<uint8_t> writeToBuffer(const T& t) { return buffer; } -template <class T, auto arg> -std::vector<uint8_t> writeToBuffer(const T& t) { - std::vector<uint8_t> buffer = allocateBuffer<T, arg>(t); - BufferWriter writer(buffer.data()); - t.template writeTo<arg>(&writer); - return buffer; -} - } // namespace minikin #endif // MINIKIN_TEST_BUFFER_UTILS_H diff --git a/tests/util/FontTestUtils.cpp b/tests/util/FontTestUtils.cpp index 5370ab6..a1025fe 100644 --- a/tests/util/FontTestUtils.cpp +++ b/tests/util/FontTestUtils.cpp @@ -106,11 +106,12 @@ std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& font xmlChar* lang = xmlGetProp(familyNode, (const xmlChar*)"lang"); std::shared_ptr<FontFamily> family; if (lang == nullptr) { - family = std::make_shared<FontFamily>(variant, std::move(fonts)); + family = FontFamily::create(variant, std::move(fonts)); } else { uint32_t langId = registerLocaleList(std::string((const char*)lang, xmlStrlen(lang))); - family = std::make_shared<FontFamily>(langId, variant, std::move(fonts), - false /* isCustomFallback */); + family = + FontFamily::create(langId, variant, std::move(fonts), + false /* isCustomFallback */, false /* isdefaultFallback */); } families.push_back(family); } @@ -119,14 +120,14 @@ std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& font } std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath) { - return std::make_shared<FontCollection>(buildFontFamily(filePath)); + return FontCollection::create(buildFontFamily(filePath)); } std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath) { auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath)); std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(font).build()); - return std::make_shared<FontFamily>(std::move(fonts)); + return FontFamily::create(std::move(fonts)); } std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang, @@ -134,8 +135,8 @@ std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const s auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath)); std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(font).build()); - return std::make_shared<FontFamily>(LocaleListCache::getId(lang), FamilyVariant::DEFAULT, - std::move(fonts), isCustomFallback); + return FontFamily::create(LocaleListCache::getId(lang), FamilyVariant::DEFAULT, + std::move(fonts), isCustomFallback, false /* isDefaultFallback */); } } // namespace minikin diff --git a/tests/util/FontTestUtils.h b/tests/util/FontTestUtils.h index 660438b..63748e4 100644 --- a/tests/util/FontTestUtils.h +++ b/tests/util/FontTestUtils.h @@ -19,9 +19,8 @@ #include <memory> -#include "minikin/FontCollection.h" - #include "PathUtils.h" +#include "minikin/FontCollection.h" namespace minikin { @@ -40,8 +39,7 @@ std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& font * The XML path and font files are needed to be in the test data directory. */ inline std::shared_ptr<FontCollection> buildFontCollectionFromXml(const std::string& xmlPath) { - return std::make_shared<FontCollection>( - getFontFamilies(getTestDataDir(), getTestDataDir() + xmlPath)); + return FontCollection::create(getFontFamilies(getTestDataDir(), getTestDataDir() + xmlPath)); } /** diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp index 1be466a..ce36ec0 100644 --- a/tests/util/FreeTypeMinikinFontForTest.cpp +++ b/tests/util/FreeTypeMinikinFontForTest.cpp @@ -112,18 +112,27 @@ void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent, const Mini extent->descent = -static_cast<float>(mFtFace->descender) * paint.size / upem; } -void writeFreeTypeMinikinFontForTest(BufferWriter* writer, const MinikinFont* typeface) { +FreeTypeMinikinFontForTestFactory::FreeTypeMinikinFontForTestFactory() : MinikinFontFactory() { + MinikinFontFactory::setInstance(this); +} + +// static +void FreeTypeMinikinFontForTestFactory::init() { + static FreeTypeMinikinFontForTestFactory factory; +} + +void FreeTypeMinikinFontForTestFactory::write(BufferWriter* writer, + const MinikinFont* typeface) const { writer->writeString(typeface->GetFontPath()); } -std::shared_ptr<MinikinFont> loadFreeTypeMinikinFontForTest(BufferReader reader) { +std::shared_ptr<MinikinFont> FreeTypeMinikinFontForTestFactory::create(BufferReader reader) const { std::string fontPath(reader.readString()); return std::make_shared<FreeTypeMinikinFontForTest>(fontPath); } -Font::TypefaceLoader* readFreeTypeMinikinFontForTest(BufferReader* reader) { +void FreeTypeMinikinFontForTestFactory::skip(BufferReader* reader) const { reader->skipString(); // fontPath - return &loadFreeTypeMinikinFontForTest; } } // namespace minikin diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h index 4cdb6d8..6903deb 100644 --- a/tests/util/FreeTypeMinikinFontForTest.h +++ b/tests/util/FreeTypeMinikinFontForTest.h @@ -17,13 +17,14 @@ #ifndef MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H #define MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H +#include <ft2build.h> + #include <string> #include "minikin/Buffer.h" #include "minikin/Font.h" #include "minikin/MinikinFont.h" - -#include <ft2build.h> +#include "minikin/MinikinFontFactory.h" #include FT_FREETYPE_H #include "minikin/Macros.h" @@ -64,9 +65,19 @@ private: MINIKIN_PREVENT_COPY_AND_ASSIGN(FreeTypeMinikinFontForTest); }; -void writeFreeTypeMinikinFontForTest(BufferWriter* writer, const MinikinFont* typeface); +class FreeTypeMinikinFontForTestFactory : MinikinFontFactory { +private: + FreeTypeMinikinFontForTestFactory(); + +public: + static void init(); + + void write(BufferWriter* writer, const MinikinFont* typeface) const override; -Font::TypefaceLoader* readFreeTypeMinikinFontForTest(BufferReader* reader); + std::shared_ptr<MinikinFont> create(BufferReader reader) const override; + + void skip(BufferReader* reader) const override; +}; } // namespace minikin diff --git a/tools/mk_hyb_file.py b/tools/mk_hyb_file.py index 89949f9..d213bc9 100755 --- a/tools/mk_hyb_file.py +++ b/tools/mk_hyb_file.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright (C) 2015 The Android Open Source Project # @@ -563,6 +563,14 @@ def verify_hyb_file(hyb_fn, pat_fn, chr_fn, hyp_fn): reconstructed_chr.remove(u'\u03B0') reconstructed_chr.append(u'\u03B0\u03B0') + if u'\u1c86' in reconstructed_chr: + reconstructed_chr.remove(u'\u1c86') + reconstructed_chr.append(u'\u1c86\u1c86') + + if u'\u1c82' in reconstructed_chr: + reconstructed_chr.remove(u'\u1c82') + reconstructed_chr.append(u'\u1c82\u1c82') + assert verify_file_sorted(reconstructed_chr, chr_fn), 'alphabet table not verified' # reconstruct trie |