diff options
author | Seigo Nonaka <nona@google.com> | 2021-05-11 19:48:09 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2021-05-11 19:48:09 +0000 |
commit | 19bc49cb554013fbb1913009b4bf7301080a2b07 (patch) | |
tree | 787f3b50764dd0713dccc274657ead797665073e | |
parent | c2f1d141ee42874f2263b250474a0e18ff6167cd (diff) | |
parent | 31aebbf1bcc3a01dfe8f7a6ba1c93c5afafc5aa2 (diff) | |
download | minikin-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.h | 94 | ||||
-rw-r--r-- | libs/minikin/FontCollection.cpp | 181 | ||||
-rw-r--r-- | libs/minikin/LayoutCore.cpp | 7 | ||||
-rw-r--r-- | tests/Android.bp | 3 | ||||
-rw-r--r-- | tests/data/EmojiBase.ttf | bin | 0 -> 1292 bytes | |||
-rw-r--r-- | tests/data/EmojiBase.ttx | 485 | ||||
-rw-r--r-- | tests/data/OverrideEmoji.ttf | bin | 0 -> 1088 bytes | |||
-rw-r--r-- | tests/data/OverrideEmoji.ttx | 470 | ||||
-rw-r--r-- | tests/data/emoji_itemization.xml | 28 | ||||
-rw-r--r-- | tests/unittest/FontCollectionItemizeTest.cpp | 75 | ||||
-rw-r--r-- | tests/unittest/FontCollectionTest.cpp | 83 |
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 Binary files differnew file mode 100644 index 0000000..2e464f7 --- /dev/null +++ b/tests/data/EmojiBase.ttf diff --git a/tests/data/EmojiBase.ttx b/tests/data/EmojiBase.ttx new file mode 100644 index 0000000..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 Binary files differnew file mode 100644 index 0000000..06bb8ca --- /dev/null +++ b/tests/data/OverrideEmoji.ttf diff --git a/tests/data/OverrideEmoji.ttx b/tests/data/OverrideEmoji.ttx new file mode 100644 index 0000000..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 |