forked from firka/flutter
Support cmap tables with platform ID == 0
Some fonts don't have cmap subtables of Microsoft Platform ID (3) and only have cmap subtables of Unicode Platform ID (0). Bug: 32505843 Test: minikin_unittest passed Test: android.graphics.cts.TypefaceTest passed Change-Id: I24aa49860790c0ae8d8e578efd728b95ec0f93ae
This commit is contained in:
@@ -23,7 +23,7 @@ namespace minikin {
|
||||
|
||||
class CmapCoverage {
|
||||
public:
|
||||
static bool getCoverage(SparseBitSet &coverage, const uint8_t* cmap_data, size_t cmap_size,
|
||||
static SparseBitSet getCoverage(const uint8_t* cmap_data, size_t cmap_size,
|
||||
bool* has_cmap_format14_subtable);
|
||||
};
|
||||
|
||||
|
||||
@@ -31,19 +31,20 @@ namespace minikin {
|
||||
// of thousands to millions. It is particularly efficient when there are
|
||||
// large gaps. The motivating example is Unicode coverage of a font, but
|
||||
// the abstraction itself is fully general.
|
||||
|
||||
class SparseBitSet {
|
||||
public:
|
||||
SparseBitSet(): mMaxVal(0), mOwnIndicesAndBitmaps(false) {
|
||||
}
|
||||
|
||||
// Clear the set
|
||||
void clear();
|
||||
// Create an empty bit set.
|
||||
SparseBitSet() : mMaxVal(0) {}
|
||||
|
||||
// Initialize the set to a new value, represented by ranges. For
|
||||
// simplicity, these ranges are arranged as pairs of values,
|
||||
// inclusive of start, exclusive of end, laid out in a uint32 array.
|
||||
void initFromRanges(const uint32_t* ranges, size_t nRanges);
|
||||
SparseBitSet(const uint32_t* ranges, size_t nRanges) : SparseBitSet() {
|
||||
initFromRanges(ranges, nRanges);
|
||||
}
|
||||
|
||||
SparseBitSet(SparseBitSet&&) = default;
|
||||
SparseBitSet& operator=(SparseBitSet&&) = default;
|
||||
|
||||
// Determine whether the value is included in the set
|
||||
bool get(uint32_t ch) const {
|
||||
@@ -65,6 +66,8 @@ public:
|
||||
static const uint32_t kNotFound = ~0u;
|
||||
|
||||
private:
|
||||
void initFromRanges(const uint32_t* ranges, size_t nRanges);
|
||||
|
||||
static const int kLogValuesPerPage = 8;
|
||||
static const int kPageMask = (1 << kLogValuesPerPage) - 1;
|
||||
static const int kLogBytesPerEl = 2;
|
||||
@@ -81,18 +84,14 @@ private:
|
||||
|
||||
uint32_t mMaxVal;
|
||||
|
||||
// True if this SparseBitSet is responsible for freeing mIndices and mBitamps.
|
||||
bool mOwnIndicesAndBitmaps;
|
||||
|
||||
uint32_t mIndexSize;
|
||||
const uint32_t* mIndices;
|
||||
uint32_t mBitmapSize;
|
||||
const element* mBitmaps;
|
||||
std::unique_ptr<uint32_t[]> mIndices;
|
||||
std::unique_ptr<element[]> mBitmaps;
|
||||
uint32_t mZeroPageIndex;
|
||||
};
|
||||
|
||||
// Note: this thing cannot be used in vectors yet. If that were important, we'd need to
|
||||
// make the copy constructor work, and probably set up move traits as well.
|
||||
// Forbid copy and assign.
|
||||
SparseBitSet(const SparseBitSet&) = delete;
|
||||
void operator=(const SparseBitSet&) = delete;
|
||||
};
|
||||
|
||||
} // namespace minikin
|
||||
|
||||
|
||||
@@ -132,76 +132,146 @@ static bool getCoverageFormat12(vector<uint32_t>& coverage, const uint8_t* data,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CmapCoverage::getCoverage(SparseBitSet& coverage, const uint8_t* cmap_data, size_t cmap_size,
|
||||
// Lower value has higher priority. 0 for the highest priority table.
|
||||
// kLowestPriority for unsupported tables.
|
||||
// This order comes from HarfBuzz's hb-ot-font.cc and needs to be kept in sync with it.
|
||||
constexpr uint8_t kLowestPriority = 255;
|
||||
uint8_t getTablePriority(uint16_t platformId, uint16_t encodingId) {
|
||||
if (platformId == 3 && encodingId == 10) {
|
||||
return 0;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 6) {
|
||||
return 1;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 4) {
|
||||
return 2;
|
||||
}
|
||||
if (platformId == 3 && encodingId == 1) {
|
||||
return 3;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 3) {
|
||||
return 4;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 2) {
|
||||
return 5;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 1) {
|
||||
return 6;
|
||||
}
|
||||
if (platformId == 0 && encodingId == 0) {
|
||||
return 7;
|
||||
}
|
||||
// Tables other than above are not supported.
|
||||
return kLowestPriority;
|
||||
}
|
||||
|
||||
SparseBitSet CmapCoverage::getCoverage(const uint8_t* cmap_data, size_t cmap_size,
|
||||
bool* has_cmap_format14_subtable) {
|
||||
vector<uint32_t> coverageVec;
|
||||
const size_t kHeaderSize = 4;
|
||||
const size_t kNumTablesOffset = 2;
|
||||
const size_t kTableSize = 8;
|
||||
const size_t kPlatformIdOffset = 0;
|
||||
const size_t kEncodingIdOffset = 2;
|
||||
const size_t kOffsetOffset = 4;
|
||||
const uint16_t kUnicodePlatformId = 0;
|
||||
const uint16_t kMicrosoftPlatformId = 3;
|
||||
const uint16_t kUnicodeBmpEncodingId = 1;
|
||||
const uint16_t kVariationSequencesEncodingId = 5;
|
||||
const uint16_t kUnicodeUcs4EncodingId = 10;
|
||||
const uint32_t kNoTable = UINT32_MAX;
|
||||
constexpr size_t kHeaderSize = 4;
|
||||
constexpr size_t kNumTablesOffset = 2;
|
||||
constexpr size_t kTableSize = 8;
|
||||
constexpr size_t kPlatformIdOffset = 0;
|
||||
constexpr size_t kEncodingIdOffset = 2;
|
||||
constexpr size_t kOffsetOffset = 4;
|
||||
constexpr size_t kFormatOffset = 0;
|
||||
constexpr uint32_t kInvalidOffset = UINT32_MAX;
|
||||
|
||||
if (kHeaderSize > cmap_size) {
|
||||
return false;
|
||||
return SparseBitSet();
|
||||
}
|
||||
uint32_t numTables = readU16(cmap_data, kNumTablesOffset);
|
||||
if (kHeaderSize + numTables * kTableSize > cmap_size) {
|
||||
return false;
|
||||
return SparseBitSet();
|
||||
}
|
||||
uint32_t bestTable = kNoTable;
|
||||
bool hasCmapFormat14Subtable = false;
|
||||
for (uint32_t i = 0; i < numTables; i++) {
|
||||
uint16_t platformId = readU16(cmap_data, kHeaderSize + i * kTableSize + kPlatformIdOffset);
|
||||
uint16_t encodingId = readU16(cmap_data, kHeaderSize + i * kTableSize + kEncodingIdOffset);
|
||||
if (platformId == kMicrosoftPlatformId && encodingId == kUnicodeUcs4EncodingId) {
|
||||
bestTable = i;
|
||||
break;
|
||||
} else if (platformId == kMicrosoftPlatformId && encodingId == kUnicodeBmpEncodingId) {
|
||||
bestTable = i;
|
||||
} else if (platformId == kUnicodePlatformId &&
|
||||
encodingId == kVariationSequencesEncodingId) {
|
||||
uint32_t offset = readU32(cmap_data, kHeaderSize + i * kTableSize + kOffsetOffset);
|
||||
if (offset <= cmap_size - 2 && readU16(cmap_data, offset) == 14) {
|
||||
hasCmapFormat14Subtable = true;
|
||||
|
||||
uint32_t bestTableOffset = kInvalidOffset;
|
||||
uint16_t bestTableFormat = 0;
|
||||
uint8_t bestTablePriority = kLowestPriority;
|
||||
*has_cmap_format14_subtable = false;
|
||||
for (uint32_t i = 0; i < numTables; ++i) {
|
||||
const uint32_t tableHeadOffset = kHeaderSize + i * kTableSize;
|
||||
const uint16_t platformId = readU16(cmap_data, tableHeadOffset + kPlatformIdOffset);
|
||||
const uint16_t encodingId = readU16(cmap_data, tableHeadOffset + kEncodingIdOffset);
|
||||
const uint32_t offset = readU32(cmap_data, tableHeadOffset + kOffsetOffset);
|
||||
|
||||
if (offset > cmap_size - 2) {
|
||||
continue; // Invalid table: not enough space to read.
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
// Ignore the (0, 5) table if we have already seen another valid one or it's in a
|
||||
// format we don't understand.
|
||||
}
|
||||
} else {
|
||||
uint32_t length;
|
||||
uint32_t language;
|
||||
|
||||
if (format == 4) {
|
||||
constexpr size_t lengthOffset = 2;
|
||||
constexpr size_t languageOffset = 4;
|
||||
constexpr size_t minTableSize = languageOffset + 2;
|
||||
if (offset > cmap_size - minTableSize) {
|
||||
continue; // Invalid table: not enough space to read.
|
||||
}
|
||||
length = readU16(cmap_data, offset + lengthOffset);
|
||||
language = readU16(cmap_data, offset + languageOffset);
|
||||
} else if (format == 12) {
|
||||
constexpr size_t lengthOffset = 4;
|
||||
constexpr size_t languageOffset = 8;
|
||||
constexpr size_t minTableSize = languageOffset + 4;
|
||||
if (offset > cmap_size - minTableSize) {
|
||||
continue; // Invalid table: not enough space to read.
|
||||
}
|
||||
length = readU32(cmap_data, offset + lengthOffset);
|
||||
language = readU32(cmap_data, offset + languageOffset);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length > cmap_size - offset) {
|
||||
continue; // Invalid table: table length is larger than whole cmap data size.
|
||||
}
|
||||
if (language != 0) {
|
||||
// Unsupported or invalid table: this is either a subtable for the Macintosh
|
||||
// platform (which we don't support), or an invalid subtable since language field
|
||||
// should be zero for non-Macintosh subtables.
|
||||
continue;
|
||||
}
|
||||
const uint8_t priority = getTablePriority(platformId, encodingId);
|
||||
if (priority < bestTablePriority) {
|
||||
bestTableOffset = offset;
|
||||
bestTablePriority = priority;
|
||||
bestTableFormat = format;
|
||||
}
|
||||
}
|
||||
if (*has_cmap_format14_subtable && bestTablePriority == 0 /* highest priority */) {
|
||||
// Already found the highest priority table and variation sequences table. No need to
|
||||
// look at remaining tables.
|
||||
break;
|
||||
}
|
||||
}
|
||||
*has_cmap_format14_subtable = hasCmapFormat14Subtable;
|
||||
#ifdef VERBOSE_DEBUG
|
||||
ALOGD("best table = %d\n", bestTable);
|
||||
#endif
|
||||
if (bestTable == kNoTable) {
|
||||
return false;
|
||||
if (bestTableOffset == kInvalidOffset) {
|
||||
return SparseBitSet();
|
||||
}
|
||||
uint32_t offset = readU32(cmap_data, kHeaderSize + bestTable * kTableSize + kOffsetOffset);
|
||||
if (offset > cmap_size - 2) {
|
||||
return false;
|
||||
}
|
||||
uint16_t format = readU16(cmap_data, offset);
|
||||
bool success = false;
|
||||
const uint8_t* tableData = cmap_data + offset;
|
||||
const size_t tableSize = cmap_size - offset;
|
||||
if (format == 4) {
|
||||
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 if (format == 12) {
|
||||
} else {
|
||||
success = getCoverageFormat12(coverageVec, tableData, tableSize);
|
||||
}
|
||||
if (success) {
|
||||
coverage.initFromRanges(&coverageVec.front(), coverageVec.size() >> 1);
|
||||
return SparseBitSet(&coverageVec.front(), coverageVec.size() >> 1);
|
||||
} else {
|
||||
return SparseBitSet();
|
||||
}
|
||||
#ifdef VERBOSE_DEBUG
|
||||
for (size_t i = 0; i < coverageVec.size(); i += 2) {
|
||||
ALOGD("%x:%x\n", coverageVec[i], coverageVec[i + 1]);
|
||||
}
|
||||
ALOGD("success = %d", success);
|
||||
#endif
|
||||
return success;
|
||||
|
||||
}
|
||||
|
||||
} // namespace minikin
|
||||
|
||||
@@ -175,8 +175,7 @@ void FontFamily::computeCoverage() {
|
||||
ALOGE("Could not get cmap table size!\n");
|
||||
return;
|
||||
}
|
||||
// TODO: Error check?
|
||||
CmapCoverage::getCoverage(mCoverage, cmapTable.get(), cmapTable.size(), &mHasVSTable);
|
||||
mCoverage = CmapCoverage::getCoverage(cmapTable.get(), cmapTable.size(), &mHasVSTable);
|
||||
|
||||
for (size_t i = 0; i < mFonts.size(); ++i) {
|
||||
std::unordered_set<AxisTag> supportedAxes = mFonts[i].getSupportedAxesLocked();
|
||||
|
||||
@@ -27,17 +27,6 @@ namespace minikin {
|
||||
|
||||
const uint32_t SparseBitSet::kNotFound;
|
||||
|
||||
void SparseBitSet::clear() {
|
||||
mMaxVal = 0;
|
||||
if (mOwnIndicesAndBitmaps) {
|
||||
delete[] mIndices;
|
||||
delete[] mBitmaps;
|
||||
mIndexSize = 0;
|
||||
mBitmapSize = 0;
|
||||
mOwnIndicesAndBitmaps = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) {
|
||||
bool haveZeroPage = false;
|
||||
uint32_t nonzeroPageEnd = 0;
|
||||
@@ -64,17 +53,12 @@ uint32_t SparseBitSet::calcNumPages(const uint32_t* ranges, size_t nRanges) {
|
||||
|
||||
void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {
|
||||
if (nRanges == 0) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
mMaxVal = ranges[nRanges * 2 - 1];
|
||||
mIndexSize = (mMaxVal + kPageMask) >> kLogValuesPerPage;
|
||||
uint32_t* indices = new uint32_t[mIndexSize];
|
||||
mIndices.reset(new uint32_t[(mMaxVal + kPageMask) >> kLogValuesPerPage]);
|
||||
uint32_t nPages = calcNumPages(ranges, nRanges);
|
||||
mBitmapSize = nPages << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
element* bitmaps = new element[mBitmapSize];
|
||||
mOwnIndicesAndBitmaps = true;
|
||||
memset(bitmaps, 0, nPages << (kLogValuesPerPage - 3));
|
||||
mBitmaps.reset(new element[nPages << (kLogValuesPerPage - kLogBitsPerEl)]());
|
||||
mZeroPageIndex = noZeroPage;
|
||||
uint32_t nonzeroPageEnd = 0;
|
||||
uint32_t currentPage = 0;
|
||||
@@ -90,32 +74,30 @@ void SparseBitSet::initFromRanges(const uint32_t* ranges, size_t nRanges) {
|
||||
mZeroPageIndex = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
}
|
||||
for (uint32_t j = nonzeroPageEnd; j < startPage; j++) {
|
||||
indices[j] = mZeroPageIndex;
|
||||
mIndices[j] = mZeroPageIndex;
|
||||
}
|
||||
}
|
||||
indices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
mIndices[startPage] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
}
|
||||
|
||||
size_t index = ((currentPage - 1) << (kLogValuesPerPage - kLogBitsPerEl)) +
|
||||
((start & kPageMask) >> kLogBitsPerEl);
|
||||
size_t nElements = (end - (start & ~kElMask) + kElMask) >> kLogBitsPerEl;
|
||||
if (nElements == 1) {
|
||||
bitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
|
||||
mBitmaps[index] |= (kElAllOnes >> (start & kElMask)) &
|
||||
(kElAllOnes << ((~end + 1) & kElMask));
|
||||
} else {
|
||||
bitmaps[index] |= kElAllOnes >> (start & kElMask);
|
||||
mBitmaps[index] |= kElAllOnes >> (start & kElMask);
|
||||
for (size_t j = 1; j < nElements - 1; j++) {
|
||||
bitmaps[index + j] = kElAllOnes;
|
||||
mBitmaps[index + j] = kElAllOnes;
|
||||
}
|
||||
bitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask);
|
||||
mBitmaps[index + nElements - 1] |= kElAllOnes << ((~end + 1) & kElMask);
|
||||
}
|
||||
for (size_t j = startPage + 1; j < endPage + 1; j++) {
|
||||
indices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
mIndices[j] = (currentPage++) << (kLogValuesPerPage - kLogBitsPerEl);
|
||||
}
|
||||
nonzeroPageEnd = endPage + 1;
|
||||
}
|
||||
mBitmaps = bitmaps;
|
||||
mIndices = indices;
|
||||
}
|
||||
|
||||
int SparseBitSet::CountLeadingZeros(element x) {
|
||||
|
||||
@@ -19,8 +19,8 @@ LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_TEST_DATA := \
|
||||
data/BoldItalic.ttf \
|
||||
data/Bold.ttf \
|
||||
data/BoldItalic.ttf \
|
||||
data/ColorEmojiFont.ttf \
|
||||
data/ColorTextMixedEmojiFont.ttf \
|
||||
data/Emoji.ttf \
|
||||
@@ -32,11 +32,14 @@ LOCAL_TEST_DATA := \
|
||||
data/NoGlyphFont.ttf \
|
||||
data/Regular.ttf \
|
||||
data/TextEmojiFont.ttf \
|
||||
data/UnicodeBMPOnly.ttf \
|
||||
data/UnicodeBMPOnly2.ttf \
|
||||
data/UnicodeUCS4.ttf \
|
||||
data/VariationSelectorTest-Regular.ttf \
|
||||
data/ZhHans.ttf \
|
||||
data/ZhHant.ttf \
|
||||
data/emoji.xml \
|
||||
data/itemize.xml \
|
||||
data/emoji.xml
|
||||
|
||||
LOCAL_TEST_DATA := $(foreach f,$(LOCAL_TEST_DATA),frameworks/minikin/tests:$(f))
|
||||
|
||||
@@ -66,6 +69,7 @@ LOCAL_SRC_FILES += \
|
||||
../util/FontTestUtils.cpp \
|
||||
../util/MinikinFontForTest.cpp \
|
||||
../util/UnicodeUtils.cpp \
|
||||
CmapCoverageTest.cpp \
|
||||
EmojiTest.cpp \
|
||||
FontCollectionTest.cpp \
|
||||
FontCollectionItemizeTest.cpp \
|
||||
|
||||
586
engine/src/flutter/tests/unittest/CmapCoverageTest.cpp
Normal file
586
engine/src/flutter/tests/unittest/CmapCoverageTest.cpp
Normal file
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
* 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 <random>
|
||||
|
||||
#include <log/log.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <minikin/SparseBitSet.h>
|
||||
#include <minikin/CmapCoverage.h>
|
||||
|
||||
namespace minikin {
|
||||
|
||||
size_t writeU16(uint16_t x, uint8_t* out, size_t offset) {
|
||||
out[offset] = x >> 8;
|
||||
out[offset + 1] = x;
|
||||
return offset + 2;
|
||||
}
|
||||
|
||||
size_t writeI16(int16_t sx, uint8_t* out, size_t offset) {
|
||||
return writeU16(static_cast<uint16_t>(sx), out, offset);
|
||||
}
|
||||
|
||||
size_t writeU32(uint32_t x, uint8_t* out, size_t offset) {
|
||||
out[offset] = x >> 24;
|
||||
out[offset + 1] = x >> 16;
|
||||
out[offset + 2] = x >> 8;
|
||||
out[offset + 3] = x;
|
||||
return offset + 4;
|
||||
}
|
||||
|
||||
// Returns valid cmap format 4 table contents. All glyph ID is same value as code point. (e.g.
|
||||
// 'a' (U+0061) is mapped to Glyph ID = 0x0061).
|
||||
// 'range' should be specified with inclusive-inclusive values.
|
||||
static std::vector<uint8_t> buildCmapFormat4Table(const std::vector<uint16_t>& ranges) {
|
||||
uint16_t segmentCount = ranges.size() / 2 + 1 /* +1 for end marker */;
|
||||
|
||||
const size_t numOfUint16 =
|
||||
8 /* format, length, languages, segCountX2, searchRange, entrySelector, rangeShift, pad */ +
|
||||
segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
|
||||
const size_t finalLength = sizeof(uint16_t) * numOfUint16;
|
||||
|
||||
std::vector<uint8_t> out(finalLength);
|
||||
size_t head = 0;
|
||||
head = writeU16(4, out.data(), head); // format
|
||||
head = writeU16(finalLength, out.data(), head); // length
|
||||
head = writeU16(0, out.data(), head); // langauge
|
||||
|
||||
const uint16_t searchRange = 2 * (1 << static_cast<int>(floor(log2(segmentCount))));
|
||||
|
||||
head = writeU16(segmentCount * 2, out.data(), head); // segCountX2
|
||||
head = writeU16(searchRange, out.data(), head); // searchRange
|
||||
head = writeU16(__builtin_ctz(searchRange) - 1, out.data(), head); // entrySelector
|
||||
head = writeU16(segmentCount * 2 - searchRange, out.data(), head); // rangeShift
|
||||
|
||||
size_t endCountHead = head;
|
||||
size_t startCountHead = head + segmentCount * sizeof(uint16_t) + 2 /* padding */;
|
||||
size_t idDeltaHead = startCountHead + segmentCount * sizeof(uint16_t);
|
||||
size_t idRangeOffsetHead = idDeltaHead + segmentCount * sizeof(uint16_t);
|
||||
|
||||
for (size_t i = 0; i < ranges.size() / 2; ++i) {
|
||||
const uint16_t begin = ranges[i * 2];
|
||||
const uint16_t end = ranges[i * 2 + 1];
|
||||
startCountHead = writeU16(begin, out.data(), startCountHead);
|
||||
endCountHead = writeU16(end, out.data(), endCountHead);
|
||||
// map glyph ID as the same value of the code point.
|
||||
idDeltaHead = writeU16(0, out.data(), idDeltaHead);
|
||||
idRangeOffsetHead = writeU16(0 /* we don't use this */, out.data(), idRangeOffsetHead);
|
||||
}
|
||||
|
||||
// fill end marker
|
||||
endCountHead = writeU16(0xFFFF, out.data(), endCountHead);
|
||||
startCountHead = writeU16(0xFFFF, out.data(), startCountHead);
|
||||
idDeltaHead = writeU16(1, out.data(), idDeltaHead);
|
||||
idRangeOffsetHead = writeU16(0, out.data(), idRangeOffsetHead);
|
||||
LOG_ALWAYS_FATAL_IF(endCountHead > finalLength);
|
||||
LOG_ALWAYS_FATAL_IF(startCountHead > finalLength);
|
||||
LOG_ALWAYS_FATAL_IF(idDeltaHead > finalLength);
|
||||
LOG_ALWAYS_FATAL_IF(idRangeOffsetHead != finalLength);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Returns valid cmap format 4 table contents. All glyph ID is same value as code point. (e.g.
|
||||
// 'a' (U+0061) is mapped to Glyph ID = 0x0061).
|
||||
// 'range' should be specified with inclusive-inclusive values.
|
||||
static std::vector<uint8_t> buildCmapFormat12Table(const std::vector<uint32_t>& ranges) {
|
||||
uint32_t numGroups = ranges.size() / 2;
|
||||
|
||||
const size_t finalLength = 2 /* format */ + 2 /* reserved */ + 4 /* length */ +
|
||||
4 /* languages */ + 4 /* numGroups */ + 12 /* size of a group */ * numGroups;
|
||||
|
||||
std::vector<uint8_t> out(finalLength);
|
||||
size_t head = 0;
|
||||
head = writeU16(12, out.data(), head); // format
|
||||
head = writeU16(0, out.data(), head); // reserved
|
||||
head = writeU32(finalLength, out.data(), head); // length
|
||||
head = writeU32(0, out.data(), head); // langauge
|
||||
head = writeU32(numGroups, out.data(), head); // numGroups
|
||||
|
||||
for (uint32_t i = 0; i < numGroups; ++i) {
|
||||
const uint32_t start = ranges[2 * i];
|
||||
const uint32_t end = ranges[2 * i + 1];
|
||||
head = writeU32(start, out.data(), head);
|
||||
head = writeU32(end, out.data(), head);
|
||||
// map glyph ID as the same value of the code point.
|
||||
// TODO: Use glyph IDs lower than 65535.
|
||||
// Cmap can store 32 bit glyph ID but due to the size of numGlyph, a font file can contain
|
||||
// up to 65535 glyphs in a file.
|
||||
head = writeU32(start, out.data(), head);
|
||||
}
|
||||
|
||||
LOG_ALWAYS_FATAL_IF(head != finalLength);
|
||||
return out;
|
||||
}
|
||||
|
||||
class CmapBuilder {
|
||||
public:
|
||||
static constexpr size_t kEncodingTableHead = 4;
|
||||
static constexpr size_t kEncodingTableSize = 8;
|
||||
|
||||
CmapBuilder(int numTables) : mNumTables(numTables), mCurrentTableIndex(0) {
|
||||
const size_t headerSize =
|
||||
2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
|
||||
out.resize(headerSize);
|
||||
writeU16(0, out.data(), 0);
|
||||
writeU16(numTables, out.data(), 2);
|
||||
}
|
||||
|
||||
void appendTable(uint16_t platformId, uint16_t encodingId,
|
||||
const std::vector<uint8_t>& table) {
|
||||
appendEncodingTable(platformId, encodingId, out.size());
|
||||
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;
|
||||
}
|
||||
|
||||
// Helper functions.
|
||||
static std::vector<uint8_t> buildSingleFormat4Cmap(uint16_t platformId, uint16_t encodingId,
|
||||
const std::vector<uint16_t>& ranges) {
|
||||
CmapBuilder builder(1);
|
||||
builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> buildSingleFormat12Cmap(uint16_t platformId, uint16_t encodingId,
|
||||
const std::vector<uint32_t>& ranges) {
|
||||
CmapBuilder builder(1);
|
||||
builder.appendTable(platformId, encodingId, buildCmapFormat12Table(ranges));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private:
|
||||
void appendEncodingTable(uint16_t platformId, uint16_t encodingId, uint32_t offset) {
|
||||
LOG_ALWAYS_FATAL_IF(mCurrentTableIndex == mNumTables);
|
||||
|
||||
const size_t currentEncodingTableHead =
|
||||
kEncodingTableHead + mCurrentTableIndex * kEncodingTableSize;
|
||||
size_t head = writeU16(platformId, out.data(), currentEncodingTableHead);
|
||||
head = writeU16(encodingId, out.data(), head);
|
||||
head = writeU32(offset, out.data(), head);
|
||||
LOG_ALWAYS_FATAL_IF((head - currentEncodingTableHead) != kEncodingTableSize);
|
||||
mCurrentTableIndex++;
|
||||
}
|
||||
|
||||
int mNumTables;
|
||||
int mCurrentTableIndex;
|
||||
std::vector<uint8_t> out;
|
||||
};
|
||||
|
||||
TEST(CmapCoverageTest, SingleFormat4_brokenCmap) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
{
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Reading beyond buffer size - space needed for tables goes beyond cmap size");
|
||||
std::vector<uint8_t> cmap =
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Reading beyond buffer size - Invalid offset in encoding table");
|
||||
std::vector<uint8_t> cmap =
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, SingleFormat4) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
struct TestCast {
|
||||
std::string testTitle;
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
} TEST_CASES[] = {
|
||||
{ "Platform 0, Encoding 0", 0, 0 },
|
||||
{ "Platform 0, Encoding 1", 0, 1 },
|
||||
{ "Platform 0, Encoding 2", 0, 2 },
|
||||
{ "Platform 0, Encoding 3", 0, 3 },
|
||||
{ "Platform 3, Encoding 1", 3, 1 },
|
||||
};
|
||||
|
||||
for (const auto& testCase : TEST_CASES) {
|
||||
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);
|
||||
EXPECT_TRUE(coverage.get('a'));
|
||||
EXPECT_FALSE(coverage.get('b'));
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, SingleFormat12) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
|
||||
struct TestCast {
|
||||
std::string testTitle;
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
} TEST_CASES[] = {
|
||||
{ "Platform 0, Encoding 4", 0, 4 },
|
||||
{ "Platform 0, Encoding 6", 0, 6 },
|
||||
{ "Platform 3, Encoding 10", 3, 10 },
|
||||
};
|
||||
|
||||
for (const auto& testCase : TEST_CASES) {
|
||||
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);
|
||||
EXPECT_TRUE(coverage.get('a'));
|
||||
EXPECT_FALSE(coverage.get('b'));
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, notSupportedEncodings) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
|
||||
struct TestCast {
|
||||
std::string testTitle;
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
} TEST_CASES[] = {
|
||||
// Any encodings with platform 2 is not supported.
|
||||
{ "Platform 2, Encoding 0", 2, 0 },
|
||||
{ "Platform 2, Encoding 1", 2, 1 },
|
||||
{ "Platform 2, Encoding 2", 2, 2 },
|
||||
{ "Platform 2, Encoding 3", 2, 3 },
|
||||
// UCS-2 or UCS-4 are supported on Platform == 3. Others are not supported.
|
||||
{ "Platform 3, Encoding 0", 3, 0 }, // Symbol
|
||||
{ "Platform 3, Encoding 2", 3, 2 }, // ShiftJIS
|
||||
{ "Platform 3, Encoding 3", 3, 3 }, // RPC
|
||||
{ "Platform 3, Encoding 4", 3, 4 }, // Big5
|
||||
{ "Platform 3, Encoding 5", 3, 5 }, // Wansung
|
||||
{ "Platform 3, Encoding 6", 3, 6 }, // Johab
|
||||
{ "Platform 3, Encoding 7", 3, 7 }, // Reserved
|
||||
{ "Platform 3, Encoding 8", 3, 8 }, // Reserved
|
||||
{ "Platform 3, Encoding 9", 3, 9 }, // Reserved
|
||||
// Uknown platforms
|
||||
{ "Platform 4, Encoding 0", 4, 0 },
|
||||
{ "Platform 5, Encoding 1", 5, 1 },
|
||||
{ "Platform 6, Encoding 0", 6, 0 },
|
||||
{ "Platform 7, Encoding 1", 7, 1 },
|
||||
};
|
||||
|
||||
for (const auto& testCase : TEST_CASES) {
|
||||
SCOPED_TRACE(testCase.testTitle.c_str());
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, brokenFormat4Table) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
{
|
||||
SCOPED_TRACE("Too small table cmap size");
|
||||
std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
|
||||
table.resize(2); // Remove trailing data.
|
||||
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Too many segments");
|
||||
std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
|
||||
writeU16(5000, table.data(), 6 /* segment count offset */); // 5000 segments.
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Inversed range");
|
||||
std::vector<uint8_t> table = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
|
||||
// Put smaller end code point to inverse the range.
|
||||
writeU16('a', table.data(), 14 /* the first element of endCount offset */);
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, brokenFormat12Table) {
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
{
|
||||
SCOPED_TRACE("Too small cmap size");
|
||||
std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
|
||||
table.resize(2); // Remove trailing data.
|
||||
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Too many groups");
|
||||
std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
|
||||
writeU32(5000, table.data(), 12 /* num group offset */); // 5000 groups.
|
||||
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Inversed range.");
|
||||
std::vector<uint8_t> table = buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
|
||||
// Put larger start code point to inverse the range.
|
||||
writeU32('b', table.data(), 16 /* start code point offset in the first group */);
|
||||
|
||||
CmapBuilder builder(1);
|
||||
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);
|
||||
EXPECT_EQ(0U, coverage.length());
|
||||
EXPECT_FALSE(has_cmap_format_14_subtable);
|
||||
}
|
||||
}
|
||||
|
||||
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 =
|
||||
buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
|
||||
std::vector<uint8_t> format4 = buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
|
||||
std::vector<uint8_t> format12 = buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));
|
||||
|
||||
{
|
||||
SCOPED_TRACE("(platform, encoding) = (3, 10) is the highest priority.");
|
||||
|
||||
struct LowerPriorityTable {
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
const std::vector<uint8_t>& table;
|
||||
} LOWER_PRIORITY_TABLES[] = {
|
||||
{ 0, 0, format4 },
|
||||
{ 0, 1, format4 },
|
||||
{ 0, 2, format4 },
|
||||
{ 0, 3, format4 },
|
||||
{ 0, 4, format12 },
|
||||
{ 0, 6, format12 },
|
||||
{ 3, 1, format4 },
|
||||
};
|
||||
|
||||
for (const auto& table : LOWER_PRIORITY_TABLES) {
|
||||
CmapBuilder builder(2);
|
||||
builder.appendTable(table.platformId, table.encodingId, table.table);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("(platform, encoding) = (3, 1) case");
|
||||
|
||||
struct LowerPriorityTable {
|
||||
uint16_t platformId;
|
||||
uint16_t encodingId;
|
||||
const std::vector<uint8_t>& table;
|
||||
} LOWER_PRIORITY_TABLES[] = {
|
||||
{ 0, 0, format4 },
|
||||
{ 0, 1, format4 },
|
||||
{ 0, 2, format4 },
|
||||
{ 0, 3, format4 },
|
||||
};
|
||||
|
||||
for (const auto& table : LOWER_PRIORITY_TABLES) {
|
||||
CmapBuilder builder(2);
|
||||
builder.appendTable(table.platformId, table.encodingId, table.table);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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'}));
|
||||
{
|
||||
SCOPED_TRACE("Unsupported format");
|
||||
CmapBuilder builder(2);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Invalid language");
|
||||
CmapBuilder builder(2);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Invalid length");
|
||||
CmapBuilder builder(2);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) {
|
||||
SparseBitSet coverage;
|
||||
bool has_cmap_format_14_subtable = false;
|
||||
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'}));
|
||||
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);
|
||||
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);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Invalid language");
|
||||
CmapBuilder builder(2);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Invalid length");
|
||||
CmapBuilder builder(2);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace minikin
|
||||
@@ -45,6 +45,12 @@ static FontLanguage createFontLanguageWithoutICUSanitization(const std::string&
|
||||
return FontLanguage(input.c_str(), input.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<FontFamily> makeFamily(const std::string& fontPath) {
|
||||
std::shared_ptr<MinikinFont> font(new MinikinFontForTest(fontPath));
|
||||
return std::make_shared<FontFamily>(
|
||||
std::vector<Font>({Font(font, FontStyle())}));
|
||||
}
|
||||
|
||||
TEST_F(FontLanguageTest, basicTests) {
|
||||
FontLanguage defaultLang;
|
||||
FontLanguage emptyLang("", 0);
|
||||
@@ -596,15 +602,8 @@ TEST_F(FontFamilyTest, createFamilyWithVariationTest) {
|
||||
const char kMultiAxisFont[] = kTestFontDir "/MultiAxis.ttf";
|
||||
const char kNoAxisFont[] = kTestFontDir "/Regular.ttf";
|
||||
|
||||
std::shared_ptr<MinikinFont> multiAxisFont(new MinikinFontForTest(kMultiAxisFont));
|
||||
std::shared_ptr<FontFamily> multiAxisFamily(
|
||||
std::shared_ptr<FontFamily>(new FontFamily(
|
||||
std::vector<Font>({Font(multiAxisFont, FontStyle())}))));
|
||||
|
||||
std::shared_ptr<MinikinFont> noAxisFont(new MinikinFontForTest(kNoAxisFont));
|
||||
std::shared_ptr<FontFamily> noAxisFamily(
|
||||
std::shared_ptr<FontFamily>(new FontFamily(
|
||||
std::vector<Font>({Font(noAxisFont, FontStyle())}))));
|
||||
std::shared_ptr<FontFamily> multiAxisFamily = makeFamily(kMultiAxisFont);
|
||||
std::shared_ptr<FontFamily> noAxisFamily = makeFamily(kNoAxisFont);
|
||||
|
||||
{
|
||||
// Do not ceate new instance if none of variations are specified.
|
||||
@@ -656,4 +655,31 @@ TEST_F(FontFamilyTest, createFamilyWithVariationTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(FontFamilyTest, coverageTableSelectionTest) {
|
||||
// This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
|
||||
// encoding ID is 1.
|
||||
const char kUnicodeEncoding1Font[] = kTestFontDir "UnicodeBMPOnly.ttf";
|
||||
|
||||
// This font supports U+0061. The cmap subtable is format 4 and its platform ID is 0 and
|
||||
// encoding ID is 3.
|
||||
const char kUnicodeEncoding3Font[] = kTestFontDir "UnicodeBMPOnly2.ttf";
|
||||
|
||||
// This font has both cmap format 4 subtable which platform ID is 0 and encoding ID is 1
|
||||
// and cmap format 14 subtable which platform ID is 0 and encoding ID is 10.
|
||||
// U+0061 is listed in both subtable but U+1F926 is only listed in latter.
|
||||
const char kUnicodeEncoding4Font[] = kTestFontDir "UnicodeUCS4.ttf";
|
||||
|
||||
std::shared_ptr<FontFamily> unicodeEnc1Font = makeFamily(kUnicodeEncoding1Font);
|
||||
std::shared_ptr<FontFamily> unicodeEnc3Font = makeFamily(kUnicodeEncoding3Font);
|
||||
std::shared_ptr<FontFamily> unicodeEnc4Font = makeFamily(kUnicodeEncoding4Font);
|
||||
|
||||
android::AutoMutex _l(gMinikinLock);
|
||||
|
||||
EXPECT_TRUE(unicodeEnc1Font->hasGlyph(0x0061, 0));
|
||||
EXPECT_TRUE(unicodeEnc3Font->hasGlyph(0x0061, 0));
|
||||
EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x0061, 0));
|
||||
|
||||
EXPECT_TRUE(unicodeEnc4Font->hasGlyph(0x1F926, 0));
|
||||
}
|
||||
|
||||
} // namespace minikin
|
||||
|
||||
@@ -32,8 +32,7 @@ TEST(SparseBitSetTest, randomTest) {
|
||||
range.push_back((range.back() - 1) + distribution(mt));
|
||||
}
|
||||
|
||||
SparseBitSet bitset;
|
||||
bitset.initFromRanges(range.data(), range.size() / 2);
|
||||
SparseBitSet bitset(range.data(), range.size() / 2);
|
||||
|
||||
uint32_t ch = 0;
|
||||
for (size_t i = 0; i < range.size() / 2; ++i) {
|
||||
|
||||
Reference in New Issue
Block a user