Support cmap tables with platform ID == 0 am: db1b6cb776

am: 008ef43100

Change-Id: Ib1e6808f542d75e06044908dc88b6e2ba3abfed7
This commit is contained in:
Seigo Nonaka 2017-04-04 17:02:31 +00:00 committed by android-build-merger
commit 6ee5922aee
15 changed files with 1315 additions and 115 deletions

View File

@ -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);
};

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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) {

Binary file not shown.

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
<GlyphOrder>
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="a"/>
</GlyphOrder>
<head>
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0x640cdb2f"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Fri Mar 17 07:31:00 2017"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="7"/>
<fontDirectionHint value="2"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="1.0"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
</hhea>
<maxp>
<tableVersion value="0x10000"/>
<maxZones value="0"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="594"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00001000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="5"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="UKWN"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="32"/>
<usLastCharIndex value="122"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="93"/>
<mtx name="a" width="500" lsb="93"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="1" language="0">
<map code="0x0061" name="a" />
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
<TTGlyph name="a" xMin="0" yMin="0" xMax="0" yMax="0" />
</glyf>
<name>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
SampleFont-Regular
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
SampleFont-Regular
</namerecord>
</name>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
</ttFont>

Binary file not shown.

View File

@ -0,0 +1,177 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
<GlyphOrder>
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="a"/>
</GlyphOrder>
<head>
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0x640cdb2f"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Fri Mar 17 07:31:00 2017"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="7"/>
<fontDirectionHint value="2"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="1.0"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
</hhea>
<maxp>
<tableVersion value="0x10000"/>
<maxZones value="0"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="594"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00001000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="5"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="UKWN"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="32"/>
<usLastCharIndex value="122"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="93"/>
<mtx name="a" width="500" lsb="93"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="2" language="0">
<map code="0x0061" name="a" />
</cmap_format_4>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
<TTGlyph name="a" xMin="0" yMin="0" xMax="0" yMax="0" />
</glyf>
<name>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
SampleFont-Regular
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
SampleFont-Regular
</namerecord>
</name>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
</ttFont>

BIN
tests/data/UnicodeUCS4.ttf Normal file

Binary file not shown.

181
tests/data/UnicodeUCS4.ttx Normal file
View File

@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
<GlyphOrder>
<GlyphID id="0" name=".notdef"/>
<GlyphID id="1" name="a"/>
</GlyphOrder>
<head>
<tableVersion value="1.0"/>
<fontRevision value="1.0"/>
<checkSumAdjustment value="0x640cdb2f"/>
<magicNumber value="0x5f0f3cf5"/>
<flags value="00000000 00000011"/>
<unitsPerEm value="1000"/>
<created value="Fri Mar 17 07:31:00 2017"/>
<macStyle value="00000000 00000000"/>
<lowestRecPPEM value="7"/>
<fontDirectionHint value="2"/>
<glyphDataFormat value="0"/>
</head>
<hhea>
<tableVersion value="1.0"/>
<ascent value="1000"/>
<descent value="-200"/>
<lineGap value="0"/>
<caretSlopeRise value="1"/>
<caretSlopeRun value="0"/>
<caretOffset value="0"/>
<reserved0 value="0"/>
<reserved1 value="0"/>
<reserved2 value="0"/>
<reserved3 value="0"/>
<metricDataFormat value="0"/>
</hhea>
<maxp>
<tableVersion value="0x10000"/>
<maxZones value="0"/>
<maxTwilightPoints value="0"/>
<maxStorage value="0"/>
<maxFunctionDefs value="0"/>
<maxInstructionDefs value="0"/>
<maxStackElements value="0"/>
<maxSizeOfInstructions value="0"/>
<maxComponentElements value="0"/>
</maxp>
<OS_2>
<!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
will be recalculated by the compiler -->
<version value="3"/>
<xAvgCharWidth value="594"/>
<usWeightClass value="400"/>
<usWidthClass value="5"/>
<fsType value="00000000 00001000"/>
<ySubscriptXSize value="650"/>
<ySubscriptYSize value="600"/>
<ySubscriptXOffset value="0"/>
<ySubscriptYOffset value="75"/>
<ySuperscriptXSize value="650"/>
<ySuperscriptYSize value="600"/>
<ySuperscriptXOffset value="0"/>
<ySuperscriptYOffset value="350"/>
<yStrikeoutSize value="50"/>
<yStrikeoutPosition value="300"/>
<sFamilyClass value="0"/>
<panose>
<bFamilyType value="0"/>
<bSerifStyle value="0"/>
<bWeight value="5"/>
<bProportion value="0"/>
<bContrast value="0"/>
<bStrokeVariation value="0"/>
<bArmStyle value="0"/>
<bLetterForm value="0"/>
<bMidline value="0"/>
<bXHeight value="0"/>
</panose>
<ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
<ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
<ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
<achVendID value="UKWN"/>
<fsSelection value="00000000 01000000"/>
<usFirstCharIndex value="32"/>
<usLastCharIndex value="122"/>
<sTypoAscender value="800"/>
<sTypoDescender value="-200"/>
<sTypoLineGap value="200"/>
<usWinAscent value="1000"/>
<usWinDescent value="200"/>
<ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
<ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
<sxHeight value="500"/>
<sCapHeight value="700"/>
<usDefaultChar value="0"/>
<usBreakChar value="32"/>
<usMaxContext value="0"/>
</OS_2>
<hmtx>
<mtx name=".notdef" width="500" lsb="93"/>
<mtx name="a" width="500" lsb="93"/>
</hmtx>
<cmap>
<tableVersion version="0"/>
<cmap_format_4 platformID="0" platEncID="1" language="0">
<map code="0x0061" name="a" />
</cmap_format_4>
<cmap_format_12 format="12" reserved="0" length="2" nGroups="1" platformID="0" platEncID="4" language="0">
<map code="0x0061" name="a" />
<map code="0x1F926" name="a" />
</cmap_format_12>
</cmap>
<loca>
<!-- The 'loca' table will be calculated by the compiler -->
</loca>
<glyf>
<TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
<TTGlyph name="a" xMin="0" yMin="0" xMax="0" yMax="0" />
</glyf>
<name>
<namerecord nameID="1" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="1" platEncID="0" langID="0x0" unicode="True">
Regular
</namerecord>
<namerecord nameID="4" platformID="1" platEncID="0" langID="0x0" unicode="True">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="1" platEncID="0" langID="0x0" unicode="True">
SampleFont-Regular
</namerecord>
<namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
Regular
</namerecord>
<namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
Sample Font
</namerecord>
<namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
SampleFont-Regular
</namerecord>
</name>
<post>
<formatType value="3.0"/>
<italicAngle value="0.0"/>
<underlinePosition value="-75"/>
<underlineThickness value="50"/>
<isFixedPitch value="0"/>
<minMemType42 value="0"/>
<maxMemType42 value="0"/>
<minMemType1 value="0"/>
<maxMemType1 value="0"/>
</post>
</ttFont>

View File

@ -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))
@ -67,6 +70,7 @@ LOCAL_SRC_FILES += \
../util/FontTestUtils.cpp \
../util/MinikinFontForTest.cpp \
../util/UnicodeUtils.cpp \
CmapCoverageTest.cpp \
EmojiTest.cpp \
FontCollectionTest.cpp \
FontCollectionItemizeTest.cpp \

View 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

View File

@ -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

View File

@ -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) {