1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <random>
18
19#include <gtest/gtest.h>
20#include <log/log.h>
21#include <minikin/CmapCoverage.h>
22#include <minikin/SparseBitSet.h>
23#include <utils/WindowsUtils.h>
24
25namespace minikin {
26
27size_t writeU16(uint16_t x, uint8_t* out, size_t offset) {
28 out[offset] = x >> 8;
29 out[offset + 1] = x;
30 return offset + 2;
31}
32
33size_t writeI16(int16_t sx, uint8_t* out, size_t offset) {
34 return writeU16(static_cast<uint16_t>(sx), out, offset);
35}
36
37size_t writeU32(uint32_t x, uint8_t* out, size_t offset) {
38 out[offset] = x >> 24;
39 out[offset + 1] = x >> 16;
40 out[offset + 2] = x >> 8;
41 out[offset + 3] = x;
42 return offset + 4;
43}
44
45// Returns valid cmap format 4 table contents. All glyph ID is same value as
46// code point. (e.g. 'a' (U+0061) is mapped to Glyph ID = 0x0061). 'range'
47// should be specified with inclusive-inclusive values.
48static std::vector<uint8_t> buildCmapFormat4Table(
49 const std::vector<uint16_t>& ranges) {
50 uint16_t segmentCount = ranges.size() / 2 + 1 /* +1 for end marker */;
51
52 const size_t numOfUint16 =
53 8 /* format, length, languages, segCountX2, searchRange, entrySelector,
54 rangeShift, pad */
55 + segmentCount * 4 /* endCount, startCount, idRange, idRangeOffset */;
56 const size_t finalLength = sizeof(uint16_t) * numOfUint16;
57
58 std::vector<uint8_t> out(finalLength);
59 size_t head = 0;
60 head = writeU16(4, out.data(), head); // format
61 head = writeU16(finalLength, out.data(), head); // length
62 head = writeU16(0, out.data(), head); // language
63
64 const uint16_t searchRange =
65 2 * (1 << static_cast<int>(floor(log2(segmentCount))));
66
67 head = writeU16(segmentCount * 2, out.data(), head); // segCountX2
68 head = writeU16(searchRange, out.data(), head); // searchRange
69#if defined(_WIN32)
70 head = writeU16(ctz_win(searchRange) - 1, out.data(), head);
71#else
72 head = writeU16(__builtin_ctz(searchRange) - 1, out.data(),
73 head); // entrySelector
74#endif
75 head =
76 writeU16(segmentCount * 2 - searchRange, out.data(), head); // rangeShift
77
78 size_t endCountHead = head;
79 size_t startCountHead =
80 head + segmentCount * sizeof(uint16_t) + 2 /* padding */;
81 size_t idDeltaHead = startCountHead + segmentCount * sizeof(uint16_t);
82 size_t idRangeOffsetHead = idDeltaHead + segmentCount * sizeof(uint16_t);
83
84 for (size_t i = 0; i < ranges.size() / 2; ++i) {
85 const uint16_t begin = ranges[i * 2];
86 const uint16_t end = ranges[i * 2 + 1];
87 startCountHead = writeU16(begin, out.data(), startCountHead);
88 endCountHead = writeU16(end, out.data(), endCountHead);
89 // map glyph ID as the same value of the code point.
90 idDeltaHead = writeU16(0, out.data(), idDeltaHead);
91 idRangeOffsetHead =
92 writeU16(0 /* we don't use this */, out.data(), idRangeOffsetHead);
93 }
94
95 // fill end marker
96 endCountHead = writeU16(0xFFFF, out.data(), endCountHead);
97 startCountHead = writeU16(0xFFFF, out.data(), startCountHead);
98 idDeltaHead = writeU16(1, out.data(), idDeltaHead);
99 idRangeOffsetHead = writeU16(0, out.data(), idRangeOffsetHead);
100 LOG_ALWAYS_FATAL_IF(endCountHead > finalLength);
101 LOG_ALWAYS_FATAL_IF(startCountHead > finalLength);
102 LOG_ALWAYS_FATAL_IF(idDeltaHead > finalLength);
103 LOG_ALWAYS_FATAL_IF(idRangeOffsetHead != finalLength);
104 return out;
105}
106
107// Returns valid cmap format 4 table contents. All glyph ID is same value as
108// code point. (e.g. 'a' (U+0061) is mapped to Glyph ID = 0x0061). 'range'
109// should be specified with inclusive-inclusive values.
110static std::vector<uint8_t> buildCmapFormat12Table(
111 const std::vector<uint32_t>& ranges) {
112 uint32_t numGroups = ranges.size() / 2;
113
114 const size_t finalLength =
115 2 /* format */ + 2 /* reserved */ + 4 /* length */ + 4 /* languages */ +
116 4 /* numGroups */ + 12 /* size of a group */ * numGroups;
117
118 std::vector<uint8_t> out(finalLength);
119 size_t head = 0;
120 head = writeU16(12, out.data(), head); // format
121 head = writeU16(0, out.data(), head); // reserved
122 head = writeU32(finalLength, out.data(), head); // length
123 head = writeU32(0, out.data(), head); // language
124 head = writeU32(numGroups, out.data(), head); // numGroups
125
126 for (uint32_t i = 0; i < numGroups; ++i) {
127 const uint32_t start = ranges[2 * i];
128 const uint32_t end = ranges[2 * i + 1];
129 head = writeU32(start, out.data(), head);
130 head = writeU32(end, out.data(), head);
131 // map glyph ID as the same value of the code point.
132 // TODO: Use glyph IDs lower than 65535.
133 // Cmap can store 32 bit glyph ID but due to the size of numGlyph, a font
134 // file can contain up to 65535 glyphs in a file.
135 head = writeU32(start, out.data(), head);
136 }
137
138 LOG_ALWAYS_FATAL_IF(head != finalLength);
139 return out;
140}
141
142class CmapBuilder {
143 public:
144 static constexpr size_t kEncodingTableHead = 4;
145 static constexpr size_t kEncodingTableSize = 8;
146
147 CmapBuilder(int numTables) : mNumTables(numTables), mCurrentTableIndex(0) {
148 const size_t headerSize =
149 2 /* version */ + 2 /* numTables */ + kEncodingTableSize * numTables;
150 out.resize(headerSize);
151 writeU16(0, out.data(), 0);
152 writeU16(numTables, out.data(), 2);
153 }
154
155 void appendTable(uint16_t platformId,
156 uint16_t encodingId,
157 const std::vector<uint8_t>& table) {
158 appendEncodingTable(platformId, encodingId, out.size());
159 out.insert(out.end(), table.begin(), table.end());
160 }
161
162 // TODO: Introduce Format 14 table builder.
163
164 std::vector<uint8_t> build() {
165 LOG_ALWAYS_FATAL_IF(mCurrentTableIndex != mNumTables);
166 return out;
167 }
168
169 // Helper functions.
170 static std::vector<uint8_t> buildSingleFormat4Cmap(
171 uint16_t platformId,
172 uint16_t encodingId,
173 const std::vector<uint16_t>& ranges) {
174 CmapBuilder builder(1);
175 builder.appendTable(platformId, encodingId, buildCmapFormat4Table(ranges));
176 return builder.build();
177 }
178
179 static std::vector<uint8_t> buildSingleFormat12Cmap(
180 uint16_t platformId,
181 uint16_t encodingId,
182 const std::vector<uint32_t>& ranges) {
183 CmapBuilder builder(1);
184 builder.appendTable(platformId, encodingId, buildCmapFormat12Table(ranges));
185 return builder.build();
186 }
187
188 private:
189 void appendEncodingTable(uint16_t platformId,
190 uint16_t encodingId,
191 uint32_t offset) {
192 LOG_ALWAYS_FATAL_IF(mCurrentTableIndex == mNumTables);
193
194 const size_t currentEncodingTableHead =
195 kEncodingTableHead + mCurrentTableIndex * kEncodingTableSize;
196 size_t head = writeU16(platformId, out.data(), currentEncodingTableHead);
197 head = writeU16(encodingId, out.data(), head);
198 head = writeU32(offset, out.data(), head);
199 LOG_ALWAYS_FATAL_IF((head - currentEncodingTableHead) !=
200 kEncodingTableSize);
201 mCurrentTableIndex++;
202 }
203
204 int mNumTables;
205 int mCurrentTableIndex;
206 std::vector<uint8_t> out;
207};
208
209TEST(CmapCoverageTest, SingleFormat4_brokenCmap) {
210 bool has_cmap_format_14_subtable = false;
211 {
212 SCOPED_TRACE("Reading beyond buffer size - Too small cmap size");
213 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
214 0, 0, std::vector<uint16_t>({'a', 'a'}));
215
216 SparseBitSet coverage = CmapCoverage::getCoverage(
217 cmap.data(), 3 /* too small */, &has_cmap_format_14_subtable);
218 EXPECT_EQ(0U, coverage.length());
219 EXPECT_FALSE(has_cmap_format_14_subtable);
220 }
221 {
222 SCOPED_TRACE(
223 "Reading beyond buffer size - space needed for tables goes beyond cmap "
224 "size");
225 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
226 0, 0, std::vector<uint16_t>({'a', 'a'}));
227
228 writeU16(1000, cmap.data(), 2 /* offset of num tables in cmap header */);
229 SparseBitSet coverage = CmapCoverage::getCoverage(
230 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
231 EXPECT_EQ(0U, coverage.length());
232 EXPECT_FALSE(has_cmap_format_14_subtable);
233 }
234 {
235 SCOPED_TRACE(
236 "Reading beyond buffer size - Invalid offset in encoding table");
237 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
238 0, 0, std::vector<uint16_t>({'a', 'a'}));
239
240 writeU16(1000, cmap.data(),
241 8 /* offset of the offset in the first encoding record */);
242 SparseBitSet coverage = CmapCoverage::getCoverage(
243 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
244 EXPECT_EQ(0U, coverage.length());
245 EXPECT_FALSE(has_cmap_format_14_subtable);
246 }
247}
248
249TEST(CmapCoverageTest, SingleFormat4) {
250 bool has_cmap_format_14_subtable = false;
251 struct TestCast {
252 std::string testTitle;
253 uint16_t platformId;
254 uint16_t encodingId;
255 } TEST_CASES[] = {
256 {"Platform 0, Encoding 0", 0, 0}, {"Platform 0, Encoding 1", 0, 1},
257 {"Platform 0, Encoding 2", 0, 2}, {"Platform 0, Encoding 3", 0, 3},
258 {"Platform 3, Encoding 1", 3, 1},
259 };
260
261 for (const auto& testCase : TEST_CASES) {
262 SCOPED_TRACE(testCase.testTitle.c_str());
263 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
264 testCase.platformId, testCase.encodingId,
265 std::vector<uint16_t>({'a', 'a'}));
266 SparseBitSet coverage = CmapCoverage::getCoverage(
267 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
268 EXPECT_TRUE(coverage.get('a'));
269 EXPECT_FALSE(coverage.get('b'));
270 EXPECT_FALSE(has_cmap_format_14_subtable);
271 }
272}
273
274TEST(CmapCoverageTest, SingleFormat12) {
275 bool has_cmap_format_14_subtable = false;
276
277 struct TestCast {
278 std::string testTitle;
279 uint16_t platformId;
280 uint16_t encodingId;
281 } TEST_CASES[] = {
282 {"Platform 0, Encoding 4", 0, 4},
283 {"Platform 0, Encoding 6", 0, 6},
284 {"Platform 3, Encoding 10", 3, 10},
285 };
286
287 for (const auto& testCase : TEST_CASES) {
288 SCOPED_TRACE(testCase.testTitle.c_str());
289 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(
290 testCase.platformId, testCase.encodingId,
291 std::vector<uint32_t>({'a', 'a'}));
292 SparseBitSet coverage = CmapCoverage::getCoverage(
293 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
294 EXPECT_TRUE(coverage.get('a'));
295 EXPECT_FALSE(coverage.get('b'));
296 EXPECT_FALSE(has_cmap_format_14_subtable);
297 }
298}
299
300TEST(CmapCoverageTest, Format12_beyondTheUnicodeLimit) {
301 bool has_cmap_format_14_subtable = false;
302 {
303 SCOPED_TRACE(
304 "Starting range is out of Unicode code point. Should be ignored.");
305 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(
306 0, 0, std::vector<uint32_t>({'a', 'a', 0x110000, 0x110000}));
307
308 SparseBitSet coverage = CmapCoverage::getCoverage(
309 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
310 EXPECT_TRUE(coverage.get('a'));
311 EXPECT_FALSE(coverage.get(0x110000));
312 EXPECT_FALSE(has_cmap_format_14_subtable);
313 }
314 {
315 SCOPED_TRACE(
316 "Ending range is out of Unicode code point. Should be ignored.");
317 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(
318 0, 0, std::vector<uint32_t>({'a', 'a', 0x10FF00, 0x110000}));
319
320 SparseBitSet coverage = CmapCoverage::getCoverage(
321 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
322 EXPECT_TRUE(coverage.get('a'));
323 EXPECT_TRUE(coverage.get(0x10FF00));
324 EXPECT_TRUE(coverage.get(0x10FFFF));
325 EXPECT_FALSE(coverage.get(0x110000));
326 EXPECT_FALSE(has_cmap_format_14_subtable);
327 }
328}
329
330TEST(CmapCoverageTest, notSupportedEncodings) {
331 bool has_cmap_format_14_subtable = false;
332
333 struct TestCast {
334 std::string testTitle;
335 uint16_t platformId;
336 uint16_t encodingId;
337 } TEST_CASES[] = {
338 // Any encodings with platform 2 is not supported.
339 {"Platform 2, Encoding 0", 2, 0},
340 {"Platform 2, Encoding 1", 2, 1},
341 {"Platform 2, Encoding 2", 2, 2},
342 {"Platform 2, Encoding 3", 2, 3},
343 // UCS-2 or UCS-4 are supported on Platform == 3. Others are not
344 // supported.
345 {"Platform 3, Encoding 0", 3, 0}, // Symbol
346 {"Platform 3, Encoding 2", 3, 2}, // ShiftJIS
347 {"Platform 3, Encoding 3", 3, 3}, // RPC
348 {"Platform 3, Encoding 4", 3, 4}, // Big5
349 {"Platform 3, Encoding 5", 3, 5}, // Wansung
350 {"Platform 3, Encoding 6", 3, 6}, // Johab
351 {"Platform 3, Encoding 7", 3, 7}, // Reserved
352 {"Platform 3, Encoding 8", 3, 8}, // Reserved
353 {"Platform 3, Encoding 9", 3, 9}, // Reserved
354 // Uknown platforms
355 {"Platform 4, Encoding 0", 4, 0},
356 {"Platform 5, Encoding 1", 5, 1},
357 {"Platform 6, Encoding 0", 6, 0},
358 {"Platform 7, Encoding 1", 7, 1},
359 };
360
361 for (const auto& testCase : TEST_CASES) {
362 SCOPED_TRACE(testCase.testTitle.c_str());
363 CmapBuilder builder(1);
364 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat4Cmap(
365 testCase.platformId, testCase.encodingId,
366 std::vector<uint16_t>({'a', 'a'}));
367 SparseBitSet coverage = CmapCoverage::getCoverage(
368 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
369 EXPECT_EQ(0U, coverage.length());
370 EXPECT_FALSE(has_cmap_format_14_subtable);
371 }
372}
373
374TEST(CmapCoverageTest, brokenFormat4Table) {
375 bool has_cmap_format_14_subtable = false;
376 {
377 SCOPED_TRACE("Too small table cmap size");
378 std::vector<uint8_t> table =
379 buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
380 table.resize(2); // Remove trailing data.
381
382 CmapBuilder builder(1);
383 builder.appendTable(0, 0, table);
384 std::vector<uint8_t> cmap = builder.build();
385
386 SparseBitSet coverage = CmapCoverage::getCoverage(
387 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
388 EXPECT_EQ(0U, coverage.length());
389 EXPECT_FALSE(has_cmap_format_14_subtable);
390 }
391 {
392 SCOPED_TRACE("Too many segments");
393 std::vector<uint8_t> table =
394 buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
395 writeU16(5000, table.data(),
396 6 /* segment count offset */); // 5000 segments.
397 CmapBuilder builder(1);
398 builder.appendTable(0, 0, table);
399 std::vector<uint8_t> cmap = builder.build();
400
401 SparseBitSet coverage = CmapCoverage::getCoverage(
402 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
403 EXPECT_EQ(0U, coverage.length());
404 EXPECT_FALSE(has_cmap_format_14_subtable);
405 }
406 {
407 SCOPED_TRACE("Inversed range");
408 std::vector<uint8_t> table =
409 buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
410 // Put smaller end code point to inverse the range.
411 writeU16('a', table.data(), 14 /* the first element of endCount offset */);
412 CmapBuilder builder(1);
413 builder.appendTable(0, 0, table);
414 std::vector<uint8_t> cmap = builder.build();
415
416 SparseBitSet coverage = CmapCoverage::getCoverage(
417 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
418 EXPECT_EQ(0U, coverage.length());
419 EXPECT_FALSE(has_cmap_format_14_subtable);
420 }
421}
422
423TEST(CmapCoverageTest, brokenFormat12Table) {
424 bool has_cmap_format_14_subtable = false;
425 {
426 SCOPED_TRACE("Too small cmap size");
427 std::vector<uint8_t> table =
428 buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
429 table.resize(2); // Remove trailing data.
430
431 CmapBuilder builder(1);
432 builder.appendTable(0, 0, table);
433 std::vector<uint8_t> cmap = builder.build();
434
435 SparseBitSet coverage = CmapCoverage::getCoverage(
436 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
437 EXPECT_EQ(0U, coverage.length());
438 EXPECT_FALSE(has_cmap_format_14_subtable);
439 }
440 {
441 SCOPED_TRACE("Too many groups");
442 std::vector<uint8_t> table =
443 buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
444 writeU32(5000, table.data(), 12 /* num group offset */); // 5000 groups.
445
446 CmapBuilder builder(1);
447 builder.appendTable(0, 0, table);
448 std::vector<uint8_t> cmap = builder.build();
449
450 SparseBitSet coverage = CmapCoverage::getCoverage(
451 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
452 EXPECT_EQ(0U, coverage.length());
453 EXPECT_FALSE(has_cmap_format_14_subtable);
454 }
455 {
456 SCOPED_TRACE("Inversed range.");
457 std::vector<uint8_t> table =
458 buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
459 // Put larger start code point to inverse the range.
460 writeU32('b', table.data(),
461 16 /* start code point offset in the first group */);
462
463 CmapBuilder builder(1);
464 builder.appendTable(0, 0, table);
465 std::vector<uint8_t> cmap = builder.build();
466
467 SparseBitSet coverage = CmapCoverage::getCoverage(
468 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
469 EXPECT_EQ(0U, coverage.length());
470 EXPECT_FALSE(has_cmap_format_14_subtable);
471 }
472 {
473 SCOPED_TRACE("Too large code point");
474 std::vector<uint8_t> cmap = CmapBuilder::buildSingleFormat12Cmap(
475 0, 0, std::vector<uint32_t>({0x110000, 0x110000}));
476
477 SparseBitSet coverage = CmapCoverage::getCoverage(
478 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
479 EXPECT_EQ(0U, coverage.length());
480 EXPECT_FALSE(has_cmap_format_14_subtable);
481 }
482}
483
484TEST(CmapCoverageTest, TableSelection_Priority) {
485 bool has_cmap_format_14_subtable = false;
486 std::vector<uint8_t> highestFormat12Table =
487 buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
488 std::vector<uint8_t> highestFormat4Table =
489 buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
490 std::vector<uint8_t> format4 =
491 buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
492 std::vector<uint8_t> format12 =
493 buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));
494
495 {
496 SCOPED_TRACE("(platform, encoding) = (3, 10) is the highest priority.");
497
498 struct LowerPriorityTable {
499 uint16_t platformId;
500 uint16_t encodingId;
501 const std::vector<uint8_t>& table;
502 } LOWER_PRIORITY_TABLES[] = {
503 {0, 0, format4}, {0, 1, format4}, {0, 2, format4}, {0, 3, format4},
504 {0, 4, format12}, {0, 6, format12}, {3, 1, format4},
505 };
506
507 for (const auto& table : LOWER_PRIORITY_TABLES) {
508 CmapBuilder builder(2);
509 builder.appendTable(table.platformId, table.encodingId, table.table);
510 builder.appendTable(3, 10, highestFormat12Table);
511 std::vector<uint8_t> cmap = builder.build();
512
513 SparseBitSet coverage = CmapCoverage::getCoverage(
514 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
515 EXPECT_TRUE(coverage.get('a')); // comes from highest table
516 EXPECT_FALSE(coverage.get('b')); // should not use other table.
517 EXPECT_FALSE(has_cmap_format_14_subtable);
518 }
519 }
520 {
521 SCOPED_TRACE("(platform, encoding) = (3, 1) case");
522
523 struct LowerPriorityTable {
524 uint16_t platformId;
525 uint16_t encodingId;
526 const std::vector<uint8_t>& table;
527 } LOWER_PRIORITY_TABLES[] = {
528 {0, 0, format4},
529 {0, 1, format4},
530 {0, 2, format4},
531 {0, 3, format4},
532 };
533
534 for (const auto& table : LOWER_PRIORITY_TABLES) {
535 CmapBuilder builder(2);
536 builder.appendTable(table.platformId, table.encodingId, table.table);
537 builder.appendTable(3, 1, highestFormat4Table);
538 std::vector<uint8_t> cmap = builder.build();
539
540 SparseBitSet coverage = CmapCoverage::getCoverage(
541 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
542 EXPECT_TRUE(coverage.get('a')); // comes from highest table
543 EXPECT_FALSE(coverage.get('b')); // should not use other table.
544 EXPECT_FALSE(has_cmap_format_14_subtable);
545 }
546 }
547}
548
549TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat4Table) {
550 SparseBitSet coverage;
551 bool has_cmap_format_14_subtable = false;
552 std::vector<uint8_t> validTable =
553 buildCmapFormat4Table(std::vector<uint16_t>({'a', 'a'}));
554 {
555 SCOPED_TRACE("Unsupported format");
556 CmapBuilder builder(2);
557 std::vector<uint8_t> table =
558 buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
559 writeU16(0, table.data(), 0 /* format offset */);
560 builder.appendTable(3, 1, table);
561 builder.appendTable(0, 0, validTable);
562 std::vector<uint8_t> cmap = builder.build();
563
564 SparseBitSet coverage = CmapCoverage::getCoverage(
565 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
566 EXPECT_TRUE(coverage.get('a')); // comes from valid table
567 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
568 EXPECT_FALSE(has_cmap_format_14_subtable);
569 }
570 {
571 SCOPED_TRACE("Invalid language");
572 CmapBuilder builder(2);
573 std::vector<uint8_t> table =
574 buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
575 writeU16(1, table.data(), 4 /* language offset */);
576 builder.appendTable(3, 1, table);
577 builder.appendTable(0, 0, validTable);
578 std::vector<uint8_t> cmap = builder.build();
579
580 SparseBitSet coverage = CmapCoverage::getCoverage(
581 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
582 EXPECT_TRUE(coverage.get('a')); // comes from valid table
583 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
584 EXPECT_FALSE(has_cmap_format_14_subtable);
585 }
586 {
587 SCOPED_TRACE("Invalid length");
588 CmapBuilder builder(2);
589 std::vector<uint8_t> table =
590 buildCmapFormat4Table(std::vector<uint16_t>({'b', 'b'}));
591 writeU16(5000, table.data(), 2 /* length offset */);
592 builder.appendTable(3, 1, table);
593 builder.appendTable(0, 0, validTable);
594 std::vector<uint8_t> cmap = builder.build();
595
596 SparseBitSet coverage = CmapCoverage::getCoverage(
597 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
598 EXPECT_TRUE(coverage.get('a')); // comes from valid table
599 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
600 EXPECT_FALSE(has_cmap_format_14_subtable);
601 }
602}
603
604TEST(CmapCoverageTest, TableSelection_SkipBrokenFormat12Table) {
605 SparseBitSet coverage;
606 bool has_cmap_format_14_subtable = false;
607 std::vector<uint8_t> validTable =
608 buildCmapFormat12Table(std::vector<uint32_t>({'a', 'a'}));
609 {
610 SCOPED_TRACE("Unsupported format");
611 CmapBuilder builder(2);
612 std::vector<uint8_t> table =
613 buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));
614 writeU16(0, table.data(), 0 /* format offset */);
615 builder.appendTable(3, 1, table);
616 builder.appendTable(0, 0, validTable);
617 std::vector<uint8_t> cmap = builder.build();
618
619 SparseBitSet coverage = CmapCoverage::getCoverage(
620 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
621 EXPECT_TRUE(coverage.get('a')); // comes from valid table
622 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
623 EXPECT_FALSE(has_cmap_format_14_subtable);
624 }
625 {
626 SCOPED_TRACE("Invalid language");
627 CmapBuilder builder(2);
628 std::vector<uint8_t> table =
629 buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));
630 writeU32(1, table.data(), 8 /* language offset */);
631 builder.appendTable(3, 1, table);
632 builder.appendTable(0, 0, validTable);
633 std::vector<uint8_t> cmap = builder.build();
634
635 SparseBitSet coverage = CmapCoverage::getCoverage(
636 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
637 EXPECT_TRUE(coverage.get('a')); // comes from valid table
638 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
639 EXPECT_FALSE(has_cmap_format_14_subtable);
640 }
641 {
642 SCOPED_TRACE("Invalid length");
643 CmapBuilder builder(2);
644 std::vector<uint8_t> table =
645 buildCmapFormat12Table(std::vector<uint32_t>({'b', 'b'}));
646 writeU32(5000, table.data(), 4 /* length offset */);
647 builder.appendTable(3, 1, table);
648 builder.appendTable(0, 0, validTable);
649 std::vector<uint8_t> cmap = builder.build();
650
651 SparseBitSet coverage = CmapCoverage::getCoverage(
652 cmap.data(), cmap.size(), &has_cmap_format_14_subtable);
653 EXPECT_TRUE(coverage.get('a')); // comes from valid table
654 EXPECT_FALSE(coverage.get('b')); // should not use invalid table.
655 EXPECT_FALSE(has_cmap_format_14_subtable);
656 }
657}
658
659} // namespace minikin
660