summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeigo Nonaka <nona@google.com>2017-03-17 18:35:24 -0700
committerSeigo Nonaka <nona@google.com>2017-05-05 16:04:27 +0000
commit9196194d76e4325c5bb0c23f22a5787a717067ed (patch)
tree7501cd14ead77bd0523a7155a9b24bd87dbbfaa9
parent818fbee83a72ca86f64527eb90b2f15ec9b28504 (diff)
downloadminikin-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.h5
-rw-r--r--include/minikin/FontFamily.h4
-rw-r--r--libs/minikin/CmapCoverage.cpp278
-rw-r--r--libs/minikin/FontCollection.cpp9
-rw-r--r--libs/minikin/FontFamily.cpp33
-rw-r--r--libs/minikin/MinikinInternal.cpp22
-rw-r--r--libs/minikin/MinikinInternal.h17
-rw-r--r--tests/stresstest/Android.mk1
-rw-r--r--tests/stresstest/FontFamilyTest.cpp75
-rw-r--r--tests/unittest/CmapCoverageTest.cpp593
-rw-r--r--tests/unittest/FontFamilyTest.cpp3
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());
}
}