summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeigo Nonaka <nona@google.com>2021-05-11 19:48:09 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-05-11 19:48:09 +0000
commit19bc49cb554013fbb1913009b4bf7301080a2b07 (patch)
tree787f3b50764dd0713dccc274657ead797665073e
parentc2f1d141ee42874f2263b250474a0e18ff6167cd (diff)
parent31aebbf1bcc3a01dfe8f7a6ba1c93c5afafc5aa2 (diff)
downloadminikin-19bc49cb554013fbb1913009b4bf7301080a2b07.tar.gz
Shape the font for selecting multiple emoji fonts. am: 31aebbf1bc
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/minikin/+/14485567 Change-Id: Icf61dfd8ae75f5b6435198046e15d1a6a07e540d
-rw-r--r--include/minikin/FontCollection.h94
-rw-r--r--libs/minikin/FontCollection.cpp181
-rw-r--r--libs/minikin/LayoutCore.cpp7
-rw-r--r--tests/Android.bp3
-rw-r--r--tests/data/EmojiBase.ttfbin0 -> 1292 bytes
-rw-r--r--tests/data/EmojiBase.ttx485
-rw-r--r--tests/data/OverrideEmoji.ttfbin0 -> 1088 bytes
-rw-r--r--tests/data/OverrideEmoji.ttx470
-rw-r--r--tests/data/emoji_itemization.xml28
-rw-r--r--tests/unittest/FontCollectionItemizeTest.cpp75
-rw-r--r--tests/unittest/FontCollectionTest.cpp83
11 files changed, 1367 insertions, 59 deletions
diff --git a/include/minikin/FontCollection.h b/include/minikin/FontCollection.h
index de18ab5..98df571 100644
--- a/include/minikin/FontCollection.h
+++ b/include/minikin/FontCollection.h
@@ -75,12 +75,99 @@ public:
}
}
+ // 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;
@@ -141,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;
diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp
index a8b96c3..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);
@@ -316,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];
@@ -329,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)) {
@@ -357,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
@@ -379,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
@@ -390,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;
}
@@ -435,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;
@@ -478,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
@@ -494,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;
@@ -532,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.
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/LayoutCore.cpp b/libs/minikin/LayoutCore.cpp
index 03c72b2..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,7 +385,7 @@ 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;
+ FakedFont fakedFont = paint.font->getBestFont(substr, run, paint.fontStyle);
auto it = fontMap.find(fakedFont.font.get());
uint8_t font_ix;
if (it == fontMap.end()) {
diff --git a/tests/Android.bp b/tests/Android.bp
index cb05901..3088ca4 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -15,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",
@@ -24,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",
@@ -33,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/EmojiBase.ttf b/tests/data/EmojiBase.ttf
new file mode 100644
index 0000000..2e464f7
--- /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..d33a2c0
--- /dev/null
+++ b/tests/data/EmojiBase.ttx
@@ -0,0 +1,485 @@
+<?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"/>
+
+ </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+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"/>
+ </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="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+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>
+ </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>
+ </LigatureSubst>
+ </Lookup>
+ </LookupList>
+ </GSUB>
+</ttFont>
diff --git a/tests/data/OverrideEmoji.ttf b/tests/data/OverrideEmoji.ttf
new file mode 100644
index 0000000..06bb8ca
--- /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..b484369
--- /dev/null
+++ b/tests/data/OverrideEmoji.ttx
@@ -0,0 +1,470 @@
+<?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"/>
+ -->
+
+ </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+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"/>
+ -->
+ </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="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+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">
+ -->
+ </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>
+ -->
+ </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/unittest/FontCollectionItemizeTest.cpp b/tests/unittest/FontCollectionItemizeTest.cpp
index 35745c7..96589c9 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,32 +82,36 @@ 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) {
+std::string getFontName(const Run& run) {
EXPECT_NE(nullptr, run.fakedFont.font.get());
return getBasename(run.fakedFont.font.get()->typeface()->GetFontPath());
}
@@ -1619,4 +1629,45 @@ TEST(FontCollectionItemizeTest, customFallbackTest) {
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"));
+}
+
} // namespace minikin
diff --git a/tests/unittest/FontCollectionTest.cpp b/tests/unittest/FontCollectionTest.cpp
index 40b6bf2..aa9d4a8 100644
--- a/tests/unittest/FontCollectionTest.cpp
+++ b/tests/unittest/FontCollectionTest.cpp
@@ -234,4 +234,87 @@ TEST(FontCollectionTest, bufferTest) {
}
}
+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