diff options
77 files changed, 4165 insertions, 489 deletions
@@ -1,7 +1,17 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_library_headers { name: "libminikin_headers", host_supported: true, export_include_dirs: ["include"], + header_libs: [ + "libgtest_prod_headers", + ], + export_header_lib_headers: [ + "libgtest_prod_headers", + ], target: { windows: { enabled: true, diff --git a/app/Android.bp b/app/Android.bp index 6c7c66b..9d6c28a 100644 --- a/app/Android.bp +++ b/app/Android.bp @@ -14,6 +14,10 @@ // see how_to_run.txt for instructions on running these tests +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_binary_host { name: "hyphtool", diff --git a/include/minikin/BoundsCache.h b/include/minikin/BoundsCache.h new file mode 100644 index 0000000..64f2b49 --- /dev/null +++ b/include/minikin/BoundsCache.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2020 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_BOUNDS_CACHE_H +#define MINIKIN_BOUNDS_CACHE_H + +#include "minikin/LayoutCache.h" + +#include <mutex> + +#include <utils/LruCache.h> + +#include "minikin/BoundsCache.h" +#include "minikin/FontCollection.h" +#include "minikin/Hasher.h" +#include "minikin/MinikinPaint.h" + +namespace minikin { + +// Cache entry +struct BoundsValue { + MinikinRect rect; + float advance; +}; + +// Used for callback for LayoutCache. +struct ValueExtractor { + void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint); + std::unique_ptr<BoundsValue> value; +}; + +class BoundsCache : private android::OnEntryRemoved<LayoutCacheKey, BoundsValue*> { +public: + void clear() { + std::lock_guard<std::mutex> lock(mMutex); + mCache.clear(); + } + + // Do not use BoundsCache inside the callback function, otherwise dead-lock may happen. + template <typename F> + void getOrCreate(const U16StringPiece& text, const Range& range, const MinikinPaint& paint, + bool dir, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, F& f) { + LayoutCacheKey key(text, range, paint, dir, startHyphen, endHyphen); + if (paint.skipCache() || range.getLength() >= LENGTH_LIMIT_CACHE) { + LayoutPiece piece = LayoutPiece(text, range, dir, paint, startHyphen, endHyphen); + f(getBounds(piece, paint), piece.advance()); + return; + } + { + std::lock_guard<std::mutex> lock(mMutex); + BoundsValue* value = mCache.get(key); + if (value != nullptr) { + f(value->rect, value->advance); + return; + } + } + // Doing text layout takes long time, so releases the mutex during doing layout. + // Don't care even if we do the same layout in other thread. + key.copyText(); + ValueExtractor ve; + LayoutCache::getInstance().getOrCreate(text, range, paint, dir, startHyphen, endHyphen, ve); + f(ve.value->rect, ve.value->advance); + { + std::lock_guard<std::mutex> lock(mMutex); + mCache.put(key, ve.value.release()); + } + } + + static BoundsCache& getInstance() { + static BoundsCache cache(kMaxEntries); + return cache; + } + + // Compute new bounding box for the layout piece. + static MinikinRect getBounds(const LayoutPiece& layoutPiece, const MinikinPaint& paint); + +protected: + BoundsCache(uint32_t maxEntries) : mCache(maxEntries) { + mCache.setOnEntryRemovedListener(this); + } + +private: + // callback for OnEntryRemoved + void operator()(LayoutCacheKey& key, BoundsValue*& value) { + key.freeText(); + delete value; + } + + std::mutex mMutex; + android::LruCache<LayoutCacheKey, BoundsValue*> mCache GUARDED_BY(mMutex) GUARDED_BY(mMutex); + // LRU cache capacity. Should be fine to be less than LayoutCache#kMaxEntries since bbox + // calculation happens less than layout calculation. + static const size_t kMaxEntries = 500; +}; + +} // namespace minikin +#endif // MINIKIN_BOUNDS_CACHE_H diff --git a/include/minikin/Buffer.h b/include/minikin/Buffer.h new file mode 100644 index 0000000..87ba3fd --- /dev/null +++ b/include/minikin/Buffer.h @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2020 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_BUFFER_H +#define MINIKIN_BUFFER_H + +#include <cstring> +#include <string_view> +#include <type_traits> +#include <utility> + +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. +class BufferReader { +public: + 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); + } + + template <typename 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); + return *data; + } + + template <typename T> + void skip() { + static_assert(std::is_pod<T>::value, "T must be a POD"); + mPos = BufferReader::align<T>(mPos); + mPos += sizeof(T); + } + + // Return a pointer to an array and its number of elements. + template <typename T> + std::pair<const T*, uint32_t> readArray() { + static_assert(std::is_pod<T>::value, "T must be a POD"); + uint32_t size = read<uint32_t>(); + mPos = BufferReader::align<T>(mPos); + const T* data = reinterpret_cast<const T*>(mData + mPos); + mPos += size * sizeof(T); + return std::make_pair(data, size); + } + + template <typename 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); + } + + std::string_view readString() { + auto [data, size] = readArray<char>(); + return std::string_view(data, size); + } + + void skipString() { skipArray<char>(); } + + const void* data() const { return mData; } + size_t pos() const { return mPos; } + +private: + const uint8_t* mData; + size_t mPos; +}; + +// This is a helper class to write data to a memory buffer. +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) {} + + BufferWriter(BufferWriter&&) = default; + BufferWriter& operator=(BufferWriter&&) = default; + + // Write a single data 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> + 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)); + } + mPos += sizeof(T); + } + + // 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> + void writeArray(const std::common_type_t<T>* data, uint32_t size) { + static_assert(std::is_pod<T>::value, "T must be a POD"); + write<uint32_t>(size); + mPos = BufferReader::align<T>(mPos); + if (mData != nullptr) { + memcpy(mData + mPos, data, size * sizeof(T)); + } + mPos += size * sizeof(T); + } + + void writeString(std::string_view string) { writeArray<char>(string.data(), string.size()); } + + // Return the number of bytes written. + size_t size() const { return mPos; } + +private: + uint8_t* mData; + size_t mPos; + + // Forbid copy and assign. + BufferWriter(const BufferWriter&) = delete; + void operator=(const BufferWriter&) = delete; +}; + +} // namespace minikin + +#endif // MINIKIN_BUFFER_H diff --git a/include/minikin/Font.h b/include/minikin/Font.h index eeb074e..67feecf 100644 --- a/include/minikin/Font.h +++ b/include/minikin/Font.h @@ -18,11 +18,14 @@ #define MINIKIN_FONT_H #include <memory> +#include <mutex> #include <unordered_set> +#include "minikin/Buffer.h" #include "minikin/FontStyle.h" #include "minikin/FontVariation.h" #include "minikin/HbUtils.h" +#include "minikin/LocaleList.h" #include "minikin/Macros.h" #include "minikin/MinikinFont.h" @@ -55,7 +58,10 @@ struct FakedFont { inline bool operator!=(const FakedFont& o) const { return !(*this == o); } // ownership is the enclosing FontCollection - const Font* font; + // FakedFont will be stored in the LayoutCache. It is not a good idea too keep font instance + // even if the enclosing FontCollection, i.e. Typeface is GC-ed. The layout cache is only + // purged when it is overflown, thus intentionally keep only reference. + const std::shared_ptr<Font>& font; FontFakery fakery; }; @@ -88,44 +94,96 @@ public: return *this; } - Font build(); + Builder& setLocaleListId(uint32_t id) { + mLocaleListId = id; + return *this; + } + + std::shared_ptr<Font> build(); private: std::shared_ptr<MinikinFont> mTypeface; uint16_t mWeight = static_cast<uint16_t>(FontStyle::Weight::NORMAL); FontStyle::Slant mSlant = FontStyle::Slant::UPRIGHT; + uint32_t mLocaleListId = kEmptyLocaleListId; bool mIsWeightSet = false; bool mIsSlantSet = false; }; - Font(Font&& o) = default; - Font& operator=(Font&& o) = default; + // 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)); + } - Font& operator=(const Font& o) { - mTypeface = o.mTypeface; - mStyle = o.mStyle; - mBaseFont = HbFontUniquePtr(hb_font_reference(o.mBaseFont.get())); - return *this; + template <TypefaceWriter typefaceWriter> + void writeTo(BufferWriter* writer) const { + mStyle.writeTo(writer); + typefaceWriter(writer, typeface().get()); } - Font(const Font& o) { *this = o; } - inline const std::shared_ptr<MinikinFont>& typeface() const { return mTypeface; } + // This locale list is just for API compatibility. This is not used in font selection or family + // fallback. + uint32_t getLocaleListId() const { return mLocaleListId; } + const std::shared_ptr<MinikinFont>& typeface() const; inline FontStyle style() const { return mStyle; } - inline const HbFontUniquePtr& baseFont() const { return mBaseFont; } + const HbFontUniquePtr& baseFont() const; + BufferReader typefaceMetadataReader() const { return mTypefaceMetadataReader; } std::unordered_set<AxisTag> getSupportedAxes() const; private: // Use Builder instead. - Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont) - : mTypeface(std::move(typeface)), mStyle(style), mBaseFont(std::move(baseFont)) {} + Font(std::shared_ptr<MinikinFont>&& typeface, FontStyle style, HbFontUniquePtr&& baseFont, + uint32_t localeListId) + : mTypeface(std::move(typeface)), + 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) {} + + void initTypefaceLocked() const EXCLUSIVE_LOCKS_REQUIRED(mTypefaceMutex); static HbFontUniquePtr prepareFont(const std::shared_ptr<MinikinFont>& typeface); static FontStyle analyzeStyle(const HbFontUniquePtr& font); - std::shared_ptr<MinikinFont> mTypeface; + // Lazy-initialized if created by readFrom(). + mutable std::shared_ptr<MinikinFont> mTypeface GUARDED_BY(mTypefaceMutex); FontStyle mStyle; - HbFontUniquePtr mBaseFont; + // Lazy-initialized if created by readFrom(). + mutable HbFontUniquePtr mBaseFont GUARDED_BY(mTypefaceMutex); + + 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; + Font(const Font& o) = delete; + Font& operator=(const Font& o) = delete; }; } // namespace minikin diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h index f136384..98df571 100644 --- a/include/minikin/FontCollection.h +++ b/include/minikin/FontCollection.h @@ -18,9 +18,14 @@ #define MINIKIN_FONT_COLLECTION_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" #include "minikin/MinikinFont.h" #include "minikin/U16StringPiece.h" @@ -35,12 +40,134 @@ 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; + } + + template <Font::TypefaceWriter typefaceWriter> + 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); + } + } + + // Helper class for representing font family match result in packed bits. + struct FamilyMatchResult { + public: + struct Builder { + public: + Builder() : mSize(0), mBits(0) {} + + Builder& add(uint8_t x) { + if (mSize >= 7) [[unlikely]] { + return *this; + } + mBits = mBits | (static_cast<uint64_t>(x) << (8 * mSize)); + mSize++; + return *this; + } + + Builder& reset() { + mSize = 0; + mBits = 0; + return *this; + } + + uint8_t size() const { return mSize; } + + bool empty() const { return size() == 0; } + + FamilyMatchResult build() { + return FamilyMatchResult(mBits | (static_cast<uint64_t>(mSize) << 56)); + } + + private: + uint8_t mSize; + uint64_t mBits; + }; + + // Helper class for iterating FamilyMatchResult + class iterator { + public: + inline bool operator==(const iterator& o) const { + return mOffset == o.mOffset && mResult == o.mResult; + } + + inline bool operator!=(const iterator& o) const { return !(*this == o); } + inline uint8_t operator*() const { return mResult[mOffset]; } + inline iterator& operator++() { + mOffset++; + return *this; + } + + private: + friend struct FamilyMatchResult; + iterator(const FamilyMatchResult& result, uint32_t offset) + : mResult(result), mOffset(offset) {} + const FamilyMatchResult& mResult; + uint32_t mOffset; + }; + + // Create empty FamilyMatchResult. + FamilyMatchResult() : mBits(0) {} + + inline uint8_t size() const { return static_cast<uint8_t>(mBits >> 56); } + + inline uint8_t operator[](uint32_t pos) const { + return static_cast<uint8_t>(mBits >> (pos * 8)); + } + + inline bool empty() const { return size() == 0; } + + inline bool operator==(const FamilyMatchResult& o) const { return mBits == o.mBits; } + + // Returns the common family indices between l and r. + static FamilyMatchResult intersect(FamilyMatchResult l, FamilyMatchResult r); + + // Iterator + inline iterator begin() const { return iterator(*this, 0); } + inline iterator end() const { return iterator(*this, size()); } + + FamilyMatchResult(const FamilyMatchResult& o) = default; + FamilyMatchResult& operator=(const FamilyMatchResult& o) = default; + + private: + explicit FamilyMatchResult(uint64_t bits) : mBits(bits) {} + uint64_t mBits; + }; + struct Run { - FakedFont fakedFont; + FamilyMatchResult familyMatch; int start; int end; }; + FakedFont getBestFont(U16StringPiece textBuf, const Run& run, FontStyle style); + // Perform the itemization until given max runs. std::vector<Run> itemize(U16StringPiece text, FontStyle style, uint32_t localeListId, FamilyVariant familyVariant, uint32_t runMax) const; @@ -68,7 +195,23 @@ public: uint32_t getId() const; + const std::vector<std::shared_ptr<FontFamily>>& getFamilies() const { return mFamilies; } + private: + FRIEND_TEST(FontCollectionTest, bufferTest); + + FontCollection(BufferReader* reader, + const std::vector<std::shared_ptr<FontFamily>>& allFontFamilies); + // Write fields of the instance, using fontFamilyToIndexMap for finding + // indices for FontFamily. + void writeTo(BufferWriter* writer, + const std::unordered_map<std::shared_ptr<FontFamily>, uint32_t>& + fontFamilyToIndexMap) const; + static void collectAllFontFamilies( + const std::vector<std::shared_ptr<FontCollection>>& fontCollections, + std::vector<std::shared_ptr<FontFamily>>* outAllFontFamilies, + std::unordered_map<std::shared_ptr<FontFamily>, uint32_t>* outFontFamilyToIndexMap); + static const int kLogCharsPerPage = 8; static const int kPageMask = (1 << kLogCharsPerPage) - 1; @@ -85,9 +228,8 @@ private: // Initialize the FontCollection. void init(const std::vector<std::shared_ptr<FontFamily>>& typefaces); - const std::shared_ptr<FontFamily>& getFamilyForChar(uint32_t ch, uint32_t vs, - uint32_t localeListId, - FamilyVariant variant) const; + FamilyMatchResult getFamilyForChar(uint32_t ch, uint32_t vs, uint32_t localeListId, + FamilyVariant variant) const; uint32_t calcFamilyScore(uint32_t ch, uint32_t vs, FamilyVariant variant, uint32_t localeListId, const std::shared_ptr<FontFamily>& fontFamily) const; @@ -116,14 +258,21 @@ private: // mFamilyVec[mRange[0xXXYY].end] instead of whole mFamilies. // This vector contains indices into mFamilies. // This vector can't be empty. - std::vector<Range> mRanges; - std::vector<uint8_t> mFamilyVec; + uint32_t mRangesCount; + const Range* mRanges; + uint32_t mFamilyVecCount; + const uint8_t* mFamilyVec; // This vector has pointers to the font family instances which have cmap 14 subtables. std::vector<std::shared_ptr<FontFamily>> mVSFamilyVec; // Set of supported axes in this collection. std::unordered_set<AxisTag> mSupportedAxes; + + // Owns allocated memory if this class is created from font families, otherwise these are + // nullptr. + std::unique_ptr<Range[]> mOwnedRanges; + std::vector<uint8_t> mOwnedFamilyVec; }; } // namespace minikin diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h index 4aafaa0..6169193 100644 --- a/include/minikin/FontFamily.h +++ b/include/minikin/FontFamily.h @@ -33,10 +33,32 @@ namespace minikin { class FontFamily { public: - explicit FontFamily(std::vector<Font>&& fonts); - FontFamily(FamilyVariant variant, std::vector<Font>&& fonts); - FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts, - bool isCustomFallback); + 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); + } FakedFont getClosestMatch(FontStyle style) const; @@ -45,8 +67,9 @@ public: // API's for enumerating the fonts in a family. These don't guarantee any particular order size_t getNumFonts() const { return mFonts.size(); } - const Font* getFont(size_t index) const { return &mFonts[index]; } - FontStyle getStyle(size_t index) const { return mFonts[index].style(); } + 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; } bool isCustomFallback() const { return mIsCustomFallback; } @@ -67,11 +90,24 @@ public: const std::vector<FontVariation>& variations) const; 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; + void computeCoverage(); uint32_t mLocaleListId; FamilyVariant mVariant; - std::vector<Font> mFonts; + std::vector<std::shared_ptr<Font>> mFonts; std::unordered_set<AxisTag> mSupportedAxes; bool mIsColorEmoji; bool mIsCustomFallback; diff --git a/include/minikin/FontFileParser.h b/include/minikin/FontFileParser.h new file mode 100644 index 0000000..34880a3 --- /dev/null +++ b/include/minikin/FontFileParser.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 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_FONT_FILE_PARSER_H +#define MINIKIN_FONT_FILE_PARSER_H + +#include "minikin/HbUtils.h" + +#include <optional> +#include <string> + +namespace minikin { + +// FontFileParser provides various parser logic for OpenType font file. +class FontFileParser { +public: + // This class does not take an ownership of buffer. Caller must free it. + FontFileParser(const void* buffer, size_t size, uint32_t index); + explicit FontFileParser(const HbFaceUniquePtr& face); + explicit FontFileParser(const HbFontUniquePtr& font); + + virtual ~FontFileParser(); + + std::optional<uint32_t> getFontRevision() const; + std::optional<std::string> getPostScriptName() const; + std::optional<bool> isPostScriptType1Font() const; + +protected: // protected for testing purposes. + static bool analyzeFontRevision(const uint8_t* head_data, size_t head_size, uint32_t* out); + static bool checkPSName(const std::string& psName); + +private: + HbFaceUniquePtr mFace; + + static HbFaceUniquePtr makeHbFace(const void* buffer, size_t size, uint32_t index); +}; + +} // namespace minikin + +#endif // MINIKIN_FONT_FILE_PARSER_H diff --git a/include/minikin/FontStyle.h b/include/minikin/FontStyle.h index 73cf427..51e4ad8 100644 --- a/include/minikin/FontStyle.h +++ b/include/minikin/FontStyle.h @@ -17,6 +17,8 @@ #ifndef MINIKIN_FONT_STYLE_H #define MINIKIN_FONT_STYLE_H +#include <minikin/Buffer.h> + namespace minikin { // FontStyle represents style information. @@ -46,6 +48,15 @@ public: constexpr FontStyle(Weight weight, Slant slant) : FontStyle(static_cast<uint16_t>(weight), slant) {} constexpr FontStyle(uint16_t weight, Slant slant) : mWeight(weight), mSlant(slant) {} + explicit FontStyle(BufferReader* reader) { + mWeight = reader->read<uint16_t>(); + mSlant = static_cast<Slant>(reader->read<uint8_t>()); + } + + void writeTo(BufferWriter* writer) const { + writer->write<uint16_t>(mWeight); + writer->write<uint8_t>(static_cast<uint8_t>(mSlant)); + } constexpr uint16_t weight() const { return mWeight; } constexpr Slant slant() const { return mSlant; } diff --git a/include/minikin/FontVariation.h b/include/minikin/FontVariation.h index 0c38d6a..e0567c1 100644 --- a/include/minikin/FontVariation.h +++ b/include/minikin/FontVariation.h @@ -24,6 +24,7 @@ namespace minikin { typedef uint32_t AxisTag; struct FontVariation { + FontVariation() = default; FontVariation(AxisTag axisTag, float value) : axisTag(axisTag), value(value) {} AxisTag axisTag; float value; diff --git a/include/minikin/Hasher.h b/include/minikin/Hasher.h index 8a79b61..4a76b29 100644 --- a/include/minikin/Hasher.h +++ b/include/minikin/Hasher.h @@ -37,6 +37,26 @@ public: return *this; } + inline Hasher& update(int32_t data) { + update(static_cast<uint32_t>(data)); + return *this; + } + + inline Hasher& update(uint64_t data) { + update(static_cast<uint32_t>(data)); + update(static_cast<uint32_t>(data >> 32)); + return *this; + } + + inline Hasher& update(float data) { + union { + float f; + uint32_t i; + } bits; + bits.f = data; + return update(bits.i); + } + inline Hasher& updateShorts(const uint16_t* data, uint32_t length) { update(length); uint32_t i; diff --git a/include/minikin/Layout.h b/include/minikin/Layout.h index 19f3109..388a7a7 100644 --- a/include/minikin/Layout.h +++ b/include/minikin/Layout.h @@ -84,7 +84,8 @@ public: // public accessors size_t nGlyphs() const { return mGlyphs.size(); } - const MinikinFont* getFont(int i) const { return mGlyphs[i].font.font->typeface().get(); } + const Font* getFont(int i) const { return mGlyphs[i].font.font.get(); } + const std::shared_ptr<Font>& getFontRef(int i) const { return mGlyphs[i].font.font; } FontFakery getFakery(int i) const { return mGlyphs[i].font.fakery; } unsigned int getGlyphId(int i) const { return mGlyphs[i].glyph_id; } float getX(int i) const { return mGlyphs[i].x; } @@ -92,8 +93,6 @@ public: float getAdvance() const { return mAdvance; } float getCharAdvance(size_t i) const { return mAdvances[i]; } const std::vector<float>& getAdvances() const { return mAdvances; } - void getBounds(MinikinRect* rect) const { rect->set(mBounds); } - const MinikinRect& getBounds() const { return mBounds; } // Purge all caches, useful in low memory conditions static void purgeCaches(); @@ -135,7 +134,6 @@ private: std::vector<float> mAdvances; float mAdvance; - MinikinRect mBounds; }; } // namespace minikin diff --git a/include/minikin/LayoutCache.h b/include/minikin/LayoutCache.h index ee51f11..a1cc34a 100644 --- a/include/minikin/LayoutCache.h +++ b/include/minikin/LayoutCache.h @@ -83,9 +83,9 @@ public: private: const uint16_t* mChars; - size_t mNchars; - size_t mStart; - size_t mCount; + uint32_t mNchars; + uint32_t mStart; + uint32_t mCount; uint32_t mId; // for the font collection FontStyle mStyle; float mSize; diff --git a/include/minikin/LayoutCore.h b/include/minikin/LayoutCore.h index 852b985..cade517 100644 --- a/include/minikin/LayoutCore.h +++ b/include/minikin/LayoutCore.h @@ -49,11 +49,10 @@ public: // Low level accessors. const std::vector<uint8_t>& fontIndices() const { return mFontIndices; } - const std::vector<uint32_t> glyphIds() const { return mGlyphIds; } - const std::vector<Point> points() const { return mPoints; } - const std::vector<float> advances() const { return mAdvances; } + const std::vector<uint32_t>& glyphIds() const { return mGlyphIds; } + const std::vector<Point>& points() const { return mPoints; } + const std::vector<float>& advances() const { return mAdvances; } float advance() const { return mAdvance; } - const MinikinRect& bounds() const { return mBounds; } const MinikinExtent& extent() const { return mExtent; } const std::vector<FakedFont>& fonts() const { return mFonts; } @@ -79,7 +78,6 @@ private: std::vector<float> mAdvances; // per code units float mAdvance; - MinikinRect mBounds; MinikinExtent mExtent; std::vector<FakedFont> mFonts; diff --git a/include/minikin/LineBreaker.h b/include/minikin/LineBreaker.h index 3410339..5d3e752 100644 --- a/include/minikin/LineBreaker.h +++ b/include/minikin/LineBreaker.h @@ -62,6 +62,9 @@ public: return mStops[i]; } } + if (mTabWidth == 0) { + return 0; + } return floor(widthSoFar / mTabWidth + 1) * mTabWidth; } diff --git a/include/minikin/LocaleList.h b/include/minikin/LocaleList.h index bfc26c7..d173a46 100644 --- a/include/minikin/LocaleList.h +++ b/include/minikin/LocaleList.h @@ -21,11 +21,17 @@ namespace minikin { +// A special ID for the empty locale list. +// This value must be 0 since the empty locale list is inserted into mLocaleLists by default. +const static uint32_t kEmptyLocaleListId = 0; + // Looks up a locale list from an internal cache and returns its ID. // If the passed locale list is not in the cache, registers it and returns newly assigned ID. // TODO: Introduce LocaleId type. uint32_t registerLocaleList(const std::string& locales); +std::string getLocaleString(uint32_t id); + } // namespace minikin #endif // MINIKIN_LOCALE_H diff --git a/include/minikin/Measurement.h b/include/minikin/Measurement.h index c8b97d1..d0aaa51 100644 --- a/include/minikin/Measurement.h +++ b/include/minikin/Measurement.h @@ -20,6 +20,8 @@ #include <cstddef> #include <cstdint> +#include <minikin/Layout.h> + namespace minikin { float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, @@ -28,6 +30,10 @@ float getRunAdvance(const float* advances, const uint16_t* buf, size_t start, si size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t start, size_t count, float advance); +void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlags, + const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, + MinikinRect* out); + } // namespace minikin #endif // MINIKIN_MEASUREMENT_H diff --git a/include/minikin/MinikinFont.h b/include/minikin/MinikinFont.h index a5ba074..4d3ba9c 100644 --- a/include/minikin/MinikinFont.h +++ b/include/minikin/MinikinFont.h @@ -34,7 +34,7 @@ struct MinikinRect; // multiple actual implementations of fonts. class MinikinFont { public: - explicit MinikinFont(int32_t uniqueId) : mUniqueId(uniqueId) {} + MinikinFont() {} virtual ~MinikinFont() {} @@ -54,6 +54,9 @@ public: virtual void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint, const FontFakery& fakery) const = 0; + // Returns the font path or an empty string. + virtual const std::string& GetFontPath() const = 0; + // Override if font can provide access to raw data virtual const void* GetFontData() const { return nullptr; } @@ -64,6 +67,8 @@ public: // Returns index within OpenType collection virtual int GetFontIndex() const { return 0; } + virtual int GetSourceId() const { return 0; } + virtual const std::vector<minikin::FontVariation>& GetAxes() const = 0; virtual std::shared_ptr<MinikinFont> createFontWithVariation( @@ -74,11 +79,6 @@ public: static uint32_t MakeTag(char c1, char c2, char c3, char c4) { return ((uint32_t)c1 << 24) | ((uint32_t)c2 << 16) | ((uint32_t)c3 << 8) | (uint32_t)c4; } - - int32_t GetUniqueId() const { return mUniqueId; } - -private: - const int32_t mUniqueId; }; } // namespace minikin diff --git a/include/minikin/MinikinRect.h b/include/minikin/MinikinRect.h index 6a3d88b..38c2180 100644 --- a/include/minikin/MinikinRect.h +++ b/include/minikin/MinikinRect.h @@ -28,6 +28,7 @@ struct MinikinRect { bool operator==(const MinikinRect& o) const { return mLeft == o.mLeft && mTop == o.mTop && mRight == o.mRight && mBottom == o.mBottom; } + bool operator!=(const MinikinRect& o) const { return !(*this == o); } float mLeft; float mTop; float mRight; diff --git a/include/minikin/SparseBitSet.h b/include/minikin/SparseBitSet.h index 9ccef12..3034243 100644 --- a/include/minikin/SparseBitSet.h +++ b/include/minikin/SparseBitSet.h @@ -17,6 +17,7 @@ #ifndef MINIKIN_SPARSE_BIT_SET_H #define MINIKIN_SPARSE_BIT_SET_H +#include <minikin/Buffer.h> #include <sys/types.h> #include <cstdint> #include <memory> @@ -42,9 +43,13 @@ public: initFromRanges(ranges, nRanges); } + explicit SparseBitSet(BufferReader* reader) : SparseBitSet() { initFromBuffer(reader); } + SparseBitSet(SparseBitSet&&) = default; SparseBitSet& operator=(SparseBitSet&&) = default; + void writeTo(BufferWriter* writer) const; + // Determine whether the value is included in the set bool get(uint32_t ch) const { if (ch >= mMaxVal) return false; @@ -64,6 +69,7 @@ public: private: void initFromRanges(const uint32_t* ranges, size_t nRanges); + void initFromBuffer(BufferReader* reader); static const uint32_t kMaximumCapacity = 0xFFFFFF; static const int kLogValuesPerPage = 8; @@ -81,11 +87,16 @@ private: static int CountLeadingZeros(element x); uint32_t mMaxVal; - - std::unique_ptr<uint16_t[]> mIndices; - std::unique_ptr<element[]> mBitmaps; + 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; + // Forbid copy and assign. SparseBitSet(const SparseBitSet&) = delete; void operator=(const SparseBitSet&) = delete; diff --git a/include/minikin/SystemFonts.h b/include/minikin/SystemFonts.h index 4108215..cf4ab75 100644 --- a/include/minikin/SystemFonts.h +++ b/include/minikin/SystemFonts.h @@ -19,6 +19,7 @@ #include <map> #include <memory> +#include <mutex> #include <string> #include "minikin/FontCollection.h" @@ -33,39 +34,78 @@ public: return getInstance().findFontCollectionInternal(familyName); } - // Do not call this function outside Zygote process. static void registerFallback(const std::string& familyName, const std::shared_ptr<FontCollection>& fc) { return getInstance().registerFallbackInternal(familyName, fc); } - // Do not call this function outside Zygote process. static void registerDefault(const std::shared_ptr<FontCollection>& fc) { return getInstance().registerDefaultInternal(fc); } + using FontMapDeleter = std::function<void()>; + + static void addFontMap(std::shared_ptr<FontCollection>&& collections) { + return getInstance().addFontMapInternal(std::move(collections)); + } + + // 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); + } + protected: // Visible for testing purposes. SystemFonts() {} virtual ~SystemFonts() {} - std::shared_ptr<FontCollection> findFontCollectionInternal(const std::string& familyName) const; + std::shared_ptr<FontCollection> findFontCollectionInternal(const std::string& familyName); void registerFallbackInternal(const std::string& familyName, const std::shared_ptr<FontCollection>& fc) { - mSystemFallbacks.insert(std::make_pair(familyName, fc)); + std::lock_guard<std::mutex> lock(mMutex); + mSystemFallbacks[familyName] = fc; } void registerDefaultInternal(const std::shared_ptr<FontCollection>& fc) { + std::lock_guard<std::mutex> lock(mMutex); mDefaultFallback = fc; } + void addFontMapInternal(std::shared_ptr<FontCollection>&& collections) { + std::lock_guard<std::mutex> lock(mMutex); + 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) { + buildFontSetLocked(); + } + func(mFonts.value()); + } + private: static SystemFonts& getInstance(); - // There is no mutex guard here since registerFallback is designed to be - // called only in Zygote. - std::map<std::string, std::shared_ptr<FontCollection>> mSystemFallbacks; - std::shared_ptr<FontCollection> mDefaultFallback; + void buildFontSetLocked() EXCLUSIVE_LOCKS_REQUIRED(mMutex); + + std::map<std::string, std::shared_ptr<FontCollection>> mSystemFallbacks GUARDED_BY(mMutex); + std::shared_ptr<FontCollection> mDefaultFallback GUARDED_BY(mMutex); + std::vector<std::shared_ptr<FontCollection>> mCollections GUARDED_BY(mMutex); + std::optional<std::vector<std::shared_ptr<Font>>> mFonts GUARDED_BY(mMutex); + + std::mutex mMutex; }; } // namespace minikin diff --git a/libs/minikin/Android.bp b/libs/minikin/Android.bp index 9c20758..8356ea4 100644 --- a/libs/minikin/Android.bp +++ b/libs/minikin/Android.bp @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_library_headers { name: "libminikin-headers-for-tests", export_include_dirs: ["."], @@ -24,10 +28,13 @@ cc_library { host_supported: true, srcs: [ "BidiUtils.cpp", + "BoundsCache.cpp", "CmapCoverage.cpp", "Emoji.cpp", + "Font.cpp", "FontCollection.cpp", "FontFamily.cpp", + "FontFileParser.cpp", "FontUtils.cpp", "GraphemeBreak.cpp", "GreedyLineBreaker.cpp", @@ -81,7 +88,6 @@ cc_library { "libutils_headers", ], export_header_lib_headers: ["libminikin_headers"], - whole_static_libs: ["libgtest_prod"], clang: true, diff --git a/libs/minikin/BoundsCache.cpp b/libs/minikin/BoundsCache.cpp new file mode 100644 index 0000000..1edc853 --- /dev/null +++ b/libs/minikin/BoundsCache.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020 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 "minikin/BoundsCache.h" + +namespace minikin { + +void ValueExtractor::operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) { + value.reset(new BoundsValue); + value->rect = BoundsCache::getBounds(layoutPiece, paint); + value->advance = layoutPiece.advance(); +} + +// static +MinikinRect BoundsCache::getBounds(const LayoutPiece& layoutPiece, const MinikinPaint& paint) { + MinikinRect pieceBounds; + MinikinRect tmpRect; + for (uint32_t i = 0; i < layoutPiece.glyphCount(); ++i) { + const FakedFont& font = layoutPiece.fontAt(i); + const Point& point = layoutPiece.pointAt(i); + + MinikinFont* minikinFont = font.font->typeface().get(); + minikinFont->GetBounds(&tmpRect, layoutPiece.glyphIdAt(i), paint, font.fakery); + tmpRect.offset(point.x, point.y); + pieceBounds.join(tmpRect); + } + return pieceBounds; +} + +} // namespace minikin diff --git a/libs/minikin/Font.cpp b/libs/minikin/Font.cpp new file mode 100644 index 0000000..c2e74b7 --- /dev/null +++ b/libs/minikin/Font.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2021 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/Font.h" + +#include <vector> + +#include <hb-ot.h> +#include <hb.h> +#include <log/log.h> + +#include "minikin/HbUtils.h" +#include "minikin/MinikinFont.h" + +#include "FontUtils.h" +#include "MinikinInternal.h" + +namespace minikin { + +std::shared_ptr<Font> Font::Builder::build() { + if (mIsWeightSet && mIsSlantSet) { + // No need to read OS/2 header of the font file. + return std::shared_ptr<Font>(new Font(std::move(mTypeface), FontStyle(mWeight, mSlant), + prepareFont(mTypeface), mLocaleListId)); + } + + HbFontUniquePtr font = prepareFont(mTypeface); + FontStyle styleFromFont = analyzeStyle(font); + if (!mIsWeightSet) { + mWeight = styleFromFont.weight(); + } + if (!mIsSlantSet) { + mSlant = styleFromFont.slant(); + } + return std::shared_ptr<Font>(new Font(std::move(mTypeface), FontStyle(mWeight, mSlant), + std::move(font), mLocaleListId)); +} + +const std::shared_ptr<MinikinFont>& Font::typeface() const { + std::lock_guard lock(mTypefaceMutex); + if (mTypeface) return mTypeface; + initTypefaceLocked(); + return mTypeface; +} + +const HbFontUniquePtr& Font::baseFont() const { + std::lock_guard lock(mTypefaceMutex); + if (mBaseFont) return mBaseFont; + initTypefaceLocked(); + mBaseFont = prepareFont(mTypeface); + return mBaseFont; +} + +void Font::initTypefaceLocked() const { + if (mTypeface) return; + MINIKIN_ASSERT(mTypefaceLoader, "mTypefaceLoader should not be empty when mTypeface is null"); + mTypeface = mTypefaceLoader(mTypefaceMetadataReader); +} + +// static +HbFontUniquePtr Font::prepareFont(const std::shared_ptr<MinikinFont>& typeface) { + const char* buf = reinterpret_cast<const char*>(typeface->GetFontData()); + size_t size = typeface->GetFontSize(); + uint32_t ttcIndex = typeface->GetFontIndex(); + + HbBlobUniquePtr blob(hb_blob_create(buf, size, HB_MEMORY_MODE_READONLY, nullptr, nullptr)); + HbFaceUniquePtr face(hb_face_create(blob.get(), ttcIndex)); + HbFontUniquePtr parent(hb_font_create(face.get())); + hb_ot_font_set_funcs(parent.get()); + + uint32_t upem = hb_face_get_upem(face.get()); + hb_font_set_scale(parent.get(), upem, upem); + + HbFontUniquePtr font(hb_font_create_sub_font(parent.get())); + std::vector<hb_variation_t> variations; + variations.reserve(typeface->GetAxes().size()); + for (const FontVariation& variation : typeface->GetAxes()) { + variations.push_back({variation.axisTag, variation.value}); + } + hb_font_set_variations(font.get(), variations.data(), variations.size()); + return font; +} + +// static +FontStyle Font::analyzeStyle(const HbFontUniquePtr& font) { + HbBlob os2Table(font, MinikinFont::MakeTag('O', 'S', '/', '2')); + if (!os2Table) { + return FontStyle(); + } + + int weight; + bool italic; + if (!::minikin::analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) { + return FontStyle(); + } + // TODO: Update weight/italic based on fvar value. + return FontStyle(static_cast<uint16_t>(weight), static_cast<FontStyle::Slant>(italic)); +} + +std::unordered_set<AxisTag> Font::getSupportedAxes() const { + HbBlob fvarTable(baseFont(), MinikinFont::MakeTag('f', 'v', 'a', 'r')); + if (!fvarTable) { + return std::unordered_set<AxisTag>(); + } + std::unordered_set<AxisTag> supportedAxes; + analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes); + return supportedAxes; +} + +} // namespace minikin diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 6cbabea..9e4dd3b 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -24,6 +24,7 @@ #include <unicode/unorm2.h> #include "minikin/Emoji.h" +#include "minikin/FontFileParser.h" #include "Locale.h" #include "LocaleListCache.h" @@ -43,6 +44,23 @@ const uint32_t TEXT_STYLE_VS = 0xFE0E; static std::atomic<uint32_t> gNextCollectionId = {0}; +namespace { + +uint32_t getGlyphCount(U16StringPiece text, uint32_t start, uint32_t end, + const HbFontUniquePtr& font) { + HbBufferUniquePtr buffer(hb_buffer_create()); + hb_buffer_set_direction(buffer.get(), HB_DIRECTION_LTR); + hb_buffer_add_utf16(buffer.get(), text.data() + start, end - start, 0, end - start); + hb_buffer_guess_segment_properties(buffer.get()); + + unsigned int numGlyphs = -1; + hb_shape(font.get(), buffer.get(), nullptr, 0); + hb_buffer_get_glyph_infos(buffer.get(), &numGlyphs); + return numGlyphs; +} + +} // namespace + FontCollection::FontCollection(std::shared_ptr<FontFamily>&& typeface) : mMaxChar(0) { std::vector<std::shared_ptr<FontFamily>> typefaces; typefaces.push_back(typeface); @@ -83,24 +101,91 @@ void FontCollection::init(const vector<std::shared_ptr<FontFamily>>& typefaces) // A font can have a glyph for a base code point and variation selector pair but no glyph for // the base code point without variation selector. The family won't be listed in the range in // this case. + mOwnedRanges = std::make_unique<Range[]>(nPages); + mRanges = mOwnedRanges.get(); + mRangesCount = nPages; for (size_t i = 0; i < nPages; i++) { - Range dummy; - mRanges.push_back(dummy); - Range* range = &mRanges.back(); - range->start = mFamilyVec.size(); + Range* range = &mOwnedRanges[i]; + range->start = mOwnedFamilyVec.size(); for (size_t j = 0; j < nTypefaces; j++) { if (lastChar[j] < (i + 1) << kLogCharsPerPage) { const std::shared_ptr<FontFamily>& family = mFamilies[j]; - mFamilyVec.push_back(static_cast<uint8_t>(j)); + mOwnedFamilyVec.push_back(static_cast<uint8_t>(j)); uint32_t nextChar = family->getCoverage().nextSetBit((i + 1) << kLogCharsPerPage); lastChar[j] = nextChar; } } - range->end = mFamilyVec.size(); + range->end = mOwnedFamilyVec.size(); } // See the comment in Range for more details. - LOG_ALWAYS_FATAL_IF(mFamilyVec.size() >= 0xFFFF, + LOG_ALWAYS_FATAL_IF(mOwnedFamilyVec.size() >= 0xFFFF, "Exceeded the maximum indexable cmap coverage."); + mFamilyVec = mOwnedFamilyVec.data(); + mFamilyVecCount = mOwnedFamilyVec.size(); +} + +FontCollection::FontCollection(BufferReader* reader, + const std::vector<std::shared_ptr<FontFamily>>& families) { + 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]); + } + } + } + // 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); +} + +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) { + 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); + } + } + 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()); +} + +// static +void FontCollection::collectAllFontFamilies( + const std::vector<std::shared_ptr<FontCollection>>& fontCollections, + 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) { + bool inserted = + outFontFamilyToIndexMap->emplace(fontFamily, outAllFontFamilies->size()).second; + if (inserted) { + outAllFontFamilies->push_back(fontFamily); + } + } + } } // Special scores for the font fallback. @@ -249,11 +334,11 @@ uint32_t FontCollection::calcVariantMatchingScore(FamilyVariant variant, // 2. Calculate a score for the font family. See comments in calcFamilyScore for the detail. // 3. Highest score wins, with ties resolved to the first font. // This method never returns nullptr. -const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs, - uint32_t localeListId, - FamilyVariant variant) const { +FontCollection::FamilyMatchResult FontCollection::getFamilyForChar(uint32_t ch, uint32_t vs, + uint32_t localeListId, + FamilyVariant variant) const { if (ch >= mMaxChar) { - return mFamilies[0]; + return FamilyMatchResult::Builder().add(0).build(); } Range range = mRanges[ch >> kLogCharsPerPage]; @@ -262,23 +347,27 @@ const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, range = {0, static_cast<uint16_t>(mFamilies.size())}; } - int bestFamilyIndex = -1; uint32_t bestScore = kUnsupportedFontScore; + FamilyMatchResult::Builder builder; + for (size_t i = range.start; i < range.end; i++) { - const std::shared_ptr<FontFamily>& family = - vs == 0 ? mFamilies[mFamilyVec[i]] : mFamilies[i]; + const uint8_t familyIndex = vs == 0 ? mFamilyVec[i] : i; + const std::shared_ptr<FontFamily>& family = mFamilies[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 // use it. - return family; + return builder.add(familyIndex).build(); } - if (score > bestScore) { - bestScore = score; - bestFamilyIndex = i; + if (score != kUnsupportedFontScore && score >= bestScore) { + if (score > bestScore) { + builder.reset(); + bestScore = score; + } + builder.add(familyIndex); } } - if (bestFamilyIndex == -1) { + if (builder.empty()) { UErrorCode errorCode = U_ZERO_ERROR; const UNormalizer2* normalizer = unorm2_getNFDInstance(&errorCode); if (U_SUCCESS(errorCode)) { @@ -290,9 +379,9 @@ const std::shared_ptr<FontFamily>& FontCollection::getFamilyForChar(uint32_t ch, return getFamilyForChar(ch, vs, localeListId, variant); } } - return mFamilies[0]; + return FamilyMatchResult::Builder().add(0).build(); } - return vs == 0 ? mFamilies[mFamilyVec[bestFamilyIndex]] : mFamilies[bestFamilyIndex]; + return builder.build(); } // Characters where we want to continue using existing font run for (or stick to the next run if @@ -312,7 +401,7 @@ static bool doesNotNeedFontSupport(uint32_t c) { // Characters where we want to continue using existing font run instead of // recomputing the best match in the fallback list. -static const uint32_t stickyWhitelist[] = { +static const uint32_t stickyAllowlist[] = { '!', ',', '-', '.', ':', ';', '?', 0x00A0, // NBSP 0x2010, // HYPHEN @@ -323,9 +412,9 @@ static const uint32_t stickyWhitelist[] = { 0x2695, // STAFF_OF_AESCULAPIUS }; -static bool isStickyWhitelisted(uint32_t c) { - for (size_t i = 0; i < sizeof(stickyWhitelist) / sizeof(stickyWhitelist[0]); i++) { - if (stickyWhitelist[i] == c) return true; +static bool isStickyAllowlisted(uint32_t c) { + for (size_t i = 0; i < sizeof(stickyAllowlist) / sizeof(stickyAllowlist[0]); i++) { + if (stickyAllowlist[i] == c) return true; } return false; } @@ -368,22 +457,45 @@ bool FontCollection::hasVariationSelector(uint32_t baseCodepoint, constexpr uint32_t REPLACEMENT_CHARACTER = 0xFFFD; -std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, FontStyle style, +FontCollection::FamilyMatchResult FontCollection::FamilyMatchResult::intersect( + FontCollection::FamilyMatchResult l, FontCollection::FamilyMatchResult r) { + if (l == r) { + return l; + } + + uint32_t li = 0; + uint32_t ri = 0; + FamilyMatchResult::Builder b; + while (li < l.size() && ri < r.size()) { + if (l[li] < r[ri]) { + li++; + } else if (l[li] > r[ri]) { + ri++; + } else { // l[li] == r[ri] + b.add(l[li]); + li++; + ri++; + } + } + return b.build(); +} + +std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, FontStyle, uint32_t localeListId, FamilyVariant familyVariant, uint32_t runMax) const { const uint16_t* string = text.data(); const uint32_t string_size = text.size(); - std::vector<Run> result; - const FontFamily* lastFamily = nullptr; - Run* run = nullptr; + FamilyMatchResult lastFamilyIndices = FamilyMatchResult(); if (string_size == 0) { - return result; + return std::vector<Run>(); } const uint32_t kEndOfString = 0xFFFFFFFF; + std::vector<Run> result; + Run* run = nullptr; uint32_t nextCh = 0; uint32_t prevCh = 0; @@ -411,15 +523,44 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo if (doesNotNeedFontSupport(ch)) { // Always continue if the character is a format character not needed to be in the font. shouldContinueRun = true; - } else if (lastFamily != nullptr && (isStickyWhitelisted(ch) || isCombining(ch))) { + } else if (!lastFamilyIndices.empty() && (isStickyAllowlisted(ch) || isCombining(ch))) { // Continue using existing font as long as it has coverage and is whitelisted. - shouldContinueRun = lastFamily->getCoverage().get(ch); + + const std::shared_ptr<FontFamily>& lastFamily = mFamilies[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); + } + } else { + shouldContinueRun = lastFamily->getCoverage().get(ch); + } } if (!shouldContinueRun) { - const std::shared_ptr<FontFamily>& family = getFamilyForChar( + FamilyMatchResult familyIndices = getFamilyForChar( ch, isVariationSelector(nextCh) ? nextCh : 0, localeListId, familyVariant); - if (utf16Pos == 0 || family.get() != lastFamily) { + bool breakRun; + if (utf16Pos == 0 || lastFamilyIndices.empty()) { + breakRun = true; + } else { + const std::shared_ptr<FontFamily>& lastFamily = mFamilies[lastFamilyIndices[0]]; + if (lastFamily->isColorEmojiFamily()) { + FamilyMatchResult intersection = + FamilyMatchResult::intersect(familyIndices, lastFamilyIndices); + if (intersection.empty()) { + breakRun = true; // None of last family can draw the given char. + } else { + lastFamilyIndices = intersection; + breakRun = false; + } + } else { + breakRun = familyIndices[0] != lastFamilyIndices[0]; + } + } + + if (breakRun) { size_t start = utf16Pos; // Workaround for combining marks and emoji modifiers until we implement // per-cluster font selection: if a combining mark or an emoji modifier is found in @@ -427,27 +568,31 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo // character to the new run. U+20E3 COMBINING ENCLOSING KEYCAP, used in emoji, is // handled properly by this since it's a combining mark too. if (utf16Pos != 0 && - (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh))) && - family != nullptr && family->getCoverage().get(prevCh)) { - const size_t prevChLength = U16_LENGTH(prevCh); - if (run != nullptr) { - run->end -= prevChLength; - if (run->start == run->end) { - result.pop_back(); + (isCombining(ch) || (isEmojiModifier(ch) && isEmojiBase(prevCh)))) { + for (uint8_t ix : familyIndices) { + if (mFamilies[ix]->getCoverage().get(prevCh)) { + const size_t prevChLength = U16_LENGTH(prevCh); + if (run != nullptr) { + run->end -= prevChLength; + if (run->start == run->end) { + result.pop_back(); + } + } + start -= prevChLength; + break; } } - start -= prevChLength; } - if (lastFamily == nullptr) { + if (lastFamilyIndices.empty()) { // This is the first family ever assigned. We are either seeing the very first // character (which means start would already be zero), or we have only seen // characters that don't need any font support (which means we need to adjust // start to be 0 to include those characters). start = 0; } - result.push_back({family->getClosestMatch(style), static_cast<int>(start), 0}); + result.push_back({familyIndices, static_cast<int>(start), 0}); run = &result.back(); - lastFamily = family.get(); + lastFamilyIndices = run->familyMatch; } } prevCh = ch; @@ -465,19 +610,42 @@ std::vector<FontCollection::Run> FontCollection::itemize(U16StringPiece text, Fo } } while (nextCh != kEndOfString); - if (lastFamily == nullptr) { + if (lastFamilyIndices.empty()) { // No character needed any font support, so it doesn't really matter which font they end up // getting displayed in. We put the whole string in one run, using the first font. - result.push_back({mFamilies[0]->getClosestMatch(style), 0, static_cast<int>(string_size)}); + result.push_back( + {FamilyMatchResult::Builder().add(0).build(), 0, static_cast<int>(string_size)}); } if (result.size() > runMax) { // The itemization has terminated since it reaches the runMax. Remove last unfinalized runs. - result.resize(runMax); + return std::vector<Run>(result.begin(), result.begin() + runMax); } + return result; } +FakedFont FontCollection::getBestFont(U16StringPiece text, const Run& run, FontStyle style) { + uint8_t bestIndex = 0; + uint32_t bestGlyphCount = 0xFFFFFFFF; + + const std::shared_ptr<FontFamily>& family = mFamilies[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 HbFontUniquePtr& font = family->getFont(0)->baseFont(); + uint32_t glyphCount = getGlyphCount(text, run.start, run.end, font); + if (glyphCount < bestGlyphCount) { + bestIndex = run.familyMatch[i]; + bestGlyphCount = glyphCount; + } + } + } else { + bestIndex = run.familyMatch[0]; + } + return mFamilies[bestIndex]->getClosestMatch(style); +} + FakedFont FontCollection::baseFontFaked(FontStyle style) { return mFamilies[0]->getClosestMatch(style); } diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index 85a5142..f1fd00a 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -18,11 +18,9 @@ #include "minikin/FontFamily.h" -#include <cstdint> +#include <algorithm> #include <vector> -#include <hb-ot.h> -#include <hb.h> #include <log/log.h> #include "minikin/CmapCoverage.h" @@ -37,82 +35,14 @@ namespace minikin { -Font Font::Builder::build() { - if (mIsWeightSet && mIsSlantSet) { - // No need to read OS/2 header of the font file. - return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), prepareFont(mTypeface)); - } - - HbFontUniquePtr font = prepareFont(mTypeface); - FontStyle styleFromFont = analyzeStyle(font); - if (!mIsWeightSet) { - mWeight = styleFromFont.weight(); - } - if (!mIsSlantSet) { - mSlant = styleFromFont.slant(); - } - return Font(std::move(mTypeface), FontStyle(mWeight, mSlant), std::move(font)); -} - -// static -HbFontUniquePtr Font::prepareFont(const std::shared_ptr<MinikinFont>& typeface) { - const char* buf = reinterpret_cast<const char*>(typeface->GetFontData()); - size_t size = typeface->GetFontSize(); - uint32_t ttcIndex = typeface->GetFontIndex(); - - HbBlobUniquePtr blob(hb_blob_create(buf, size, HB_MEMORY_MODE_READONLY, nullptr, nullptr)); - HbFaceUniquePtr face(hb_face_create(blob.get(), ttcIndex)); - HbFontUniquePtr parent(hb_font_create(face.get())); - hb_ot_font_set_funcs(parent.get()); - - uint32_t upem = hb_face_get_upem(face.get()); - hb_font_set_scale(parent.get(), upem, upem); - - HbFontUniquePtr font(hb_font_create_sub_font(parent.get())); - std::vector<hb_variation_t> variations; - variations.reserve(typeface->GetAxes().size()); - for (const FontVariation& variation : typeface->GetAxes()) { - variations.push_back({variation.axisTag, variation.value}); - } - hb_font_set_variations(font.get(), variations.data(), variations.size()); - return font; -} - -// static -FontStyle Font::analyzeStyle(const HbFontUniquePtr& font) { - HbBlob os2Table(font, MinikinFont::MakeTag('O', 'S', '/', '2')); - if (!os2Table) { - return FontStyle(); - } - - int weight; - bool italic; - if (!::minikin::analyzeStyle(os2Table.get(), os2Table.size(), &weight, &italic)) { - return FontStyle(); - } - // TODO: Update weight/italic based on fvar value. - return FontStyle(static_cast<uint16_t>(weight), static_cast<FontStyle::Slant>(italic)); -} - -std::unordered_set<AxisTag> Font::getSupportedAxes() const { - HbBlob fvarTable(mBaseFont, MinikinFont::MakeTag('f', 'v', 'a', 'r')); - if (!fvarTable) { - return std::unordered_set<AxisTag>(); - } - std::unordered_set<AxisTag> supportedAxes; - analyzeAxes(fvarTable.get(), fvarTable.size(), &supportedAxes); - return supportedAxes; -} - -FontFamily::FontFamily(std::vector<Font>&& fonts) +FontFamily::FontFamily(std::vector<std::shared_ptr<Font>>&& fonts) : FontFamily(FamilyVariant::DEFAULT, std::move(fonts)) {} -FontFamily::FontFamily(FamilyVariant variant, std::vector<Font>&& fonts) - : FontFamily(LocaleListCache::kEmptyListId, variant, std::move(fonts), - false /* isCustomFallback */) {} +FontFamily::FontFamily(FamilyVariant variant, std::vector<std::shared_ptr<Font>>&& fonts) + : FontFamily(kEmptyLocaleListId, variant, std::move(fonts), false /* isCustomFallback */) {} -FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector<Font>&& fonts, - bool isCustomFallback) +FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, + std::vector<std::shared_ptr<Font>>&& fonts, bool isCustomFallback) : mLocaleListId(localeListId), mVariant(variant), mFonts(std::move(fonts)), @@ -123,6 +53,83 @@ FontFamily::FontFamily(uint32_t localeListId, FamilyVariant variant, std::vector 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) { + // FamilyVariant is uint8_t + static_assert(sizeof(FamilyVariant) == 1); + FamilyVariant variant = 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); + // 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); +} + +// Write fields other than mFonts. +void FontFamily::writeToInternal(BufferWriter* writer) const { + 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->write<uint8_t>(mIsColorEmoji); + writer->write<uint8_t>(mIsCustomFallback); + 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); + } + } +} + +void FontFamily::writeLocaleListInternal(BufferWriter* writer) const { + LocaleListCache::writeTo(writer, mLocaleListId); +} // Compute a matching metric between two styles - 0 is an exact match static int computeMatch(FontStyle style1, FontStyle style2) { if (style1 == style2) return 0; @@ -144,21 +151,23 @@ static FontFakery computeFakery(FontStyle wanted, FontStyle actual) { } FakedFont FontFamily::getClosestMatch(FontStyle style) const { - const Font* bestFont = &mFonts[0]; + int bestIndex = 0; + Font* bestFont = mFonts[bestIndex].get(); int bestMatch = computeMatch(bestFont->style(), style); for (size_t i = 1; i < mFonts.size(); i++) { - const Font& font = mFonts[i]; - int match = computeMatch(font.style(), style); + Font* font = mFonts[i].get(); + int match = computeMatch(font->style(), style); if (i == 0 || match < bestMatch) { - bestFont = &font; + bestFont = font; + bestIndex = i; bestMatch = match; } } - return FakedFont{bestFont, computeFakery(style, bestFont->style())}; + return FakedFont{mFonts[bestIndex], computeFakery(style, bestFont->style())}; } void FontFamily::computeCoverage() { - const Font* font = getClosestMatch(FontStyle()).font; + const std::shared_ptr<Font>& font = getClosestMatch(FontStyle()).font; HbBlob cmapTable(font->baseFont(), MinikinFont::MakeTag('c', 'm', 'a', 'p')); if (cmapTable.get() == nullptr) { ALOGE("Could not get cmap table size!\n"); @@ -168,7 +177,7 @@ void FontFamily::computeCoverage() { mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mCmapFmt14Coverage); for (size_t i = 0; i < mFonts.size(); ++i) { - std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxes(); + std::unordered_set<AxisTag> supportedAxes = mFonts[i]->getSupportedAxes(); mSupportedAxes.insert(supportedAxes.begin(), supportedAxes.end()); } } @@ -216,10 +225,10 @@ std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( return nullptr; } - std::vector<Font> fonts; - for (const Font& font : mFonts) { + std::vector<std::shared_ptr<Font>> fonts; + for (const auto& font : mFonts) { bool supportedVariations = false; - std::unordered_set<AxisTag> supportedAxes = font.getSupportedAxes(); + std::unordered_set<AxisTag> supportedAxes = font->getSupportedAxes(); if (!supportedAxes.empty()) { for (const FontVariation& variation : variations) { if (supportedAxes.find(variation.axisTag) != supportedAxes.end()) { @@ -230,12 +239,13 @@ std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( } std::shared_ptr<MinikinFont> minikinFont; if (supportedVariations) { - minikinFont = font.typeface()->createFontWithVariation(variations); + minikinFont = font->typeface()->createFontWithVariation(variations); } if (minikinFont == nullptr) { - minikinFont = font.typeface(); + fonts.push_back(font); + } else { + fonts.push_back(Font::Builder(minikinFont).setStyle(font->style()).build()); } - fonts.push_back(Font::Builder(minikinFont).setStyle(font.style()).build()); } return std::shared_ptr<FontFamily>( diff --git a/libs/minikin/FontFileParser.cpp b/libs/minikin/FontFileParser.cpp new file mode 100644 index 0000000..1bcb711 --- /dev/null +++ b/libs/minikin/FontFileParser.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2021 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 "minikin/FontFileParser.h" + +#define LOG_TAG "Minikin" + +#include <cstdint> +#include <optional> +#include <string> +#include <vector> + +#include <hb-ot.h> +#include <hb.h> + +#include "MinikinInternal.h" +#include "minikin/MinikinFont.h" + +namespace minikin { + +namespace { + +class SafeFontBufferReader { +public: + SafeFontBufferReader(const void* buffer, size_t size) + : mBuffer(reinterpret_cast<const uint8_t*>(buffer)), + mSize(size), + mPos(0), + mError(false) {} + + template <typename T> + T readBE() { + if (mError) return T(); + + if ((mSize - mPos) < sizeof(T)) { + mError = true; + return T(); + } + const T* data = reinterpret_cast<const T*>(mBuffer + mPos); + mPos += sizeof(T); + return *data; + } + + uint16_t readU16() { + if (mError) return 0; + + if ((mSize - mPos) < 2) { + mError = true; + return 0; + } + uint16_t out = ((uint32_t)mBuffer[mPos]) << 8 | ((uint32_t)mBuffer[mPos + 1]); + mPos += 2; + return out; + }; + + uint32_t readU32() { + if (mError) return 0; + + if ((mSize - mPos) < 4) { + mError = true; + return 0; + } + + uint32_t out = ((uint32_t)mBuffer[mPos]) << 24 | ((uint32_t)mBuffer[mPos + 1]) << 16 | + ((uint32_t)mBuffer[mPos + 2]) << 8 | ((uint32_t)mBuffer[mPos + 3]); + mPos += 4; + return out; + }; + + void seek(size_t pos) { + if (mError) return; + + if (pos > mSize) { + mError = true; + } else { + mPos = pos; + } + } + + size_t remaining() const { + if (mError) return 0; + return mSize - mPos; + } + + bool error() const { return mError; } + +private: + const uint8_t* mBuffer; + size_t mSize; + size_t mPos; + bool mError; +}; + +bool isPostScriptNameAllowedChar(char c) { + // OpenType spec says only ASCII codes 33 to 126, ecept for the '[', ']', '(', ')', '{', '}', + // '<', '>', '/', '%'. + if (!(33 <= c && c <= 126)) { + return false; + } + if (c == '[' || c == ']' || c == '(' || c == ')' || c == '{' || c == '}' || c == '<' || + c == '>' || c == '/' || c == '%') { + return false; + } + + return true; +} + +} // namespace + +// static +bool FontFileParser::analyzeFontRevision(const uint8_t* head_data, size_t head_size, + uint32_t* out) { + SafeFontBufferReader reader(head_data, head_size); + + if (reader.remaining() < 8) { + return false; // At least head table has 8 bytes, for version and fontRevision + } + + uint32_t majorVersion = reader.readU16(); + if (reader.error()) return false; + uint32_t minorVersion = reader.readU16(); + if (reader.error()) return false; + + // Invalid head table header. + if (majorVersion != 1 && minorVersion != 0) return false; + + *out = reader.readU32(); + if (reader.error()) return false; + return true; +} + +// static +bool FontFileParser::checkPSName(const std::string& psName) { + if (psName.size() > 63) return false; + + for (auto c : psName) { + if (!isPostScriptNameAllowedChar(c)) { + return false; + } + } + return true; +} + +FontFileParser::FontFileParser(const void* buffer, size_t size, uint32_t index) + : mFace(makeHbFace(buffer, size, index)) {} + +FontFileParser::FontFileParser(const HbFaceUniquePtr& face) + : mFace(hb_face_reference(face.get())) {} + +FontFileParser::FontFileParser(const HbFontUniquePtr& font) + : mFace(hb_face_reference(hb_font_get_face(font.get()))) {} + +FontFileParser::~FontFileParser() {} + +// static +HbFaceUniquePtr FontFileParser::makeHbFace(const void* buffer, size_t size, uint32_t index) { + HbBlobUniquePtr blob(hb_blob_create(reinterpret_cast<const char*>(buffer), size, + HB_MEMORY_MODE_READONLY, nullptr, nullptr)); + return HbFaceUniquePtr(hb_face_create(blob.get(), index)); +} + +std::optional<uint32_t> FontFileParser::getFontRevision() const { + if (!mFace) return std::optional<uint32_t>(); + + HbBlob headTable(mFace, MinikinFont::MakeTag('h', 'e', 'a', 'd')); + if (!headTable) return std::optional<uint32_t>(); + + uint32_t out = 0; + if (!analyzeFontRevision(headTable.get(), headTable.size(), &out)) { + return std::optional<uint32_t>(); + } + + return out; +} + +std::optional<std::string> FontFileParser::getPostScriptName() const { + if (!mFace) return std::optional<std::string>(); + + unsigned int size = 64; // PostScript name is up to 63 characters. + char buf[64] = {}; + + uint32_t result = hb_ot_name_get_utf8(mFace.get(), HB_OT_NAME_ID_POSTSCRIPT_NAME, + HB_LANGUAGE_INVALID, &size, buf); + + if (result == 0) { // not found. + return std::optional<std::string>(); + } + + std::string out(buf, size); + + if (!checkPSName(out)) { // Contains invalid characters. + return std::optional<std::string>(); + } + + return out; +} + +std::optional<bool> FontFileParser::isPostScriptType1Font() const { + if (!mFace) return std::optional<bool>(); + + HbBlob cffTable(mFace, MinikinFont::MakeTag('C', 'F', 'F', ' ')); + HbBlob cff2Table(mFace, MinikinFont::MakeTag('C', 'F', 'F', '2')); + return cffTable || cff2Table; +} + +} // namespace minikin diff --git a/libs/minikin/GreedyLineBreaker.cpp b/libs/minikin/GreedyLineBreaker.cpp index f6952a9..9bcb22d 100644 --- a/libs/minikin/GreedyLineBreaker.cpp +++ b/libs/minikin/GreedyLineBreaker.cpp @@ -255,7 +255,7 @@ bool GreedyLineBreaker::tryLineBreakWithHyphenation(const Range& range, WordBrea // TODO: Respect trailing line end spaces. bool GreedyLineBreaker::doLineBreakWithGraphemeBounds(const Range& range) { - double width = mMeasuredText.widths[range.getStart()]; + float width = mMeasuredText.widths[range.getStart()]; // Starting from + 1 since at least one character needs to be assigned to a line. for (uint32_t i = range.getStart() + 1; i < range.getEnd(); ++i) { diff --git a/libs/minikin/Layout.cpp b/libs/minikin/Layout.cpp index b902648..e7d41c8 100644 --- a/libs/minikin/Layout.cpp +++ b/libs/minikin/Layout.cpp @@ -158,9 +158,6 @@ void Layout::appendLayout(const LayoutPiece& src, size_t start, float extraAdvan mAdvances[start] += extraAdvance; } } - MinikinRect srcBounds(src.bounds()); - srcBounds.offset(mAdvance, 0); - mBounds.join(srcBounds); mAdvance += src.advance() + extraAdvance; } diff --git a/libs/minikin/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp index 6087646..f1d0de8 100644 --- a/libs/minikin/LayoutCore.cpp +++ b/libs/minikin/LayoutCore.cpp @@ -355,8 +355,9 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool mPoints.reserve(count); HbBufferUniquePtr buffer(hb_buffer_create()); - std::vector<FontCollection::Run> items = paint.font->itemize( - textBuf.substr(range), paint.fontStyle, paint.localeListId, paint.familyVariant); + U16StringPiece substr = textBuf.substr(range); + std::vector<FontCollection::Run> items = + paint.font->itemize(substr, paint.fontStyle, paint.localeListId, paint.familyVariant); std::vector<hb_feature_t> features; // Disable default-on non-required ligature features if letter-spacing @@ -384,14 +385,14 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool isRtl ? run_ix >= 0 : run_ix < static_cast<int>(items.size()); isRtl ? --run_ix : ++run_ix) { FontCollection::Run& run = items[run_ix]; - const FakedFont& fakedFont = run.fakedFont; - auto it = fontMap.find(fakedFont.font); + FakedFont fakedFont = paint.font->getBestFont(substr, run, paint.fontStyle); + auto it = fontMap.find(fakedFont.font.get()); uint8_t font_ix; if (it == fontMap.end()) { // First time to see this font. font_ix = mFonts.size(); mFonts.push_back(fakedFont); - fontMap.insert(std::make_pair(fakedFont.font, font_ix)); + fontMap.insert(std::make_pair(fakedFont.font.get(), font_ix)); // We override some functions which are not thread safe. HbFontUniquePtr font(hb_font_create_sub_font(fakedFont.font->baseFont().get())); @@ -421,8 +422,6 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool hb_font_set_ppem(hbFont.get(), size * scaleX, size); hb_font_set_scale(hbFont.get(), HBFloatToFixed(size * scaleX), HBFloatToFixed(size)); - const bool is_color_bitmap_font = isColorBitmapFont(hbFont); - // TODO: if there are multiple scripts within a font in an RTL run, // we need to reorder those runs. This is unlikely with our current // font stack, but should be done for correctness. @@ -502,23 +501,6 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool mGlyphIds.push_back(glyph_ix); mPoints.emplace_back(x + xoff, y + yoff); float xAdvance = HBFixedToFloat(positions[i].x_advance); - MinikinRect glyphBounds; - hb_glyph_extents_t extents = {}; - if (is_color_bitmap_font && - hb_font_get_glyph_extents(hbFont.get(), glyph_ix, &extents)) { - // Note that it is technically possible for a TrueType font to have outline and - // embedded bitmap at the same time. We ignore modified bbox of hinted outline - // glyphs in that case. - glyphBounds.mLeft = roundf(HBFixedToFloat(extents.x_bearing)); - glyphBounds.mTop = roundf(HBFixedToFloat(-extents.y_bearing)); - glyphBounds.mRight = roundf(HBFixedToFloat(extents.x_bearing + extents.width)); - glyphBounds.mBottom = - roundf(HBFixedToFloat(-extents.y_bearing - extents.height)); - } else { - fakedFont.font->typeface()->GetBounds(&glyphBounds, glyph_ix, paint, - fakedFont.fakery); - } - glyphBounds.offset(xoff, yoff); if (clusterBaseIndex < count) { mAdvances[clusterBaseIndex] += xAdvance; @@ -526,8 +508,6 @@ LayoutPiece::LayoutPiece(const U16StringPiece& textBuf, const Range& range, bool ALOGE("cluster %zu (start %zu) out of bounds of count %zu", clusterBaseIndex, start, count); } - glyphBounds.offset(x, y); - mBounds.join(glyphBounds); x += xAdvance; } if (numGlyphs) { diff --git a/libs/minikin/LineBreakerUtil.h b/libs/minikin/LineBreakerUtil.h index eb058cc..8b383a4 100644 --- a/libs/minikin/LineBreakerUtil.h +++ b/libs/minikin/LineBreakerUtil.h @@ -36,7 +36,7 @@ namespace minikin { // paragraphs, accuracy could degrade using only 32-bit float. Note however that float is used // extensively on the Java side for this. This is a typedef so that we can easily change it based // on performance/accuracy tradeoff. -typedef double ParaWidth; +typedef float ParaWidth; // Hyphenates a string potentially containing non-breaking spaces. std::vector<HyphenationType> hyphenate(const U16StringPiece& string, const Hyphenator& hypenator); diff --git a/libs/minikin/Locale.cpp b/libs/minikin/Locale.cpp index c1ec389..553f61a 100644 --- a/libs/minikin/Locale.cpp +++ b/libs/minikin/Locale.cpp @@ -34,6 +34,18 @@ uint32_t registerLocaleList(const std::string& locales) { return LocaleListCache::getId(locales); } +std::string getLocaleString(uint32_t localeId) { + const LocaleList& localeList = LocaleListCache::getById(localeId); + std::string out; + for (size_t i = 0; i < localeList.size(); ++i) { + if (i != 0) { + out += ","; + } + out += localeList[i].getString(); + } + return out; +} + // Check if a language code supports extension such as emoji and line break etc. according to its // subtag static bool isSubtag(const char* buf, size_t bufLen, const char* subtag, size_t subtagLen) { diff --git a/libs/minikin/Locale.h b/libs/minikin/Locale.h index 8052d6c..7557301 100644 --- a/libs/minikin/Locale.h +++ b/libs/minikin/Locale.h @@ -95,7 +95,17 @@ public: // Parse from string Locale(const StringPiece& buf); - bool operator==(const Locale other) const { + // Parse from identifier. See getIdentifier() for the identifier format. + explicit Locale(uint64_t identifier) + : mScript(extractBits(identifier, 29, 20)), + mLanguage(extractBits(identifier, 49, 15)), + mRegion(extractBits(identifier, 14, 15)), + mSubScriptBits(scriptToSubScriptBits(mScript)), + mVariant(static_cast<Variant>(extractBits(identifier, 0, 2))), + mEmojiStyle(static_cast<EmojiStyle>(extractBits(identifier, 12, 2))), + mLBStyle(static_cast<LineBreakStyle>(extractBits(identifier, 10, 2))) {} + + bool operator==(const Locale& other) const { return !isUnsupported() && isEqualScript(other) && mLanguage == other.mLanguage && mRegion == other.mRegion && mVariant == other.mVariant && mLBStyle == other.mLBStyle && mEmojiStyle == other.mEmojiStyle; @@ -134,13 +144,13 @@ public: // Identifier pattern: // |-------|-------|-------|-------|-------|-------|-------|-------| - // lllllllllllllll Language Code - // ssssssssssssssssssss Script Code - // rrrrrrrrrrrrrrr Region Code - // ee Emoji Style - // bb Line Break Style - // XXXXXXXX Free - // vv German Variant + // lllllllllllllll Language Code (15 bits) + // ssssssssssssssssssss Script Code (20 bits) + // rrrrrrrrrrrrrrr Region Code (15 bits) + // ee Emoji Style (2 bits) + // bb Line Break Style (2 bits) + // XXXXXXXX Free (8 bits) + // vv German Variant (2 bits) uint64_t getIdentifier() const { return ((uint64_t)mLanguage << 49) | ((uint64_t)mScript << 29) | ((uint64_t)mRegion << 14) | ((uint64_t)mEmojiStyle << 12) | ((uint64_t)mLBStyle << 10) | (uint64_t)mVariant; @@ -181,6 +191,10 @@ private: void resolveUnicodeExtension(const char* buf, size_t length); + inline static uint64_t extractBits(uint64_t value, uint8_t shift, uint8_t nBits) { + return (value >> shift) & ((1 << nBits) - 1); + } + static uint8_t scriptToSubScriptBits(uint32_t rawScript); static LineBreakStyle resolveLineBreakStyle(const char* buf, size_t length); diff --git a/libs/minikin/LocaleListCache.cpp b/libs/minikin/LocaleListCache.cpp index 15156cd..38800f7 100644 --- a/libs/minikin/LocaleListCache.cpp +++ b/libs/minikin/LocaleListCache.cpp @@ -21,15 +21,16 @@ #include <unordered_set> #include <log/log.h> +#include <minikin/Hasher.h> +#include <minikin/LocaleList.h> #include <unicode/uloc.h> +#include <unicode/umachine.h> #include "Locale.h" #include "MinikinInternal.h" namespace minikin { -const uint32_t LocaleListCache::kEmptyListId; - // Returns the text length of output. static size_t toLanguageTag(char* output, size_t outSize, const StringPiece& locale) { output[0] = '\0'; @@ -70,7 +71,7 @@ static size_t toLanguageTag(char* output, size_t outSize, const StringPiece& loc } uErr = U_ZERO_ERROR; - outLength = uloc_toLanguageTag(likelyChars, output, outSize, FALSE, &uErr); + outLength = uloc_toLanguageTag(likelyChars, output, outSize, false, &uErr); if (U_FAILURE(uErr)) { // unable to build a proper locale identifier ALOGD("uloc_toLanguageTag(\"%s\") failed: %s", likelyChars, u_errorName(uErr)); @@ -106,15 +107,39 @@ static std::vector<Locale> parseLocaleList(const std::string& input) { return result; } +size_t LocaleListCache::LocaleVectorHash::operator()(const std::vector<Locale>& locales) const { + Hasher hasher; + for (const auto& locale : locales) { + uint64_t id = locale.getIdentifier(); + hasher.update(static_cast<uint32_t>((id >> 32) & 0xFFFFFFFF)); + hasher.update(static_cast<uint32_t>(id & 0xFFFFFFFF)); + } + return hasher.hash(); +} + LocaleListCache::LocaleListCache() { - // Insert an empty locale list for mapping default locale list to kEmptyListId. + // Insert an empty locale list for mapping default locale list to kEmptyLocaleListId. // The default locale list has only one Locale and it is the unsupported locale. mLocaleLists.emplace_back(); - mLocaleListLookupTable.insert(std::make_pair("", kEmptyListId)); + mLocaleListLookupTable.emplace(std::vector<Locale>(), kEmptyLocaleListId); + mLocaleListStringCache.emplace("", kEmptyLocaleListId); } uint32_t LocaleListCache::getIdInternal(const std::string& locales) { std::lock_guard<std::mutex> lock(mMutex); + const auto& it = mLocaleListStringCache.find(locales); + if (it != mLocaleListStringCache.end()) { + return it->second; + } + uint32_t id = getIdInternal(parseLocaleList(locales)); + mLocaleListStringCache.emplace(locales, id); + return id; +} + +uint32_t LocaleListCache::getIdInternal(std::vector<Locale>&& locales) { + if (locales.empty()) { + return kEmptyLocaleListId; + } const auto& it = mLocaleListLookupTable.find(locales); if (it != mLocaleListLookupTable.end()) { return it->second; @@ -122,15 +147,31 @@ uint32_t LocaleListCache::getIdInternal(const std::string& locales) { // Given locale list is not in cache. Insert it and return newly assigned ID. const uint32_t nextId = mLocaleLists.size(); - LocaleList fontLocales(parseLocaleList(locales)); - if (fontLocales.empty()) { - return kEmptyListId; - } + mLocaleListLookupTable.emplace(locales, nextId); + LocaleList fontLocales(std::move(locales)); mLocaleLists.push_back(std::move(fontLocales)); - mLocaleListLookupTable.insert(std::make_pair(locales, nextId)); return nextId; } +uint32_t LocaleListCache::readFromInternal(BufferReader* reader) { + uint32_t size = reader->read<uint32_t>(); + std::vector<Locale> locales; + locales.reserve(size); + for (uint32_t i = 0; i < size; i++) { + locales.emplace_back(reader->read<uint64_t>()); + } + std::lock_guard<std::mutex> lock(mMutex); + return getIdInternal(std::move(locales)); +} + +void LocaleListCache::writeToInternal(BufferWriter* writer, uint32_t id) { + const LocaleList& localeList = getByIdInternal(id); + writer->write<uint32_t>(localeList.size()); + for (size_t i = 0; i < localeList.size(); i++) { + writer->write<uint64_t>(localeList[i].getIdentifier()); + } +} + const LocaleList& LocaleListCache::getByIdInternal(uint32_t id) { std::lock_guard<std::mutex> lock(mMutex); MINIKIN_ASSERT(id < mLocaleLists.size(), "Lookup by unknown locale list ID."); diff --git a/libs/minikin/LocaleListCache.h b/libs/minikin/LocaleListCache.h index 61e3f81..a83b1c8 100644 --- a/libs/minikin/LocaleListCache.h +++ b/libs/minikin/LocaleListCache.h @@ -20,6 +20,7 @@ #include <mutex> #include <unordered_map> +#include "minikin/Buffer.h" #include "minikin/Macros.h" #include "Locale.h" @@ -28,30 +29,39 @@ namespace minikin { class LocaleListCache { public: - // A special ID for the empty locale list. - // This value must be 0 since the empty locale list is inserted into mLocaleLists by - // default. - const static uint32_t kEmptyListId = 0; - // A special ID for the invalid locale list. const static uint32_t kInvalidListId = (uint32_t)(-1); // Returns the locale list ID for the given string representation of LocaleList. - // Caller should acquire a lock before calling the method. static inline uint32_t getId(const std::string& locales) { return getInstance().getIdInternal(locales); } - // Caller should acquire a lock before calling the method. + // Returns the locale list ID for the LocaleList serialized in the buffer. + static inline uint32_t readFrom(BufferReader* reader) { + return getInstance().readFromInternal(reader); + } + + static inline void writeTo(BufferWriter* writer, uint32_t id) { + return getInstance().writeToInternal(writer, id); + } + static inline const LocaleList& getById(uint32_t id) { return getInstance().getByIdInternal(id); } private: + struct LocaleVectorHash { + size_t operator()(const std::vector<Locale>& locales) const; + }; + LocaleListCache(); // Singleton ~LocaleListCache() {} uint32_t getIdInternal(const std::string& locales); + uint32_t getIdInternal(std::vector<Locale>&& locales) EXCLUSIVE_LOCKS_REQUIRED(mMutex); + uint32_t readFromInternal(BufferReader* reader); + void writeToInternal(BufferWriter* writer, uint32_t id); const LocaleList& getByIdInternal(uint32_t id); // Caller should acquire a lock before calling the method. @@ -62,8 +72,18 @@ private: std::vector<LocaleList> mLocaleLists GUARDED_BY(mMutex); - // A map from the string representation of the font locale list to the ID. - std::unordered_map<std::string, uint32_t> mLocaleListLookupTable GUARDED_BY(mMutex); + // A map from the list of locale identifier to the ID. + // + // Locale's operator==() doesn't have reflexivity for unsupported locales, + // but it won't cause problems because we never store unsupported locales in + // LocaleListCache. See parseLocaleList() in LocaleListCache.cpp. + std::unordered_map<std::vector<Locale>, uint32_t, LocaleVectorHash> mLocaleListLookupTable + GUARDED_BY(mMutex); + + // A cache map from the string representation of the font locale list to the ID. + // This is a mere cache over mLocaleListLookupTable. Some LocaleList objects may be in + // mLocaleListLookupTable even if they are not in mLocaleListStringCache. + std::unordered_map<std::string, uint32_t> mLocaleListStringCache GUARDED_BY(mMutex); std::mutex mMutex; }; diff --git a/libs/minikin/MeasuredText.cpp b/libs/minikin/MeasuredText.cpp index af0d0ee..1bf3942 100644 --- a/libs/minikin/MeasuredText.cpp +++ b/libs/minikin/MeasuredText.cpp @@ -213,10 +213,20 @@ class BoundsCompositor { public: BoundsCompositor() : mAdvance(0) {} - void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& /* paint */) { - MinikinRect tmpBounds = layoutPiece.bounds(); - tmpBounds.offset(mAdvance, 0); - mBounds.join(tmpBounds); + void operator()(const LayoutPiece& layoutPiece, const MinikinPaint& paint) { + MinikinRect pieceBounds; + MinikinRect tmpRect; + for (uint32_t i = 0; i < layoutPiece.glyphCount(); ++i) { + const FakedFont& font = layoutPiece.fontAt(i); + const Point& point = layoutPiece.pointAt(i); + + MinikinFont* minikinFont = font.font->typeface().get(); + minikinFont->GetBounds(&tmpRect, layoutPiece.glyphIdAt(i), paint, font.fakery); + tmpRect.offset(point.x, point.y); + pieceBounds.join(tmpRect); + } + pieceBounds.offset(mAdvance, 0); + mBounds.join(pieceBounds); mAdvance += layoutPiece.advance(); } diff --git a/libs/minikin/Measurement.cpp b/libs/minikin/Measurement.cpp index 5d110c4..66886da 100644 --- a/libs/minikin/Measurement.cpp +++ b/libs/minikin/Measurement.cpp @@ -19,6 +19,9 @@ #include <cfloat> #include <cmath> +#include "BidiUtils.h" +#include "LayoutSplitter.h" +#include "minikin/BoundsCache.h" #include "minikin/GraphemeBreak.h" namespace minikin { @@ -118,4 +121,40 @@ size_t getOffsetForAdvance(const float* advances, const uint16_t* buf, size_t st return best; } +struct BoundsComposer { + BoundsComposer() : mAdvance(0) {} + + void operator()(const MinikinRect& rect, float advance) { + MinikinRect tmp = rect; + tmp.offset(mAdvance, 0); + mBounds.join(tmp); + mAdvance += advance; + } + + float mAdvance; + MinikinRect mBounds; +}; + +void getBounds(const U16StringPiece& str, const Range& range, Bidi bidiFlag, + const MinikinPaint& paint, StartHyphenEdit startHyphen, EndHyphenEdit endHyphen, + MinikinRect* out) { + BoundsComposer bc; + for (const BidiText::RunInfo info : BidiText(str, range, bidiFlag)) { + for (const auto [context, piece] : LayoutSplitter(str, info.range, info.isRtl)) { + const StartHyphenEdit pieceStartHyphen = + (piece.getStart() == range.getStart()) ? startHyphen : StartHyphenEdit::NO_EDIT; + const EndHyphenEdit pieceEndHyphen = + (piece.getEnd() == range.getEnd()) ? endHyphen : EndHyphenEdit::NO_EDIT; + BoundsCache::getInstance().getOrCreate(str.substr(context), piece - context.getStart(), + paint, info.isRtl, pieceStartHyphen, + pieceEndHyphen, bc); + // Increment word spacing for spacer + if (piece.getLength() == 1 && isWordSpace(str[piece.getStart()])) { + bc.mAdvance += paint.wordSpacing; + } + } + } + *out = bc.mBounds; +} + } // namespace minikin diff --git a/libs/minikin/SparseBitSet.cpp b/libs/minikin/SparseBitSet.cpp index 66d6c02..41151f1 100644 --- a/libs/minikin/SparseBitSet.cpp +++ b/libs/minikin/SparseBitSet.cpp @@ -55,9 +55,14 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { return; } mMaxVal = maxVal; - mIndices.reset(new uint16_t[(mMaxVal + kPageMask) >> kLogValuesPerPage]); + mIndicesCount = (mMaxVal + kPageMask) >> kLogValuesPerPage; + // Avoid zero-filling mOwnedIndices. + mOwnedIndices.reset(new uint16_t[mIndicesCount]); + mIndices = mOwnedIndices.get(); uint32_t nPages = calcNumPages(ranges, nRanges); - mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]()); + mBitmapsCount = nPages << (kLogValuesPerPage - kLogBitsPerEl); + mOwnedBitmaps = std::make_unique<element[]>(mBitmapsCount); + mBitmaps = mOwnedBitmaps.get(); mZeroPageIndex = noZeroPage; uint32_t nonzeroPageEnd = 0; uint32_t currentPage = 0; @@ -73,32 +78,52 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) { mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); } for (uint32_t j = nonzeroPageEnd; j < startPage; j++) { - mIndices[j] = mZeroPageIndex; + mOwnedIndices[j] = mZeroPageIndex; } } - mIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + mOwnedIndices[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) { - mBitmaps[index] |= + mOwnedBitmaps[index] |= (kElAllOnes >> (start & kElMask)) & (kElAllOnes << ((~end + 1) & kElMask)); } else { - mBitmaps[index] |= kElAllOnes >> (start & kElMask); + mOwnedBitmaps[index] |= kElAllOnes >> (start & kElMask); for (size_t j = 1; j < nElements - 1; j++) { - mBitmaps[index + j] = kElAllOnes; + mOwnedBitmaps[index + j] = kElAllOnes; } - mBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); + mOwnedBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask); } for (size_t j = startPage + 1; j < endPage + 1; j++) { - mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl); + mOwnedIndices[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>(); +} + +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); +} + int SparseBitSet::CountLeadingZeros(element x) { // Note: GCC / clang builtin return sizeof(element) <= sizeof(int) ? __builtin_clz(x) : __builtin_clzl(x); diff --git a/libs/minikin/SystemFonts.cpp b/libs/minikin/SystemFonts.cpp index 287fc61..9c8fa66 100644 --- a/libs/minikin/SystemFonts.cpp +++ b/libs/minikin/SystemFonts.cpp @@ -26,7 +26,8 @@ SystemFonts& SystemFonts::getInstance() { } std::shared_ptr<FontCollection> SystemFonts::findFontCollectionInternal( - const std::string& familyName) const { + const std::string& familyName) { + std::lock_guard<std::mutex> lock(mMutex); auto it = mSystemFallbacks.find(familyName); if (it != mSystemFallbacks.end()) { return it->second; @@ -35,4 +36,22 @@ std::shared_ptr<FontCollection> SystemFonts::findFontCollectionInternal( return mDefaultFallback; } +void SystemFonts::buildFontSetLocked() { + std::unordered_set<FontFamily*> uniqueFamilies; + + for (const auto& collection : mCollections) { + for (const auto& family : collection->getFamilies()) { + uniqueFamilies.insert(family.get()); + } + } + + std::vector<std::shared_ptr<Font>> result; + for (const auto family : uniqueFamilies) { + for (size_t i = 0; i < family->getNumFonts(); ++i) { + result.push_back(family->getFontRef(i)); + } + } + mFonts = std::move(result); +} + } // namespace minikin diff --git a/tests/Android.bp b/tests/Android.bp index 1d8c019..3088ca4 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -1,8 +1,13 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + filegroup { name: "minikin-test-data", srcs: [ "data/Arabic.ttf", "data/Ascii.ttf", + "data/Bbox.ttf", "data/Bold.ttf", "data/BoldItalic.ttf", "data/Cherokee.ttf", @@ -10,6 +15,7 @@ filegroup { "data/ColorTextMixedEmojiFont.ttf", "data/CustomExtent.ttf", "data/Emoji.ttf", + "data/EmojiBase.ttf", "data/Hiragana.ttf", "data/Italic.ttf", "data/Ja.ttf", @@ -19,6 +25,7 @@ filegroup { "data/MultiAxis.ttf", "data/NoCmapFormat14.ttf", "data/NoGlyphFont.ttf", + "data/OverrideEmoji.ttf", "data/Regular.ttf", "data/TextEmojiFont.ttf", "data/UnicodeBMPOnly.ttf", @@ -28,6 +35,7 @@ filegroup { "data/ZhHans.ttf", "data/ZhHant.ttf", "data/emoji.xml", + "data/emoji_itemization.xml", "data/itemize.xml", ], } diff --git a/tests/data/Ascii.ttf b/tests/data/Ascii.ttf Binary files differindex be6d5fe..2e6835b 100644 --- a/tests/data/Ascii.ttf +++ b/tests/data/Ascii.ttf diff --git a/tests/data/Ascii.ttx b/tests/data/Ascii.ttx index 74eba96..c0a2ef6 100644 --- a/tests/data/Ascii.ttx +++ b/tests/data/Ascii.ttx @@ -18,6 +18,7 @@ <GlyphOrder> <GlyphID id="0" name=".notdef"/> <GlyphID id="1" name="1em"/> + <GlyphID id="2" name="2em"/> </GlyphOrder> <head> @@ -117,6 +118,7 @@ <hmtx> <mtx name=".notdef" width="50" lsb="0"/> <mtx name="1em" width="100" lsb="0"/> + <mtx name="2em" width="200" lsb="0"/> </hmtx> <cmap> @@ -217,6 +219,12 @@ <map code="0x007C" name="1em" /> <!-- '|' --> <map code="0x007D" name="1em" /> <!-- '}' --> <map code="0x007E" name="1em" /> <!-- '~' --> + <map code="0x02B1" name="2em" /> <!-- 'α' --> + <map code="0x02B2" name="2em" /> <!-- 'β' --> + <map code="0x02B3" name="2em" /> <!-- 'γ' --> + <map code="0x02B4" name="2em" /> <!-- 'δ' --> + <map code="0x02B5" name="2em" /> <!-- 'ε' --> + <map code="0x02B6" name="2em" /> <!-- 'ζ' --> </cmap_format_12> </cmap> @@ -235,6 +243,15 @@ </contour> <instructions><assembly></assembly></instructions> </TTGlyph> + <TTGlyph name="2em" xMin="0" yMin="0" xMax="200" yMax="200"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="200" y="0" on="1" /> + <pt x="200" y="200" on="1" /> + <pt x="0" y="200" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> </glyf> <name> diff --git a/tests/data/Bbox.ttf b/tests/data/Bbox.ttf Binary files differnew file mode 100644 index 0000000..c89c59c --- /dev/null +++ b/tests/data/Bbox.ttf diff --git a/tests/data/Bbox.ttx b/tests/data/Bbox.ttx new file mode 100644 index 0000000..e7d34bd --- /dev/null +++ b/tests/data/Bbox.ttx @@ -0,0 +1,265 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2020 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0"> + + <GlyphOrder> + <GlyphID id="0" name=".notdef"/> + <GlyphID id="1" name="1emx1em"/> + <GlyphID id="2" name="2emx2em"/> + <GlyphID id="3" name="3emx3em"/> + <GlyphID id="4" name="2emx2em_lsb_1em"/> + <GlyphID id="5" name="1emx1em_y1em_origin"/> + </GlyphOrder> + + <head> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Fri Mar 17 07:26:00 2017"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="1.0"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + </hhea> + + <maxp> + <tableVersion value="0x10000"/> + <maxZones value="0"/> + <maxTwilightPoints value="0"/> + <maxStorage value="0"/> + <maxFunctionDefs value="0"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="0"/> + <maxSizeOfInstructions value="0"/> + <maxComponentElements value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="32"/> + <usLastCharIndex value="122"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="1emx1em" width="1000" lsb="0"/> + <mtx name="2emx2em" width="2000" lsb="0"/> + <mtx name="3emx3em" width="3000" lsb="0"/> + <mtx name="2emx2em_lsb_1em" width="2000" lsb="1000"/> + <mtx name="1emx1em_y1em_origin" width="1000" lsb="0"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0"> + <map code="0x0028" name="1emx1em" /> + <map code="0x0061" name="1emx1em" /> + <map code="0x0062" name="2emx2em" /> + <map code="0x0063" name="3emx3em" /> + <map code="0x0064" name="2emx2em_lsb_1em" /> + <map code="0x0065" name="1emx1em_y1em_origin" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" /> + <TTGlyph name="1emx1em" xMin="0" yMin="0" xMax="1000" yMax="1000"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="500" y="1000" on="1" /> + <pt x="1000" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + <TTGlyph name="2emx2em" xMin="0" yMin="0" xMax="2000" yMax="2000"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="1000" y="2000" on="1" /> + <pt x="2000" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + <TTGlyph name="3emx3em" xMin="0" yMin="0" xMax="3000" yMax="3000"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="1500" y="3000" on="1" /> + <pt x="3000" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + <TTGlyph name="2emx2em_lsb_1em" xMin="0" yMin="0" xMax="2000" yMax="2000"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="1000" y="2000" on="1" /> + <pt x="2000" y="0" on="1" /> + </contour> + <instructions /> + </TTGlyph> + <TTGlyph name="1emx1em_y1em_origin" xMin="0" yMin="1000" xMax="1000" yMax="2000"> + <contour> + <pt x="0" y="1000" on="1" /> + <pt x="500" y="2000" on="1" /> + <pt x="1000" y="1000" on="1" /> + </contour> + <instructions /> + </TTGlyph> + </glyf> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <ScriptRecord index="0"> + <ScriptTag value="latn"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <FeatureRecord index="0"> + <FeatureTag value="rtlm"/> + <Feature> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <Lookup index="0"> + <LookupType value="1"/> + <LookupFlag value="0"/> + <SingleSubst index="0" Format="2"> + <Substitution in="1emx1em" out="3emx3em" /> + </SingleSubst> + </Lookup> + </LookupList> + + </GSUB> + + <name> + <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409"> + Copyright (C) 2017 The Android Open Source Project + </namerecord> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + Sample Font + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + SampleFont-Regular + </namerecord> + <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409"> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + 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. + </namerecord> + <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409"> + http://www.apache.org/licenses/LICENSE-2.0 + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + +</ttFont> diff --git a/tests/data/EmojiBase.ttf b/tests/data/EmojiBase.ttf Binary files differnew file mode 100644 index 0000000..659226d --- /dev/null +++ b/tests/data/EmojiBase.ttf diff --git a/tests/data/EmojiBase.ttx b/tests/data/EmojiBase.ttx new file mode 100644 index 0000000..e6e3da8 --- /dev/null +++ b/tests/data/EmojiBase.ttx @@ -0,0 +1,537 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2021 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9"> + + <GlyphOrder> + <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> + <GlyphID id="0" name=".notdef"/> + + <!-- Compbining characters for Emoji --> + <GlyphID id="1" name="U+200D"/> + <GlyphID id="2" name="U+1F3FB"/> + <GlyphID id="3" name="U+1F3FC"/> + + <!-- Random Emoji --> + <GlyphID id="4" name="U+1F9B0"/> + <GlyphID id="5" name="U+1F9B1"/> + <GlyphID id="6" name="U+1F9B2"/> + <GlyphID id="7" name="U+1F9B3"/> + <GlyphID id="8" name="U+1F9B4"/> + <GlyphID id="9" name="U+1F9B5"/> + <GlyphID id="10" name="U+1F9B6"/> + <GlyphID id="11" name="U+1F9B7"/> + <GlyphID id="12" name="U+1F9B8"/> + <GlyphID id="13" name="U+1F9B9"/> + + <!-- Unassigned Code Points --> + <GlyphID id="14" name="U+E0000"/> + <GlyphID id="15" name="U+E0001"/> + + <!-- Ligature Form. Not in cmap --> + <GlyphID id="16" name="LigaForm1"/> + <GlyphID id="17" name="LigaForm2"/> + <GlyphID id="18" name="LigaForm3"/> + <GlyphID id="19" name="LigaForm4"/> + <GlyphID id="20" name="LigaForm5"/> + <GlyphID id="21" name="LigaForm6"/> + <GlyphID id="22" name="LigaForm7"/> + + <!-- Regional Indicators --> + <GlyphID id="23" name="U+1F1E6"/> + <GlyphID id="24" name="U+1F1E7"/> + + </GlyphOrder> + + <head> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Sep 9 08:01:17 2015"/> + <modified value="Wed Sep 9 08:48:07 2015"/> + <xMin value="0"/> + <yMin value="0"/> + <xMax value="0"/> + <yMax value="0"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <indexToLocFormat value="0"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <advanceWidthMax value="500"/> + <minLeftSideBearing value="0"/> + <minRightSideBearing value="0"/> + <xMaxExtent value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + <numberOfHMetrics value="1"/> + </hhea> + + <maxp> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="0x10000"/> + <numGlyphs value="8"/> + <maxPoints value="0"/> + <maxContours value="0"/> + <maxCompositePoints value="0"/> + <maxCompositeContours value="0"/> + <maxZones value="2"/> + <maxTwilightPoints value="12"/> + <maxStorage value="28"/> + <maxFunctionDefs value="119"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="61"/> + <maxSizeOfInstructions value="2967"/> + <maxComponentElements value="0"/> + <maxComponentDepth value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="48"/> + <usLastCharIndex value="65535"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="U+200D" width="500" lsb="93"/> + <mtx name="U+1F1E6" width="500" lsb="93"/> + <mtx name="U+1F1E7" width="500" lsb="93"/> + <mtx name="U+1F3FB" width="500" lsb="93"/> + <mtx name="U+1F3FC" width="500" lsb="93"/> + <mtx name="U+1F9B0" width="500" lsb="93"/> + <mtx name="U+1F9B1" width="500" lsb="93"/> + <mtx name="U+1F9B2" width="500" lsb="93"/> + <mtx name="U+1F9B3" width="500" lsb="93"/> + <mtx name="U+1F9B4" width="500" lsb="93"/> + <mtx name="U+1F9B5" width="500" lsb="93"/> + <mtx name="U+1F9B6" width="500" lsb="93"/> + <mtx name="U+1F9B7" width="500" lsb="93"/> + <mtx name="U+1F9B8" width="500" lsb="93"/> + <mtx name="U+1F9B9" width="500" lsb="93"/> + <mtx name="U+E0000" width="500" lsb="93"/> + <mtx name="U+E0001" width="500" lsb="93"/> + <mtx name="LigaForm1" width="500" lsb="93"/> + <mtx name="LigaForm2" width="500" lsb="93"/> + <mtx name="LigaForm3" width="500" lsb="93"/> + <mtx name="LigaForm4" width="500" lsb="93"/> + <mtx name="LigaForm5" width="500" lsb="93"/> + <mtx name="LigaForm6" width="500" lsb="93"/> + <mtx name="LigaForm7" width="500" lsb="93"/> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0"> + <map code="0x200D" name="U+200D" /> + <map code="0x1F1E6" name="U+1F1E6" /> + <map code="0x1F1E7" name="U+1F1E7" /> + <map code="0x1F3FB" name="U+1F3FB" /> + <map code="0x1F3FC" name="U+1F3FC" /> + <map code="0x1F9B0" name="U+1F9B0" /> + <map code="0x1F9B1" name="U+1F9B1" /> + <map code="0x1F9B2" name="U+1F9B2" /> + <map code="0x1F9B3" name="U+1F9B3" /> + <map code="0x1F9B4" name="U+1F9B4" /> + <map code="0x1F9B5" name="U+1F9B5" /> + <map code="0x1F9B6" name="U+1F9B6" /> + <map code="0x1F9B7" name="U+1F9B7" /> + <map code="0x1F9B8" name="U+1F9B8" /> + <map code="0x1F9B9" name="U+1F9B9" /> + <map code="0xE0000" name="U+E0000" /> + <map code="0xE0001" name="U+E0001" /> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + + <!-- The xMin, yMin, xMax and yMax values + will be recalculated by the compiler. --> + + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+200D" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F1E6" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F1E7" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F3FB" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F3FC" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B0" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B1" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B2" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B3" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B4" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B5" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B6" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B7" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B8" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B9" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+E0000" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+E0001" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm1" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm2" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm3" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm4" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm5" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm6" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm7" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + </glyf> + + <name> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + EmojiBaseFont + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + EmojiBaseFont + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + EmojiBaseFont + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <FeatureRecord index="0"> + <FeatureTag value="ccmp"/> + <Feature> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <Lookup index="0"> + <LookupType value="4"/> + <LookupFlag value="0"/> + <LigatureSubst index="0" Format="1"> + <LigatureSet glyph="U+1F9B0"> + <!-- U+1F9B0 U+1F3FB -> LigaForm1 --> + <Ligature components="U+1F3FB" glyph="LigaForm1"/> + + <!-- U+1F9B0 U+1F3FC -> LigaForm2 --> + <Ligature components="U+1F3FC" glyph="LigaForm2"/> + </LigatureSet> + <LigatureSet glyph="U+1F9B1"> + <!-- U+1F9B1 U+1F3FB -> LigaForm3 --> + <Ligature components="U+1F3FB" glyph="LigaForm3"/> + </LigatureSet> + <LigatureSet glyph="U+1F9B2"> + <Ligature components="U+200D,U+1F9B3,U+200D,U+1F9B4" glyph="LigaForm4"/> + </LigatureSet> + <LigatureSet glyph="U+1F9B6"> + <!-- U+1F9B6 U+1F3FB -> LigaForm3 --> + <Ligature components="U+1F3FB" glyph="LigaForm3"/> + </LigatureSet> + <LigatureSet glyph="U+1F1E6"> + <Ligature components="U+1F1E6" glyph="LigaForm6"/> + <Ligature components="U+1F1E7" glyph="LigaForm7"/> + </LigatureSet> + </LigatureSubst> + </Lookup> + </LookupList> + </GSUB> +</ttFont> diff --git a/tests/data/OverrideEmoji.ttf b/tests/data/OverrideEmoji.ttf Binary files differnew file mode 100644 index 0000000..890796b --- /dev/null +++ b/tests/data/OverrideEmoji.ttf diff --git a/tests/data/OverrideEmoji.ttx b/tests/data/OverrideEmoji.ttx new file mode 100644 index 0000000..d5d4091 --- /dev/null +++ b/tests/data/OverrideEmoji.ttx @@ -0,0 +1,521 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Copyright (C) 2021 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. +--> +<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.9"> + + <GlyphOrder> + <!-- The 'id' attribute is only for humans; it is ignored when parsed. --> + <GlyphID id="0" name=".notdef"/> + + <!-- Compbining characters for Emoji --> + <GlyphID id="1" name="U+200D"/> + <GlyphID id="2" name="U+1F3FB"/> + <GlyphID id="3" name="U+1F3FC"/> + + <!-- Random Emoji --> + <GlyphID id="4" name="U+1F9B0"/> + <GlyphID id="5" name="U+1F9B1"/> + <GlyphID id="6" name="U+1F9B2"/> + <GlyphID id="7" name="U+1F9B3"/> + <GlyphID id="8" name="U+1F9B4"/> + <GlyphID id="9" name="U+1F9B5"/> + <!-- + Following four glyphs are removed from EmojiBase.ttf for verifying fallback. + <GlyphID id="10" name="U+1F9B6"/> + <GlyphID id="11" name="U+1F9B7"/> + <GlyphID id="12" name="U+1F9B8"/> + <GlyphID id="13" name="U+1F9B9"/> + --> + + <!-- Unassigned Code Points --> + <GlyphID id="14" name="U+E0000"/> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <GlyphID id="15" name="U+E0001"/> + --> + + <!-- Ligature Form. Not in cmap --> + <GlyphID id="16" name="LigaForm1"/> + <GlyphID id="17" name="LigaForm2"/> + <GlyphID id="18" name="LigaForm3"/> + <!-- + Following glyphs are removed from EmojiBase.ttf for verifying fallback. + <GlyphID id="19" name="LigaForm4"/> + <GlyphID id="20" name="LigaForm5"/> + --> + <GlyphID id="21" name="LigaForm6"/> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <GlyphID id="22" name="LigaForm7"/> + --> + + <GlyphID id="23" name="U+1F1E6"/> + <GlyphID id="24" name="U+1F1E7"/> + </GlyphOrder> + + <head> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="1.0"/> + <fontRevision value="1.0"/> + <checkSumAdjustment value="0x640cdb2f"/> + <magicNumber value="0x5f0f3cf5"/> + <flags value="00000000 00000011"/> + <unitsPerEm value="1000"/> + <created value="Wed Sep 9 08:01:17 2015"/> + <modified value="Wed Sep 9 08:48:07 2015"/> + <xMin value="0"/> + <yMin value="0"/> + <xMax value="0"/> + <yMax value="0"/> + <macStyle value="00000000 00000000"/> + <lowestRecPPEM value="7"/> + <fontDirectionHint value="2"/> + <indexToLocFormat value="0"/> + <glyphDataFormat value="0"/> + </head> + + <hhea> + <tableVersion value="0x00010000"/> + <ascent value="1000"/> + <descent value="-200"/> + <lineGap value="0"/> + <advanceWidthMax value="500"/> + <minLeftSideBearing value="0"/> + <minRightSideBearing value="0"/> + <xMaxExtent value="0"/> + <caretSlopeRise value="1"/> + <caretSlopeRun value="0"/> + <caretOffset value="0"/> + <reserved0 value="0"/> + <reserved1 value="0"/> + <reserved2 value="0"/> + <reserved3 value="0"/> + <metricDataFormat value="0"/> + <numberOfHMetrics value="1"/> + </hhea> + + <maxp> + <!-- Most of this table will be recalculated by the compiler --> + <tableVersion value="0x10000"/> + <numGlyphs value="8"/> + <maxPoints value="0"/> + <maxContours value="0"/> + <maxCompositePoints value="0"/> + <maxCompositeContours value="0"/> + <maxZones value="2"/> + <maxTwilightPoints value="12"/> + <maxStorage value="28"/> + <maxFunctionDefs value="119"/> + <maxInstructionDefs value="0"/> + <maxStackElements value="61"/> + <maxSizeOfInstructions value="2967"/> + <maxComponentElements value="0"/> + <maxComponentDepth value="0"/> + </maxp> + + <OS_2> + <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex' + will be recalculated by the compiler --> + <version value="3"/> + <xAvgCharWidth value="594"/> + <usWeightClass value="400"/> + <usWidthClass value="5"/> + <fsType value="00000000 00001000"/> + <ySubscriptXSize value="650"/> + <ySubscriptYSize value="600"/> + <ySubscriptXOffset value="0"/> + <ySubscriptYOffset value="75"/> + <ySuperscriptXSize value="650"/> + <ySuperscriptYSize value="600"/> + <ySuperscriptXOffset value="0"/> + <ySuperscriptYOffset value="350"/> + <yStrikeoutSize value="50"/> + <yStrikeoutPosition value="300"/> + <sFamilyClass value="0"/> + <panose> + <bFamilyType value="0"/> + <bSerifStyle value="0"/> + <bWeight value="5"/> + <bProportion value="0"/> + <bContrast value="0"/> + <bStrokeVariation value="0"/> + <bArmStyle value="0"/> + <bLetterForm value="0"/> + <bMidline value="0"/> + <bXHeight value="0"/> + </panose> + <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/> + <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/> + <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/> + <achVendID value="UKWN"/> + <fsSelection value="00000000 01000000"/> + <usFirstCharIndex value="48"/> + <usLastCharIndex value="65535"/> + <sTypoAscender value="800"/> + <sTypoDescender value="-200"/> + <sTypoLineGap value="200"/> + <usWinAscent value="1000"/> + <usWinDescent value="200"/> + <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/> + <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/> + <sxHeight value="500"/> + <sCapHeight value="700"/> + <usDefaultChar value="0"/> + <usBreakChar value="32"/> + <usMaxContext value="0"/> + </OS_2> + + <hmtx> + <mtx name=".notdef" width="500" lsb="93"/> + <mtx name="U+200D" width="500" lsb="93"/> + <mtx name="U+1F1E6" width="500" lsb="93"/> + <mtx name="U+1F1E7" width="500" lsb="93"/> + <mtx name="U+1F3FB" width="500" lsb="93"/> + <mtx name="U+1F3FC" width="500" lsb="93"/> + <mtx name="U+1F9B0" width="500" lsb="93"/> + <mtx name="U+1F9B1" width="500" lsb="93"/> + <mtx name="U+1F9B2" width="500" lsb="93"/> + <mtx name="U+1F9B3" width="500" lsb="93"/> + <mtx name="U+1F9B4" width="500" lsb="93"/> + <mtx name="U+1F9B5" width="500" lsb="93"/> + <!-- + Following four glyphs are removed from EmojiBase.ttf for verifying fallback. + <mtx name="U+1F9B6" width="500" lsb="93"/> + <mtx name="U+1F9B7" width="500" lsb="93"/> + <mtx name="U+1F9B8" width="500" lsb="93"/> + <mtx name="U+1F9B9" width="500" lsb="93"/> + --> + <mtx name="U+E0000" width="500" lsb="93"/> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <mtx name="U+1F9B6" width="500" lsb="93"/> + --> + <mtx name="U+E0001" width="500" lsb="93"/> + <mtx name="LigaForm1" width="500" lsb="93"/> + <mtx name="LigaForm2" width="500" lsb="93"/> + <mtx name="LigaForm3" width="500" lsb="93"/> + <!-- + Following two glyphs are removed from EmojiBase.ttf for verifying fallback. + <mtx name="LigaForm4" width="500" lsb="93"/> + <mtx name="LigaForm5" width="500" lsb="93"/> + --> + <mtx name="LigaForm6" width="500" lsb="93"/> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <mtx name="LigaForm7" width="500" lsb="93"/> + --> + </hmtx> + + <cmap> + <tableVersion version="0"/> + <cmap_format_12 format="12" reserved="0" length="3" nGroups="6" platformID="3" platEncID="1" language="0"> + <map code="0x200D" name="U+200D" /> + <map code="0x1F1E6" name="U+1F1E6" /> + <map code="0x1F1E7" name="U+1F1E7" /> + <map code="0x1F3FB" name="U+1F3FB" /> + <map code="0x1F3FC" name="U+1F3FC" /> + <map code="0x1F9B0" name="U+1F9B0" /> + <map code="0x1F9B1" name="U+1F9B1" /> + <map code="0x1F9B2" name="U+1F9B2" /> + <map code="0x1F9B3" name="U+1F9B3" /> + <map code="0x1F9B4" name="U+1F9B4" /> + <map code="0x1F9B5" name="U+1F9B5" /> + <!-- + Following four glyphs are removed from EmojiBase.ttf for verifying fallback. + <map code="0x1F9B6" name="U+1F9B6" /> + <map code="0x1F9B7" name="U+1F9B7" /> + <map code="0x1F9B8" name="U+1F9B8" /> + <map code="0x1F9B9" name="U+1F9B9" /> + --> + <map code="0xE0000" name="U+E0000" /> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <map code="0xE0001" name="U+E0001" /> + --> + </cmap_format_12> + </cmap> + + <loca> + <!-- The 'loca' table will be calculated by the compiler --> + </loca> + + <glyf> + + <!-- The xMin, yMin, xMax and yMax values + will be recalculated by the compiler. --> + + <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+200D" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F1E6" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F1E7" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F3FB" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F3FC" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B0" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B1" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B2" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B3" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B4" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="U+1F9B5" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <!-- + Following four glyphs are removed from EmojiBase.ttf for verifying fallback. + <TTGlyph name="U+1F9B6"> + <TTGlyph name="U+1F9B7"> + <TTGlyph name="U+1F9B8"> + <TTGlyph name="U+1F9B9"> + --> + <TTGlyph name="U+E0000" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <TTGlyph name="U+E0001"/> + --> + <TTGlyph name="LigaForm1" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm2" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <TTGlyph name="LigaForm3" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <!-- + Following two glyphs are removed from EmojiBase.ttf for verifying fallback. + <TTGlyph name="LigaForm4"> + <TTGlyph name="LigaForm5"> + --> + <TTGlyph name="LigaForm6" xMin="0" yMin="0" xMax="100" yMax="100"> + <contour> + <pt x="0" y="0" on="1" /> + <pt x="100" y="0" on="1" /> + <pt x="100" y="100" on="1" /> + <pt x="0" y="100" on="1" /> + </contour> + <instructions><assembly></assembly></instructions> + </TTGlyph> + <!-- + Following glyph is removed from EmojiBase.ttf for verifying fallback. + <TTGlyph name="LigaForm7"> + --> + </glyf> + + <name> + <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> + OverrideEmojiFont + </namerecord> + <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409"> + Regular + </namerecord> + <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409"> + OverrideEmojiFont + </namerecord> + <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409"> + OverrideEmojiFont + </namerecord> + </name> + + <post> + <formatType value="3.0"/> + <italicAngle value="0.0"/> + <underlinePosition value="-75"/> + <underlineThickness value="50"/> + <isFixedPitch value="0"/> + <minMemType42 value="0"/> + <maxMemType42 value="0"/> + <minMemType1 value="0"/> + <maxMemType1 value="0"/> + </post> + + <GSUB> + <Version value="0x00010000"/> + <ScriptList> + <ScriptRecord index="0"> + <ScriptTag value="DFLT"/> + <Script> + <DefaultLangSys> + <ReqFeatureIndex value="65535"/> + <FeatureIndex index="0" value="0"/> + </DefaultLangSys> + </Script> + </ScriptRecord> + </ScriptList> + <FeatureList> + <FeatureRecord index="0"> + <FeatureTag value="ccmp"/> + <Feature> + <LookupListIndex index="0" value="0"/> + </Feature> + </FeatureRecord> + </FeatureList> + <LookupList> + <Lookup index="0"> + <LookupType value="4"/> + <LookupFlag value="0"/> + <LigatureSubst index="0" Format="1"> + <LigatureSet glyph="U+1F9B0"> + <!-- U+1F9B0 U+1F3FB -> LigaForm0 --> + <Ligature components="U+1F3FB" glyph="LigaForm1"/> + + <!-- U+1F9B0 U+1F3FC -> LigaForm1 --> + <!-- + Following ligature is removed from the EmojiBase.ttf for verifying fallback + <Ligature components="U+1F3FC" glyph="LigaForm2"/> + --> + </LigatureSet> + <!-- + Following ligature is removed from the EmojiBase.ttf for verifying fallback + <LigatureSet glyph="U+1F9B1"> + <Ligature components="U+1F3FB" glyph="LigaForm3"/> + </LigatureSet> + --> + <LigatureSet glyph="U+1F9B2"> + <Ligature components="U+200D,U+1F9B3" glyph="LigaForm3"/> + </LigatureSet> + <!-- + Following ligature is removed from the EmojIBase.ttf for verifying fallback. + <LigatureSet glyph="U+1F9B6"> + <Ligature components="U+1F3FB" glyph="LigaForm3"/> + </LigatureSet> + --> + <LigatureSet glyph="U+1F1E6"> + <Ligature components="U+1F1E6" glyph="LigaForm6"/> + <Ligature components="U+1F1E7" glyph=".notdef"/> + </LigatureSet> + </LigatureSubst> + </Lookup> + </LookupList> + </GSUB> +</ttFont> diff --git a/tests/data/emoji_itemization.xml b/tests/data/emoji_itemization.xml new file mode 100644 index 0000000..d4213f9 --- /dev/null +++ b/tests/data/emoji_itemization.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<familyset version="22"> + <!-- Place NoGlyphFont here since the first font can be chosen if no font is + available for the code point. --> + <family> + <font weight="400" style="normal">Ascii.ttf</font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">EmojiSubset.ttf</font> + </family> + <family lang="und-Zsye"> + <font weight="400" style="normal">EmojiBase.ttf</font> + </family> +</familyset> diff --git a/tests/perftests/Android.bp b/tests/perftests/Android.bp index 83db1c5..19ed8eb 100644 --- a/tests/perftests/Android.bp +++ b/tests/perftests/Android.bp @@ -14,6 +14,10 @@ // limitations under the License. // +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_benchmark { name: "minikin_perftests", test_suites: ["device-tests"], diff --git a/tests/perftests/main.cpp b/tests/perftests/main.cpp index 11904e9..88e1900 100644 --- a/tests/perftests/main.cpp +++ b/tests/perftests/main.cpp @@ -13,32 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include <fcntl.h> -#include <sys/mman.h> -#include <sys/stat.h> #include <benchmark/benchmark.h> -#include <cutils/log.h> -#include <unicode/uclean.h> -#include <unicode/udata.h> int main(int argc, char** argv) { - const char* fn = "/apex/com.android.i18n/etc/icu/" U_ICUDATA_NAME ".dat"; - int fd = open(fn, O_RDONLY); - LOG_ALWAYS_FATAL_IF(fd == -1); - struct stat st; - LOG_ALWAYS_FATAL_IF(fstat(fd, &st) != 0); - void* data = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - - UErrorCode errorCode = U_ZERO_ERROR; - udata_setCommonData(data, &errorCode); - LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode)); - u_init(&errorCode); - LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode)); - benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); - u_cleanup(); return 0; } diff --git a/tests/stresstest/Android.bp b/tests/stresstest/Android.bp index fb73e09..784dc6a 100644 --- a/tests/stresstest/Android.bp +++ b/tests/stresstest/Android.bp @@ -14,6 +14,10 @@ // see how_to_run.txt for instructions on running these tests +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_test { name: "minikin_stress_tests", diff --git a/tests/stresstest/FontFamilyTest.cpp b/tests/stresstest/FontFamilyTest.cpp index 7554314..7a9813e 100644 --- a/tests/stresstest/FontFamilyTest.cpp +++ b/tests/stresstest/FontFamilyTest.cpp @@ -36,7 +36,7 @@ TEST_P(FontFamilyHarfBuzzCompatibilityTest, CoverageTest) { int ttcIndex = GetParam().second; auto font = std::make_shared<FreeTypeMinikinFontForTest>(fontPath); - std::vector<Font> fonts; + 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)); diff --git a/tests/unittest/Android.bp b/tests/unittest/Android.bp index 0971a06..cae9003 100644 --- a/tests/unittest/Android.bp +++ b/tests/unittest/Android.bp @@ -14,6 +14,10 @@ // see how_to_run.txt for instructions on running these tests +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_test { name: "minikin_tests", test_suites: ["device-tests"], @@ -41,12 +45,15 @@ cc_test { srcs: [ "AndroidLineBreakerHelperTest.cpp", "BidiUtilsTest.cpp", + "BufferTest.cpp", + "BoundsCacheTest.cpp", "CmapCoverageTest.cpp", "EmojiTest.cpp", "FontTest.cpp", "FontCollectionTest.cpp", "FontCollectionItemizeTest.cpp", "FontFamilyTest.cpp", + "FontFileParserTest.cpp", "FontLanguageListCacheTest.cpp", "FontUtilsTest.cpp", "HasherTest.cpp", diff --git a/tests/unittest/BoundsCacheTest.cpp b/tests/unittest/BoundsCacheTest.cpp new file mode 100644 index 0000000..8c727f9 --- /dev/null +++ b/tests/unittest/BoundsCacheTest.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "minikin/BoundsCache.h" + +#include "FontTestUtils.h" +#include "LocaleListCache.h" +#include "UnicodeUtils.h" + +namespace minikin { + +class TestableBoundsCache : public BoundsCache { +public: + TestableBoundsCache(uint32_t maxEntries) : BoundsCache(maxEntries) {} +}; + +class BoundsCapture { +public: + BoundsCapture() {} + + void operator()(const MinikinRect& rect, float advance) { + mRect = rect; + mAdvance = advance; + } + + const MinikinRect& rect() const { return mRect; } + float advance() const { return mAdvance; } + +private: + MinikinRect mRect; + float mAdvance; +}; + +TEST(BoundsCacheTest, cacheHitTest) { + auto text = utf8ToUtf16("android"); + Range range(0, text.size()); + MinikinPaint paint(buildFontCollection("Ascii.ttf")); + + TestableBoundsCache boundsCache(10); + + BoundsCapture bounds1; + boundsCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, bounds1); + + BoundsCapture bounds2; + boundsCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, bounds2); + + EXPECT_EQ(bounds1.rect(), bounds2.rect()); + EXPECT_EQ(bounds1.advance(), bounds2.advance()); +} + +TEST(BoundsCacheTest, cacheMissTest) { + auto text1 = utf8ToUtf16("android"); + auto text2 = utf8ToUtf16("αβγδζ"); + MinikinPaint paint(buildFontCollection("Ascii.ttf")); + + TestableBoundsCache boundsCache(10); + + BoundsCapture bounds1; + BoundsCapture bounds2; + + { + SCOPED_TRACE("Different text"); + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds1); + boundsCache.getOrCreate(text2, Range(0, text2.size()), paint, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + EXPECT_NE(bounds1.rect(), bounds2.rect()); + EXPECT_NE(bounds1.advance(), bounds2.advance()); + } + { + SCOPED_TRACE("Different range"); + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds1); + boundsCache.getOrCreate(text1, Range(1, text1.size()), paint, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + EXPECT_NE(bounds1.rect(), bounds2.rect()); + EXPECT_NE(bounds1.advance(), bounds2.advance()); + } + { + SCOPED_TRACE("Different collection"); + MinikinPaint paint1(buildFontCollection("Ascii.ttf")); + paint1.size = 10.0f; + paint1.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds1); + MinikinPaint paint2(buildFontCollection("Emoji.ttf")); + paint2.size = 10.0f; + paint2.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + EXPECT_NE(bounds1.rect(), bounds2.rect()); + EXPECT_NE(bounds1.advance(), bounds2.advance()); + } + { + SCOPED_TRACE("Different size"); + auto collection = buildFontCollection("Ascii.ttf"); + MinikinPaint paint1(collection); + paint1.size = 10.0f; + paint1.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds1); + MinikinPaint paint2(collection); + paint2.size = 20.0f; + paint2.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + EXPECT_NE(bounds1.rect(), bounds2.rect()); + EXPECT_NE(bounds1.advance(), bounds2.advance()); + } + { + SCOPED_TRACE("Different letter spacing"); + auto collection = buildFontCollection("Ascii.ttf"); + MinikinPaint paint1(collection); + paint1.letterSpacing = 0.0f; + paint1.size = 10.0f; + paint1.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint1, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds1); + MinikinPaint paint2(collection); + paint2.letterSpacing = 1.0f; + paint2.size = 10.0f; + paint2.scaleX = 1.0f; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint2, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + EXPECT_NE(bounds1.rect(), bounds2.rect()); + EXPECT_NE(bounds1.advance(), bounds2.advance()); + } +} + +TEST(BoundsCacheTest, cacheOverflowTest) { + auto text = utf8ToUtf16("android"); + Range range(0, text.size()); + MinikinPaint paint(buildFontCollection("Ascii.ttf")); + + TestableBoundsCache boundsCache(5); + + BoundsCapture bounds1; + boundsCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, bounds1); + + for (char c = 'a'; c <= 'z'; c++) { + auto text1 = utf8ToUtf16(std::string(10, c)); + BoundsCapture bounds2; + boundsCache.getOrCreate(text1, Range(0, text1.size()), paint, false /* LTR */, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, bounds2); + } + + BoundsCapture bounds3; + boundsCache.getOrCreate(text, range, paint, false /* LTR */, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, bounds3); + EXPECT_EQ(bounds1.rect(), bounds3.rect()); + EXPECT_EQ(bounds1.advance(), bounds3.advance()); +} + +} // namespace minikin diff --git a/tests/unittest/BufferTest.cpp b/tests/unittest/BufferTest.cpp new file mode 100644 index 0000000..8b1db33 --- /dev/null +++ b/tests/unittest/BufferTest.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2020 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 "minikin/Buffer.h" + +#include <gtest/gtest.h> + +namespace minikin { + +class TestObject { +public: + void writeTo(BufferWriter* writer) const { + // Total size = 20 + // uint8_t (1) + writer->write<uint8_t>(0xAB); + // padding (1), uint16_t (2) + writer->write<uint16_t>(0xCDEF); + // uint8_t(1) + writer->write<uint8_t>(0x01); + // padding (3), array size (4), uint32_t (4) * 2 + uint32_t uint32Array[] = {0x98765432, 0x98765433}; + writer->writeArray<uint32_t>(uint32Array, 2); + } +}; + +TEST(BufferTest, testMeasureWriteRead) { + TestObject testObject; + BufferWriter fakeWriter(nullptr); + testObject.writeTo(&fakeWriter); + ASSERT_EQ(fakeWriter.size(), 20u); + std::vector<uint8_t> buffer(fakeWriter.size()); + + BufferWriter writer(buffer.data()); + testObject.writeTo(&writer); + ASSERT_EQ(writer.size(), buffer.size()); + + BufferReader reader(buffer.data()); + ASSERT_EQ(reader.data(), buffer.data()); + ASSERT_EQ(reader.pos(), 0u); + ASSERT_EQ(reader.read<uint8_t>(), 0xABu); + ASSERT_EQ(reader.pos(), 1u); + ASSERT_EQ(reader.read<uint16_t>(), 0xCDEFu); + ASSERT_EQ(reader.pos(), 4u); + ASSERT_EQ(reader.read<uint8_t>(), 0x01u); + ASSERT_EQ(reader.pos(), 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); +} + +TEST(BufferTest, testSkip) { + TestObject testObject; + BufferWriter fakeWriter(nullptr); + testObject.writeTo(&fakeWriter); + ASSERT_EQ(fakeWriter.size(), 20u); + std::vector<uint8_t> buffer(fakeWriter.size()); + + BufferWriter writer(buffer.data()); + testObject.writeTo(&writer); + ASSERT_EQ(writer.size(), buffer.size()); + + BufferReader reader(buffer.data()); + ASSERT_EQ(reader.data(), buffer.data()); + ASSERT_EQ(reader.pos(), 0u); + reader.skip<uint8_t>(); + ASSERT_EQ(reader.pos(), 1u); + reader.read<uint16_t>(); + ASSERT_EQ(reader.pos(), 4u); + reader.skip<uint8_t>(); + ASSERT_EQ(reader.pos(), 5u); + reader.skipArray<uint32_t>(); + ASSERT_EQ(reader.pos(), 20u); +} + +} // namespace minikin diff --git a/tests/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp index 8cd95aa..6f1e194 100644 --- a/tests/unittest/FontCollectionItemizeTest.cpp +++ b/tests/unittest/FontCollectionItemizeTest.cpp @@ -21,6 +21,7 @@ #include <gtest/gtest.h> #include "minikin/FontFamily.h" +#include "minikin/FontFileParser.h" #include "minikin/LocaleList.h" #include "minikin/MinikinPaint.h" @@ -55,10 +56,15 @@ const char kMixedEmojiFont[] = "ColorTextMixedEmojiFont.ttf"; const char kHasCmapFormat14Font[] = "NoCmapFormat14.ttf"; const char kNoCmapFormat14Font[] = "VariationSelectorTest-Regular.ttf"; +struct Run { + FakedFont fakedFont; + int start; + int end; +}; + // Utility functions for calling itemize function. -std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection, - const char* str, FontStyle style, - const std::string& localeList) { +std::vector<Run> itemize(const std::shared_ptr<FontCollection>& collection, const char* str, + FontStyle style, const std::string& localeList) { const size_t BUF_SIZE = 256; uint16_t buf[BUF_SIZE]; size_t len; @@ -76,35 +82,38 @@ std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& for (uint32_t i = 0; i < runMax; ++i) { EXPECT_EQ(result[i].start, resultWithRunMax[i].start); EXPECT_EQ(result[i].end, resultWithRunMax[i].end); - EXPECT_EQ(result[i].fakedFont, resultWithRunMax[i].fakedFont); + EXPECT_EQ(result[i].familyMatch, resultWithRunMax[i].familyMatch); } } - return result; + std::vector<Run> runs; + for (const auto& r : result) { + runs.push_back( + {collection->getBestFont(U16StringPiece(buf, len), r, style), r.start, r.end}); + } + return runs; } // Overloaded version for default font style. -std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection, - const char* str, const std::string& localeList) { +std::vector<Run> itemize(const std::shared_ptr<FontCollection>& collection, const char* str, + const std::string& localeList) { return itemize(collection, str, FontStyle(), localeList); } // Overloaded version for empty locale list id. -std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection, - const char* str, FontStyle style) { +std::vector<Run> itemize(const std::shared_ptr<FontCollection>& collection, const char* str, + FontStyle style) { return itemize(collection, str, style, ""); } // Overloaded version for default font style and empty locale list id. -std::vector<FontCollection::Run> itemize(const std::shared_ptr<FontCollection>& collection, - const char* str) { +std::vector<Run> itemize(const std::shared_ptr<FontCollection>& collection, const char* str) { return itemize(collection, str, FontStyle(), ""); } // Utility function to obtain font path associated with run. -std::string getFontName(const FontCollection::Run& run) { - EXPECT_NE(nullptr, run.fakedFont.font); - return getBasename( - ((FreeTypeMinikinFontForTest*)run.fakedFont.font->typeface().get())->fontPath()); +std::string getFontName(const Run& run) { + EXPECT_NE(nullptr, run.fakedFont.font.get()); + return getBasename(run.fakedFont.font.get()->typeface()->GetFontPath()); } // Utility function to obtain LocaleList from string. @@ -515,13 +524,13 @@ TEST(FontCollectionItemizeTest, itemize_variationSelector) { ASSERT_EQ(1U, runs.size()); EXPECT_EQ(0, runs[0].start); EXPECT_EQ(1, runs[0].end); - EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0])); + EXPECT_TRUE(runs[0].fakedFont.font.get() == nullptr || kLatinFont == getFontName(runs[0])); runs = itemize(collection, "U+FE00", "zh-Hant"); ASSERT_EQ(1U, runs.size()); EXPECT_EQ(0, runs[0].start); EXPECT_EQ(1, runs[0].end); - EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0])); + EXPECT_TRUE(runs[0].fakedFont.font.get() == nullptr || kLatinFont == getFontName(runs[0])); // First font family (Regular.ttf) supports U+203C but doesn't support U+203C U+FE0F. // Emoji.ttf font supports U+203C U+FE0F. Emoji.ttf should be selected. @@ -652,13 +661,13 @@ TEST(FontCollectionItemizeTest, itemize_variationSelectorSupplement) { ASSERT_EQ(1U, runs.size()); EXPECT_EQ(0, runs[0].start); EXPECT_EQ(2, runs[0].end); - EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0])); + EXPECT_TRUE(runs[0].fakedFont.font.get() == nullptr || kLatinFont == getFontName(runs[0])); runs = itemize(collection, "U+E0100", "zh-Hant"); ASSERT_EQ(1U, runs.size()); EXPECT_EQ(0, runs[0].start); EXPECT_EQ(2, runs[0].end); - EXPECT_TRUE(runs[0].fakedFont.font == nullptr || kLatinFont == getFontName(runs[0])); + EXPECT_TRUE(runs[0].fakedFont.font.get() == nullptr || kLatinFont == getFontName(runs[0])); } TEST(FontCollectionItemizeTest, itemize_no_crash) { @@ -926,7 +935,7 @@ TEST(FontCollectionItemizeTest, itemize_LocaleScore) { // Prepare first font which doesn't supports U+9AA8 auto firstFamilyMinikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kNoGlyphFont)); - std::vector<Font> fonts; + std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(firstFamilyMinikinFont).build()); auto firstFamily = std::make_shared<FontFamily>(registerLocaleList("und"), FamilyVariant::DEFAULT, @@ -941,7 +950,7 @@ TEST(FontCollectionItemizeTest, itemize_LocaleScore) { for (size_t i = 0; i < testCase.fontLocales.size(); ++i) { auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(kJAFont)); - std::vector<Font> fonts; + 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), @@ -953,14 +962,15 @@ TEST(FontCollectionItemizeTest, itemize_LocaleScore) { // Do itemize auto runs = itemize(collection, "U+9AA8", testCase.userPreferredLocale); ASSERT_EQ(1U, runs.size()); - ASSERT_NE(nullptr, runs[0].fakedFont.font); + ASSERT_NE(nullptr, runs[0].fakedFont.font.get()); // First family doesn't support U+9AA8 and others support it, so the first font should not // be selected. - EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font->typeface().get()); + EXPECT_NE(firstFamilyMinikinFont.get(), runs[0].fakedFont.font.get()->typeface().get()); // Lookup used font family by MinikinFont*. - const int usedLocaleIndex = fontLocaleIdxMap[runs[0].fakedFont.font->typeface().get()]; + const int usedLocaleIndex = + fontLocaleIdxMap[runs[0].fakedFont.font.get()->typeface().get()]; EXPECT_EQ(testCase.selectedFontIndex, usedLocaleIndex); } } @@ -1521,10 +1531,10 @@ TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS) { // Both fontA/fontB support U+35A8 but don't support U+35A8 U+E0100. The first font should be // selected. auto runs = itemize(collection, "U+35A8 U+E0100"); - EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(familyA->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(reversedCollection, "U+35A8 U+E0100"); - EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(familyB->getFont(0), runs[0].fakedFont.font.get()); } // For b/29585939 @@ -1544,10 +1554,10 @@ TEST(FontCollectionItemizeTest, itemizeShouldKeepOrderForVS2) { // Both hasCmapFormat14Font/noCmapFormat14Font support U+5380 but don't support U+5380 U+E0100. // The first font should be selected. auto runs = itemize(collection, "U+5380 U+E0100"); - EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(hasCmapFormat14Family->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(reversedCollection, "U+5380 U+E0100"); - EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(noCmapFormat14Family->getFont(0), runs[0].fakedFont.font.get()); } TEST(FontCollectionItemizeTest, colorEmojiSelectionTest) { @@ -1561,44 +1571,44 @@ TEST(FontCollectionItemizeTest, colorEmojiSelectionTest) { // 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"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "en-US,en-Zsym"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "en-US,en-Zsye"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "en-US,en-Zsye"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-Zsym-JP"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-Zsym-JP"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-Zsye-JP"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-Zsye-JP"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-JP-u-em-text"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-JP-u-em-text"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-JP-u-em-emoji"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-JP-u-em-emoji"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-JP,und-Zsym"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-JP,und-Zsym"); - EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(textEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+203C", "ja-JP,und-Zsye"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "U+23E9", "ja-JP,und-Zsye"); - EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(colorEmojiFamily->getFont(0), runs[0].fakedFont.font.get()); } TEST(FontCollectionItemizeTest, customFallbackTest) { @@ -1612,11 +1622,60 @@ TEST(FontCollectionItemizeTest, customFallbackTest) { auto collection = std::make_shared<FontCollection>(families); auto runs = itemize(collection, "'a'", ""); - EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "'a'", "en-US"); - EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font.get()); runs = itemize(collection, "'a'", "ja-JP"); - EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font); + EXPECT_EQ(customFallbackFamily->getFont(0), runs[0].fakedFont.font.get()); +} + +std::string itemizeEmojiAndFontPostScriptName(const std::string& txt) { + auto firstFamily = buildFontFamily(kAsciiFont); + auto OverrideEmojiFamily = buildFontFamily("OverrideEmoji.ttf", "und-Zsye"); + auto emojiBaseFamily = buildFontFamily("EmojiBase.ttf", "und-Zsye"); + + std::vector<std::shared_ptr<FontFamily>> families = {firstFamily, OverrideEmojiFamily, + emojiBaseFamily}; + + auto collection = std::make_shared<FontCollection>(families); + auto runs = itemize(collection, txt.c_str()); + + EXPECT_EQ(1u, runs.size()); + return FontFileParser(runs[0].fakedFont.font->baseFont()).getPostScriptName().value(); +} + +TEST(FontCollectionItemizeTest, emojiFallback) { + // OverrideEmojiFont supports U+1F9B0, U+E0000, U+1F3FB and U+1F9B0 U+1F3FB sequence. + // Use Override font. + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0")); + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+E0000")); + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0 U+1F3FB")); + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B0 U+FE0F U+1F3FB")); + + // OverrideEmojiFont doesn't suppot U+1F9B6 U+E0001 and U+1F3FC. + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6")); + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+E0001")); + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6 U+1F3FC")); + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B6 U+FE0F U+1F3FC")); + + // OverrideEmojiFont support U+1F9B1, U+1F3FB but doesn't support the sequence U+1F9B1 U+1F3FB. + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B1")); + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F3FB")); + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B1 U+1F3FB")); + EXPECT_EQ("EmojiBaseFont", itemizeEmojiAndFontPostScriptName("U+1F9B1 U+FE0F U+1F3FB")); + + // Find the longest sequence if two sequences are supported. + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F9B2 U+200D U+1F9B3")); + EXPECT_EQ("EmojiBaseFont", + itemizeEmojiAndFontPostScriptName("U+1F9B2 U+200D U+1F9B3 U+200D U+1F9B4")); +} + +TEST(FontCollectionItemizeTest, emojiFlagFallback) { + // If the OverrideEmojiFont supports U+1F1E6 U+1F1E6, use that font. + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F1E6 U+1F1E6")); + + // Even if the OverrideEmojiFont directs to .notdef (i.e. Tofu glyph) for the sequence, use it. + EXPECT_EQ("OverrideEmojiFont", itemizeEmojiAndFontPostScriptName("U+1F1E6 U+1F1E7")); } } // namespace minikin diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp index 6b39508..aa9d4a8 100644 --- a/tests/unittest/FontCollectionTest.cpp +++ b/tests/unittest/FontCollectionTest.cpp @@ -19,6 +19,7 @@ #include <gtest/gtest.h> #include "FontTestUtils.h" +#include "FreeTypeMinikinFontForTest.h" #include "MinikinInternal.h" namespace minikin { @@ -56,22 +57,23 @@ void expectVSGlyphs(const FontCollection* fc, uint32_t codepoint, const std::set } } -TEST(FontCollectionTest, hasVariationSelectorTest) { - auto fc = buildFontCollection(kVsTestFont); - +void expectVSGlyphsForVsTestFont(const FontCollection* fc) { EXPECT_FALSE(fc->hasVariationSelector(0x82A6, 0)); - expectVSGlyphs(fc.get(), 0x82A6, - std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102})); + expectVSGlyphs(fc, 0x82A6, std::set<uint32_t>({0xFE00, 0xFE0E, 0xE0100, 0xE0101, 0xE0102})); EXPECT_FALSE(fc->hasVariationSelector(0x845B, 0)); - expectVSGlyphs(fc.get(), 0x845B, - std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103})); + expectVSGlyphs(fc, 0x845B, std::set<uint32_t>({0xFE01, 0xFE0E, 0xE0101, 0xE0102, 0xE0103})); EXPECT_FALSE(fc->hasVariationSelector(0x537F, 0)); - expectVSGlyphs(fc.get(), 0x537F, std::set<uint32_t>({0xFE0E})); + expectVSGlyphs(fc, 0x537F, std::set<uint32_t>({0xFE0E})); EXPECT_FALSE(fc->hasVariationSelector(0x717D, 0)); - expectVSGlyphs(fc.get(), 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103})); + expectVSGlyphs(fc, 0x717D, std::set<uint32_t>({0xFE02, 0xE0102, 0xE0103})); +} + +TEST(FontCollectionTest, hasVariationSelectorTest) { + auto fc = buildFontCollection(kVsTestFont); + expectVSGlyphsForVsTestFont(fc.get()); } const char kEmojiXmlFile[] = "emoji.xml"; @@ -175,4 +177,144 @@ TEST(FontCollectionTest, createWithVariations) { } } +std::vector<uint8_t> writeToBuffer( + const std::vector<std::shared_ptr<FontCollection>>& collections) { + BufferWriter fakeWriter(nullptr); + FontCollection::writeVector<writeFreeTypeMinikinFontForTest>(&fakeWriter, collections); + std::vector<uint8_t> buffer(fakeWriter.size()); + BufferWriter writer(buffer.data()); + FontCollection::writeVector<writeFreeTypeMinikinFontForTest>(&writer, collections); + return buffer; +} + +TEST(FontCollectionTest, bufferTest) { + { + 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); + EXPECT_EQ(1u, copied.size()); + expectVSGlyphsForVsTestFont(copied[0].get()); + EXPECT_EQ(original[0]->getSupportedTags(), copied[0]->getSupportedTags()); + // Id will be different. + EXPECT_NE(original[0]->getId(), copied[0]->getId()); + std::vector<uint8_t> newBuffer = writeToBuffer(copied); + EXPECT_EQ(buffer, newBuffer); + } + { + // 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); + 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); + EXPECT_EQ(2u, copied.size()); + EXPECT_EQ(copied[0]->mFamilies[0], copied[1]->mFamilies[0]); + std::vector<uint8_t> newBuffer = writeToBuffer(copied); + EXPECT_EQ(buffer, newBuffer); + } + { + // Test axes. + // This font has 'wdth' and 'wght' axes. + const char kMultiAxisFont[] = "MultiAxis.ttf"; + std::vector<std::shared_ptr<FontCollection>> original( + {buildFontCollection(kMultiAxisFont)}); + std::vector<uint8_t> buffer = writeToBuffer(original); + BufferReader reader(buffer.data()); + auto copied = FontCollection::readVector<readFreeTypeMinikinFontForTest>(&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'))); + std::vector<uint8_t> newBuffer = writeToBuffer(copied); + EXPECT_EQ(buffer, newBuffer); + } +} + +TEST(FontCollectionTest, FamilyMatchResultBuilderTest) { + using Builder = FontCollection::FamilyMatchResult::Builder; + EXPECT_TRUE(Builder().empty()); + EXPECT_EQ(0u, Builder().size()); + EXPECT_EQ(1u, Builder().add(5).size()); + EXPECT_EQ(2u, Builder().add(5).add(4).size()); + + // Reset + EXPECT_TRUE(Builder().add(5).reset().empty()); + EXPECT_EQ(0u, Builder().add(5).reset().size()); +} + +TEST(FontCollectionTest, FamilyMatchResultTest) { + using Builder = FontCollection::FamilyMatchResult::Builder; + + auto r = Builder().build(); + EXPECT_EQ(0u, r.size()); + EXPECT_TRUE(r.empty()); + + r = Builder().add(1).build(); + EXPECT_EQ(1u, r.size()); + EXPECT_FALSE(r.empty()); + EXPECT_EQ(1u, r[0]); + + r = Builder().add(1).add(2).build(); + EXPECT_EQ(2u, r.size()); + EXPECT_FALSE(r.empty()); + EXPECT_EQ(1u, r[0]); + EXPECT_EQ(2u, r[1]); +} + +TEST(FontCollectionTest, FamilyMatchResultTest_BuilderHoldeFirst7) { + auto b = FontCollection::FamilyMatchResult::Builder(); + for (uint8_t i = 0; i < 128; ++i) { + b.add(i); + } + auto r = b.build(); + EXPECT_EQ(7u, r.size()); + EXPECT_FALSE(r.empty()); + EXPECT_EQ(0u, r[0]); + EXPECT_EQ(1u, r[1]); + EXPECT_EQ(2u, r[2]); + EXPECT_EQ(3u, r[3]); + EXPECT_EQ(4u, r[4]); + EXPECT_EQ(5u, r[5]); + EXPECT_EQ(6u, r[6]); +} + +TEST(FontCollectionTest, FamilyMatchResultTest_iterator) { + auto b = FontCollection::FamilyMatchResult::Builder(); + for (uint8_t i = 0; i < 7; ++i) { + b.add(i); + } + auto r = b.build(); + EXPECT_EQ(7u, r.size()); + EXPECT_FALSE(r.empty()); + int i = 0; + for (auto v : r) { + EXPECT_EQ(i, v); + i++; + } +} + +TEST(FontCollectionTest, FamilyMatchResultTest_intersect) { + using Builder = FontCollection::FamilyMatchResult::Builder; + + EXPECT_EQ(Builder().add(1).add(2).add(3).build(), + FontCollection::FamilyMatchResult::intersect(Builder().add(1).add(2).add(3).build(), + Builder().add(1).add(2).add(3).build())); + + EXPECT_EQ(Builder().build(), + FontCollection::FamilyMatchResult::intersect(Builder().add(1).add(2).add(3).build(), + Builder().build())); + + EXPECT_EQ(Builder().build(), + FontCollection::FamilyMatchResult::intersect(Builder().add(2).add(4).add(6).build(), + Builder().add(1).add(3).add(5).build())); + + EXPECT_EQ(Builder().add(1).add(3).build(), + FontCollection::FamilyMatchResult::intersect(Builder().add(1).add(2).add(3).build(), + Builder().add(1).add(3).add(5).build())); +} + } // namespace minikin diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp index 2b70faf..fd2fc9a 100644 --- a/tests/unittest/FontFamilyTest.cpp +++ b/tests/unittest/FontFamilyTest.cpp @@ -20,6 +20,7 @@ #include "minikin/LocaleList.h" +#include "BufferUtils.h" #include "FontTestUtils.h" #include "FreeTypeMinikinFontForTest.h" #include "LocaleListCache.h" @@ -156,6 +157,40 @@ TEST(LocaleTest, testReconstruction) { EXPECT_EQ("zzz-Zzzz-999", createLocaleWithoutICUSanitization("zzz-Zzzz-999").getString()); } +TEST(LocaleTest, ReconstructFromIdentifierTest) { + std::string locales[] = { + // Language + "en", + "fil", + "und", + // Script + "en-Latn", + "fil-Taga", + "und-Zsye", + // Region + "en-US", + "fil-PH", + "es-419", + // Variant + "de-Latn-DE", + "de-Latn-DE-1901", + "de-Latn-DE-1996", + // Line break style + "ja-JP-u-lb-loose", + "ja-JP-u-lb-normal", + "ja-JP-u-lb-strict", + // Emoji subtag + "es-Latn-419-u-em-emoji", + // Everything + "de-Latn-DE-1996-u-lb-loose-u-em-emoji", + }; + for (const std::string& locale : locales) { + EXPECT_EQ(createLocaleWithoutICUSanitization(locale), + Locale(createLocaleWithoutICUSanitization(locale).getIdentifier())) + << "locale = " << locale; + } +} + TEST(LocaleTest, ScriptEqualTest) { EXPECT_TRUE(createLocale("en").isEqualScript(createLocale("en"))); EXPECT_TRUE(createLocale("en-Latn").isEqualScript(createLocale("en"))); @@ -515,9 +550,7 @@ void expectVSGlyphs(FontFamily* family, uint32_t codepoint, const std::set<uint3 } } -TEST_F(FontFamilyTest, hasVariationSelectorTest) { - std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont); - +void expectVSGlyphsForVsTestFont(FontFamily* family) { const uint32_t kVS1 = 0xFE00; const uint32_t kVS2 = 0xFE01; const uint32_t kVS3 = 0xFE02; @@ -528,23 +561,28 @@ TEST_F(FontFamilyTest, hasVariationSelectorTest) { const uint32_t kSupportedChar1 = 0x82A6; EXPECT_TRUE(family->getCoverage().get(kSupportedChar1)); - expectVSGlyphs(family.get(), kSupportedChar1, std::set<uint32_t>({kVS1, kVS17, kVS18, kVS19})); + expectVSGlyphs(family, kSupportedChar1, std::set<uint32_t>({kVS1, kVS17, kVS18, kVS19})); const uint32_t kSupportedChar2 = 0x845B; EXPECT_TRUE(family->getCoverage().get(kSupportedChar2)); - expectVSGlyphs(family.get(), kSupportedChar2, std::set<uint32_t>({kVS2, kVS18, kVS19, kVS20})); + expectVSGlyphs(family, kSupportedChar2, std::set<uint32_t>({kVS2, kVS18, kVS19, kVS20})); const uint32_t kNoVsSupportedChar = 0x537F; EXPECT_TRUE(family->getCoverage().get(kNoVsSupportedChar)); - expectVSGlyphs(family.get(), kNoVsSupportedChar, std::set<uint32_t>()); + expectVSGlyphs(family, kNoVsSupportedChar, std::set<uint32_t>()); const uint32_t kVsOnlySupportedChar = 0x717D; EXPECT_FALSE(family->getCoverage().get(kVsOnlySupportedChar)); - expectVSGlyphs(family.get(), kVsOnlySupportedChar, std::set<uint32_t>({kVS3, kVS19, kVS20})); + expectVSGlyphs(family, kVsOnlySupportedChar, std::set<uint32_t>({kVS3, kVS19, kVS20})); const uint32_t kNotSupportedChar = 0x845C; EXPECT_FALSE(family->getCoverage().get(kNotSupportedChar)); - expectVSGlyphs(family.get(), kNotSupportedChar, std::set<uint32_t>()); + expectVSGlyphs(family, kNotSupportedChar, std::set<uint32_t>()); +} + +TEST_F(FontFamilyTest, hasVariationSelectorTest) { + std::shared_ptr<FontFamily> family = buildFontFamily(kVsTestFont); + expectVSGlyphsForVsTestFont(family.get()); } TEST_F(FontFamilyTest, hasVSTableTest) { @@ -728,7 +766,7 @@ TEST_F(FontFamilyTest, closestMatch) { for (const TestCase& testCase : testCases) { std::vector<std::shared_ptr<MinikinFont>> dummyFonts; - std::vector<Font> fonts; + std::vector<std::shared_ptr<Font>> fonts; for (auto familyStyle : testCase.familyStyles) { std::shared_ptr<MinikinFont> dummyFont( new FreeTypeMinikinFontForTest(getTestFontPath(kTestFont))); @@ -756,4 +794,41 @@ 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); + } + { + // Font with axes + constexpr char kMultiAxisFont[] = "MultiAxis.ttf"; + std::shared_ptr<FontFamily> original = buildFontFamily(kMultiAxisFont); + 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->supportedAxes(), copied->supportedAxes()); + std::vector<uint8_t> newBuffer = + writeToBuffer<FontFamily, writeFreeTypeMinikinFontForTest>(*copied); + ASSERT_EQ(buffer, newBuffer); + } +} + } // namespace minikin diff --git a/tests/unittest/FontFileParserTest.cpp b/tests/unittest/FontFileParserTest.cpp new file mode 100644 index 0000000..d960217 --- /dev/null +++ b/tests/unittest/FontFileParserTest.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 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 "minikin/FontFileParser.h" + +#include <gtest/gtest.h> + +#include "FontTestUtils.h" +#include "FreeTypeMinikinFontForTest.h" +#include "PathUtils.h" + +namespace minikin { +namespace { + +static size_t writeU16(uint16_t x, uint8_t* out, size_t offset) { + out[offset] = x >> 8; + out[offset + 1] = x; + return offset + 2; +} + +static size_t writeU32(uint32_t x, uint8_t* out, size_t offset) { + out[offset] = x >> 24; + out[offset + 1] = x >> 16; + out[offset + 2] = x >> 8; + out[offset + 3] = x; + return offset + 4; +} + +class TestableFontFileParser : public FontFileParser { +public: + using FontFileParser::analyzeFontRevision; + using FontFileParser::checkPSName; +}; + +// Returns valid head table contents. +static std::vector<uint8_t> buildHeadTable(uint32_t fontRevision) { + std::vector<uint8_t> out(46); + size_t head = writeU16(1, out.data(), 0); // major version + head = writeU16(0, out.data(), head); // minor version + head = writeU32(fontRevision, out.data(), head); // fontRevision + head = writeU32(0xB1B0AFBA, out.data(), head); // checksum. (random value) + head = writeU32(0x5F0F3CF5, out.data(), head); // magicNumber + head = writeU16(0, out.data(), head); // flasgs + head = writeU16(1024, out.data(), head); // unitsPerEm + head = writeU32(123457890, out.data(), head); // created (random value) + head = writeU32(123457890, out.data(), head); // modified (random value) + head = writeU16(0, out.data(), head); // xMin + head = writeU16(100, out.data(), head); // yMin + head = writeU16(1024, out.data(), head); // xMax + head = writeU16(2048, out.data(), head); // yMax + head = writeU16(0, out.data(), head); // macStyle + head = writeU16(10, out.data(), head); // lowestRecPPEM + head = writeU16(1, out.data(), head); // fontDirectionHint + head = writeU16(1, out.data(), head); // indexToLocFormat + head = writeU16(0, out.data(), head); // glyphDataFormat; + + return out; +} + +TEST(FontFileParserTest, analyzeFontRevision) { + uint32_t rev = 0x12345678; + std::vector<uint8_t> head = buildHeadTable(rev); + + uint32_t out = 0; + EXPECT_TRUE(TestableFontFileParser::analyzeFontRevision(head.data(), head.size(), &out)); + EXPECT_EQ(rev, out); +} + +TEST(FontFileParserTest, headInvalidLength) { + uint32_t rev = 0x12345678; + std::vector<uint8_t> head = buildHeadTable(rev); + + uint32_t out = 0; + EXPECT_FALSE(TestableFontFileParser::analyzeFontRevision(head.data(), 6, &out)); +} + +TEST(FontFileParserTest, parseFontForRev) { + auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); + auto parser = FontFileParser(minikinFont->GetFontData(), minikinFont->GetFontSize(), 0); + + auto revision = parser.getFontRevision(); + EXPECT_TRUE(revision.has_value()); + EXPECT_EQ(0x00010000u, revision.value()); +} + +TEST(FontFileParser, checkPSName) { + EXPECT_TRUE(TestableFontFileParser::checkPSName("Roboto-Regular")); + EXPECT_TRUE(TestableFontFileParser::checkPSName("NotoColorEmoji")); + + // Space character is not allowed. + EXPECT_FALSE(TestableFontFileParser::checkPSName("Roboto Regular")); + EXPECT_FALSE(TestableFontFileParser::checkPSName("Noto Color Emoji")); + + // parens are not not allowed. + EXPECT_FALSE(TestableFontFileParser::checkPSName("Roboto (Regular)")); + EXPECT_FALSE(TestableFontFileParser::checkPSName("Noto <Color> {Emoji}")); + + // control characters are not allowed + EXPECT_FALSE(TestableFontFileParser::checkPSName("Roboto-Regular\b")); + EXPECT_FALSE(TestableFontFileParser::checkPSName("NotoColorEmoji\t")); + + // Up to 63 character is allowed. + EXPECT_FALSE(TestableFontFileParser::checkPSName(std::string(64, 'a'))); + + // Only printable ASCII is allowed. + EXPECT_FALSE(TestableFontFileParser::checkPSName("ろぼとふぉんと")); +} + +TEST(FontFileParserTest, parseFontForPSName) { + auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); + auto parser = FontFileParser(minikinFont->GetFontData(), minikinFont->GetFontSize(), 0); + + auto psName = parser.getPostScriptName(); + EXPECT_TRUE(psName.has_value()); + EXPECT_EQ("SampleFont-Regular", psName.value()); +} + +} // namespace +} // namespace minikin diff --git a/tests/unittest/FontLanguageListCacheTest.cpp b/tests/unittest/FontLanguageListCacheTest.cpp index e957cfc..e68922d 100644 --- a/tests/unittest/FontLanguageListCacheTest.cpp +++ b/tests/unittest/FontLanguageListCacheTest.cpp @@ -61,4 +61,25 @@ TEST(LocaleListCacheTest, getById) { EXPECT_EQ(japanese, locales2[1]); } +TEST(LocaleListCacheTest, buffer) { + std::string locales[] = {"en", "jp", "en,zh-Hans"}; + // Measure + BufferWriter fakeWriter(nullptr); + for (const std::string& locale : locales) { + LocaleListCache::writeTo(&fakeWriter, LocaleListCache::getId(locale)); + } + // Write + std::vector<uint8_t> buffer(fakeWriter.size()); + BufferWriter writer(buffer.data()); + for (const std::string& locale : locales) { + LocaleListCache::writeTo(&writer, LocaleListCache::getId(locale)); + } + // Read + BufferReader reader(buffer.data()); + for (const std::string& locale : locales) { + EXPECT_EQ(LocaleListCache::getId(locale), LocaleListCache::readFrom(&reader)) + << "locale = " << locale; + } +} + } // namespace minikin diff --git a/tests/unittest/FontTest.cpp b/tests/unittest/FontTest.cpp index ff2f9bc..68f5b51 100644 --- a/tests/unittest/FontTest.cpp +++ b/tests/unittest/FontTest.cpp @@ -18,28 +18,25 @@ #include <gtest/gtest.h> +#include "BufferUtils.h" #include "FontTestUtils.h" #include "FreeTypeMinikinFontForTest.h" namespace minikin { -TEST(FontTest, CopyTest) { +TEST(FontTest, BufferTest) { auto minikinFont = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath("Ascii.ttf")); - { - Font font = Font::Builder(minikinFont).build(); - { - Font copied(font); - EXPECT_EQ(font.typeface(), copied.typeface()); - EXPECT_EQ(font.style(), copied.style()); - EXPECT_EQ(font.baseFont(), copied.baseFont()); - } - { - Font copied = font; - EXPECT_EQ(font.typeface(), copied.typeface()); - EXPECT_EQ(font.style(), copied.style()); - EXPECT_EQ(font.baseFont(), copied.baseFont()); - } - } + std::shared_ptr<Font> original = Font::Builder(minikinFont).build(); + std::vector<uint8_t> buffer = writeToBuffer<Font, writeFreeTypeMinikinFontForTest>(*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); + EXPECT_EQ(buffer, newBuffer); } } // namespace minikin diff --git a/tests/unittest/GreedyLineBreakerTest.cpp b/tests/unittest/GreedyLineBreakerTest.cpp index 13cc03c..e9da1a1 100644 --- a/tests/unittest/GreedyLineBreakerTest.cpp +++ b/tests/unittest/GreedyLineBreakerTest.cpp @@ -93,6 +93,33 @@ private: std::vector<uint8_t> mHyphenationPattern; }; +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); + MinikinPaint paint(fc); + paint.size = 56.0f; // Make 1em=56px + paint.scaleX = 1; + paint.letterSpacing = -0.093f; + paint.localeListId = LocaleListCache::getId("en-US"); + const std::vector<uint16_t> textBuffer = utf8ToUtf16("8888888888888888888"); + + float measured = Layout::measureText(textBuffer, Range(0, textBuffer.size()), Bidi::LTR, paint, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr); + + builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); + std::unique_ptr<MeasuredText> measuredText = + builder.build(textBuffer, false /* compute hyphenation */, + false /* compute full layout */, nullptr /* no hint */); + RectangleLineWidth rectangleLineWidth(measured); + TabStops tabStops(nullptr, 0, 10); + LineBreakResult r = breakLineGreedy(textBuffer, *measuredText, rectangleLineWidth, tabStops, + false /* do hyphenation */); + + EXPECT_EQ(1u, r.breakPoints.size()); +} + TEST_F(GreedyLineBreakerTest, testBreakWithoutHyphenation) { constexpr bool NO_HYPHEN = false; // No hyphenation in this test case. const std::vector<uint16_t> textBuf = utf8ToUtf16("This is an example text."); diff --git a/tests/unittest/HasherTest.cpp b/tests/unittest/HasherTest.cpp index 8e11cc6..02cbda8 100644 --- a/tests/unittest/HasherTest.cpp +++ b/tests/unittest/HasherTest.cpp @@ -35,4 +35,9 @@ TEST(HasherTest, hasherTest) { EXPECT_EQ(hasher.hash(), hasher.hash()); } +TEST(HasherTest, hasherTestFloat) { + float x = 1.1f; + EXPECT_NE(Hasher().update(x).hash(), Hasher().update(1).hash()); +} + } // namespace minikin diff --git a/tests/unittest/ICUEnvironment.h b/tests/unittest/ICUEnvironment.h deleted file mode 100644 index 8fd285f..0000000 --- a/tests/unittest/ICUEnvironment.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef MINIKIN_TEST_ICU_ENVIRONMENT_H -#define MINIKIN_TEST_ICU_ENVIRONMENT_H - -// low level file access for mapping ICU data -#include <fcntl.h> -#include <sys/mman.h> -#include <sys/stat.h> - -#include <cutils/log.h> -#include <gtest/gtest.h> -#include <unicode/uclean.h> -#include <unicode/udata.h> - -namespace minikin { - -class ICUEnvironment : public testing::Environment { -public: - ICUEnvironment() : testing::Environment(), mData(nullptr), mSize(0) {} - - void* mData; - size_t mSize; - - virtual void SetUp() override { - const char* fn = "/apex/com.android.i18n/etc/icu/" U_ICUDATA_NAME ".dat"; - int fd = open(fn, O_RDONLY); - LOG_ALWAYS_FATAL_IF(fd == -1); - struct stat sb; - LOG_ALWAYS_FATAL_IF(fstat(fd, &sb) != 0); - - mSize = sb.st_size; - void* mData = mmap(nullptr, mSize, PROT_READ, MAP_SHARED, fd, 0); - close(fd); - - UErrorCode errorCode = U_ZERO_ERROR; - udata_setCommonData(mData, &errorCode); - LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode)); - - errorCode = U_ZERO_ERROR; - u_init(&errorCode); - LOG_ALWAYS_FATAL_IF(U_FAILURE(errorCode)); - } - - virtual void TearDown() override { - u_cleanup(); - munmap(mData, mSize); - } -}; - -} // namespace minikin -#endif // MINIKIN_TEST_ICU_ENVIRONMENT_H diff --git a/tests/unittest/LayoutCoreTest.cpp b/tests/unittest/LayoutCoreTest.cpp index ef972a0..2ab7543 100644 --- a/tests/unittest/LayoutCoreTest.cpp +++ b/tests/unittest/LayoutCoreTest.cpp @@ -74,7 +74,6 @@ TEST(LayoutPieceTest, doLayoutTest) { auto layout = buildLayout("I", {"LayoutTestFont.ttf"}); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -87,7 +86,6 @@ TEST(LayoutPieceTest, doLayoutTest) { EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -103,7 +101,6 @@ TEST(LayoutPieceTest, doLayoutTest) { EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 60.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-100.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -130,7 +127,6 @@ TEST(LayoutPieceTest, doLayoutTest_MultiFont) { EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(10.0f, 0), layout.pointAt(1)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent()); EXPECT_EQ(2u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -146,7 +142,6 @@ TEST(LayoutPieceTest, doLayoutTest_MultiFont) { EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); EXPECT_EQ(Point(20.0f, 0), layout.pointAt(1)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 30.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-160.0f, 40.0f), layout.extent()); EXPECT_EQ(2u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -168,7 +163,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("fi", {"Ligature.ttf"}); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -181,7 +175,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("ff", {"Ligature.ttf"}); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -194,7 +187,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("fi", {"Ligature.ttf"}, "'liga' off"); EXPECT_EQ(1u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -207,7 +199,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("ff", {"Ligature.ttf"}, "'liga' off"); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -222,7 +213,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("fii", {"Ligature.ttf"}); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); @@ -238,7 +228,6 @@ TEST(LayoutPieceTest, doLayoutTest_Ligature) { auto layout = buildLayout("if", {"Ligature.ttf"}); EXPECT_EQ(2u, layout.glyphCount()); EXPECT_EQ(Point(0, 0), layout.pointAt(0)); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.bounds()); EXPECT_EQ(MinikinExtent(-80.0f, 20.0f), layout.extent()); EXPECT_EQ(1u, layout.fonts().size()); EXPECT_TRUE(layout.fontAt(0).font); diff --git a/tests/unittest/LayoutTest.cpp b/tests/unittest/LayoutTest.cpp index 4b97cee..7771051 100644 --- a/tests/unittest/LayoutTest.cpp +++ b/tests/unittest/LayoutTest.cpp @@ -20,6 +20,7 @@ #include "minikin/FontCollection.h" #include "minikin/LayoutPieces.h" +#include "minikin/Measurement.h" #include "FontTestUtils.h" #include "UnicodeUtils.h" @@ -35,6 +36,12 @@ static void expectAdvances(const std::vector<float>& expected, const std::vector } } +static void getBounds(const U16StringPiece& text, Bidi bidiFlags, const MinikinPaint& paint, + MinikinRect* out) { + getBounds(text, Range(0, text.size()), bidiFlags, paint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, out); +} + class LayoutTest : public testing::Test { protected: LayoutTest() : mCollection(nullptr) {} @@ -64,7 +71,8 @@ TEST_F(LayoutTest, doLayoutTest) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(70.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(70.0f, rect.mRight); @@ -82,7 +90,8 @@ TEST_F(LayoutTest, doLayoutTest) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(90.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(90.0f, rect.mRight); @@ -100,7 +109,8 @@ TEST_F(LayoutTest, doLayoutTest) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(160.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(160.0f, rect.mRight); @@ -118,7 +128,8 @@ TEST_F(LayoutTest, doLayoutTest) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(110.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(110.0f, rect.mRight); @@ -148,7 +159,8 @@ TEST_F(LayoutTest, doLayoutTest_wordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(70.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(70.0f, rect.mRight); @@ -166,7 +178,8 @@ TEST_F(LayoutTest, doLayoutTest_wordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(95.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(95.0f, rect.mRight); @@ -185,7 +198,8 @@ TEST_F(LayoutTest, doLayoutTest_wordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(170.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(170.0f, rect.mRight); @@ -205,7 +219,8 @@ TEST_F(LayoutTest, doLayoutTest_wordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(120.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(120.0f, rect.mRight); @@ -238,7 +253,8 @@ TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(70.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(70.0f, rect.mRight); @@ -256,7 +272,8 @@ TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(85.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(85.0f, rect.mRight); @@ -275,7 +292,8 @@ TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(140.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(140.0f, rect.mRight); @@ -295,7 +313,8 @@ TEST_F(LayoutTest, doLayoutTest_negativeWordSpacing) { Layout layout(text, range, Bidi::LTR, paint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); EXPECT_EQ(100.0f, layout.getAdvance()); - layout.getBounds(&rect); + + getBounds(text, Bidi::LTR, paint, &rect); EXPECT_EQ(0.0f, rect.mLeft); EXPECT_EQ(10.0f, rect.mTop); EXPECT_EQ(100.0f, rect.mRight); diff --git a/tests/unittest/MeasuredTextTest.cpp b/tests/unittest/MeasuredTextTest.cpp index 1934ed8..e5766a1 100644 --- a/tests/unittest/MeasuredTextTest.cpp +++ b/tests/unittest/MeasuredTextTest.cpp @@ -19,6 +19,7 @@ #include <gtest/gtest.h> #include "minikin/LineBreaker.h" +#include "minikin/Measurement.h" #include "FontTestUtils.h" #include "UnicodeUtils.h" @@ -75,6 +76,34 @@ TEST(MeasuredTextTest, getBoundsTest) { EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), mt->getBounds(text, Range(0, text.size()))); } +TEST(MeasuredTextTest, getBoundsTest_LTR) { + auto text = utf8ToUtf16("\u0028"); // U+0028 has 1em in LTR, 3em in RTL. + auto font = buildFontCollection("Bbox.ttf"); + + MeasuredTextBuilder builder; + MinikinPaint paint(font); + paint.size = 10.0f; + builder.addStyleRun(0, text.size(), std::move(paint), false /* is RTL */); + auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, + nullptr /* no hint */); + + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), mt->getBounds(text, Range(0, 1))); +} + +TEST(MeasuredTextTest, getBoundsTest_RTL) { + auto text = utf8ToUtf16("\u0028"); // U+0028 has 1em in LTR, 3em in RTL. + auto font = buildFontCollection("Bbox.ttf"); + + MeasuredTextBuilder builder; + MinikinPaint paint(font); + paint.size = 10.0f; + builder.addStyleRun(0, text.size(), std::move(paint), true /* is RTL */); + auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, + nullptr /* no hint */); + + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), mt->getBounds(text, Range(0, 2))); +} + TEST(MeasuredTextTest, getBoundsTest_multiStyle) { auto text = utf8ToUtf16("Hello, World!"); auto font = buildFontCollection("Ascii.ttf"); @@ -155,6 +184,7 @@ TEST(MeasuredTextTest, buildLayoutTest) { auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, nullptr /* no hint */); + MinikinRect rect; MinikinPaint samePaint(font); samePaint.size = 10.0f; @@ -171,7 +201,9 @@ TEST(MeasuredTextTest, buildLayoutTest) { EXPECT_EQ(10.0f, layout.getAdvance()); EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 1), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -185,7 +217,9 @@ TEST(MeasuredTextTest, buildLayoutTest) { EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(10.0f, layout.getCharAdvance(1)); EXPECT_EQ(2u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 2), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -196,7 +230,9 @@ TEST(MeasuredTextTest, buildLayoutTest) { EXPECT_EQ(10.0f, layout.getAdvance()); EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(1, 2), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, text.size()), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -210,7 +246,9 @@ TEST(MeasuredTextTest, buildLayoutTest) { } EXPECT_EQ(130.0f, layout.getAdvance()); EXPECT_EQ(text.size(), layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, text.size()), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 130.0f, 0.0f), rect); } TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { @@ -229,6 +267,7 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, nullptr /* no hint */); + MinikinRect rect; MinikinPaint samePaint(font); samePaint.size = 10.0f; @@ -245,7 +284,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { EXPECT_EQ(10.0f, layout.getAdvance()); EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 1), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -259,7 +300,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(10.0f, layout.getCharAdvance(1)); EXPECT_EQ(2u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 2), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 20.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(1, 2), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -270,7 +313,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { EXPECT_EQ(10.0f, layout.getAdvance()); EXPECT_EQ(10.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(1, 2), Bidi::LTR, samePaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 10.0f, 10.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(7, 7), fullContext, samePaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -287,7 +332,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle) { EXPECT_EQ(20.0f, layout.getAdvance()); EXPECT_EQ(20.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(7, 8), Bidi::LTR, samePaint2, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), rect); } TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { @@ -302,6 +349,7 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, nullptr /* no hint */); + MinikinRect rect; MinikinPaint differentPaint(font); differentPaint.size = 20.0f; @@ -318,7 +366,9 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { EXPECT_EQ(20.0f, layout.getAdvance()); EXPECT_EQ(20.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 1), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -332,7 +382,9 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { EXPECT_EQ(20.0f, layout.getCharAdvance(0)); EXPECT_EQ(20.0f, layout.getCharAdvance(1)); EXPECT_EQ(2u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 20.0f, 40.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 2), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 20.0f, 40.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -343,7 +395,9 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { EXPECT_EQ(20.0f, layout.getAdvance()); EXPECT_EQ(20.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(1, 2), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 20.0f, 20.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -357,7 +411,9 @@ TEST(MeasuredTextTest, buildLayoutTest_differentPaint) { } EXPECT_EQ(260.0f, layout.getAdvance()); EXPECT_EQ(text.size(), layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 20.0f, 260.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, text.size()), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 20.0f, 260.0f, 0.0f), rect); } TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { @@ -376,6 +432,7 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { auto mt = builder.build(text, true /* hyphenation */, true /* full layout */, nullptr /* no hint */); + MinikinRect rect; MinikinPaint differentPaint(font); differentPaint.size = 30.0f; @@ -392,7 +449,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(30.0f, layout.getAdvance()); EXPECT_EQ(30.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 1), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, 2), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -406,7 +465,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(30.0f, layout.getCharAdvance(0)); EXPECT_EQ(30.0f, layout.getCharAdvance(1)); EXPECT_EQ(2u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, 2), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(1, 2), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -417,7 +478,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(30.0f, layout.getAdvance()); EXPECT_EQ(30.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(1, 2), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(7, 7), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -432,7 +495,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(30.0f, layout.getAdvance()); EXPECT_EQ(30.0f, layout.getCharAdvance(0)); EXPECT_EQ(1u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(7, 8), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 30.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(6, 8), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -446,7 +511,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { EXPECT_EQ(30.0f, layout.getCharAdvance(0)); EXPECT_EQ(30.0f, layout.getCharAdvance(1)); EXPECT_EQ(2u, layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(6, 8), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 60.0f, 0.0f), rect); layout = mt->buildLayout(text, Range(0, text.size()), fullContext, differentPaint, StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT); @@ -460,7 +527,9 @@ TEST(MeasuredTextTest, buildLayoutTest_multiStyle_differentPaint) { } EXPECT_EQ(390.0f, layout.getAdvance()); EXPECT_EQ(text.size(), layout.getAdvances().size()); - EXPECT_EQ(MinikinRect(0.0f, 30.0f, 390.0f, 0.0f), layout.getBounds()); + getBounds(text, Range(0, text.size()), Bidi::LTR, differentPaint, StartHyphenEdit::NO_EDIT, + EndHyphenEdit::NO_EDIT, &rect); + EXPECT_EQ(MinikinRect(0.0f, 30.0f, 390.0f, 0.0f), rect); } } // namespace minikin diff --git a/tests/unittest/OptimalLineBreakerTest.cpp b/tests/unittest/OptimalLineBreakerTest.cpp index 51aab60..d6801cc 100644 --- a/tests/unittest/OptimalLineBreakerTest.cpp +++ b/tests/unittest/OptimalLineBreakerTest.cpp @@ -2080,5 +2080,33 @@ TEST_F(OptimalLineBreakerTest, testControllCharAfterSpace) { << toString(textBuf, actual); } } + +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); + MinikinPaint paint(fc); + paint.size = 56.0f; // Make 1em=56px + paint.scaleX = 1; + paint.letterSpacing = -0.093f; + paint.localeListId = LocaleListCache::getId("en-US"); + const std::vector<uint16_t> textBuffer = utf8ToUtf16("8888888888888888888"); + + float measured = Layout::measureText(textBuffer, Range(0, textBuffer.size()), Bidi::LTR, paint, + StartHyphenEdit::NO_EDIT, EndHyphenEdit::NO_EDIT, nullptr); + + builder.addStyleRun(0, textBuffer.size(), std::move(paint), false); + std::unique_ptr<MeasuredText> measuredText = + builder.build(textBuffer, false /* compute hyphenation */, + false /* compute full layout */, nullptr /* no hint */); + RectangleLineWidth rectangleLineWidth(measured); + TabStops tabStops(nullptr, 0, 10); + LineBreakResult r = doLineBreak(textBuffer, *measuredText, BreakStrategy::Balanced, + HyphenationFrequency::None, measured); + + EXPECT_EQ(1u, r.breakPoints.size()); +} + } // namespace } // namespace minikin diff --git a/tests/unittest/SparseBitSetTest.cpp b/tests/unittest/SparseBitSetTest.cpp index 03a14d9..8c67964 100644 --- a/tests/unittest/SparseBitSetTest.cpp +++ b/tests/unittest/SparseBitSetTest.cpp @@ -20,6 +20,8 @@ #include <gtest/gtest.h> +#include "BufferUtils.h" + namespace minikin { TEST(SparseBitSetTest, randomTest) { @@ -52,4 +54,29 @@ TEST(SparseBitSetTest, randomTest) { } } +TEST(SparseBitSetTest, bufferTest) { + std::vector<uint32_t> range({10, 20}); + SparseBitSet originalBitset(range.data(), range.size() / 2); + std::vector<uint8_t> buffer = writeToBuffer(originalBitset); + BufferReader reader(buffer.data()); + SparseBitSet bitset(&reader); + + for (size_t i = 0; i < 10; ++i) ASSERT_FALSE(bitset.get(i)) << i; + for (size_t i = 10; i < 20; ++i) ASSERT_TRUE(bitset.get(i)) << i; + for (size_t i = 20; i < 30; ++i) ASSERT_FALSE(bitset.get(i)) << i; + std::vector<uint8_t> newBuffer = writeToBuffer(bitset); + ASSERT_EQ(buffer, newBuffer); +} + +TEST(SparseBitSetTest, emptyBitSetBufferTest) { + SparseBitSet empty; + std::vector<uint8_t> buffer = writeToBuffer(empty); + BufferReader reader(buffer.data()); + SparseBitSet bitset(&reader); + + ASSERT_FALSE(bitset.get(0)); + std::vector<uint8_t> newBuffer = writeToBuffer(bitset); + ASSERT_EQ(buffer, newBuffer); +} + } // namespace minikin diff --git a/tests/unittest/SystemFontsTest.cpp b/tests/unittest/SystemFontsTest.cpp index fe603a9..f1b0109 100644 --- a/tests/unittest/SystemFontsTest.cpp +++ b/tests/unittest/SystemFontsTest.cpp @@ -21,6 +21,7 @@ #include "minikin/FontCollection.h" #include "FontTestUtils.h" +#include "PathUtils.h" namespace minikin { namespace { @@ -30,10 +31,18 @@ public: TestableSystemFonts() : SystemFonts() {} virtual ~TestableSystemFonts() {} - std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) const { + std::shared_ptr<FontCollection> findFontCollection(const std::string& familyName) { return findFontCollectionInternal(familyName); } + void addFontMap(std::shared_ptr<FontCollection>&& collections) { + addFontMapInternal(std::move(collections)); + } + + void getFontSet(std::function<void(const std::vector<std::shared_ptr<Font>>&)> func) { + getFontSetInternal(func); + } + void registerFallback(const std::string& familyName, const std::shared_ptr<FontCollection>& fc) { registerFallbackInternal(familyName, fc); @@ -66,5 +75,44 @@ TEST(SystemFontsTest, registerDefaultAndFallback) { EXPECT_EQ(fc2, systemFonts.findFontCollection("sans")); } +TEST(SystemFontsTest, updateDefaultAndFallback) { + TestableSystemFonts systemFonts; + auto fc1 = buildFontCollection("Ascii.ttf"); + auto fc2 = buildFontCollection("Bold.ttf"); + systemFonts.registerDefault(fc1); + systemFonts.registerFallback("sans", fc2); + systemFonts.registerDefault(fc2); + systemFonts.registerFallback("sans", fc1); + EXPECT_EQ(fc2, systemFonts.findFontCollection("unknown-name")); + EXPECT_EQ(fc1, systemFonts.findFontCollection("sans")); +} + +TEST(SystemFontTest, getAvailableFont_dedupFonts) { + TestableSystemFonts systemFonts; + auto asciiFamily = buildFontFamily("Ascii.ttf"); + auto boldFamily = buildFontFamily("Bold.ttf"); + auto boldItalicFamily = buildFontFamily("BoldItalic.ttf"); + + 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)); + + systemFonts.addFontMap(std::move(fc1)); + systemFonts.addFontMap(std::move(fc2)); + + systemFonts.getFontSet([](const std::vector<std::shared_ptr<Font>>& fonts) { + EXPECT_EQ(3u, fonts.size()); // Ascii, Bold and BoldItalic + std::unordered_set<std::string> fontPaths; + for (const auto& font : fonts) { + fontPaths.insert(getBasename(font->typeface()->GetFontPath())); + } + + EXPECT_TRUE(fontPaths.find("Ascii.ttf") != fontPaths.end()); + EXPECT_TRUE(fontPaths.find("Bold.ttf") != fontPaths.end()); + EXPECT_TRUE(fontPaths.find("BoldItalic.ttf") != fontPaths.end()); + }); +} + } // namespace } // namespace minikin diff --git a/tests/unittest/TestMain.cpp b/tests/unittest/TestMain.cpp index 6680fbd..05e3da4 100644 --- a/tests/unittest/TestMain.cpp +++ b/tests/unittest/TestMain.cpp @@ -14,12 +14,9 @@ * limitations under the License. */ -#include "ICUEnvironment.h" - #include <gtest/gtest.h> int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); - ::testing::AddGlobalTestEnvironment(new minikin::ICUEnvironment); return RUN_ALL_TESTS(); } diff --git a/tests/util/Android.bp b/tests/util/Android.bp index 8bf125b..1d3d8cf 100644 --- a/tests/util/Android.bp +++ b/tests/util/Android.bp @@ -1,3 +1,7 @@ +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + cc_library_static { name: "libminikin-tests-util", srcs: [ diff --git a/tests/util/BufferUtils.h b/tests/util/BufferUtils.h new file mode 100644 index 0000000..355e74e --- /dev/null +++ b/tests/util/BufferUtils.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 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_TEST_BUFFER_UTILS_H +#define MINIKIN_TEST_BUFFER_UTILS_H + +#include <minikin/Buffer.h> +#include <vector> + +namespace minikin { + +template <class T> +std::vector<uint8_t> allocateBuffer(const T& t) { + BufferWriter writer(nullptr); + t.writeTo(&writer); + // Fill with 0xFF for debugging. + return std::vector<uint8_t>(writer.size(), 0xFFu); +} + +template <class T, auto arg> +std::vector<uint8_t> allocateBuffer(const T& t) { + BufferWriter writer(nullptr); + t.template writeTo<arg>(&writer); + // Fill with 0xFF for debugging. + return std::vector<uint8_t>(writer.size(), 0xFFu); +} + +template <class T> +std::vector<uint8_t> writeToBuffer(const T& t) { + std::vector<uint8_t> buffer = allocateBuffer(t); + BufferWriter writer(buffer.data()); + t.writeTo(&writer); + 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 4143c04..5370ab6 100644 --- a/tests/util/FontTestUtils.cpp +++ b/tests/util/FontTestUtils.cpp @@ -67,7 +67,7 @@ std::vector<std::shared_ptr<FontFamily>> getFontFamilies(const std::string& font } } - std::vector<Font> fonts; + std::vector<std::shared_ptr<Font>> fonts; for (xmlNode* fontNode = familyNode->children; fontNode; fontNode = fontNode->next) { if (xmlStrcmp(fontNode->name, (const xmlChar*)"font") != 0) { continue; @@ -124,7 +124,7 @@ std::shared_ptr<FontCollection> buildFontCollection(const std::string& filePath) std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath) { auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath)); - std::vector<Font> fonts; + std::vector<std::shared_ptr<Font>> fonts; fonts.push_back(Font::Builder(font).build()); return std::make_shared<FontFamily>(std::move(fonts)); } @@ -132,7 +132,7 @@ std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath) { std::shared_ptr<FontFamily> buildFontFamily(const std::string& filePath, const std::string& lang, bool isCustomFallback) { auto font = std::make_shared<FreeTypeMinikinFontForTest>(getTestFontPath(filePath)); - std::vector<Font> fonts; + 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); diff --git a/tests/util/FreeTypeMinikinFontForTest.cpp b/tests/util/FreeTypeMinikinFontForTest.cpp index 1ea0631..1be466a 100644 --- a/tests/util/FreeTypeMinikinFontForTest.cpp +++ b/tests/util/FreeTypeMinikinFontForTest.cpp @@ -37,8 +37,6 @@ namespace minikin { namespace { -static int uniqueId = 0; - constexpr FT_Int32 LOAD_FLAG = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH; @@ -62,7 +60,7 @@ void loadGlyphOrDie(uint32_t glyphId, float size, FT_Face face) { } // namespace FreeTypeMinikinFontForTest::FreeTypeMinikinFontForTest(const std::string& font_path, int index) - : MinikinFont(uniqueId++), mFontPath(font_path), mFontIndex(index) { + : mFontPath(font_path), mFontIndex(index) { int fd = open(font_path.c_str(), O_RDONLY); LOG_ALWAYS_FATAL_IF(fd == -1, "Open failed: %s", font_path.c_str()); struct stat st = {}; @@ -114,4 +112,18 @@ void FreeTypeMinikinFontForTest::GetFontExtent(MinikinExtent* extent, const Mini extent->descent = -static_cast<float>(mFtFace->descender) * paint.size / upem; } +void writeFreeTypeMinikinFontForTest(BufferWriter* writer, const MinikinFont* typeface) { + writer->writeString(typeface->GetFontPath()); +} + +std::shared_ptr<MinikinFont> loadFreeTypeMinikinFontForTest(BufferReader reader) { + std::string fontPath(reader.readString()); + return std::make_shared<FreeTypeMinikinFontForTest>(fontPath); +} + +Font::TypefaceLoader* readFreeTypeMinikinFontForTest(BufferReader* reader) { + reader->skipString(); // fontPath + return &loadFreeTypeMinikinFontForTest; +} + } // namespace minikin diff --git a/tests/util/FreeTypeMinikinFontForTest.h b/tests/util/FreeTypeMinikinFontForTest.h index 4b6ea05..4cdb6d8 100644 --- a/tests/util/FreeTypeMinikinFontForTest.h +++ b/tests/util/FreeTypeMinikinFontForTest.h @@ -19,6 +19,8 @@ #include <string> +#include "minikin/Buffer.h" +#include "minikin/Font.h" #include "minikin/MinikinFont.h" #include <ft2build.h> @@ -43,8 +45,7 @@ public: void GetFontExtent(MinikinExtent* extent, const MinikinPaint& paint, const FontFakery& fakery) const override; - const std::string& fontPath() const { return mFontPath; } - + const std::string& GetFontPath() const override { return mFontPath; } const void* GetFontData() const { return mFontData; } size_t GetFontSize() const { return mFontSize; } int GetFontIndex() const { return mFontIndex; } @@ -63,6 +64,10 @@ private: MINIKIN_PREVENT_COPY_AND_ASSIGN(FreeTypeMinikinFontForTest); }; +void writeFreeTypeMinikinFontForTest(BufferWriter* writer, const MinikinFont* typeface); + +Font::TypefaceLoader* readFreeTypeMinikinFontForTest(BufferReader* reader); + } // namespace minikin #endif // MINIKIN_TEST_FREE_TYPE_MINIKIN_FONT_FOR_TEST_H |