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/private/ppb_testing_private.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
21REGISTER_TEST_CASE(TrueTypeFont);
22
23#define MAKE_TABLE_TAG(a, b, c, d) ((a) << 24) + ((b) << 16) + ((c) << 8) + (d)
24
25namespace {
26
27const PP_Resource kInvalidResource = 0;
28const PP_Instance kInvalidInstance = 0;
29
30// TrueType font header and table entry structs. See
31// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
32struct 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
40struct FontDirectoryEntry {
41  uint32_t tag;
42  uint32_t checksum;
43  uint32_t offset;
44  uint32_t logical_length;
45};
46
47uint32_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
52uint16_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
59TestTrueTypeFont::TestTrueTypeFont(TestingInstance* instance)
60    : TestCase(instance),
61      ppb_truetype_font_interface_(NULL),
62      ppb_core_interface_(NULL),
63      ppb_var_interface_(NULL) {
64}
65
66bool 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
88TestTrueTypeFont::~TestTrueTypeFont() {
89}
90
91void 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
100std::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
131std::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
192std::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
219std::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
256std::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
303std::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