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 | |
25 | namespace minikin { |
26 | |
27 | size_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 | |
33 | size_t writeI16(int16_t sx, uint8_t* out, size_t offset) { |
34 | return writeU16(static_cast<uint16_t>(sx), out, offset); |
35 | } |
36 | |
37 | size_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. |
48 | static 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. |
110 | static 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 | |
142 | class 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 = |
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 | |
209 | TEST(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 | |
249 | TEST(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 | |
274 | TEST(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 | |
300 | TEST(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 | |
330 | TEST(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 | |
374 | TEST(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 | |
423 | TEST(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 | |
484 | TEST(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 | |
549 | TEST(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 | |
604 | TEST(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 | |