blob: 24c081b911e4fce22ef45d86f948261970c14f91 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Tests PPB_TrueTypeFont interface.
#include "ppapi/tests/test_truetype_font.h"
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <limits>
#include "ppapi/c/private/ppb_testing_private.h"
#include "ppapi/cpp/completion_callback.h"
#include "ppapi/cpp/dev/truetype_font_dev.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/var.h"
#include "ppapi/tests/test_utils.h"
#include "ppapi/tests/testing_instance.h"
REGISTER_TEST_CASE(TrueTypeFont);
#define MAKE_TABLE_TAG(a, b, c, d) ((a) << 24) + ((b) << 16) + ((c) << 8) + (d)
namespace {
const PP_Resource kInvalidResource = 0;
const PP_Instance kInvalidInstance = 0;
// TrueType font header and table entry structs. See
// https://developer.apple.com/fonts/TTRefMan/RM06/Chap6.html
struct FontHeader {
int32_t font_type;
uint16_t num_tables;
uint16_t search_range;
uint16_t entry_selector;
uint16_t range_shift;
};
struct FontDirectoryEntry {
uint32_t tag;
uint32_t checksum;
uint32_t offset;
uint32_t logical_length;
};
uint32_t ReadBigEndian32(const void* ptr) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(ptr);
return (data[3] << 0) | (data[2] << 8) | (data[1] << 16) | (data[0] << 24);
}
uint16_t ReadBigEndian16(const void* ptr) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(ptr);
return (data[1] << 0) | (data[0] << 8);
}
}
TestTrueTypeFont::TestTrueTypeFont(TestingInstance* instance)
: TestCase(instance),
ppb_truetype_font_interface_(NULL),
ppb_core_interface_(NULL),
ppb_var_interface_(NULL) {
}
bool TestTrueTypeFont::Init() {
ppb_truetype_font_interface_ = static_cast<const PPB_TrueTypeFont_Dev*>(
pp::Module::Get()->GetBrowserInterface(PPB_TRUETYPEFONT_DEV_INTERFACE));
if (!ppb_truetype_font_interface_)
instance_->AppendError("PPB_TrueTypeFont_Dev interface not available");
ppb_core_interface_ = static_cast<const PPB_Core*>(
pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE));
if (!ppb_core_interface_)
instance_->AppendError("PPB_Core interface not available");
ppb_var_interface_ = static_cast<const PPB_Var*>(
pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE));
if (!ppb_var_interface_)
instance_->AppendError("PPB_Var interface not available");
return
ppb_truetype_font_interface_ &&
ppb_core_interface_ &&
ppb_var_interface_;
}
TestTrueTypeFont::~TestTrueTypeFont() {
}
void TestTrueTypeFont::RunTests(const std::string& filter) {
RUN_TEST(GetFontFamilies, filter);
RUN_TEST(GetFontsInFamily, filter);
RUN_TEST(Create, filter);
RUN_TEST(Describe, filter);
RUN_TEST(GetTableTags, filter);
RUN_TEST(GetTable, filter);
}
std::string TestTrueTypeFont::TestGetFontFamilies() {
{
// A valid instance should be able to enumerate fonts.
TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(pp::TrueTypeFont_Dev::GetFontFamilies(instance_,
cc.GetCallback()));
const std::vector<pp::Var> font_families = cc.output();
// We should get some font families on any platform.
ASSERT_NE(0, font_families.size());
ASSERT_EQ(static_cast<int32_t>(font_families.size()), cc.result());
// Make sure at least one family is a non-empty string.
ASSERT_NE(0, font_families[0].AsString().size());
}
{
// Using an invalid instance should fail.
TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(
ppb_truetype_font_interface_->GetFontFamilies(
kInvalidInstance,
cc.GetCallback().output(),
cc.GetCallback().pp_completion_callback()));
ASSERT_TRUE(cc.result() == PP_ERROR_FAILED ||
cc.result() == PP_ERROR_BADARGUMENT);
ASSERT_EQ(0, cc.output().size());
}
PASS();
}
std::string TestTrueTypeFont::TestGetFontsInFamily() {
{
// Get the list of all font families.
TestCompletionCallbackWithOutput< std::vector<pp::Var> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(pp::TrueTypeFont_Dev::GetFontFamilies(instance_,
cc.GetCallback()));
// Try to use a common family that is likely to have multiple variations.
const std::vector<pp::Var> families = cc.output();
pp::Var family("Arial");
if (std::find(families.begin(), families.end(), family) == families.end()) {
family = pp::Var("Times");
if (std::find(families.begin(), families.end(), family) == families.end())
family = families[0]; // Just use the first family.
}
// GetFontsInFamily: A valid instance should be able to enumerate fonts
// in a given family.
TestCompletionCallbackWithOutput< std::vector<pp::TrueTypeFontDesc_Dev> >
cc2(instance_->pp_instance(), false);
cc2.WaitForResult(pp::TrueTypeFont_Dev::GetFontsInFamily(
instance_,
family,
cc2.GetCallback()));
std::vector<pp::TrueTypeFontDesc_Dev> fonts_in_family = cc2.output();
ASSERT_NE(0, fonts_in_family.size());
ASSERT_EQ(static_cast<int32_t>(fonts_in_family.size()), cc2.result());
// We should be able to create any of the returned fonts without fallback.
for (size_t i = 0; i < fonts_in_family.size(); ++i) {
pp::TrueTypeFontDesc_Dev& font_in_family = fonts_in_family[i];
pp::TrueTypeFont_Dev font(instance_, font_in_family);
TestCompletionCallbackWithOutput<pp::TrueTypeFontDesc_Dev> cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.Describe(cc.GetCallback()));
const pp::TrueTypeFontDesc_Dev desc = cc.output();
ASSERT_EQ(family, desc.family());
ASSERT_EQ(font_in_family.style(), desc.style());
ASSERT_EQ(font_in_family.weight(), desc.weight());
}
}
{
// Using an invalid instance should fail.
TestCompletionCallbackWithOutput< std::vector<pp::TrueTypeFontDesc_Dev> >
cc(instance_->pp_instance(), false);
pp::Var family("Times");
cc.WaitForResult(
ppb_truetype_font_interface_->GetFontsInFamily(
kInvalidInstance,
family.pp_var(),
cc.GetCallback().output(),
cc.GetCallback().pp_completion_callback()));
ASSERT_TRUE(cc.result() == PP_ERROR_FAILED ||
cc.result() == PP_ERROR_BADARGUMENT);
ASSERT_EQ(0, cc.output().size());
}
PASS();
}
std::string TestTrueTypeFont::TestCreate() {
PP_Resource font;
PP_TrueTypeFontDesc_Dev desc = {
PP_MakeUndefined(),
PP_TRUETYPEFONTFAMILY_SERIF,
PP_TRUETYPEFONTSTYLE_NORMAL,
PP_TRUETYPEFONTWEIGHT_NORMAL,
PP_TRUETYPEFONTWIDTH_NORMAL,
PP_TRUETYPEFONTCHARSET_DEFAULT
};
// Creating a font from an invalid instance returns an invalid resource.
font = ppb_truetype_font_interface_->Create(kInvalidInstance, &desc);
ASSERT_EQ(kInvalidResource, font);
ASSERT_NE(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font));
// Creating a font from a valid instance returns a font resource.
font = ppb_truetype_font_interface_->Create(instance_->pp_instance(), &desc);
ASSERT_NE(kInvalidResource, font);
ASSERT_EQ(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font));
ppb_core_interface_->ReleaseResource(font);
// Once released, the resource shouldn't be a font.
ASSERT_NE(PP_TRUE, ppb_truetype_font_interface_->IsTrueTypeFont(font));
PASS();
}
std::string TestTrueTypeFont::TestDescribe() {
pp::TrueTypeFontDesc_Dev create_desc;
create_desc.set_generic_family(PP_TRUETYPEFONTFAMILY_SERIF);
create_desc.set_style(PP_TRUETYPEFONTSTYLE_NORMAL);
create_desc.set_weight(PP_TRUETYPEFONTWEIGHT_NORMAL);
pp::TrueTypeFont_Dev font(instance_, create_desc);
// Describe: See what font-matching did with a generic font. We should always
// be able to Create a generic Serif font.
TestCompletionCallbackWithOutput<pp::TrueTypeFontDesc_Dev> cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.Describe(cc.GetCallback()));
const pp::TrueTypeFontDesc_Dev desc = cc.output();
ASSERT_NE(0, desc.family().AsString().size());
ASSERT_EQ(PP_TRUETYPEFONTFAMILY_SERIF, desc.generic_family());
ASSERT_EQ(PP_TRUETYPEFONTSTYLE_NORMAL, desc.style());
ASSERT_EQ(PP_TRUETYPEFONTWEIGHT_NORMAL, desc.weight());
// Describe an invalid resource should fail.
PP_TrueTypeFontDesc_Dev fail_desc;
memset(&fail_desc, 0, sizeof(fail_desc));
fail_desc.family = PP_MakeUndefined();
// Create a shallow copy to check that no data is changed.
PP_TrueTypeFontDesc_Dev fail_desc_copy;
memcpy(&fail_desc_copy, &fail_desc, sizeof(fail_desc));
cc.WaitForResult(
ppb_truetype_font_interface_->Describe(
kInvalidResource,
&fail_desc,
cc.GetCallback().pp_completion_callback()));
ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result());
ASSERT_EQ(PP_VARTYPE_UNDEFINED, fail_desc.family.type);
ASSERT_EQ(0, memcmp(&fail_desc, &fail_desc_copy, sizeof(fail_desc)));
PASS();
}
std::string TestTrueTypeFont::TestGetTableTags() {
pp::TrueTypeFontDesc_Dev desc;
pp::TrueTypeFont_Dev font(instance_, desc);
{
TestCompletionCallbackWithOutput< std::vector<uint32_t> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.GetTableTags(cc.GetCallback()));
std::vector<uint32_t> tags = cc.output();
ASSERT_NE(0, tags.size());
ASSERT_EQ(static_cast<int32_t>(tags.size()), cc.result());
// Tags will vary depending on the actual font that the host platform
// chooses. Check that all required TrueType tags are present.
const int required_tag_count = 9;
uint32_t required_tags[required_tag_count] = {
// Note: these must be sorted for std::includes below.
MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
MAKE_TABLE_TAG('g', 'l', 'y', 'f'),
MAKE_TABLE_TAG('h', 'e', 'a', 'd'),
MAKE_TABLE_TAG('h', 'h', 'e', 'a'),
MAKE_TABLE_TAG('h', 'm', 't', 'x'),
MAKE_TABLE_TAG('l', 'o', 'c', 'a'),
MAKE_TABLE_TAG('m', 'a', 'x', 'p'),
MAKE_TABLE_TAG('n', 'a', 'm', 'e'),
MAKE_TABLE_TAG('p', 'o', 's', 't')
};
std::sort(tags.begin(), tags.end());
ASSERT_TRUE(std::includes(tags.begin(),
tags.end(),
required_tags,
required_tags + required_tag_count));
}
{
// Invalid resource should fail and write no data.
TestCompletionCallbackWithOutput< std::vector<uint32_t> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(
ppb_truetype_font_interface_->GetTableTags(
kInvalidResource,
cc.GetCallback().output(),
cc.GetCallback().pp_completion_callback()));
ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result());
ASSERT_EQ(0, cc.output().size());
}
PASS();
}
std::string TestTrueTypeFont::TestGetTable() {
pp::TrueTypeFontDesc_Dev desc;
pp::TrueTypeFont_Dev font(instance_, desc);
{
// Getting a required table from a valid font should succeed.
TestCompletionCallbackWithOutput< std::vector<char> > cc1(
instance_->pp_instance(), false);
cc1.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
0, std::numeric_limits<int32_t>::max(),
cc1.GetCallback()));
const std::vector<char> cmap_data = cc1.output();
ASSERT_NE(0, cmap_data.size());
ASSERT_EQ(static_cast<int32_t>(cmap_data.size()), cc1.result());
// Passing 0 for the table tag should return the entire font.
TestCompletionCallbackWithOutput< std::vector<char> > cc2(
instance_->pp_instance(), false);
cc2.WaitForResult(font.GetTable(0 /* table_tag */,
0, std::numeric_limits<int32_t>::max(),
cc2.GetCallback()));
const std::vector<char> entire_font = cc2.output();
ASSERT_NE(0, entire_font.size());
ASSERT_EQ(static_cast<int32_t>(entire_font.size()), cc2.result());
// Verify that the CMAP table is in entire_font, and that it's identical
// to the one we retrieved above. Note that since the font header and table
// directory are in file (big-endian) order, we need to byte swap tags and
// numbers.
const size_t kHeaderSize = sizeof(FontHeader);
const size_t kEntrySize = sizeof(FontDirectoryEntry);
ASSERT_TRUE(kHeaderSize < entire_font.size());
FontHeader header;
memcpy(&header, &entire_font[0], kHeaderSize);
uint16_t num_tables = ReadBigEndian16(&header.num_tables);
std::vector<FontDirectoryEntry> directory(num_tables);
size_t directory_size = kEntrySize * num_tables;
ASSERT_TRUE(kHeaderSize + directory_size < entire_font.size());
memcpy(&directory[0], &entire_font[kHeaderSize], directory_size);
const FontDirectoryEntry* cmap_entry = NULL;
for (uint16_t i = 0; i < num_tables; i++) {
if (ReadBigEndian32(&directory[i].tag) ==
MAKE_TABLE_TAG('c', 'm', 'a', 'p')) {
cmap_entry = &directory[i];
break;
}
}
ASSERT_NE(NULL, cmap_entry);
uint32_t logical_length = ReadBigEndian32(&cmap_entry->logical_length);
uint32_t table_offset = ReadBigEndian32(&cmap_entry->offset);
ASSERT_EQ(static_cast<size_t>(logical_length), cmap_data.size());
ASSERT_TRUE(static_cast<size_t>(table_offset + logical_length) <
entire_font.size());
const char* cmap_table = &entire_font[0] + table_offset;
ASSERT_EQ(0, memcmp(cmap_table, &cmap_data[0], cmap_data.size()));
// Use offset and max_data_length to restrict the data. Read a part of
// the 'CMAP' table.
TestCompletionCallbackWithOutput< std::vector<char> > cc3(
instance_->pp_instance(), false);
const int32_t kOffset = 4;
int32_t partial_cmap_size = static_cast<int32_t>(cmap_data.size() - 64);
cc3.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
kOffset,
partial_cmap_size,
cc3.GetCallback()));
const std::vector<char> partial_cmap_data = cc3.output();
ASSERT_EQ(partial_cmap_data.size(), static_cast<size_t>(cc3.result()));
ASSERT_EQ(partial_cmap_data.size(), static_cast<size_t>(partial_cmap_size));
ASSERT_EQ(0, memcmp(cmap_table + kOffset, &partial_cmap_data[0],
partial_cmap_size));
}
{
// Getting an invalid table should fail ('zzzz' should be safely invalid).
TestCompletionCallbackWithOutput< std::vector<char> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('z', 'z', 'z', 'z'),
0, std::numeric_limits<int32_t>::max(),
cc.GetCallback()));
ASSERT_EQ(0, cc.output().size());
ASSERT_EQ(PP_ERROR_FAILED, cc.result());
}
{
// GetTable on an invalid resource should fail with a bad resource error
// and write no data.
TestCompletionCallbackWithOutput< std::vector<char> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(
ppb_truetype_font_interface_->GetTable(
kInvalidResource,
MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
0, std::numeric_limits<int32_t>::max(),
cc.GetCallback().output(),
cc.GetCallback().pp_completion_callback()));
ASSERT_EQ(PP_ERROR_BADRESOURCE, cc.result());
ASSERT_EQ(0, cc.output().size());
}
{
// Negative offset should fail with a bad argument error and write no data.
TestCompletionCallbackWithOutput< std::vector<char> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
-100, 0,
cc.GetCallback()));
ASSERT_EQ(PP_ERROR_BADARGUMENT, cc.result());
ASSERT_EQ(0, cc.output().size());
}
{
// Offset larger than file size succeeds but returns no data.
TestCompletionCallbackWithOutput< std::vector<char> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
1 << 28, 0,
cc.GetCallback()));
ASSERT_EQ(PP_OK, cc.result());
ASSERT_EQ(0, cc.output().size());
}
{
// Negative max_data_length should fail with a bad argument error and write
// no data.
TestCompletionCallbackWithOutput< std::vector<char> > cc(
instance_->pp_instance(), false);
cc.WaitForResult(font.GetTable(MAKE_TABLE_TAG('c', 'm', 'a', 'p'),
0, -100,
cc.GetCallback()));
ASSERT_EQ(PP_ERROR_BADARGUMENT, cc.result());
ASSERT_EQ(0, cc.output().size());
}
PASS();
}