1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 // 5 // Tests PPB_TrueTypeFont interface. 6 7 #include "ppapi/tests/test_truetype_font.h" 8 9 #include <string.h> 10 #include <algorithm> 11 #include <limits> 12 13 #include "ppapi/c/dev/ppb_testing_dev.h" 14 #include "ppapi/cpp/completion_callback.h" 15 #include "ppapi/cpp/dev/truetype_font_dev.h" 16 #include "ppapi/cpp/instance.h" 17 #include "ppapi/cpp/var.h" 18 #include "ppapi/tests/test_utils.h" 19 #include "ppapi/tests/testing_instance.h" 20 21 REGISTER_TEST_CASE(TrueTypeFont); 22 23 #define MAKE_TABLE_TAG(a, b, c, d) ((a) << 24) + ((b) << 16) + ((c) << 8) + (d) 24 25 namespace { 26 27 const PP_Resource kInvalidResource = 0; 28 const PP_Instance kInvalidInstance = 0; 29 30 // TrueType font header and table entry structs. See 31 // https://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html 32 struct FontHeader { 33 int32_t font_type; 34 uint16_t num_tables; 35 uint16_t search_range; 36 uint16_t entry_selector; 37 uint16_t range_shift; 38 }; 39 40 struct FontDirectoryEntry { 41 uint32_t tag; 42 uint32_t checksum; 43 uint32_t offset; 44 uint32_t logical_length; 45 }; 46 47 uint32_t ReadBigEndian32(const void* ptr) { 48 const uint8_t* data = reinterpret_cast<const uint8_t*>(ptr); 49 return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24); 50 } 51 52 uint16_t ReadBigEndian16(const void* ptr) { 53 const uint8_t* data = reinterpret_cast<const uint8_t*>(ptr); 54 return (data[1] << 0) | (data[0] << 8); 55 } 56 57 } 58 59 TestTrueTypeFont::TestTrueTypeFont(TestingInstance* instance) 60 : TestCase(instance), 61 ppb_truetype_font_interface_(NULL), 62 ppb_core_interface_(NULL), 63 ppb_var_interface_(NULL) { 64 } 65 66 bool TestTrueTypeFont::Init() { 67 ppb_truetype_font_interface_ = static_cast<const PPB_TrueTypeFont_Dev*>( 68 pp::Module::Get()->GetBrowserInterface(PPB_TRUETYPEFONT_DEV_INTERFACE)); 69 if (!ppb_truetype_font_interface_) 70 instance_->AppendError("PPB_TrueTypeFont_Dev interface not available"); 71 72 ppb_core_interface_ = static_cast<const PPB_Core*>( 73 pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE)); 74 if (!ppb_core_interface_) 75 instance_->AppendError("PPB_Core interface not available"); 76 77 ppb_var_interface_ = static_cast<const PPB_Var*>( 78 pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); 79 if (!ppb_var_interface_) 80 instance_->AppendError("PPB_Var interface not available"); 81 82 return 83 ppb_truetype_font_interface_ && 84 ppb_core_interface_ && 85 ppb_var_interface_; 86 } 87 88 TestTrueTypeFont::~TestTrueTypeFont() { 89 } 90 91 void TestTrueTypeFont::RunTests(const std::string& filter) { 92 RUN_TEST(GetFontFamilies, filter); 93 RUN_TEST(GetFontsInFamily, filter); 94 RUN_TEST(Create, filter); 95 RUN_TEST(Describe, filter); 96 RUN_TEST(GetTableTags, filter); 97 RUN_TEST(GetTable, filter); 98 } 99 100 std::string TestTrueTypeFont::TestGetFontFamilies() { 101 { 102 // A valid instance should be able to enumerate fonts. 103 TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc( 104 instance_->pp_instance(), false); 105 cc.WaitForResult(pp::TrueTypeFont_Dev::GetFontFamilies(instance_, 106 cc.GetCallback())); 107 const std::vector<pp::Var> font_families = cc.output(); 108 // We should get some font families on any platform. 109 ASSERT_NE(0, font_families.size()); 110 ASSERT_EQ(static_cast<int32_t>(font_families.size()), cc.result()); 111 // Make sure at least one family is a non-empty string. 112 ASSERT_NE(0, font_families[0].AsString().size()); 113 } 114 { 115 // Using an invalid instance should fail. 116 TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc( 117 instance_->pp_instance(), false); 118 cc.WaitForResult( 119 ppb_truetype_font_interface_->GetFontFamilies( 120 kInvalidInstance, 121 cc.GetCallback().output(), 122 cc.GetCallback().pp_completion_callback())); 123 ASSERT_TRUE(cc.result() == PP_ERROR_FAILED || 124 cc.result() == PP_ERROR_BADARGUMENT); 125 ASSERT_EQ(0, cc.output().size()); 126 } 127 128 PASS(); 129 } 130 131 std::string TestTrueTypeFont::TestGetFontsInFamily() { 132 { 133 // Get the list of all font families. 134 TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc( 135 instance_->pp_instance(), false); 136 cc.WaitForResult(pp::TrueTypeFont_Dev::GetFontFamilies(instance_, 137 cc.GetCallback())); 138 // Try to use a common family that is likely to have multiple variations. 139 const std::vector<pp::Var> families = cc.output(); 140 pp::Var family("Arial"); 141 if (std::find(families.begin(), families.end(), family) == families.end()) { 142 family = pp::Var("Times"); 143 if (std::find(families.begin(), families.end(), family) == families.end()) 144 family = families[0]; // Just use the first family. 145 } 146 147 // GetFontsInFamily: A valid instance should be able to enumerate fonts 148 // in a given family. 149 TestCompletionCallbackWithOutput< std::vector<pp::TrueTypeFontDesc_Dev> > 150 cc2(instance_->pp_instance(), false); 151 cc2.WaitForResult(pp::TrueTypeFont_Dev::GetFontsInFamily( 152 instance_, 153 family, 154 cc2.GetCallback())); 155 std::vector<pp::TrueTypeFontDesc_Dev> fonts_in_family = cc2.output(); 156 ASSERT_NE(0, fonts_in_family.size()); 157 ASSERT_EQ(static_cast<int32_t>(fonts_in_family.size()), cc2.result()); 158 159 // We should be able to create any of the returned fonts without fallback. 160 for (size_t i = 0; i < fonts_in_family.size(); ++i) { 161 pp::TrueTypeFontDesc_Dev& font_in_family = fonts_in_family[i]; 162 pp::TrueTypeFont_Dev font(instance_, font_in_family); 163 TestCompletionCallbackWithOutput<pp::TrueTypeFontDesc_Dev> cc( 164 instance_->pp_instance(), false); 165 cc.WaitForResult(font.Describe(cc.GetCallback())); 166 const pp::TrueTypeFontDesc_Dev desc = cc.output(); 167 168 ASSERT_EQ(family, desc.family()); 169 ASSERT_EQ(font_in_family.style(), desc.style()); 170 ASSERT_EQ(font_in_family.weight(), desc.weight()); 171 } 172 } 173 { 174 // Using an invalid instance should fail. 175 TestCompletionCallbackWithOutput< std::vector<pp::TrueTypeFontDesc_Dev> > 176 cc(instance_->pp_instance(), false); 177 pp::Var family("Times"); 178 cc.WaitForResult( 179 ppb_truetype_font_interface_->GetFontsInFamily( 180 kInvalidInstance, 181 family.pp_var(), 182 cc.GetCallback().output(), 183 cc.GetCallback().pp_completion_callback())); 184 ASSERT_TRUE(cc.result() == PP_ERROR_FAILED || 185 cc.result() == PP_ERROR_BADARGUMENT); 186 ASSERT_EQ(0, cc.output().size()); 187 } 188 189 PASS(); 190 } 191 192 std::string TestTrueTypeFont::TestCreate() { 193 PP_Resource font; 194 PP_TrueTypeFontDesc_Dev desc = { 195 PP_MakeUndefined(), 196 PP_TRUETYPEFONTFAMILY_SERIF, 197 PP_TRUETYPEFONTSTYLE_NORMAL, 198 PP_TRUETYPEFONTWEIGHT_NORMAL, 199 PP_TRUETYPEFONTWIDTH_NORMAL, 200 PP_TRUETYPEFONTCHARSET_DEFAULT 201 }; 202 // Creating a font from an invalid instance returns an invalid resource. 203 font = ppb_truetype_font_interface_->Create(kInvalidInstance, &desc); 204 ASSERT_EQ(kInvalidResource, font); 205 ASSERT_NE(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font)); 206 207 // Creating a font from a valid instance returns a font resource. 208 font = ppb_truetype_font_interface_->Create(instance_->pp_instance(), &desc); 209 ASSERT_NE(kInvalidResource, font); 210 ASSERT_EQ(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font)); 211 212 ppb_core_interface_->ReleaseResource(font); 213 // Once released, the resource shouldn't be a font. 214 ASSERT_NE(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font)); 215 216 PASS(); 217 } 218 219 std::string TestTrueTypeFont::TestDescribe() { 220 pp::TrueTypeFontDesc_Dev create_desc; 221 create_desc.set_generic_family(PP_TRUETYPEFONTFAMILY_SERIF); 222 create_desc.set_style(PP_TRUETYPEFONTSTYLE_NORMAL); 223 create_desc.set_weight(PP_TRUETYPEFONTWEIGHT_NORMAL); 224 pp::TrueTypeFont_Dev font(instance_, create_desc); 225 // Describe: See what font-matching did with a generic font. We should always 226 // be able to Create a generic Serif font. 227 TestCompletionCallbackWithOutput<pp::TrueTypeFontDesc_Dev> cc( 228 instance_->pp_instance(), false); 229 cc.WaitForResult(font.Describe(cc.GetCallback())); 230 const pp::TrueTypeFontDesc_Dev desc = cc.output(); 231 ASSERT_NE(0, desc.family().AsString().size()); 232 ASSERT_EQ(PP_TRUETYPEFONTFAMILY_SERIF, desc.generic_family()); 233 ASSERT_EQ(PP_TRUETYPEFONTSTYLE_NORMAL, desc.style()); 234 ASSERT_EQ(PP_TRUETYPEFONTWEIGHT_NORMAL, desc.weight()); 235 236 // Describe an invalid resource should fail. 237 PP_TrueTypeFontDesc_Dev fail_desc; 238 memset(&fail_desc, 0, sizeof(fail_desc)); 239 fail_desc.family = PP_MakeUndefined(); 240 // Create a shallow copy to check that no data is changed. 241 PP_TrueTypeFontDesc_Dev fail_desc_copy; 242 memcpy(&fail_desc_copy, &fail_desc, sizeof(fail_desc)); 243 244 cc.WaitForResult( 245 ppb_truetype_font_interface_->Describe( 246 kInvalidResource, 247 &fail_desc, 248 cc.GetCallback().pp_completion_callback())); 249 ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result()); 250 ASSERT_EQ(PP_VARTYPE_UNDEFINED, fail_desc.family.type); 251 ASSERT_EQ(0, memcmp(&fail_desc, &fail_desc_copy, sizeof(fail_desc))); 252 253 PASS(); 254 } 255 256 std::string TestTrueTypeFont::TestGetTableTags() { 257 pp::TrueTypeFontDesc_Dev desc; 258 pp::TrueTypeFont_Dev font(instance_, desc); 259 { 260 TestCompletionCallbackWithOutput< std::vector<uint32_t> > cc( 261 instance_->pp_instance(), false); 262 cc.WaitForResult(font.GetTableTags(cc.GetCallback())); 263 std::vector<uint32_t> tags = cc.output(); 264 ASSERT_NE(0, tags.size()); 265 ASSERT_EQ(static_cast<int32_t>(tags.size()), cc.result()); 266 // Tags will vary depending on the actual font that the host platform 267 // chooses. Check that all required TrueType tags are present. 268 const int required_tag_count = 9; 269 uint32_t required_tags[required_tag_count] = { 270 // Note: these must be sorted for std::includes below. 271 MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 272 MAKE_TABLE_TAG('g', 'l', 'y', 'f'), 273 MAKE_TABLE_TAG('h', 'e', 'a', 'd'), 274 MAKE_TABLE_TAG('h', 'h', 'e', 'a'), 275 MAKE_TABLE_TAG('h', 'm', 't', 'x'), 276 MAKE_TABLE_TAG('l', 'o', 'c', 'a'), 277 MAKE_TABLE_TAG('m', 'a', 'x', 'p'), 278 MAKE_TABLE_TAG('n', 'a', 'm', 'e'), 279 MAKE_TABLE_TAG('p', 'o', 's', 't') 280 }; 281 std::sort(tags.begin(), tags.end()); 282 ASSERT_TRUE(std::includes(tags.begin(), 283 tags.end(), 284 required_tags, 285 required_tags + required_tag_count)); 286 } 287 { 288 // Invalid resource should fail and write no data. 289 TestCompletionCallbackWithOutput< std::vector<uint32_t> > cc( 290 instance_->pp_instance(), false); 291 cc.WaitForResult( 292 ppb_truetype_font_interface_->GetTableTags( 293 kInvalidResource, 294 cc.GetCallback().output(), 295 cc.GetCallback().pp_completion_callback())); 296 ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result()); 297 ASSERT_EQ(0, cc.output().size()); 298 } 299 300 PASS(); 301 } 302 303 std::string TestTrueTypeFont::TestGetTable() { 304 pp::TrueTypeFontDesc_Dev desc; 305 pp::TrueTypeFont_Dev font(instance_, desc); 306 307 { 308 // Getting a required table from a valid font should succeed. 309 TestCompletionCallbackWithOutput< std::vector<char> > cc1( 310 instance_->pp_instance(), false); 311 cc1.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 312 0, std::numeric_limits<int32_t>::max(), 313 cc1.GetCallback())); 314 const std::vector<char> cmap_data = cc1.output(); 315 ASSERT_NE(0, cmap_data.size()); 316 ASSERT_EQ(static_cast<int32_t>(cmap_data.size()), cc1.result()); 317 318 // Passing 0 for the table tag should return the entire font. 319 TestCompletionCallbackWithOutput< std::vector<char> > cc2( 320 instance_->pp_instance(), false); 321 cc2.WaitForResult(font.GetTable(0 /* table_tag */, 322 0, std::numeric_limits<int32_t>::max(), 323 cc2.GetCallback())); 324 const std::vector<char> entire_font = cc2.output(); 325 ASSERT_NE(0, entire_font.size()); 326 ASSERT_EQ(static_cast<int32_t>(entire_font.size()), cc2.result()); 327 328 // Verify that the CMAP table is in entire_font, and that it's identical 329 // to the one we retrieved above. Note that since the font header and table 330 // directory are in file (big-endian) order, we need to byte swap tags and 331 // numbers. 332 const size_t kHeaderSize = sizeof(FontHeader); 333 const size_t kEntrySize = sizeof(FontDirectoryEntry); 334 ASSERT_TRUE(kHeaderSize < entire_font.size()); 335 FontHeader header; 336 memcpy(&header, &entire_font[0], kHeaderSize); 337 uint16_t num_tables = ReadBigEndian16(&header.num_tables); 338 std::vector<FontDirectoryEntry> directory(num_tables); 339 size_t directory_size = kEntrySize * num_tables; 340 ASSERT_TRUE(kHeaderSize + directory_size < entire_font.size()); 341 memcpy(&directory[0], &entire_font[kHeaderSize], directory_size); 342 const FontDirectoryEntry* cmap_entry = NULL; 343 for (uint16_t i = 0; i < num_tables; i++) { 344 if (ReadBigEndian32(&directory[i].tag) == 345 MAKE_TABLE_TAG('c', 'm', 'a', 'p')) { 346 cmap_entry = &directory[i]; 347 break; 348 } 349 } 350 ASSERT_NE(NULL, cmap_entry); 351 352 uint32_t logical_length = ReadBigEndian32(&cmap_entry->logical_length); 353 uint32_t table_offset = ReadBigEndian32(&cmap_entry->offset); 354 ASSERT_EQ(static_cast<size_t>(logical_length), cmap_data.size()); 355 ASSERT_TRUE(static_cast<size_t>(table_offset + logical_length) < 356 entire_font.size()); 357 const char* cmap_table = &entire_font[0] + table_offset; 358 ASSERT_EQ(0, memcmp(cmap_table, &cmap_data[0], cmap_data.size())); 359 360 // Use offset and max_data_length to restrict the data. Read a part of 361 // the 'CMAP' table. 362 TestCompletionCallbackWithOutput< std::vector<char> > cc3( 363 instance_->pp_instance(), false); 364 const int32_t kOffset = 4; 365 int32_t partial_cmap_size = static_cast<int32_t>(cmap_data.size() - 64); 366 cc3.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 367 kOffset, 368 partial_cmap_size, 369 cc3.GetCallback())); 370 const std::vector<char> partial_cmap_data = cc3.output(); 371 ASSERT_EQ(partial_cmap_data.size(), static_cast<size_t>(cc3.result())); 372 ASSERT_EQ(partial_cmap_data.size(), static_cast<size_t>(partial_cmap_size)); 373 ASSERT_EQ(0, memcmp(cmap_table + kOffset, &partial_cmap_data[0], 374 partial_cmap_size)); 375 } 376 { 377 // Getting an invalid table should fail ('zzzz' should be safely invalid). 378 TestCompletionCallbackWithOutput< std::vector<char> > cc( 379 instance_->pp_instance(), false); 380 cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('z', 'z', 'z', 'z'), 381 0, std::numeric_limits<int32_t>::max(), 382 cc.GetCallback())); 383 ASSERT_EQ(0, cc.output().size()); 384 ASSERT_EQ(PP_ERROR_FAILED, cc.result()); 385 } 386 { 387 // GetTable on an invalid resource should fail with a bad resource error 388 // and write no data. 389 TestCompletionCallbackWithOutput< std::vector<char> > cc( 390 instance_->pp_instance(), false); 391 cc.WaitForResult( 392 ppb_truetype_font_interface_->GetTable( 393 kInvalidResource, 394 MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 395 0, std::numeric_limits<int32_t>::max(), 396 cc.GetCallback().output(), 397 cc.GetCallback().pp_completion_callback())); 398 ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result()); 399 ASSERT_EQ(0, cc.output().size()); 400 } 401 { 402 // Negative offset should fail with a bad argument error and write no data. 403 TestCompletionCallbackWithOutput< std::vector<char> > cc( 404 instance_->pp_instance(), false); 405 cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 406 -100, 0, 407 cc.GetCallback())); 408 ASSERT_EQ(PP_ERROR_BADARGUMENT, cc.result()); 409 ASSERT_EQ(0, cc.output().size()); 410 } 411 { 412 // Offset larger than file size succeeds but returns no data. 413 TestCompletionCallbackWithOutput< std::vector<char> > cc( 414 instance_->pp_instance(), false); 415 cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 416 1 << 28, 0, 417 cc.GetCallback())); 418 ASSERT_EQ(PP_OK, cc.result()); 419 ASSERT_EQ(0, cc.output().size()); 420 } 421 { 422 // Negative max_data_length should fail with a bad argument error and write 423 // no data. 424 TestCompletionCallbackWithOutput< std::vector<char> > cc( 425 instance_->pp_instance(), false); 426 cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'), 427 0, -100, 428 cc.GetCallback())); 429 ASSERT_EQ(PP_ERROR_BADARGUMENT, cc.result()); 430 ASSERT_EQ(0, cc.output().size()); 431 } 432 433 PASS(); 434 } 435