blob: 04743c086105bf1f2fe1509bef0cdd08173edc9c [file] [log] [blame]
// Copyright 2015 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/dom/font_list.h"
#include "base/i18n/char_iterator.h"
#include "cobalt/dom/font_cache.h"
namespace cobalt {
namespace dom {
namespace {
const char16 kHorizontalEllipsisValue = 0x2026;
} // namespace
FontList::FontList(FontCache* font_cache, const FontListKey& font_list_key)
: font_cache_(font_cache),
style_(font_list_key.style),
size_(font_list_key.size),
is_font_metrics_set_(false),
font_metrics_(0, 0, 0, 0),
is_space_width_set_(false),
space_width_(0),
is_ellipsis_info_set_(false),
ellipsis_width_(0),
character_fallback_typeface_map_(
font_cache_->GetCharacterFallbackTypefaceMap(style_)) {
// Add all of the family names to the font list fonts.
for (size_t i = 0; i < font_list_key.family_names.size(); ++i) {
fonts_.push_back(FontListFont(font_list_key.family_names[i]));
}
// Add an empty font at the end in order to fall back to the default typeface.
fonts_.push_back(FontListFont(""));
}
bool FontList::IsVisible() const {
for (size_t i = 0; i < fonts_.size(); ++i) {
// While any font in the font list is loading with an active timer, the font
// is made transparent. "In cases where textual content is loaded before
// downloadable fonts are available, user agents may... render text
// transparently with fallback fonts to avoid a flash of text using a
// fallback font. In cases where the font download fails user agents must
// display text, simply leaving transparent text is considered
// non-conformant behavior."
// https://www.w3.org/TR/css3-fonts/#font-face-loading
if (fonts_[i].state() == FontListFont::kLoadingWithTimerActiveState) {
return false;
}
}
return true;
}
void FontList::ResetLoadingFonts() {
bool found_loaded_font = false;
for (size_t i = 0; i < fonts_.size(); ++i) {
FontListFont& font_list_font = fonts_[i];
if (font_list_font.state() == FontListFont::kLoadingWithTimerActiveState ||
font_list_font.state() == FontListFont::kLoadingWithTimerExpiredState) {
font_list_font.set_state(FontListFont::kUnrequestedState);
// If a loaded font hasn't been found yet, then the cached values need to
// be reset. It'll potentially change the primary font.
if (!found_loaded_font) {
primary_font_ = NULL;
is_font_metrics_set_ = false;
is_space_width_set_ = false;
is_ellipsis_info_set_ = false;
ellipsis_font_ = NULL;
}
} else if (font_list_font.state() == FontListFont::kLoadedState) {
found_loaded_font = true;
}
}
}
scoped_refptr<render_tree::GlyphBuffer> FontList::CreateGlyphBuffer(
const char16* text_buffer, int32 text_length, bool is_rtl) {
return font_cache_->CreateGlyphBuffer(text_buffer, text_length, is_rtl, this);
}
float FontList::GetTextWidth(const char16* text_buffer, int32 text_length,
bool is_rtl,
render_tree::FontVector* maybe_used_fonts) {
return font_cache_->GetTextWidth(text_buffer, text_length, is_rtl, this,
maybe_used_fonts);
}
const render_tree::FontMetrics& FontList::GetFontMetrics() {
// The font metrics are lazily generated. If they haven't been set yet, it's
// time to set them.
if (!is_font_metrics_set_) {
is_font_metrics_set_ = true;
font_metrics_ = GetPrimaryFont()->GetFontMetrics();
}
return font_metrics_;
}
render_tree::FontMetrics FontList::GetFontMetrics(
const render_tree::FontVector& fonts) {
// Call GetFontMetrics to ensure that the primary metrics have been
// generated.
render_tree::TypefaceId primary_typeface_id =
GetPrimaryFont()->GetTypefaceId();
const render_tree::FontMetrics& primary_metrics = GetFontMetrics();
// Initially set the font metrics values to the primary font metrics. It is
// included regardless of the the contents of the vector.
float max_ascent = primary_metrics.ascent();
float max_descent = primary_metrics.descent();
float max_leading = primary_metrics.leading();
float max_x_height = primary_metrics.x_height();
// Calculate the max metrics values from all of the fonts.
for (size_t i = 0; i < fonts.size(); ++i) {
// If this is the primary font, simply skip it. It has already been
// included.
if (fonts[i]->GetTypefaceId() == primary_typeface_id) {
continue;
}
render_tree::FontMetrics current_metrics = fonts[i]->GetFontMetrics();
if (current_metrics.ascent() > max_ascent) {
max_ascent = current_metrics.ascent();
}
if (current_metrics.descent() > max_descent) {
max_descent = current_metrics.descent();
}
if (current_metrics.leading() > max_leading) {
max_leading = current_metrics.leading();
}
if (current_metrics.x_height() > max_x_height) {
max_x_height = current_metrics.x_height();
}
}
return render_tree::FontMetrics(max_ascent, max_descent, max_leading,
max_x_height);
}
char16 FontList::GetEllipsisValue() const { return kHorizontalEllipsisValue; }
const scoped_refptr<render_tree::Font>& FontList::GetEllipsisFont() {
GenerateEllipsisInfo();
return ellipsis_font_;
}
float FontList::GetEllipsisWidth() {
GenerateEllipsisInfo();
return ellipsis_width_;
}
float FontList::GetSpaceWidth() {
GenerateSpaceWidth();
return space_width_;
}
const scoped_refptr<render_tree::Font>& FontList::GetCharacterFont(
int32 utf32_character, render_tree::GlyphIndex* glyph_index) {
// Walk the list of fonts, requesting any encountered that are in an
// unrequested state. The first font encountered that has the character is the
// character font.
for (size_t i = 0; i < fonts_.size(); ++i) {
FontListFont& font_list_font = fonts_[i];
if (font_list_font.state() == FontListFont::kUnrequestedState) {
RequestFont(i);
}
if (font_list_font.state() == FontListFont::kLoadedState) {
*glyph_index =
font_list_font.font()->GetGlyphForCharacter(utf32_character);
if (*glyph_index != render_tree::kInvalidGlyphIndex) {
return font_list_font.font();
}
}
}
return GetFallbackCharacterFont(utf32_character, glyph_index);
}
const scoped_refptr<render_tree::Font>& FontList::GetFallbackCharacterFont(
int32 utf32_character, render_tree::GlyphIndex* glyph_index) {
scoped_refptr<render_tree::Typeface>& fallback_typeface =
character_fallback_typeface_map_[utf32_character];
if (fallback_typeface == NULL) {
fallback_typeface =
font_cache_->GetCharacterFallbackTypeface(utf32_character, style_);
}
*glyph_index = fallback_typeface->GetGlyphForCharacter(utf32_character);
// Check to see if the typeface id already maps to a specific font. If it does
// simply return that font.
scoped_refptr<render_tree::Font>& fallback_font =
fallback_typeface_to_font_map_[fallback_typeface->GetId()];
if (fallback_font == NULL) {
fallback_font = fallback_typeface_to_font_map_[fallback_typeface->GetId()] =
font_cache_->GetFontFromTypefaceAndSize(fallback_typeface, size_);
}
return fallback_font;
}
const scoped_refptr<render_tree::Font>& FontList::GetPrimaryFont() {
// The primary font is lazily generated. If it hasn't been set yet, then it's
// time to do it now.
if (!primary_font_) {
// Walk the list of fonts, requesting any encountered that are in an
// unrequested state. The first font encountered that is loaded is
// the primary font.
for (size_t i = 0; i < fonts_.size(); ++i) {
FontListFont& font_list_font = fonts_[i];
if (font_list_font.state() == FontListFont::kUnrequestedState) {
RequestFont(i);
}
if (font_list_font.state() == FontListFont::kLoadedState) {
primary_font_ = font_list_font.font();
break;
}
}
}
DCHECK(primary_font_);
return primary_font_;
}
void FontList::RequestFont(size_t index) {
FontListFont& font_list_font = fonts_[index];
FontListFont::State state;
// Request the font from the font cache; the state of the font will be set
// during the call.
scoped_refptr<render_tree::Font> render_tree_font = font_cache_->TryGetFont(
font_list_font.family_name(), style_, size_, &state);
if (state == FontListFont::kLoadedState) {
DCHECK(render_tree_font != NULL);
// Walk all of the fonts in the list preceding the loaded font. If they have
// the same typeface as the loaded font, then set the font list font as a
// duplicate. There's no reason to have multiple fonts in the list with the
// same typeface.
render_tree::TypefaceId typeface_id = render_tree_font->GetTypefaceId();
for (size_t i = 0; i < index; ++i) {
FontListFont& check_font = fonts_[i];
if (check_font.state() == FontListFont::kLoadedState &&
check_font.font()->GetTypefaceId() == typeface_id) {
font_list_font.set_state(FontListFont::kDuplicateState);
break;
}
}
// If this font wasn't a duplicate, then its time to initialize its font
// data. This font is now available to use.
if (font_list_font.state() != FontListFont::kDuplicateState) {
font_list_font.set_state(FontListFont::kLoadedState);
font_list_font.set_font(render_tree_font);
}
} else {
font_list_font.set_state(state);
}
}
void FontList::GenerateEllipsisInfo() {
if (!is_ellipsis_info_set_) {
render_tree::GlyphIndex ellipsis_glyph = render_tree::kInvalidGlyphIndex;
ellipsis_font_ = GetCharacterFont(GetEllipsisValue(), &ellipsis_glyph);
ellipsis_width_ = ellipsis_font_->GetGlyphWidth(ellipsis_glyph);
is_ellipsis_info_set_ = true;
}
}
void FontList::GenerateSpaceWidth() {
if (!is_space_width_set_) {
const scoped_refptr<render_tree::Font>& primary_font = GetPrimaryFont();
render_tree::GlyphIndex space_glyph =
primary_font->GetGlyphForCharacter(' ');
space_width_ = primary_font->GetGlyphWidth(space_glyph);
DCHECK_GT(space_width_, 0);
is_space_width_set_ = true;
}
}
} // namespace dom
} // namespace cobalt