diff options
author | Seigo Nonaka <nona@google.com> | 2017-03-17 18:35:24 -0700 |
---|---|---|
committer | Seigo Nonaka <nona@google.com> | 2017-05-05 16:04:27 +0000 |
commit | 9196194d76e4325c5bb0c23f22a5787a717067ed (patch) | |
tree | 7501cd14ead77bd0523a7155a9b24bd87dbbfaa9 | |
parent | 818fbee83a72ca86f64527eb90b2f15ec9b28504 (diff) | |
download | minikin-oreo-dev.tar.gz |
Compute SparseBitSet for cmap format 14 subtables.oreo-dev
By computing cmap format 14 subtables similar to cmap format 4 or 12,
there are following benefits:
- FontCollection::hasVariationSelector becomes 70x faster for the worst
case, 10x faster for the normal case.
- FontFamily::hasGlyph becomes lock-free.
- FontCollection::itemize becomes 2x faster than before for some cases.
On the other hand, following negative things happen:
- Initial FontFamily construction takes 2.5x longer than before, but this
only happens in Zygote and should not happen at application launch time.
- FontFamily construction from buffer takes longer time than before but
it is still around 800µs.
- Consumes additionally memory (about 30kB for NotoSansCJK-Regular.ttc
of ttc index 0, 2.5kB for NotoColorEmoji.ttf), but this is shared across
all applications. No additional memory is necessary if the font doesn't
support variation sequences.
Bug: 36401726
Test: minikin_tests passed
Test: minikin_stress_tests passed
Test: android.graphics.cts.PaintTest passed
Merged-In: I3ebc9ad5050fb9ab6283810d31ae8d13af168ec5
Change-Id: Ia84c4b56aed35a899223e33c1a4a15734de58c33
-rw-r--r-- | include/minikin/CmapCoverage.h | 5 | ||||
-rw-r--r-- | include/minikin/FontFamily.h | 4 | ||||
-rw-r--r-- | libs/minikin/CmapCoverage.cpp | 278 | ||||
-rw-r--r-- | libs/minikin/FontCollection.cpp | 9 | ||||
-rw-r--r-- | libs/minikin/FontFamily.cpp | 33 | ||||
-rw-r--r-- | libs/minikin/MinikinInternal.cpp | 22 | ||||
-rw-r--r-- | libs/minikin/MinikinInternal.h | 17 | ||||
-rw-r--r-- | tests/stresstest/Android.mk | 1 | ||||
-rw-r--r-- | tests/stresstest/FontFamilyTest.cpp | 75 | ||||
-rw-r--r-- | tests/unittest/CmapCoverageTest.cpp | 593 | ||||
-rw-r--r-- | tests/unittest/FontFamilyTest.cpp | 3 |
11 files changed, 895 insertions, 145 deletions
diff --git a/include/minikin/CmapCoverage.h b/include/minikin/CmapCoverage.h index 5136d86..af5960d 100644 --- a/include/minikin/CmapCoverage.h +++ b/include/minikin/CmapCoverage.h @@ -19,12 +19,15 @@ #include <minikin/SparseBitSet.h> +#include <memory> +#include <vector> + namespace minikin { class CmapCoverage { public: static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size, - bool* has_cmap_format14_subtable); + std::vector<std::unique_ptr<SparseBitSet>>* out); }; } // namespace minikin diff --git a/include/minikin/FontFamily.h b/include/minikin/FontFamily.h index babed73..04c95bc 100644 --- a/include/minikin/FontFamily.h +++ b/include/minikin/FontFamily.h @@ -151,7 +151,7 @@ public: bool hasGlyph(uint32_t codepoint, uint32_t variationSelector) const; // Returns true if this font family has a variaion sequence table (cmap format 14 subtable). - bool hasVSTable() const { return mHasVSTable; } + bool hasVSTable() const { return !mCmapFmt14Coverage.empty(); } // Creates new FontFamily based on this family while applying font variations. Returns nullptr // if none of variations apply to this family. @@ -167,7 +167,7 @@ private: std::unordered_set<AxisTag> mSupportedAxes; SparseBitSet mCoverage; - bool mHasVSTable; + std::vector<std::unique_ptr<SparseBitSet>> mCmapFmt14Coverage; // Forbid copying and assignment. FontFamily(const FontFamily&) = delete; diff --git a/libs/minikin/CmapCoverage.cpp b/libs/minikin/CmapCoverage.cpp index a953304..9659d0c 100644 --- a/libs/minikin/CmapCoverage.cpp +++ b/libs/minikin/CmapCoverage.cpp @@ -18,6 +18,7 @@ #define LOG_TAG "Minikin" +#include <algorithm> #include <vector> using std::vector; @@ -27,13 +28,22 @@ using std::vector; #include <minikin/CmapCoverage.h> #include "MinikinInternal.h" +#include <MinikinInternal.h> + namespace minikin { +constexpr uint32_t U32MAX = std::numeric_limits<uint32_t>::max(); + // These could perhaps be optimized to use __builtin_bswap16 and friends. static uint32_t readU16(const uint8_t* data, size_t offset) { return ((uint32_t)data[offset]) << 8 | ((uint32_t)data[offset + 1]); } +static uint32_t readU24(const uint8_t* data, size_t offset) { + return ((uint32_t)data[offset]) << 16 | ((uint32_t)data[offset + 1]) << 8 | + ((uint32_t)data[offset + 2]); +} + static uint32_t readU32(const uint8_t* data, size_t offset) { return ((uint32_t)data[offset]) << 24 | ((uint32_t)data[offset + 1]) << 16 | ((uint32_t)data[offset + 2]) << 8 | ((uint32_t)data[offset + 3]); @@ -51,6 +61,99 @@ static void addRange(vector<uint32_t> &coverage, uint32_t start, uint32_t end) { } } +struct Range { + uint32_t start; // inclusive + uint32_t end; // exclusive + + static Range InvalidRange() { + return Range({ U32MAX, U32MAX }); + } + + inline bool isValid() const { + return start != U32MAX && end != U32MAX; + } + + // Returns true if left and right intersect. + inline static bool intersects(const Range& left, const Range& right) { + return left.isValid() && right.isValid() && + left.start < right.end && right.start < left.end; + } + + // Returns merged range. This method assumes left and right are not invalid ranges and they have + // an intersection. + static Range merge(const Range& left, const Range& right) { + return Range({ std::min(left.start, right.start), std::max(left.end, right.end) }); + } +}; + +// Returns Range from given ranges vector. Returns InvalidRange if i is out of range. +static inline Range getRange(const std::vector<uint32_t>& r, size_t i) { + return i + 1 < r.size() ? Range({ r[i], r[i + 1] }) : Range::InvalidRange(); +} + +// Merge two sorted lists of ranges into one sorted list. +static std::vector<uint32_t> mergeRanges( + const std::vector<uint32_t>& lRanges, const std::vector<uint32_t>& rRanges) { + std::vector<uint32_t> out; + + const size_t lsize = lRanges.size(); + const size_t rsize = rRanges.size(); + out.reserve(lsize + rsize); + size_t ri = 0; + size_t li = 0; + while (li < lsize || ri < rsize) { + Range left = getRange(lRanges, li); + Range right = getRange(rRanges, ri); + + if (!right.isValid()) { + // No ranges left in rRanges. Just put all remaining ranges in lRanges. + do { + Range r = getRange(lRanges, li); + addRange(out, r.start, r.end); + li += 2; + } while (li < lsize); + break; + } else if (!left.isValid()) { + // No ranges left in lRanges. Just put all remaining ranges in rRanges. + do { + Range r = getRange(rRanges, ri); + addRange(out, r.start, r.end); + ri += 2; + } while (ri < rsize); + break; + } else if (!Range::intersects(left, right)) { + // No intersection. Add smaller range. + if (left.start < right.start) { + addRange(out, left.start, left.end); + li += 2; + } else { + addRange(out, right.start, right.end); + ri += 2; + } + } else { + Range merged = Range::merge(left, right); + li += 2; + ri += 2; + left = getRange(lRanges, li); + right = getRange(rRanges, ri); + while (Range::intersects(merged, left) || Range::intersects(merged, right)) { + if (Range::intersects(merged, left)) { + merged = Range::merge(merged, left); + li += 2; + left = getRange(lRanges, li); + } else { + merged = Range::merge(merged, right); + ri += 2; + right = getRange(rRanges, ri); + } + } + addRange(out, merged.start, merged.end); + } + } + + return out; +} + // Get the coverage information out of a Format 4 subtable, storing it in the coverage vector static bool getCoverageFormat4(vector<uint32_t>& coverage, const uint8_t* data, size_t size) { const size_t kSegCountOffset = 6; @@ -176,8 +279,130 @@ uint8_t getTablePriority(uint16_t platformId, uint16_t encodingId) { return kLowestPriority; } +// Get merged coverage information from default UVS Table and non-default UVS Table. Note that this +// function assumes code points in both default UVS Table and non-default UVS table are stored in +// ascending order. This is required by the standard. +static bool getVSCoverage(std::vector<uint32_t>* out_ranges, const uint8_t* data, size_t size, + uint32_t defaultUVSTableOffset, uint32_t nonDefaultUVSTableOffset, + const SparseBitSet& baseCoverage) { + // Need to merge supported ranges from default UVS Table and non-default UVS Table. + // First, collect all supported code points from non default UVS table. + std::vector<uint32_t> rangesFromNonDefaultUVSTable; + if (nonDefaultUVSTableOffset != 0) { + constexpr size_t kHeaderSize = 4; + constexpr size_t kUVSMappingRecordSize = 5; + + const uint8_t* nonDefaultUVSTable = data + nonDefaultUVSTableOffset; + // This subtraction doesn't underflow since the caller already checked + // size > nonDefaultUVSTableOffset. + const size_t nonDefaultUVSTableRemaining = size - nonDefaultUVSTableOffset; + if (nonDefaultUVSTableRemaining < kHeaderSize) { + return false; + } + const uint32_t numRecords = readU32(nonDefaultUVSTable, 0); + if (numRecords * kUVSMappingRecordSize + kHeaderSize > nonDefaultUVSTableRemaining) { + return false; + } + for (uint32_t i = 0; i < numRecords; ++i) { + const size_t recordOffset = kHeaderSize + kUVSMappingRecordSize * i; + const uint32_t codePoint = readU24(nonDefaultUVSTable, recordOffset); + addRange(rangesFromNonDefaultUVSTable, codePoint, codePoint + 1); + } + } + + // Then, construct range from default UVS Table with merging code points from non default UVS + // table. + std::vector<uint32_t> rangesFromDefaultUVSTable; + if (defaultUVSTableOffset != 0) { + constexpr size_t kHeaderSize = 4; + constexpr size_t kUnicodeRangeRecordSize = 4; + + const uint8_t* defaultUVSTable = data + defaultUVSTableOffset; + // This subtraction doesn't underflow since the caller already checked + // size > defaultUVSTableOffset. + const size_t defaultUVSTableRemaining = size - defaultUVSTableOffset; + + if (defaultUVSTableRemaining < kHeaderSize) { + return false; + } + const uint32_t numRecords = readU32(defaultUVSTable, 0); + if (numRecords * kUnicodeRangeRecordSize + kHeaderSize > defaultUVSTableRemaining) { + return false; + } + + for (uint32_t i = 0; i < numRecords; ++i) { + const size_t recordOffset = kHeaderSize + kUnicodeRangeRecordSize * i; + const uint32_t startCp = readU24(defaultUVSTable, recordOffset); + const uint8_t rangeLength = defaultUVSTable[recordOffset + 3]; + + // Then insert range from default UVS Table, but exclude if the base codepoint is not + // supported. + for (uint32_t cp = startCp; cp <= startCp + rangeLength; ++cp) { + // All codepoints in default UVS table should go to the glyphs of the codepoints + // without variation selectors. We need to check the default glyph availability and + // exclude the codepoint if it is not supported by defualt cmap table. + if (baseCoverage.get(cp)) { + addRange(rangesFromDefaultUVSTable, cp, cp + 1 /* exclusive */); + } + } + } + } + *out_ranges = mergeRanges(rangesFromDefaultUVSTable, rangesFromNonDefaultUVSTable); + return true; +} + +static void getCoverageFormat14(std::vector<std::unique_ptr<SparseBitSet>>* out, + const uint8_t* data, size_t size, const SparseBitSet& baseCoverage) { + constexpr size_t kHeaderSize = 10; + constexpr size_t kRecordSize = 11; + constexpr size_t kLengthOffset = 2; + constexpr size_t kNumRecordOffset = 6; + + out->clear(); + if (size < kHeaderSize) { + return; + } + + const uint32_t length = readU32(data, kLengthOffset); + if (size < length) { + return; + } + + uint32_t numRecords = readU32(data, kNumRecordOffset); + if (numRecords == 0 || kHeaderSize + kRecordSize * numRecords > length) { + return; + } + + for (uint32_t i = 0; i < numRecords; ++i) { + // Insert from the largest code points since it determines the size of the output vector. + const uint32_t recordHeadOffset = kHeaderSize + kRecordSize * (numRecords - i - 1); + const uint32_t vsCodePoint = readU24(data, recordHeadOffset); + const uint32_t defaultUVSOffset = readU32(data, recordHeadOffset + 3); + const uint32_t nonDefaultUVSOffset = readU32(data, recordHeadOffset + 7); + if (defaultUVSOffset > length || nonDefaultUVSOffset > length) { + continue; + } + + const uint16_t vsIndex = getVsIndex(vsCodePoint); + if (vsIndex == INVALID_VS_INDEX) { + continue; + } + std::vector<uint32_t> ranges; + if (!getVSCoverage(&ranges, data, length, defaultUVSOffset, nonDefaultUVSOffset, + baseCoverage)) { + continue; + } + if (out->size() < vsIndex + 1) { + out->resize(vsIndex + 1); + } + (*out)[vsIndex].reset(new SparseBitSet(ranges.data(), ranges.size() >> 1)); + } + + out->shrink_to_fit(); +} + SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size, - bool* has_cmap_format14_subtable) { + std::vector<std::unique_ptr<SparseBitSet>>* out) { constexpr size_t kHeaderSize = 4; constexpr size_t kNumTablesOffset = 2; constexpr size_t kTableSize = 8; @@ -185,7 +410,7 @@ SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_siz constexpr size_t kEncodingIdOffset = 2; constexpr size_t kOffsetOffset = 4; constexpr size_t kFormatOffset = 0; - constexpr uint32_t kInvalidOffset = UINT32_MAX; + constexpr uint32_t kNoTable = UINT32_MAX; if (kHeaderSize > cmap_size) { return SparseBitSet(); @@ -195,10 +420,10 @@ SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_siz return SparseBitSet(); } - uint32_t bestTableOffset = kInvalidOffset; + uint32_t bestTableOffset = kNoTable; uint16_t bestTableFormat = 0; uint8_t bestTablePriority = kLowestPriority; - *has_cmap_format14_subtable = false; + uint32_t vsTableOffset = kNoTable; for (uint32_t i = 0; i < numTables; ++i) { const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize; const uint16_t platformId = readU16(cmap_data, tableHeadOffset + kPlatformIdOffset); @@ -211,8 +436,8 @@ SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_siz const uint16_t format = readU16(cmap_data, offset + kFormatOffset); if (platformId == 0 /* Unicode */ && encodingId == 5 /* Variation Sequences */) { - if (!(*has_cmap_format14_subtable) && format == 14) { - *has_cmap_format14_subtable = true; + if (vsTableOffset == kNoTable && format == 14) { + vsTableOffset = offset; } else { // Ignore the (0, 5) table if we have already seen another valid one or it's in a // format we don't understand. @@ -259,30 +484,37 @@ SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_siz bestTableFormat = format; } } - if (*has_cmap_format14_subtable && bestTablePriority == 0 /* highest priority */) { + if (vsTableOffset != kNoTable && bestTablePriority == 0 /* highest priority */) { // Already found the highest priority table and variation sequences table. No need to // look at remaining tables. break; } } - if (bestTableOffset == kInvalidOffset) { - return SparseBitSet(); - } - const uint8_t* tableData = cmap_data + bestTableOffset; - const size_t tableSize = cmap_size - bestTableOffset; - vector<uint32_t> coverageVec; - bool success; - if (bestTableFormat == 4) { - success = getCoverageFormat4(coverageVec, tableData, tableSize); - } else { - success = getCoverageFormat12(coverageVec, tableData, tableSize); - } - if (success) { - return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1); - } else { - return SparseBitSet(); + + SparseBitSet coverage; + + if (bestTableOffset != kNoTable) { + const uint8_t* tableData = cmap_data + bestTableOffset; + const size_t tableSize = cmap_size - bestTableOffset; + bool success; + vector<uint32_t> coverageVec; + if (bestTableFormat == 4) { + success = getCoverageFormat4(coverageVec, tableData, tableSize); + } else { + success = getCoverageFormat12(coverageVec, tableData, tableSize); + } + + if (success) { + coverage = SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1); + } } + if (vsTableOffset != kNoTable) { + const uint8_t* tableData = cmap_data + vsTableOffset; + const size_t tableSize = cmap_size - vsTableOffset; + getCoverageFormat14(out, tableData, tableSize, coverage); + } + return coverage; } } // namespace minikin diff --git a/libs/minikin/FontCollection.cpp b/libs/minikin/FontCollection.cpp index 02ed9dc..871d974 100644 --- a/libs/minikin/FontCollection.cpp +++ b/libs/minikin/FontCollection.cpp @@ -320,10 +320,6 @@ static bool isStickyWhitelisted(uint32_t c) { return false; } -static bool isVariationSelector(uint32_t c) { - return (0xFE00 <= c && c <= 0xFE0F) || (0xE0100 <= c && c <= 0xE01EF); -} - bool FontCollection::hasVariationSelector(uint32_t baseCodepoint, uint32_t variationSelector) const { if (!isVariationSelector(variationSelector)) { @@ -333,8 +329,6 @@ bool FontCollection::hasVariationSelector(uint32_t baseCodepoint, return false; } - android::AutoMutex _l(gMinikinLock); - // Currently mRanges can not be used here since it isn't aware of the variation sequence. for (size_t i = 0; i < mVSFamilyVec.size(); i++) { if (mVSFamilyVec[i]->hasGlyph(baseCodepoint, variationSelector)) { @@ -342,6 +336,9 @@ bool FontCollection::hasVariationSelector(uint32_t baseCodepoint, } } + // TODO: We can remove this lock by precomputing color emoji information. + android::AutoMutex _l(gMinikinLock); + // Even if there is no cmap format 14 subtable entry for the given sequence, should return true // for <char, text presentation selector> case since we have special fallback rule for the // sequence. Note that we don't need to restrict this to already standardized variation diff --git a/libs/minikin/FontFamily.cpp b/libs/minikin/FontFamily.cpp index 39d374b..a93cb4f 100644 --- a/libs/minikin/FontFamily.cpp +++ b/libs/minikin/FontFamily.cpp @@ -105,7 +105,7 @@ FontFamily::FontFamily(int variant, std::vector<Font>&& fonts) } FontFamily::FontFamily(uint32_t langId, int variant, std::vector<Font>&& fonts) - : mLangId(langId), mVariant(variant), mFonts(std::move(fonts)), mHasVSTable(false) { + : mLangId(langId), mVariant(variant), mFonts(std::move(fonts)) { computeCoverage(); } @@ -175,7 +175,7 @@ void FontFamily::computeCoverage() { ALOGE("Could not get cmap table size!\n"); return; } - mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mHasVSTable); + mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mCmapFmt14Coverage); for (size_t i = 0; i < mFonts.size(); ++i) { std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked(); @@ -184,19 +184,28 @@ void FontFamily::computeCoverage() { } bool FontFamily::hasGlyph(uint32_t codepoint, uint32_t variationSelector) const { - assertMinikinLocked(); - if (variationSelector != 0 && !mHasVSTable) { - // Early exit if the variation selector is specified but the font doesn't have a cmap format - // 14 subtable. + if (variationSelector == 0) { + return mCoverage.get(codepoint); + } + + if (mCmapFmt14Coverage.empty()) { return false; } - const FontStyle defaultStyle; - hb_font_t* font = getHbFontLocked(getClosestMatch(defaultStyle).font); - uint32_t unusedGlyph; - bool result = hb_font_get_glyph(font, codepoint, variationSelector, &unusedGlyph); - hb_font_destroy(font); - return result; + const uint16_t vsIndex = getVsIndex(variationSelector); + + if (vsIndex >= mCmapFmt14Coverage.size()) { + // Even if vsIndex is INVALID_VS_INDEX, we reach here since INVALID_VS_INDEX is defined to + // be at the maximum end of the range. + return false; + } + + const std::unique_ptr<SparseBitSet>& bitset = mCmapFmt14Coverage[vsIndex]; + if (bitset.get() == nullptr) { + return false; + } + + return bitset->get(codepoint); } std::shared_ptr<FontFamily> FontFamily::createFamilyWithVariation( diff --git a/libs/minikin/MinikinInternal.cpp b/libs/minikin/MinikinInternal.cpp index 88acc50..cfa43bc 100644 --- a/libs/minikin/MinikinInternal.cpp +++ b/libs/minikin/MinikinInternal.cpp @@ -41,4 +41,26 @@ hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag) { return blob; } +inline static bool isBMPVariationSelector(uint32_t codePoint) { + return VS1 <= codePoint && codePoint <= VS16; +} + +inline static bool isVariationSelectorSupplement(uint32_t codePoint) { + return VS17 <= codePoint && codePoint <= VS256; +} + +uint16_t getVsIndex(uint32_t codePoint) { + if (isBMPVariationSelector(codePoint)) { + return codePoint - VS1; + } else if (isVariationSelectorSupplement(codePoint)) { + return codePoint - VS17 + 16; + } else { + return INVALID_VS_INDEX; + } +} + +bool isVariationSelector(uint32_t codePoint) { + return isBMPVariationSelector(codePoint) || isVariationSelectorSupplement(codePoint); +} + } // namespace minikin diff --git a/libs/minikin/MinikinInternal.h b/libs/minikin/MinikinInternal.h index 1ed0816..a59e55d 100644 --- a/libs/minikin/MinikinInternal.h +++ b/libs/minikin/MinikinInternal.h @@ -40,6 +40,23 @@ hb_blob_t* getFontTable(const MinikinFont* minikinFont, uint32_t tag); constexpr uint32_t MAX_UNICODE_CODE_POINT = 0x10FFFF; +constexpr uint32_t VS1 = 0xFE00; +constexpr uint32_t VS16 = 0xFE0F; +constexpr uint32_t VS17 = 0xE0100; +constexpr uint32_t VS256 = 0xE01EF; + +// Returns variation selector index. This is one unit less than the variation selector number. For +// example, VARIATION SELECTOR-25 maps to 24. +// [0x00-0x0F] for U+FE00..U+FE0F +// [0x10-0xFF] for U+E0100..U+E01EF +// INVALID_VS_INDEX for other input. +constexpr uint16_t INVALID_VS_INDEX = 0xFFFF; +uint16_t getVsIndex(uint32_t codePoint); + +// Returns true if the code point is a variation selector. +// Note that this function returns false for Mongolian free variation selectors. +bool isVariationSelector(uint32_t codePoint); + // An RAII wrapper for hb_blob_t class HbBlob { public: diff --git a/tests/stresstest/Android.mk b/tests/stresstest/Android.mk index 961978b..b655a74 100644 --- a/tests/stresstest/Android.mk +++ b/tests/stresstest/Android.mk @@ -44,6 +44,7 @@ LOCAL_STATIC_LIBRARIES += \ LOCAL_SRC_FILES += \ ../util/FontTestUtils.cpp \ ../util/MinikinFontForTest.cpp \ + FontFamilyTest.cpp \ MultithreadTest.cpp \ LOCAL_C_INCLUDES := \ diff --git a/tests/stresstest/FontFamilyTest.cpp b/tests/stresstest/FontFamilyTest.cpp new file mode 100644 index 0000000..9d289e5 --- /dev/null +++ b/tests/stresstest/FontFamilyTest.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "../util/FontTestUtils.h" +#include "../util/MinikinFontForTest.h" +#include "HbFontCache.h" +#include "MinikinInternal.h" +#include "minikin/FontCollection.h" +#include "minikin/Layout.h" + +namespace minikin { + +typedef std::pair<std::string, int> TestParam; + +class FontFamilyHarfBuzzCompatibilityTest : public ::testing::TestWithParam<TestParam> {}; + +TEST_P(FontFamilyHarfBuzzCompatibilityTest, CoverageTest) { + const std::string& fontPath = GetParam().first; + int ttcIndex = GetParam().second; + + std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath, ttcIndex)); + std::shared_ptr<FontFamily> family = + std::make_shared<FontFamily>(std::vector<Font>({Font(font, FontStyle())})); + + android::AutoMutex _l(gMinikinLock); + hb_font_t* hbFont = getHbFontLocked(font.get()); + + for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) { + uint32_t unusedGlyph; + EXPECT_EQ(family->hasGlyph(codePoint, 0 /* variation selector */), + static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, 0 /* variation selector */, + &unusedGlyph))); + } + + for (uint32_t vs = VS1; vs < VS256; ++vs) { + // Move to variation selectors supplements after variation selectors. + if (vs == VS16 + 1) { + vs = VS17; + } + for (uint32_t codePoint = 0; codePoint < MAX_UNICODE_CODE_POINT; ++codePoint) { + uint32_t unusedGlyph; + ASSERT_EQ(family->hasGlyph(codePoint, vs), + static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph))) + << "Inconsistent Result: " << fontPath << "#" << ttcIndex + << ": U+" << std::hex << codePoint << " U+" << std::hex << vs + << " Minikin: " << family->hasGlyph(codePoint, vs) + << " HarfBuzz: " + << static_cast<bool>(hb_font_get_glyph(hbFont, codePoint, vs, &unusedGlyph)); + + } + } + hb_font_destroy(hbFont); +} + +INSTANTIATE_TEST_CASE_P(FontFamilyTest, + FontFamilyHarfBuzzCompatibilityTest, + ::testing::Values( + TestParam("/system/fonts/NotoSansCJK-Regular.ttc", 0), + TestParam("/system/fonts/NotoColorEmoji.ttf", 0))); +} // namespace minikin diff --git a/tests/unittest/CmapCoverageTest.cpp b/tests/unittest/CmapCoverageTest.cpp index dd61940..edf1ca1 100644 --- a/tests/unittest/CmapCoverageTest.cpp +++ b/tests/unittest/CmapCoverageTest.cpp @@ -18,11 +18,21 @@ #include <log/log.h> #include <gtest/gtest.h> -#include <minikin/SparseBitSet.h> #include <minikin/CmapCoverage.h> +#include <minikin/SparseBitSet.h> + +#include "MinikinInternal.h" namespace minikin { +static constexpr uint16_t VS_PLATFORM_ID = 0; +static constexpr uint16_t VS_ENCODING_ID = 5; + +size_t writeU8(uint8_t x, uint8_t* out, size_t offset) { + out[offset] = x; + return offset + 1; +} + size_t writeU16(uint16_t x, uint8_t* out, size_t offset) { out[offset] = x >> 8; out[offset + 1] = x; @@ -33,6 +43,13 @@ size_t writeI16(int16_t sx, uint8_t* out, size_t offset) { return writeU16(static_cast<uint16_t>(sx), out, offset); } +size_t writeU24(uint32_t x, uint8_t* out, size_t offset) { + out[offset] = x >> 16; + out[offset + 1] = x >> 8; + out[offset + 2] = x; + return offset + 3; +} + size_t writeU32(uint32_t x, uint8_t* out, size_t offset) { out[offset] = x >> 24; out[offset + 1] = x >> 16; @@ -125,6 +142,89 @@ static std::vector<uint8_t> buildCmapFormat12Table(const std::vector<uint32_t>& return out; } +struct VariationSelectorRecord { + uint32_t codePoint; + std::vector<uint32_t> defaultUVSRanges; + std::vector<uint32_t> nonDefaultUVS; + + std::vector<uint8_t> getDefaultUVSAsBinary() const { + if (defaultUVSRanges.empty()) { + return std::vector<uint8_t>(); + } + const size_t numOfRanges = defaultUVSRanges.size() / 2; + const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ + + numOfRanges * 4 /* size of Unicode Range Table */; + + std::vector<uint8_t> out(length); + size_t head = 0; + head = writeU32(numOfRanges, out.data(), head); + for (size_t i = 0; i < numOfRanges; ++i) { + const uint32_t startUnicodeValue = defaultUVSRanges[i * 2]; + const uint32_t endUnicodeValue = defaultUVSRanges[i * 2 + 1]; + head = writeU24(startUnicodeValue, out.data(), head); + head = writeU8(endUnicodeValue - startUnicodeValue, out.data(), head); + } + LOG_ALWAYS_FATAL_IF(head != length); + return out; + } + + std::vector<uint8_t> getNonDefaultUVSAsBinary() const { + if (nonDefaultUVS.empty()) { + return std::vector<uint8_t>(); + } + const size_t length = sizeof(uint32_t) /* numUnicodeValueRanges */ + + nonDefaultUVS.size() * 5 /* size of UVS Mapping Record */; + + std::vector<uint8_t> out(length); + size_t head = 0; + head = writeU32(nonDefaultUVS.size(), out.data(), head); + for (uint32_t codePoint : nonDefaultUVS) { + head = writeU24(codePoint, out.data(), head); + head = writeU16(4 /* fixed glyph id */, out.data(), head); + } + LOG_ALWAYS_FATAL_IF(head != length); + return out; + } +}; + +static std::vector<uint8_t> buildCmapFormat14Table( + const std::vector<VariationSelectorRecord>& vsRecords) { + + const size_t headerLength = sizeof(uint16_t) /* format */ + sizeof(uint32_t) /* length */ + + sizeof(uint32_t) /* numVarSelectorRecords */ + + 11 /* size of variation selector record */ * vsRecords.size(); + + std::vector<uint8_t> out(headerLength); + size_t head = 0; + head = writeU16(14, out.data(), head); // format + head += sizeof(uint32_t); // length will be filled later + head = writeU32(vsRecords.size(), out.data(), head); // numVarSelectorRecords; + + for (const auto& record : vsRecords) { + const uint32_t vsCodePoint = record.codePoint; + head = writeU24(vsCodePoint, out.data(), head); + + std::vector<uint8_t> defaultUVS = record.getDefaultUVSAsBinary(); + if (defaultUVS.empty()) { + head = writeU32(0, out.data(), head); + } else { + head = writeU32(out.size(), out.data(), head); + out.insert(out.end(), defaultUVS.begin(), defaultUVS.end()); + } + + std::vector<uint8_t> nonDefaultUVS = record.getNonDefaultUVSAsBinary(); + if (nonDefaultUVS.empty()) { + head = writeU32(0, out.data(), head); + } else { + head = writeU32(out.size(), out.data(), head); + out.insert(out.end(), nonDefaultUVS.begin(), nonDefaultUVS.end()); + } + } + LOG_ALWAYS_FATAL_IF(head != headerLength); + writeU32(out.size(), out.data(), 2); // fill the length. + return out; +} + class CmapBuilder { public: static constexpr size_t kEncodingTableHead = 4; @@ -144,8 +244,6 @@ public: out.insert(out.end(), table.begin(), table.end()); } - // TODO: Introduce Format 14 table builder. - std::vector<uint8_t> build() { LOG_ALWAYS_FATAL_IF(mCurrentTableIndex != mNumTables); return out; @@ -185,16 +283,16 @@ private: }; TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("Reading beyond buffer size - Too small cmap size"); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>({'a', 'a'})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), 3 /* too small */, &has_cmap_format_14_subtable); + SparseBitSet coverage = + CmapCoverage::getCoverage(cmap.data(), 3 /* too small */, &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Reading beyond buffer size - space needed for tables goes beyond cmap size"); @@ -202,10 +300,9 @@ TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>({'a', 'a'})); writeU16(1000, cmap.data(), 2 /* offset of num tables in cmap header */); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Reading beyond buffer size - Invalid offset in encoding table"); @@ -213,15 +310,14 @@ TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { CmapBuilder::buildSingleFormat4Cmap(0, 0, std::vector<uint16_t>({'a', 'a'})); writeU16(1000, cmap.data(), 8 /* offset of the offset in the first encoding record */); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, SingleFormat4) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; struct TestCast { std::string testTitle; uint16_t platformId; @@ -238,16 +334,15 @@ TEST(CmapCoverageTest, SingleFormat4) { SCOPED_TRACE(testCase.testTitle.c_str()); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap( testCase.platformId, testCase.encodingId, std::vector<uint16_t>({'a', 'a'})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); EXPECT_FALSE(coverage.get('b')); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, SingleFormat12) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; struct TestCast { std::string testTitle; @@ -263,44 +358,41 @@ TEST(CmapCoverageTest, SingleFormat12) { SCOPED_TRACE(testCase.testTitle.c_str()); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap( testCase.platformId, testCase.encodingId, std::vector<uint32_t>({'a', 'a'})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); EXPECT_FALSE(coverage.get('b')); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, Format12_beyondTheUnicodeLimit) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("Starting range is out of Unicode code point. Should be ignored."); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector<uint32_t>({'a', 'a', 0x110000, 0x110000})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); EXPECT_FALSE(coverage.get(0x110000)); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Ending range is out of Unicode code point. Should be ignored."); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector<uint32_t>({'a', 'a', 0x10FF00, 0x110000})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); EXPECT_TRUE(coverage.get(0x10FF00)); EXPECT_TRUE(coverage.get(0x10FFFF)); EXPECT_FALSE(coverage.get(0x110000)); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, notSupportedEncodings) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; struct TestCast { std::string testTitle; @@ -334,15 +426,14 @@ TEST(CmapCoverageTest, notSupportedEncodings) { CmapBuilder builder(1); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap( testCase.platformId, testCase.encodingId, std::vector<uint16_t>({'a', 'a'})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, brokenFormat4Table) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("Too small table cmap size"); std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'})); @@ -352,10 +443,9 @@ TEST(CmapCoverageTest, brokenFormat4Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Too many segments"); @@ -365,10 +455,9 @@ TEST(CmapCoverageTest, brokenFormat4Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Inversed range"); @@ -379,15 +468,14 @@ TEST(CmapCoverageTest, brokenFormat4Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, brokenFormat12Table) { - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("Too small cmap size"); std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); @@ -397,10 +485,9 @@ TEST(CmapCoverageTest, brokenFormat12Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Too many groups"); @@ -411,10 +498,9 @@ TEST(CmapCoverageTest, brokenFormat12Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Inversed range."); @@ -426,25 +512,22 @@ TEST(CmapCoverageTest, brokenFormat12Table) { builder.appendTable(0, 0, table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Too large code point"); std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector<uint32_t>({0x110000, 0x110000})); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_EQ(0U, coverage.length()); - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, TableSelection_Priority) { - bool has_cmap_format_14_subtable = false; std::vector<uint8_t> highestFormat12Table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); std::vector<uint8_t> highestFormat4Table = @@ -452,6 +535,7 @@ TEST(CmapCoverageTest, TableSelection_Priority) { std::vector<uint8_t> format4 = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); std::vector<uint8_t> format12 = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("(platform, encoding) = (3, 10) is the highest priority."); @@ -475,11 +559,10 @@ TEST(CmapCoverageTest, TableSelection_Priority) { builder.appendTable(3, 10, highestFormat12Table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from highest table EXPECT_FALSE(coverage.get('b')); // should not use other table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } { @@ -502,123 +585,437 @@ TEST(CmapCoverageTest, TableSelection_Priority) { builder.appendTable(3, 1, highestFormat4Table); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from highest table EXPECT_FALSE(coverage.get('b')); // should not use other table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } } TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat4Table) { - SparseBitSet coverage; - bool has_cmap_format_14_subtable = false; - std::vector<uint8_t> validTable = - buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'})); + std::vector<uint8_t> validTable = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'})); + std::vector<std::unique_ptr<SparseBitSet>> vsTables; { SCOPED_TRACE("Unsupported format"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); writeU16(0, table.data(), 0 /* format offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Invalid language"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); writeU16(1, table.data(), 4 /* language offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Invalid length"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'})); writeU16(5000, table.data(), 2 /* length offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } } TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) { - SparseBitSet coverage; - bool has_cmap_format_14_subtable = false; + std::vector<std::unique_ptr<SparseBitSet>> vsTables; std::vector<uint8_t> validTable = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); { SCOPED_TRACE("Unsupported format"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); writeU16(0, table.data(), 0 /* format offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Invalid language"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); writeU32(1, table.data(), 8 /* language offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); } { SCOPED_TRACE("Invalid length"); CmapBuilder builder(2); - std::vector<uint8_t> table = - buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); + std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'})); writeU32(5000, table.data(), 4 /* length offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector<uint8_t> cmap = builder.build(); - SparseBitSet coverage = CmapCoverage::getCoverage( - cmap.data(), cmap.size(), &has_cmap_format_14_subtable); + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); EXPECT_TRUE(coverage.get('a')); // comes from valid table EXPECT_FALSE(coverage.get('b')); // should not use invalid table. - EXPECT_FALSE(has_cmap_format_14_subtable); + EXPECT_TRUE(vsTables.empty()); + } +} + +TEST(CmapCoverageTest, TableSelection_VSTable) { + std::vector<uint8_t> smallLetterTable = + buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'})); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0E, { 'a', 'b' }, {} /* no non-default UVS table */ }, + { 0xFE0F, {} /* no default UVS table */, { 'a', 'b'} }, + { 0xE0100, { 'a', 'a' }, { 'b'} }, + })); + CmapBuilder builder(2); + builder.appendTable(3, 1, smallLetterTable); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + EXPECT_TRUE(coverage.get('a')); + ASSERT_FALSE(vsTables.empty()); + + const uint16_t vs15Index = getVsIndex(0xFE0E); + ASSERT_LT(vs15Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs15Index]); + EXPECT_TRUE(vsTables[vs15Index]->get('a')); + EXPECT_TRUE(vsTables[vs15Index]->get('b')); + + const uint16_t vs16Index = getVsIndex(0xFE0F); + ASSERT_LT(vs16Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs16Index]); + EXPECT_TRUE(vsTables[vs16Index]->get('a')); + EXPECT_TRUE(vsTables[vs16Index]->get('b')); + + const uint16_t vs17Index = getVsIndex(0xE0100); + ASSERT_LT(vs17Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs17Index]); + EXPECT_TRUE(vsTables[vs17Index]->get('a')); + EXPECT_TRUE(vsTables[vs17Index]->get('b')); +} + +TEST(CmapCoverageTest, TableSelection_InterSection) { + std::vector<uint8_t> smallLetterTable = + buildCmapFormat12Table(std::vector<uint32_t>({'a', 'z'})); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0E, { 'a', 'e' }, { 'c', 'd', } }, + { 0xFE0F, { 'c', 'e'} , { 'a', 'b', 'c', 'd', 'e'} }, + { 0xE0100, { 'a', 'c' }, { 'b', 'c', 'd' } }, + { 0xE0101, { 'b', 'd'} , { 'a', 'b', 'c', 'd'} }, + { 0xE0102, { 'a', 'c', 'd', 'g'} , { 'b', 'c', 'd', 'e', 'f', 'g', 'h'} }, + { 0xE0103, { 'a', 'f'} , { 'b', 'd', } }, + })); + CmapBuilder builder(2); + builder.appendTable(3, 1, smallLetterTable); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + EXPECT_TRUE(coverage.get('a')); + ASSERT_FALSE(vsTables.empty()); + + const uint16_t vs15Index = getVsIndex(0xFE0E); + ASSERT_LT(vs15Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs15Index]); + EXPECT_TRUE(vsTables[vs15Index]->get('a')); + EXPECT_TRUE(vsTables[vs15Index]->get('b')); + EXPECT_TRUE(vsTables[vs15Index]->get('c')); + EXPECT_TRUE(vsTables[vs15Index]->get('d')); + EXPECT_TRUE(vsTables[vs15Index]->get('e')); + + const uint16_t vs16Index = getVsIndex(0xFE0F); + ASSERT_LT(vs16Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs16Index]); + EXPECT_TRUE(vsTables[vs16Index]->get('a')); + EXPECT_TRUE(vsTables[vs16Index]->get('b')); + EXPECT_TRUE(vsTables[vs16Index]->get('c')); + EXPECT_TRUE(vsTables[vs16Index]->get('d')); + EXPECT_TRUE(vsTables[vs16Index]->get('e')); + + const uint16_t vs17Index = getVsIndex(0xE0100); + ASSERT_LT(vs17Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs17Index]); + EXPECT_TRUE(vsTables[vs17Index]->get('a')); + EXPECT_TRUE(vsTables[vs17Index]->get('b')); + EXPECT_TRUE(vsTables[vs17Index]->get('c')); + EXPECT_TRUE(vsTables[vs17Index]->get('d')); + + const uint16_t vs18Index = getVsIndex(0xE0101); + ASSERT_LT(vs18Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs18Index]); + EXPECT_TRUE(vsTables[vs18Index]->get('a')); + EXPECT_TRUE(vsTables[vs18Index]->get('b')); + EXPECT_TRUE(vsTables[vs18Index]->get('c')); + EXPECT_TRUE(vsTables[vs18Index]->get('d')); + + const uint16_t vs19Index = getVsIndex(0xE0102); + ASSERT_LT(vs19Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs19Index]); + EXPECT_TRUE(vsTables[vs19Index]->get('a')); + EXPECT_TRUE(vsTables[vs19Index]->get('b')); + EXPECT_TRUE(vsTables[vs19Index]->get('c')); + EXPECT_TRUE(vsTables[vs19Index]->get('d')); + EXPECT_TRUE(vsTables[vs19Index]->get('e')); + EXPECT_TRUE(vsTables[vs19Index]->get('f')); + EXPECT_TRUE(vsTables[vs19Index]->get('g')); + EXPECT_TRUE(vsTables[vs19Index]->get('h')); + + const uint16_t vs20Index = getVsIndex(0xE0103); + ASSERT_LT(vs20Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs20Index]); + EXPECT_TRUE(vsTables[vs20Index]->get('a')); + EXPECT_TRUE(vsTables[vs20Index]->get('b')); + EXPECT_TRUE(vsTables[vs20Index]->get('c')); + EXPECT_TRUE(vsTables[vs20Index]->get('d')); + EXPECT_TRUE(vsTables[vs20Index]->get('e')); + EXPECT_TRUE(vsTables[vs20Index]->get('f')); +} + +TEST(CmapCoverageTest, TableSelection_brokenVSTable) { + std::vector<uint8_t> cmap12Table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); + { + SCOPED_TRACE("Too small cmap size"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0E, { 'a', 'a' }, { 'b' } } + })); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage( + cmap.data(), 3 /* too small size */, &vsTables); + EXPECT_FALSE(coverage.get('a')); + ASSERT_TRUE(vsTables.empty()); + } + { + SCOPED_TRACE("Too many variation records"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } } + })); + writeU32(5000, vsTable.data(), 6 /* numVarSelectorRecord offset */); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + ASSERT_TRUE(vsTables.empty()); + } + { + SCOPED_TRACE("Invalid default UVS offset in variation records"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } } + })); + writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the first record */); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + ASSERT_TRUE(vsTables.empty()); + } + { + SCOPED_TRACE("Invalid non default UVS offset in variation records"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } } + })); + writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + ASSERT_TRUE(vsTables.empty()); + } + { + SCOPED_TRACE("Too many ranges entry in default UVS table"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } } + })); + // 21 is the offset of the numUnicodeValueRanges in the fist defulat UVS table. + writeU32(5000, vsTable.data(), 21); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + ASSERT_TRUE(vsTables.empty()); + } + { + SCOPED_TRACE("Too many ranges entry in non default UVS table"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } } + })); + // 29 is the offset of the numUnicodeValueRanges in the fist defulat UVS table. + writeU32(5000, vsTable.data(), 29); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + ASSERT_TRUE(vsTables.empty()); } } +TEST(CmapCoverageTest, TableSelection_brokenVSTable_bestEffort) { + std::vector<uint8_t> cmap12Table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'})); + { + SCOPED_TRACE("Invalid default UVS offset in variation records"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0E, { 'a', 'a' }, { 'b' } }, + { 0xFE0F, { 'a', 'a' }, { 'b' } }, + })); + writeU32(5000, vsTable.data(), 13 /* defaultUVSffset offset in the record for 0xFE0E */); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + + const uint16_t vs16Index = getVsIndex(0xFE0F); + ASSERT_LT(vs16Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs16Index]); + EXPECT_TRUE(vsTables[vs16Index]->get('a')); + EXPECT_TRUE(vsTables[vs16Index]->get('b')); + + const uint16_t vs15Index = getVsIndex(0xFE0E); + EXPECT_FALSE(vsTables[vs15Index]); + } + { + SCOPED_TRACE("Invalid non default UVS offset in variation records"); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0E, { 'a', 'a' }, { 'b' } }, + { 0xFE0F, { 'a', 'a' }, { 'b' } }, + })); + writeU32(5000, vsTable.data(), 17 /* nonDefaultUVSffset offset in the first record */); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + + const uint16_t vs16Index = getVsIndex(0xFE0F); + ASSERT_LT(vs16Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs16Index]); + EXPECT_TRUE(vsTables[vs16Index]->get('a')); + EXPECT_TRUE(vsTables[vs16Index]->get('b')); + + const uint16_t vs15Index = getVsIndex(0xFE0E); + EXPECT_FALSE(vsTables[vs15Index]); + } + { + SCOPED_TRACE("Unknown variation selectors."); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'a' }, { 'b' } }, + { 0xEFFFF, { 'a', 'a' }, { 'b' } }, + })); + CmapBuilder builder(2); + builder.appendTable(3, 1, cmap12Table); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + + const uint16_t vs16Index = getVsIndex(0xFE0F); + ASSERT_LT(vs16Index, vsTables.size()); + ASSERT_TRUE(vsTables[vs16Index]); + EXPECT_TRUE(vsTables[vs16Index]->get('a')); + EXPECT_TRUE(vsTables[vs16Index]->get('b')); + } +} + +// Used only for better looking of range definition. +#define RANGE(x, y) x, y + +TEST(CmapCoverageTest, TableSelection_defaultUVSPointMissingGlyph) { + std::vector<uint8_t> baseTable = buildCmapFormat12Table(std::vector<uint32_t>( + {RANGE('a', 'e'), RANGE('g', 'h'), RANGE('j', 'j'), RANGE('m', 'z')})); + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { 'a', 'z' }, { } } + })); + + CmapBuilder builder(2); + builder.appendTable(3, 1, baseTable); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + const uint16_t vsIndex = getVsIndex(0xFE0F); + ASSERT_LT(vsIndex, vsTables.size()); + ASSERT_TRUE(vsTables[vsIndex]); + + for (char c = 'a'; c <= 'z'; ++c) { + // Default UVS table points the variation sequence to the glyph of the base code point. + // Thus, if the base code point is not supported, we should exclude them. + EXPECT_EQ(coverage.get(c), vsTables[vsIndex]->get(c)) << c; + } +} + +#undef RANGE + +TEST(CmapCoverageTest, TableSelection_vsTableOnly) { + std::vector<uint8_t> vsTable = buildCmapFormat14Table(std::vector<VariationSelectorRecord>({ + { 0xFE0F, { }, { 'a' } } + })); + + CmapBuilder builder(1); + builder.appendTable(VS_PLATFORM_ID, VS_ENCODING_ID, vsTable); + std::vector<uint8_t> cmap = builder.build(); + + std::vector<std::unique_ptr<SparseBitSet>> vsTables; + SparseBitSet coverage = CmapCoverage::getCoverage(cmap.data(), cmap.size(), &vsTables); + const uint16_t vsIndex = getVsIndex(0xFE0F); + ASSERT_LT(vsIndex, vsTables.size()); + ASSERT_TRUE(vsTables[vsIndex]); + EXPECT_TRUE(vsTables[vsIndex]->get('a')); +} } // namespace minikin diff --git a/tests/unittest/FontFamilyTest.cpp b/tests/unittest/FontFamilyTest.cpp index 5a775a3..90e2a64 100644 --- a/tests/unittest/FontFamilyTest.cpp +++ b/tests/unittest/FontFamilyTest.cpp @@ -539,8 +539,6 @@ TEST_F(FontFamilyTest, hasVariationSelectorTest) { std::shared_ptr<FontFamily> family( new FontFamily(std::vector<Font>{ Font(minikinFont, FontStyle()) })); - android::AutoMutex _l(gMinikinLock); - const uint32_t kVS1 = 0xFE00; const uint32_t kVS2 = 0xFE01; const uint32_t kVS3 = 0xFE02; @@ -592,7 +590,6 @@ TEST_F(FontFamilyTest, hasVSTableTest) { new MinikinFontForTest(testCase.fontPath)); std::shared_ptr<FontFamily> family(new FontFamily( std::vector<Font>{ Font(minikinFont, FontStyle()) })); - android::AutoMutex _l(gMinikinLock); EXPECT_EQ(testCase.hasVSTable, family->hasVSTable()); } } |