blob: e1778f35eead59146ffc136c7b66df30921a9fb9 [file] [log] [blame]
// Copyright 2015 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 "third_party/blink/renderer/platform/fonts/font_fallback_iterator.h"
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#include "third_party/blink/renderer/platform/fonts/font_description.h"
#include "third_party/blink/renderer/platform/fonts/font_fallback_list.h"
#include "third_party/blink/renderer/platform/fonts/segmented_font_data.h"
#include "third_party/blink/renderer/platform/fonts/simple_font_data.h"
namespace blink {
FontFallbackIterator::FontFallbackIterator(
const FontDescription& description,
scoped_refptr<FontFallbackList> fallback_list,
FontFallbackPriority font_fallback_priority)
: font_description_(description),
font_fallback_list_(std::move(fallback_list)),
current_font_data_index_(0),
segmented_face_index_(0),
fallback_stage_(kFontGroupFonts),
font_fallback_priority_(font_fallback_priority) {}
bool FontFallbackIterator::AlreadyLoadingRangeForHintChar(UChar32 hint_char) {
for (auto* it = tracked_loading_range_sets_.begin();
it != tracked_loading_range_sets_.end(); ++it) {
if ((*it)->Contains(hint_char))
return true;
}
return false;
}
bool FontFallbackIterator::RangeSetContributesForHint(
const Vector<UChar32>& hint_list,
const FontDataForRangeSet* segmented_face) {
for (auto* it = hint_list.begin(); it != hint_list.end(); ++it) {
if (segmented_face->Contains(*it)) {
if (!AlreadyLoadingRangeForHintChar(*it))
return true;
}
}
return false;
}
void FontFallbackIterator::WillUseRange(const AtomicString& family,
const FontDataForRangeSet& range_set) {
FontSelector* selector = font_fallback_list_->GetFontSelector();
if (!selector)
return;
selector->WillUseRange(font_description_, family, range_set);
}
scoped_refptr<FontDataForRangeSet> FontFallbackIterator::UniqueOrNext(
scoped_refptr<FontDataForRangeSet> candidate,
const Vector<UChar32>& hint_list) {
if (!candidate->HasFontData())
return Next(hint_list);
SkTypeface* candidate_typeface =
candidate->FontData()->PlatformData().Typeface();
if (!candidate_typeface)
return Next(hint_list);
uint32_t candidate_id = candidate_typeface->uniqueID();
if (unique_font_data_for_range_sets_returned_.Contains(candidate_id)) {
return Next(hint_list);
}
// We don't want to skip subsetted ranges because HarfBuzzShaper's behavior
// depends on the subsetting.
if (candidate->IsEntireRange())
unique_font_data_for_range_sets_returned_.insert(candidate_id);
// Save first candidate to be returned if all other fonts fail, and we need
// it to render the .notdef glyph.
if (!first_candidate_)
first_candidate_ = candidate;
return candidate;
}
bool FontFallbackIterator::NeedsHintList() const {
if (fallback_stage_ == kSegmentedFace)
return true;
if (fallback_stage_ != kFontGroupFonts)
return false;
const FontData* font_data = font_fallback_list_->FontDataAt(
font_description_, current_font_data_index_);
if (!font_data)
return false;
return font_data->IsSegmented();
}
scoped_refptr<FontDataForRangeSet> FontFallbackIterator::Next(
const Vector<UChar32>& hint_list) {
if (fallback_stage_ == kOutOfLuck)
return base::AdoptRef(new FontDataForRangeSet());
if (fallback_stage_ == kFallbackPriorityFonts) {
// Only try one fallback priority font,
// then proceed to regular system fallback.
fallback_stage_ = kSystemFonts;
scoped_refptr<FontDataForRangeSet> fallback_priority_font_range =
base::AdoptRef(
new FontDataForRangeSet(FallbackPriorityFont(hint_list[0])));
if (fallback_priority_font_range->HasFontData())
return UniqueOrNext(std::move(fallback_priority_font_range), hint_list);
return Next(hint_list);
}
if (fallback_stage_ == kSystemFonts) {
// We've reached pref + system fallback.
scoped_refptr<SimpleFontData> system_font = UniqueSystemFontForHintList(hint_list);
if (system_font) {
// Fallback fonts are not retained in the FontDataCache.
return UniqueOrNext(base::AdoptRef(new FontDataForRangeSet(system_font)),
hint_list);
}
// If we don't have options from the system fallback anymore or had
// previously returned them, we only have the last resort font left.
// TODO: crbug.com/42217 Improve this by doing the last run with a last
// resort font that has glyphs for everything, for example the Unicode
// LastResort font, not just Times or Arial.
FontCache* font_cache = FontCache::GetFontCache();
fallback_stage_ = kFirstCandidateForNotdefGlyph;
scoped_refptr<SimpleFontData> last_resort =
font_cache->GetLastResortFallbackFont(font_description_).get();
return UniqueOrNext(
base::AdoptRef(new FontDataForRangeSetFromCache(last_resort)),
hint_list);
}
if (fallback_stage_ == kFirstCandidateForNotdefGlyph) {
fallback_stage_ = kOutOfLuck;
if (!first_candidate_)
FontCache::CrashWithFontInfo(&font_description_);
return first_candidate_;
}
DCHECK(fallback_stage_ == kFontGroupFonts ||
fallback_stage_ == kSegmentedFace);
const FontData* font_data = font_fallback_list_->FontDataAt(
font_description_, current_font_data_index_);
if (!font_data) {
// If there is no fontData coming from the fallback list, it means
// we are now looking at system fonts, either for prioritized symbol
// or emoji fonts or by calling system fallback API.
fallback_stage_ = IsNonTextFallbackPriority(font_fallback_priority_)
? kFallbackPriorityFonts
: kSystemFonts;
return Next(hint_list);
}
// Otherwise we've received a fontData from the font-family: set of fonts,
// and a non-segmented one in this case.
if (!font_data->IsSegmented()) {
// Skip forward to the next font family for the next call to next().
current_font_data_index_++;
if (!font_data->IsLoading()) {
scoped_refptr<SimpleFontData> non_segmented =
const_cast<SimpleFontData*>(To<SimpleFontData>(font_data));
// The fontData object that we have here is tracked in m_fontList of
// FontFallbackList and gets released in the font cache when the
// FontFallbackList is destroyed.
return UniqueOrNext(
base::AdoptRef(new FontDataForRangeSet(non_segmented)), hint_list);
}
return Next(hint_list);
}
// Iterate over ranges of a segmented font below.
const auto* segmented = To<SegmentedFontData>(font_data);
if (fallback_stage_ != kSegmentedFace) {
segmented_face_index_ = 0;
fallback_stage_ = kSegmentedFace;
}
DCHECK_LT(segmented_face_index_, segmented->NumFaces());
scoped_refptr<FontDataForRangeSet> current_segmented_face =
segmented->FaceAt(segmented_face_index_);
segmented_face_index_++;
if (segmented_face_index_ == segmented->NumFaces()) {
// Switch from iterating over a segmented face to the next family from
// the font-family: group of fonts.
fallback_stage_ = kFontGroupFonts;
current_font_data_index_++;
}
if (RangeSetContributesForHint(hint_list, current_segmented_face.get())) {
const SimpleFontData* current_segmented_face_font_data =
current_segmented_face->FontData();
if (const CustomFontData* current_segmented_face_custom_font_data =
current_segmented_face_font_data->GetCustomFontData())
current_segmented_face_custom_font_data->BeginLoadIfNeeded();
if (!current_segmented_face_font_data->IsLoading())
return UniqueOrNext(current_segmented_face, hint_list);
tracked_loading_range_sets_.push_back(current_segmented_face);
}
return Next(hint_list);
}
scoped_refptr<SimpleFontData> FontFallbackIterator::FallbackPriorityFont(
UChar32 hint) {
return FontCache::GetFontCache()->FallbackFontForCharacter(
font_description_, hint,
font_fallback_list_->PrimarySimpleFontData(font_description_),
font_fallback_priority_);
}
static inline unsigned ChooseHintIndex(const Vector<UChar32>& hint_list) {
// crbug.com/618178 has a test case where no Myanmar font is ever found,
// because the run starts with a punctuation character with a script value of
// common. Our current font fallback code does not find a very meaningful
// result for this.
// TODO crbug.com/668706 - Improve this situation.
// So if we have multiple hint characters (which indicates that a
// multi-character grapheme or more failed to shape, then we can try to be
// smarter and select the first character that has an actual script value.
DCHECK(hint_list.size());
if (hint_list.size() <= 1)
return 0;
for (wtf_size_t i = 1; i < hint_list.size(); ++i) {
if (Character::HasDefiniteScript(hint_list[i]))
return i;
}
return 0;
}
scoped_refptr<SimpleFontData> FontFallbackIterator::UniqueSystemFontForHintList(
const Vector<UChar32>& hint_list) {
// When we're asked for a fallback for the same characters again, we give up
// because the shaper must have previously tried shaping with the font
// already.
if (!hint_list.size())
return nullptr;
FontCache* font_cache = FontCache::GetFontCache();
UChar32 hint = hint_list[ChooseHintIndex(hint_list)];
if (!hint || previously_asked_for_hint_.Contains(hint))
return nullptr;
previously_asked_for_hint_.insert(hint);
return font_cache->FallbackFontForCharacter(
font_description_, hint,
font_fallback_list_->PrimarySimpleFontData(font_description_));
}
} // namespace blink