| // Copyright 2014 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. |
| |
| #include "content/browser/renderer_host/pepper/pepper_truetype_font.h" |
| |
| #import <ApplicationServices/ApplicationServices.h> |
| |
| #include <stdio.h> |
| |
| #include "base/compiler_specific.h" |
| #include "base/mac/foundation_util.h" |
| #include "base/mac/scoped_cftyperef.h" |
| #include "base/mac/scoped_nsautorelease_pool.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/sys_byteorder.h" |
| #include "ppapi/c/dev/ppb_truetype_font_dev.h" |
| #include "ppapi/c/pp_errors.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| static bool FindFloat(CFDictionaryRef dict, CFStringRef name, float* value) { |
| CFNumberRef num; |
| return CFDictionaryGetValueIfPresent( |
| dict, name, reinterpret_cast<const void**>(&num)) && |
| CFNumberIsFloatType(num) && |
| CFNumberGetValue(num, kCFNumberFloatType, value); |
| } |
| |
| float GetMacWeight(PP_TrueTypeFontWeight_Dev weight) { |
| // Map values from NORMAL (400) to HEAVY (900) to the range [0 .. 1], and |
| // values below NORMAL to the range [-0.6 .. 0]. NORMAL should map to 0. |
| float normal = PP_TRUETYPEFONTWEIGHT_NORMAL; |
| float heavy = PP_TRUETYPEFONTWEIGHT_HEAVY; |
| return (weight - normal) / (heavy - normal); |
| } |
| |
| PP_TrueTypeFontWeight_Dev GetPepperWeight(float weight) { |
| // Perform the inverse mapping of GetMacWeight. |
| return static_cast<PP_TrueTypeFontWeight_Dev>( |
| weight * (PP_TRUETYPEFONTWEIGHT_HEAVY - PP_TRUETYPEFONTWEIGHT_NORMAL) + |
| PP_TRUETYPEFONTWEIGHT_NORMAL); |
| } |
| |
| float GetMacWidth(PP_TrueTypeFontWidth_Dev width) { |
| // Map values from NORMAL (4) to ULTRA_EXPANDED (8) to the range [0 .. 1], |
| // and values below NORMAL to the range [-1 .. 0]. Normal should map to 0. |
| float normal = PP_TRUETYPEFONTWIDTH_NORMAL; |
| float ultra_expanded = PP_TRUETYPEFONTWIDTH_ULTRAEXPANDED; |
| return (width - normal) / (ultra_expanded - normal); |
| } |
| |
| PP_TrueTypeFontWidth_Dev GetPepperWidth(float width) { |
| // Perform the inverse mapping of GetMacWeight. |
| return static_cast<PP_TrueTypeFontWidth_Dev>( |
| width * |
| (PP_TRUETYPEFONTWIDTH_ULTRAEXPANDED - PP_TRUETYPEFONTWIDTH_NORMAL) + |
| PP_TRUETYPEFONTWIDTH_NORMAL); |
| } |
| |
| #define MAKE_TABLE_TAG(a, b, c, d) ((a) << 24) + ((b) << 16) + ((c) << 8) + (d) |
| |
| // 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; |
| }; |
| static_assert(sizeof(FontHeader) == 12, "FontHeader wrong size"); |
| |
| struct FontDirectoryEntry { |
| uint32_t tag; |
| uint32_t checksum; |
| uint32_t offset; |
| uint32_t logical_length; |
| }; |
| static_assert(sizeof(FontDirectoryEntry) == 16, |
| "FontDirectoryEntry wrong size"); |
| |
| uint32_t CalculateChecksum(char* table, int32_t table_length) { |
| uint32_t sum = 0; |
| uint32_t* current = reinterpret_cast<uint32_t*>(table); |
| uint32_t length = (table_length + 3) / 4; |
| // Raw font data is big-endian. |
| while (length-- > 0) |
| sum += base::NetToHost32(*current++); |
| return sum; |
| } |
| |
| class PepperTrueTypeFontMac : public PepperTrueTypeFont { |
| public: |
| PepperTrueTypeFontMac(); |
| |
| // PepperTrueTypeFont implementation. |
| int32_t Initialize(ppapi::proxy::SerializedTrueTypeFontDesc* desc) override; |
| int32_t GetTableTags(std::vector<uint32_t>* tags) override; |
| int32_t GetTable(uint32_t table_tag, |
| int32_t offset, |
| int32_t max_data_length, |
| std::string* data) override; |
| |
| private: |
| ~PepperTrueTypeFontMac() override; |
| |
| virtual int32_t GetEntireFont(int32_t offset, |
| int32_t max_data_length, |
| std::string* data); |
| |
| base::ScopedCFTypeRef<CTFontRef> font_ref_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PepperTrueTypeFontMac); |
| }; |
| |
| PepperTrueTypeFontMac::PepperTrueTypeFontMac() { |
| } |
| |
| PepperTrueTypeFontMac::~PepperTrueTypeFontMac() { |
| } |
| |
| int32_t PepperTrueTypeFontMac::Initialize( |
| ppapi::proxy::SerializedTrueTypeFontDesc* desc) { |
| // Create the font in a nested scope, so we can use the same variable names |
| // when we get the actual font characteristics. |
| { |
| // Create attributes and traits dictionaries. |
| base::ScopedCFTypeRef<CFMutableDictionaryRef> attributes_ref( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, |
| 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| |
| base::ScopedCFTypeRef<CFMutableDictionaryRef> traits_ref( |
| CFDictionaryCreateMutable(kCFAllocatorDefault, |
| 0, |
| &kCFTypeDictionaryKeyCallBacks, |
| &kCFTypeDictionaryValueCallBacks)); |
| if (!attributes_ref || !traits_ref) |
| return PP_ERROR_FAILED; |
| |
| CFDictionaryAddValue(attributes_ref, kCTFontTraitsAttribute, traits_ref); |
| |
| // Use symbolic traits to specify traits when possible. |
| CTFontSymbolicTraits symbolic_traits = 0; |
| if (desc->style & PP_TRUETYPEFONTSTYLE_ITALIC) |
| symbolic_traits |= kCTFontItalicTrait; |
| if (desc->weight == PP_TRUETYPEFONTWEIGHT_BOLD) |
| symbolic_traits |= kCTFontBoldTrait; |
| if (desc->width == PP_TRUETYPEFONTWIDTH_CONDENSED) |
| symbolic_traits |= kCTFontCondensedTrait; |
| else if (desc->width == PP_TRUETYPEFONTWIDTH_EXPANDED) |
| symbolic_traits |= kCTFontExpandedTrait; |
| |
| base::ScopedCFTypeRef<CFNumberRef> symbolic_traits_ref(CFNumberCreate( |
| kCFAllocatorDefault, kCFNumberSInt32Type, &symbolic_traits)); |
| if (!symbolic_traits_ref) |
| return PP_ERROR_FAILED; |
| CFDictionaryAddValue(traits_ref, kCTFontSymbolicTrait, symbolic_traits_ref); |
| |
| // Font family matching doesn't work using family classes in symbolic |
| // traits. Instead, map generic_family to font families that are always |
| // available. |
| std::string family(desc->family); |
| if (family.empty()) { |
| switch (desc->generic_family) { |
| case PP_TRUETYPEFONTFAMILY_SERIF: |
| family = "Times"; |
| break; |
| case PP_TRUETYPEFONTFAMILY_SANSSERIF: |
| family = "Helvetica"; |
| break; |
| case PP_TRUETYPEFONTFAMILY_CURSIVE: |
| family = "Apple Chancery"; |
| break; |
| case PP_TRUETYPEFONTFAMILY_FANTASY: |
| family = "Papyrus"; |
| break; |
| case PP_TRUETYPEFONTFAMILY_MONOSPACE: |
| family = "Courier"; |
| break; |
| } |
| } |
| |
| base::ScopedCFTypeRef<CFStringRef> name_ref( |
| base::SysUTF8ToCFStringRef(family)); |
| if (name_ref) |
| CFDictionaryAddValue( |
| attributes_ref, kCTFontFamilyNameAttribute, name_ref); |
| |
| if (desc->weight != PP_TRUETYPEFONTWEIGHT_NORMAL && |
| desc->weight != PP_TRUETYPEFONTWEIGHT_BOLD) { |
| float weight = GetMacWeight(desc->weight); |
| base::ScopedCFTypeRef<CFNumberRef> weight_trait_ref( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &weight)); |
| if (weight_trait_ref) |
| CFDictionaryAddValue(traits_ref, kCTFontWeightTrait, weight_trait_ref); |
| } |
| |
| if (desc->width != PP_TRUETYPEFONTWIDTH_NORMAL && |
| desc->width != PP_TRUETYPEFONTWIDTH_CONDENSED && |
| desc->width != PP_TRUETYPEFONTWIDTH_EXPANDED) { |
| float width = GetMacWidth(desc->width); |
| base::ScopedCFTypeRef<CFNumberRef> width_trait_ref( |
| CFNumberCreate(kCFAllocatorDefault, kCFNumberFloat32Type, &width)); |
| if (width_trait_ref) |
| CFDictionaryAddValue(traits_ref, kCTFontWidthTrait, width_trait_ref); |
| } |
| |
| base::ScopedCFTypeRef<CTFontDescriptorRef> desc_ref( |
| CTFontDescriptorCreateWithAttributes(attributes_ref)); |
| |
| if (desc_ref) |
| font_ref_.reset(CTFontCreateWithFontDescriptor(desc_ref, 0, NULL)); |
| |
| if (!font_ref_.get()) |
| return PP_ERROR_FAILED; |
| } |
| |
| // Now query to get the actual font characteristics. |
| base::ScopedCFTypeRef<CTFontDescriptorRef> desc_ref( |
| CTFontCopyFontDescriptor(font_ref_)); |
| |
| base::ScopedCFTypeRef<CFStringRef> family_name_ref( |
| base::mac::CFCast<CFStringRef>( |
| CTFontDescriptorCopyAttribute(desc_ref, kCTFontFamilyNameAttribute))); |
| desc->family = base::SysCFStringRefToUTF8(family_name_ref); |
| |
| base::ScopedCFTypeRef<CFDictionaryRef> traits_ref( |
| base::mac::CFCast<CFDictionaryRef>( |
| CTFontDescriptorCopyAttribute(desc_ref, kCTFontTraitsAttribute))); |
| |
| desc->style = PP_TRUETYPEFONTSTYLE_NORMAL; |
| CTFontSymbolicTraits symbolic_traits(CTFontGetSymbolicTraits(font_ref_)); |
| if (symbolic_traits & kCTFontItalicTrait) |
| desc->style = static_cast<PP_TrueTypeFontStyle_Dev>( |
| desc->style | PP_TRUETYPEFONTSTYLE_ITALIC); |
| if (symbolic_traits & kCTFontBoldTrait) { |
| desc->weight = PP_TRUETYPEFONTWEIGHT_BOLD; |
| } else { |
| float weight; |
| if (FindFloat(traits_ref, kCTFontWeightTrait, &weight)) |
| desc->weight = GetPepperWeight(weight); |
| } |
| if (symbolic_traits & kCTFontCondensedTrait) { |
| desc->width = PP_TRUETYPEFONTWIDTH_CONDENSED; |
| } else if (symbolic_traits & kCTFontExpandedTrait) { |
| desc->width = PP_TRUETYPEFONTWIDTH_EXPANDED; |
| } else { |
| float width; |
| if (FindFloat(traits_ref, kCTFontWidthTrait, &width)) |
| desc->width = GetPepperWidth(width); |
| } |
| |
| // Character set isn't supported on Mac. |
| desc->charset = PP_TRUETYPEFONTCHARSET_DEFAULT; |
| return PP_OK; |
| } |
| |
| int32_t PepperTrueTypeFontMac::GetTableTags(std::vector<uint32_t>* tags) { |
| if (!font_ref_.get()) |
| return PP_ERROR_FAILED; |
| base::ScopedCFTypeRef<CFArrayRef> tag_array( |
| CTFontCopyAvailableTables(font_ref_, kCTFontTableOptionNoOptions)); |
| if (!tag_array) |
| return PP_ERROR_FAILED; |
| |
| // Items returned by CTFontCopyAvailableTables are not boxed. Whose bright |
| // idea was this? |
| CFIndex length = CFArrayGetCount(tag_array); |
| tags->resize(length); |
| for (CFIndex i = 0; i < length; ++i) { |
| (*tags)[i] = |
| reinterpret_cast<uintptr_t>(CFArrayGetValueAtIndex(tag_array, i)); |
| } |
| return length; |
| } |
| |
| int32_t PepperTrueTypeFontMac::GetTable(uint32_t table_tag, |
| int32_t offset, |
| int32_t max_data_length, |
| std::string* data) { |
| if (!font_ref_.get()) |
| return PP_ERROR_FAILED; |
| |
| if (!table_tag) |
| return GetEntireFont(offset, max_data_length, data); |
| |
| base::ScopedCFTypeRef<CFDataRef> table_ref( |
| CTFontCopyTable(font_ref_, |
| static_cast<CTFontTableTag>(table_tag), |
| kCTFontTableOptionNoOptions)); |
| if (!table_ref) |
| return PP_ERROR_FAILED; |
| |
| CFIndex table_size = CFDataGetLength(table_ref); |
| CFIndex safe_offset = |
| std::min(base::checked_cast<CFIndex>(offset), table_size); |
| CFIndex safe_length = std::min(table_size - safe_offset, |
| base::checked_cast<CFIndex>(max_data_length)); |
| data->resize(safe_length); |
| CFDataGetBytes(table_ref, |
| CFRangeMake(safe_offset, safe_length), |
| reinterpret_cast<UInt8*>(&(*data)[0])); |
| |
| return safe_length; |
| } |
| |
| int32_t PepperTrueTypeFontMac::GetEntireFont(int32_t offset, |
| int32_t max_data_length, |
| std::string* data) { |
| // Reconstruct the font header, table directory, and tables. |
| std::vector<uint32_t> table_tags; |
| int32_t table_count = GetTableTags(&table_tags); |
| if (table_count < 0) |
| return table_count; // PPAPI error code. |
| |
| // Allocate enough room for the header and the table directory entries. |
| std::string font( |
| sizeof(FontHeader) + sizeof(FontDirectoryEntry) * table_count, 0); |
| // Map the OS X font type value to a TrueType scalar type. |
| base::ScopedCFTypeRef<CFNumberRef> font_type_ref( |
| base::mac::CFCast<CFNumberRef>( |
| CTFontCopyAttribute(font_ref_, kCTFontFormatAttribute))); |
| int32_t font_type; |
| CFNumberGetValue(font_type_ref, kCFNumberSInt32Type, &font_type); |
| switch (font_type) { |
| case kCTFontFormatOpenTypePostScript: |
| font_type = MAKE_TABLE_TAG('O', 'T', 'T', 'O'); |
| break; |
| case kCTFontFormatTrueType: |
| case kCTFontFormatBitmap: |
| font_type = MAKE_TABLE_TAG('t', 'r', 'u', 'e'); |
| break; |
| case kCTFontFormatPostScript: |
| font_type = MAKE_TABLE_TAG('t', 'y', 'p', '1'); |
| break; |
| case kCTFontFormatOpenTypeTrueType: |
| case kCTFontFormatUnrecognized: |
| default: |
| font_type = MAKE_TABLE_TAG(0, 1, 0, 0); |
| break; |
| } |
| |
| // Calculate the rest of the header values. |
| uint16_t num_tables = base::checked_cast<uint16_t>(table_count); |
| uint16_t entry_selector = 0; |
| uint16_t search_range = 1; |
| while (search_range < (num_tables >> 1)) { |
| entry_selector++; |
| search_range <<= 1; |
| } |
| search_range <<= 4; |
| uint16_t range_shift = (num_tables << 4) - search_range; |
| |
| // Write the header, with values in big-endian order. |
| FontHeader* font_header = reinterpret_cast<FontHeader*>(&font[0]); |
| font_header->font_type = base::HostToNet32(font_type); |
| font_header->num_tables = base::HostToNet16(num_tables); |
| font_header->search_range = base::HostToNet16(search_range); |
| font_header->entry_selector = base::HostToNet16(entry_selector); |
| font_header->range_shift = base::HostToNet16(range_shift); |
| |
| for (int32_t i = 0; i < table_count; i++) { |
| // Get the table data. |
| std::string table; |
| int32_t table_size = |
| GetTable(table_tags[i], 0, std::numeric_limits<int32_t>::max(), &table); |
| if (table_size < 0) |
| return table_size; // PPAPI error code. |
| |
| // Append it to the font data so far, and zero pad so tables stay aligned. |
| size_t table_offset = font.size(); |
| font.append(table); |
| size_t padding = font.size() & 0x3; |
| font.append(padding, 0); |
| |
| // Fill in the directory entry for this table. |
| FontDirectoryEntry* entry = reinterpret_cast<FontDirectoryEntry*>( |
| &font[0] + sizeof(FontHeader) + i * sizeof(FontDirectoryEntry)); |
| entry->tag = base::HostToNet32(table_tags[i]); |
| entry->checksum = |
| base::HostToNet32(CalculateChecksum(&font[table_offset], table_size)); |
| entry->offset = base::HostToNet32(table_offset); |
| entry->logical_length = base::HostToNet32(table_size); |
| // TODO(bbudge) set the 'head' table checksumAdjustment. |
| } |
| |
| // Extract a substring if the caller specified an offset or max data length. |
| int32_t font_size = base::checked_cast<int32_t>(font.size()); |
| int32_t safe_offset = std::min(offset, font_size); |
| int32_t safe_length = std::min(font_size - safe_offset, max_data_length); |
| if (safe_offset || safe_length != font_size) |
| font = font.substr(safe_offset, safe_length); |
| |
| data->clear(); |
| data->swap(font); |
| return safe_length; |
| } |
| |
| } // namespace |
| |
| // static |
| PepperTrueTypeFont* PepperTrueTypeFont::Create() { |
| return new PepperTrueTypeFontMac(); |
| } |
| |
| } // namespace content |