/* * 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 #include #include #include #include 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(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 buildCmapFormat4Table( const std::vector& 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 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(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 buildCmapFormat12Table( const std::vector& 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 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& table) { appendEncodingTable(platformId, encodingId, out.size()); out.insert(out.end(), table.begin(), table.end()); } // TODO: Introduce Format 14 table builder. std::vector build() { LOG_ALWAYS_FATAL_IF(mCurrentTableIndex != mNumTables); return out; } // Helper functions. static std::vector buildSingleFormat4Cmap( uint16_t platformId, uint16_t encodingId, const std::vector& ranges) { CmapBuilder builder(1); builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges)); return builder.build(); } static std::vector buildSingleFormat12Cmap( uint16_t platformId, uint16_t encodingId, const std::vector& 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 out; }; TEST(CmapCoverageTest, SingleFormat4_brokenCmap) { bool has_cmap_format_14_subtable = false; { SCOPED_TRACE("Reading beyond buffer size - Too small cmap size"); std::vector cmap = CmapBuilder::buildSingleFormat4Cmap( 0, 0, std::vector({'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 cmap = CmapBuilder::buildSingleFormat4Cmap( 0, 0, std::vector({'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 cmap = CmapBuilder::buildSingleFormat4Cmap( 0, 0, std::vector({'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 cmap = CmapBuilder::buildSingleFormat4Cmap( testCase.platformId, testCase.encodingId, std::vector({'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 cmap = CmapBuilder::buildSingleFormat12Cmap( testCase.platformId, testCase.encodingId, std::vector({'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, Format12_beyondTheUnicodeLimit) { bool has_cmap_format_14_subtable = false; { SCOPED_TRACE( "Starting range is out of Unicode code point. Should be ignored."); std::vector cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector({'a', 'a', 0x110000, 0x110000})); SparseBitSet coverage = CmapCoverage::getCoverage( cmap.data(), cmap.size(), &has_cmap_format_14_subtable); EXPECT_TRUE(coverage.get('a')); EXPECT_FALSE(coverage.get(0x110000)); EXPECT_FALSE(has_cmap_format_14_subtable); } { SCOPED_TRACE( "Ending range is out of Unicode code point. Should be ignored."); std::vector cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector({'a', 'a', 0x10FF00, 0x110000})); SparseBitSet coverage = CmapCoverage::getCoverage( cmap.data(), cmap.size(), &has_cmap_format_14_subtable); EXPECT_TRUE(coverage.get('a')); EXPECT_TRUE(coverage.get(0x10FF00)); EXPECT_TRUE(coverage.get(0x10FFFF)); EXPECT_FALSE(coverage.get(0x110000)); EXPECT_FALSE(has_cmap_format_14_subtable); } } 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 cmap = CmapBuilder::buildSingleFormat4Cmap( testCase.platformId, testCase.encodingId, std::vector({'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 table = buildCmapFormat4Table(std::vector({'a', 'a'})); table.resize(2); // Remove trailing data. CmapBuilder builder(1); builder.appendTable(0, 0, table); std::vector 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 table = buildCmapFormat4Table(std::vector({'a', 'a'})); writeU16(5000, table.data(), 6 /* segment count offset */); // 5000 segments. CmapBuilder builder(1); builder.appendTable(0, 0, table); std::vector 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 table = buildCmapFormat4Table(std::vector({'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 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 table = buildCmapFormat12Table(std::vector({'a', 'a'})); table.resize(2); // Remove trailing data. CmapBuilder builder(1); builder.appendTable(0, 0, table); std::vector 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 table = buildCmapFormat12Table(std::vector({'a', 'a'})); writeU32(5000, table.data(), 12 /* num group offset */); // 5000 groups. CmapBuilder builder(1); builder.appendTable(0, 0, table); std::vector 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 table = buildCmapFormat12Table(std::vector({'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 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 large code point"); std::vector cmap = CmapBuilder::buildSingleFormat12Cmap( 0, 0, std::vector({0x110000, 0x110000})); 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 highestFormat12Table = buildCmapFormat12Table(std::vector({'a', 'a'})); std::vector highestFormat4Table = buildCmapFormat4Table(std::vector({'a', 'a'})); std::vector format4 = buildCmapFormat4Table(std::vector({'b', 'b'})); std::vector format12 = buildCmapFormat12Table(std::vector({'b', 'b'})); { SCOPED_TRACE("(platform, encoding) = (3, 10) is the highest priority."); struct LowerPriorityTable { uint16_t platformId; uint16_t encodingId; const std::vector& 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 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& 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 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 validTable = buildCmapFormat4Table(std::vector({'a', 'a'})); { SCOPED_TRACE("Unsupported format"); CmapBuilder builder(2); std::vector table = buildCmapFormat4Table(std::vector({'b', 'b'})); writeU16(0, table.data(), 0 /* format offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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 table = buildCmapFormat4Table(std::vector({'b', 'b'})); writeU16(1, table.data(), 4 /* language offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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 table = buildCmapFormat4Table(std::vector({'b', 'b'})); writeU16(5000, table.data(), 2 /* length offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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 validTable = buildCmapFormat12Table(std::vector({'a', 'a'})); { SCOPED_TRACE("Unsupported format"); CmapBuilder builder(2); std::vector table = buildCmapFormat12Table(std::vector({'b', 'b'})); writeU16(0, table.data(), 0 /* format offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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 table = buildCmapFormat12Table(std::vector({'b', 'b'})); writeU32(1, table.data(), 8 /* language offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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 table = buildCmapFormat12Table(std::vector({'b', 'b'})); writeU32(5000, table.data(), 4 /* length offset */); builder.appendTable(3, 1, table); builder.appendTable(0, 0, validTable); std::vector 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