blob: 6196cc67689eba098e39bc0389606ae6a85bbe99 [file] [log] [blame]
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkFontStyleSet_cobalt.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <cmath>
#include <limits>
#include "base/debug/trace_event.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "cobalt/renderer/rasterizer/skia/skia/src/ports/SkTypeface_cobalt.h"
#include "SkOSFile.h"
namespace {
int MatchScore(const SkFontStyle& pattern, const SkFontStyle& candidate) {
// This logic is taken from Skia and is based upon the algorithm specified
// within the spec:
// https://www.w3.org/TR/css-fonts-3/#font-matching-algorithm
int score = 0;
// CSS style (italic/oblique)
// Being italic trumps all valid weights which are not italic.
// Note that newer specs differentiate between italic and oblique.
if (pattern.isItalic() == candidate.isItalic()) {
score += 1001;
}
// The 'closer' to the target weight, the higher the score.
// 1000 is the 'heaviest' recognized weight
if (pattern.weight() == candidate.weight()) {
score += 1000;
} else if (pattern.weight() <= 500) {
if (400 <= pattern.weight() && pattern.weight() < 450) {
if (450 <= candidate.weight() && candidate.weight() <= 500) {
score += 500;
}
}
if (candidate.weight() <= pattern.weight()) {
score += 1000 - pattern.weight() + candidate.weight();
} else {
score += 1000 - candidate.weight();
}
} else if (pattern.weight() > 500) {
if (candidate.weight() > pattern.weight()) {
score += 1000 + pattern.weight() - candidate.weight();
} else {
score += candidate.weight();
}
}
return score;
}
} // namespace
// These functions are used by FreeType during FT_Open_Face.
extern "C" {
static unsigned long sk_cobalt_ft_stream_io(FT_Stream ftStream,
unsigned long offset,
unsigned char* buffer,
unsigned long count) {
SkStreamAsset* stream =
static_cast<SkStreamAsset*>(ftStream->descriptor.pointer);
stream->seek(offset);
return stream->read(buffer, count);
}
static void sk_cobalt_ft_stream_close(FT_Stream) {}
}
SkFontStyleSet_Cobalt::SkFontStyleSet_Cobalt(
const FontFamilyInfo& family_info, const char* base_path,
SkFileMemoryChunkStreamManager* const local_typeface_stream_manager,
SkMutex* const manager_owned_mutex)
: local_typeface_stream_manager_(local_typeface_stream_manager),
manager_owned_mutex_(manager_owned_mutex),
is_fallback_family_(family_info.is_fallback_family),
language_(family_info.language),
page_ranges_(family_info.page_ranges),
is_character_map_generated_(!is_fallback_family_) {
TRACE_EVENT0("cobalt::renderer",
"SkFontStyleSet_Cobalt::SkFontStyleSet_Cobalt()");
DCHECK(manager_owned_mutex_);
if (family_info.names.count() == 0) {
return;
}
family_name_ = family_info.names[0];
for (int i = 0; i < family_info.fonts.count(); ++i) {
const FontFileInfo& font_file = family_info.fonts[i];
SkString file_path(SkOSPath::Join(base_path, font_file.file_name.c_str()));
// Validate that the file exists at this location. If it does not, then skip
// over it; it isn't being added to the set.
if (!sk_exists(file_path.c_str(), kRead_SkFILE_Flag)) {
DLOG(INFO) << "Failed to find font file: " << file_path.c_str();
continue;
}
SkFontStyle style(font_file.weight, SkFontStyle::kNormal_Width,
font_file.style == FontFileInfo::kItalic_FontStyle
? SkFontStyle::kItalic_Slant
: SkFontStyle::kUpright_Slant);
std::string full_font_name;
if (!font_file.full_font_name.isEmpty()) {
full_font_name = std::string(font_file.full_font_name.c_str(),
font_file.full_font_name.size());
}
std::string postscript_name;
if (!font_file.postscript_name.isEmpty()) {
postscript_name = std::string(font_file.postscript_name.c_str(),
font_file.postscript_name.size());
}
styles_.push_back().reset(
SkNEW_ARGS(SkFontStyleSetEntry_Cobalt,
(file_path, font_file.index, style, full_font_name,
postscript_name, font_file.disable_synthetic_bolding)));
}
}
int SkFontStyleSet_Cobalt::count() {
SkAutoMutexAcquire scoped_mutex(*manager_owned_mutex_);
return styles_.count();
}
void SkFontStyleSet_Cobalt::getStyle(int index, SkFontStyle* style,
SkString* name) {
// SkFontStyleSet_Cobalt does not support publicly interacting with entries
// via index, as entries can potentially be removed, thereby invalidating the
// indices.
NOTREACHED();
}
SkTypeface* SkFontStyleSet_Cobalt::createTypeface(int index) {
// SkFontStyleSet_Cobalt does not support publicly interacting with entries
// via index, as entries can potentially be removed, thereby invalidating the
// indices.
NOTREACHED();
return NULL;
}
SkTypeface* SkFontStyleSet_Cobalt::matchStyle(const SkFontStyle& pattern) {
SkAutoMutexAcquire scoped_mutex(*manager_owned_mutex_);
return MatchStyleWithoutLocking(pattern);
}
SkTypeface* SkFontStyleSet_Cobalt::MatchStyleWithoutLocking(
const SkFontStyle& pattern) {
SkTypeface* typeface = NULL;
while (typeface == NULL && styles_.count() > 0) {
typeface = TryRetrieveTypefaceAndRemoveStyleOnFailure(
GetClosestStyleIndex(pattern));
}
return typeface;
}
SkTypeface* SkFontStyleSet_Cobalt::MatchFullFontName(const std::string& name) {
for (int i = 0; i < styles_.count(); ++i) {
if (styles_[i]->full_font_name == name) {
return TryRetrieveTypefaceAndRemoveStyleOnFailure(i);
}
}
return NULL;
}
SkTypeface* SkFontStyleSet_Cobalt::MatchFontPostScriptName(
const std::string& name) {
for (int i = 0; i < styles_.count(); ++i) {
if (styles_[i]->font_postscript_name == name) {
return TryRetrieveTypefaceAndRemoveStyleOnFailure(i);
}
}
return NULL;
}
SkTypeface* SkFontStyleSet_Cobalt::TryRetrieveTypefaceAndRemoveStyleOnFailure(
int style_index) {
DCHECK(style_index >= 0 && style_index < styles_.count());
SkFontStyleSetEntry_Cobalt* style = styles_[style_index];
// If the typeface doesn't already exist, then attempt to create it.
if (style->typeface == NULL) {
CreateStreamProviderTypeface(style);
// If the creation attempt failed and the typeface is still NULL, then
// remove the entry from the set's styles.
if (style->typeface == NULL) {
styles_[style_index].swap(&styles_.back());
styles_.pop_back();
return NULL;
}
}
return SkRef(style->typeface.get());
}
bool SkFontStyleSet_Cobalt::ContainsTypeface(const SkTypeface* typeface) {
for (int i = 0; i < styles_.count(); ++i) {
if (styles_[i]->typeface == typeface) {
return true;
}
}
return false;
}
bool SkFontStyleSet_Cobalt::ContainsCharacter(const SkFontStyle& style,
SkUnichar character) {
// If page ranges exist for this style set, then verify that this character
// falls within the ranges. Otherwise, the style set is treated as having a
// page range containing all characters.
size_t num_ranges = page_ranges_.count();
if (num_ranges > 0) {
int16 page = font_character_map::GetPage(character);
// Verify that the last page range is not less than the page containing the
// character. If it's less, then this character can't be contained
// within the pages. Otherwise, this last range acts as a sentinel for the
// search.
if (page_ranges_[num_ranges - 1].second < page) {
return false;
}
int range_index = 0;
while (page_ranges_[range_index].second < page) {
++range_index;
}
if (page_ranges_[range_index].first > page) {
return false;
}
}
// If this point is reached, then the character is contained within one of
// the font style set's page ranges. Now, verify that the specific character
// is supported by the font style set.
// The character map is lazily generated. Generate it now if it isn't already
// generated.
if (!is_character_map_generated_) {
TRACE_EVENT0("cobalt::renderer",
"SkFontStyleSet_Cobalt::ContainsCharacter() and "
"!is_character_map_generated_");
// Attempt to load the closest font style from the set. If it fails to load,
// it will be removed from the set and, as long as font styles remain in the
// set, the logic will be attempted again.
while (styles_.count() > 0) {
int style_index = GetClosestStyleIndex(style);
SkFontStyleSetEntry_Cobalt* closest_style = styles_[style_index];
SkFileMemoryChunkStreamProvider* stream_provider =
local_typeface_stream_manager_->GetStreamProvider(
closest_style->font_file_path.c_str());
// Create a snapshot prior to loading any additional memory chunks. In the
// case where the typeface does not end up being created, this enables the
// stream provider to purge newly created memory chunks, while retaining
// any pre-existing ones.
scoped_ptr<const SkFileMemoryChunks> memory_chunks_snapshot(
stream_provider->CreateMemoryChunksSnapshot());
SkAutoTUnref<SkFileMemoryChunkStream> stream(
stream_provider->OpenStream());
if (GenerateStyleFaceInfo(closest_style, stream)) {
if (CharacterMapContainsCharacter(character)) {
CreateStreamProviderTypeface(closest_style, stream_provider);
return true;
} else {
// If a typeface was not created, destroy the stream and purge any
// newly created memory chunks. The stream must be destroyed first or
// it will retain references to memory chunks, preventing them from
// being purged.
stream.reset(NULL);
stream_provider->PurgeUnusedMemoryChunks();
return false;
}
}
styles_[style_index].swap(&styles_.back());
styles_.pop_back();
}
is_character_map_generated_ = true;
}
return CharacterMapContainsCharacter(character);
}
bool SkFontStyleSet_Cobalt::CharacterMapContainsCharacter(SkUnichar character) {
font_character_map::CharacterMap::iterator page_iterator =
character_map_.find(font_character_map::GetPage(character));
return page_iterator != character_map_.end() &&
page_iterator->second.test(
font_character_map::GetPageCharacterIndex(character));
}
bool SkFontStyleSet_Cobalt::GenerateStyleFaceInfo(
SkFontStyleSetEntry_Cobalt* style, SkStreamAsset* stream) {
if (style->is_face_info_generated) {
return true;
}
TRACE_EVENT0("cobalt::renderer", "GenerateStyleFaceInfo()");
FT_Library freetype_lib;
if (FT_Init_FreeType(&freetype_lib) != 0) {
return false;
}
FT_StreamRec streamRec;
memset(&streamRec, 0, sizeof(streamRec));
streamRec.size = stream->getLength();
streamRec.descriptor.pointer = stream;
streamRec.read = sk_cobalt_ft_stream_io;
streamRec.close = sk_cobalt_ft_stream_close;
FT_Open_Args args;
memset(&args, 0, sizeof(args));
args.flags = FT_OPEN_STREAM;
args.stream = &streamRec;
FT_Face face;
FT_Error err = FT_Open_Face(freetype_lib, &args, style->face_index, &face);
if (err) {
FT_Done_FreeType(freetype_lib);
return false;
}
int face_style = SkTypeface::kNormal;
if (face->style_flags & FT_STYLE_FLAG_BOLD) {
face_style |= SkTypeface::kBold;
}
if (face->style_flags & FT_STYLE_FLAG_ITALIC) {
face_style |= SkTypeface::kItalic;
}
style->face_name.set(face->family_name);
style->face_style = static_cast<SkTypeface::Style>(face_style);
style->face_is_fixed_pitch = FT_IS_FIXED_WIDTH(face);
style->is_face_info_generated = true;
// Map out this family's characters if they haven't been generated yet.
if (!is_character_map_generated_) {
FT_UInt glyph_index;
int last_page = -1;
font_character_map::PageCharacters* page_characters = NULL;
SkUnichar code_point = FT_Get_First_Char(face, &glyph_index);
while (glyph_index) {
int page = font_character_map::GetPage(code_point);
if (page != last_page) {
page_characters = &character_map_[page];
last_page = page;
}
page_characters->set(
font_character_map::GetPageCharacterIndex(code_point));
code_point = FT_Get_Next_Char(face, code_point, &glyph_index);
}
is_character_map_generated_ = true;
}
// release this font.
FT_Done_Face(face);
// shut down FreeType.
FT_Done_FreeType(freetype_lib);
return true;
}
int SkFontStyleSet_Cobalt::GetClosestStyleIndex(const SkFontStyle& pattern) {
int closest_index = 0;
int max_score = std::numeric_limits<int>::min();
for (int i = 0; i < styles_.count(); ++i) {
int score = MatchScore(pattern, styles_[i]->font_style);
if (score > max_score) {
closest_index = i;
max_score = score;
}
}
return closest_index;
}
void SkFontStyleSet_Cobalt::CreateStreamProviderTypeface(
SkFontStyleSetEntry_Cobalt* style_entry,
SkFileMemoryChunkStreamProvider* stream_provider /*=NULL*/) {
TRACE_EVENT0("cobalt::renderer",
"SkFontStyleSet_Cobalt::CreateStreamProviderTypeface()");
if (!stream_provider) {
stream_provider = local_typeface_stream_manager_->GetStreamProvider(
style_entry->font_file_path.c_str());
}
SkAutoTUnref<SkFileMemoryChunkStream> stream(stream_provider->OpenStream());
if (GenerateStyleFaceInfo(style_entry, stream)) {
LOG(INFO) << "Scanned font from file: " << style_entry->face_name.c_str()
<< "(" << style_entry->face_style << ")";
style_entry->typeface.reset(
SkNEW_ARGS(SkTypeface_CobaltStreamProvider,
(stream_provider, style_entry->face_index,
style_entry->face_style, style_entry->face_is_fixed_pitch,
family_name_, style_entry->disable_synthetic_bolding)));
} else {
LOG(ERROR) << "Failed to scan font: "
<< style_entry->font_file_path.c_str();
}
}
void SkFontStyleSet_Cobalt::PurgeUnreferencedTypefaces() {
// Walk each of the styles looking for any that have a non-NULL typeface.
// These are purged if they are unreferenced outside of the style set.
for (int i = 0; i < styles_.count(); ++i) {
SkAutoTUnref<SkTypeface>& typeface = styles_[i]->typeface;
if (typeface.get() != NULL && typeface->getRefCnt() == 1) {
typeface.reset(NULL);
}
}
}