summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 14:51:50 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2021-06-21 14:51:50 +0000
commitb6a302a674ae3671f16437e6983ab88ea6cc3a0e (patch)
treeec5bc1c91ca33edd214357b76698c03f0f128600
parentc3bd9a03ecb1a19393046c522f5b6c91d615f7c5 (diff)
parent667ff94664fe2500ade9ab3b6c81ed82c08f8dd4 (diff)
downloadminikin-android12-mainline-extservices-release.tar.gz
Change-Id: I2e432e55a9ea8dad1af932c301af039d34e10915
-rw-r--r--Android.bp10
-rw-r--r--app/Android.bp4
-rw-r--r--include/minikin/BoundsCache.h110
-rw-r--r--include/minikin/Buffer.h151
-rw-r--r--include/minikin/Font.h90
-rw-r--r--include/minikin/FontCollection.h161
-rw-r--r--include/minikin/FontFamily.h50
-rw-r--r--include/minikin/FontFileParser.h53
-rw-r--r--include/minikin/FontStyle.h11
-rw-r--r--include/minikin/FontVariation.h1
-rw-r--r--include/minikin/Hasher.h20
-rw-r--r--include/minikin/Layout.h6
-rw-r--r--include/minikin/LayoutCache.h6
-rw-r--r--include/minikin/LayoutCore.h8
-rw-r--r--include/minikin/LineBreaker.h3
-rw-r--r--include/minikin/LocaleList.h6
-rw-r--r--include/minikin/Measurement.h6
-rw-r--r--include/minikin/MinikinFont.h12
-rw-r--r--include/minikin/MinikinRect.h1
-rw-r--r--include/minikin/SparseBitSet.h17
-rw-r--r--include/minikin/SystemFonts.h56
-rw-r--r--libs/minikin/Android.bp8
-rw-r--r--libs/minikin/BoundsCache.cpp43
-rw-r--r--libs/minikin/Font.cpp125
-rw-r--r--libs/minikin/FontCollection.cpp264
-rw-r--r--libs/minikin/FontFamily.cpp188
-rw-r--r--libs/minikin/FontFileParser.cpp219
-rw-r--r--libs/minikin/GreedyLineBreaker.cpp2
-rw-r--r--libs/minikin/Layout.cpp3
-rw-r--r--libs/minikin/LayoutCore.cpp32
-rw-r--r--libs/minikin/LineBreakerUtil.h2
-rw-r--r--libs/minikin/Locale.cpp12
-rw-r--r--libs/minikin/Locale.h30
-rw-r--r--libs/minikin/LocaleListCache.cpp61
-rw-r--r--libs/minikin/LocaleListCache.h38
-rw-r--r--libs/minikin/MeasuredText.cpp18
-rw-r--r--libs/minikin/Measurement.cpp39
-rw-r--r--libs/minikin/SparseBitSet.cpp43
-rw-r--r--libs/minikin/SystemFonts.cpp21
-rw-r--r--tests/Android.bp8
-rw-r--r--tests/data/Ascii.ttfbin1848 -> 1944 bytes
-rw-r--r--tests/data/Ascii.ttx17
-rw-r--r--tests/data/Bbox.ttfbin0 -> 2016 bytes
-rw-r--r--tests/data/Bbox.ttx265
-rw-r--r--tests/data/EmojiBase.ttfbin0 -> 1428 bytes
-rw-r--r--tests/data/EmojiBase.ttx537
-rw-r--r--tests/data/OverrideEmoji.ttfbin0 -> 1200 bytes
-rw-r--r--tests/data/OverrideEmoji.ttx521
-rw-r--r--tests/data/emoji_itemization.xml28
-rw-r--r--tests/perftests/Android.bp4
-rw-r--r--tests/perftests/main.cpp20
-rw-r--r--tests/stresstest/Android.bp4
-rw-r--r--tests/stresstest/FontFamilyTest.cpp2
-rw-r--r--tests/unittest/Android.bp7
-rw-r--r--tests/unittest/BoundsCacheTest.cpp172
-rw-r--r--tests/unittest/BufferTest.cpp90
-rw-r--r--tests/unittest/FontCollectionItemizeTest.cpp153
-rw-r--r--tests/unittest/FontCollectionTest.cpp160
-rw-r--r--tests/unittest/FontFamilyTest.cpp93
-rw-r--r--tests/unittest/FontFileParserTest.cpp132
-rw-r--r--tests/unittest/FontLanguageListCacheTest.cpp21
-rw-r--r--tests/unittest/FontTest.cpp29
-rw-r--r--tests/unittest/GreedyLineBreakerTest.cpp27
-rw-r--r--tests/unittest/HasherTest.cpp5
-rw-r--r--tests/unittest/ICUEnvironment.h66
-rw-r--r--tests/unittest/LayoutCoreTest.cpp11
-rw-r--r--tests/unittest/LayoutTest.cpp43
-rw-r--r--tests/unittest/MeasuredTextTest.cpp105
-rw-r--r--tests/unittest/OptimalLineBreakerTest.cpp28
-rw-r--r--tests/unittest/SparseBitSetTest.cpp27
-rw-r--r--tests/unittest/SystemFontsTest.cpp50
-rw-r--r--tests/unittest/TestMain.cpp3
-rw-r--r--tests/util/Android.bp4
-rw-r--r--tests/util/BufferUtils.h59
-rw-r--r--tests/util/FontTestUtils.cpp6
-rw-r--r--tests/util/FreeTypeMinikinFontForTest.cpp18
-rw-r--r--tests/util/FreeTypeMinikinFontForTest.h9
77 files changed, 4165 insertions, 489 deletions
diff --git a/Android.bp b/Android.bp
index adbd4e6..f5aaf31 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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
index be6d5fe..2e6835b 100644
--- a/tests/data/Ascii.ttf
+++ b/tests/data/Ascii.ttf
Binary files differ
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
new file mode 100644
index 0000000..c89c59c
--- /dev/null
+++ b/tests/data/Bbox.ttf
Binary files differ
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
new file mode 100644
index 0000000..659226d
--- /dev/null
+++ b/tests/data/EmojiBase.ttf
Binary files differ
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
new file mode 100644
index 0000000..890796b
--- /dev/null
+++ b/tests/data/OverrideEmoji.ttf
Binary files differ
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