| // Copyright 2015 The Chromium Authors |
| // 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/dwrite_font_proxy_impl_win.h" |
| |
| #include <shlobj.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/i18n/case_conversion.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/scoped_blocking_call.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "content/browser/renderer_host/dwrite_font_file_util_win.h" |
| #include "content/common/features.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "third_party/blink/public/common/font_unique_name_lookup/font_unique_name_table.pb.h" |
| #include "third_party/blink/public/common/font_unique_name_lookup/icu_fold_case_util.h" |
| #include "ui/gfx/win/direct_write.h" |
| #include "ui/gfx/win/text_analysis_source.h" |
| |
| namespace mswr = Microsoft::WRL; |
| |
| namespace content { |
| |
| namespace { |
| |
| // These are the fonts that Blink tries to load in getLastResortFallbackFont, |
| // and will crash if none can be loaded. |
| constexpr auto kLastResortFontNames = std::to_array<const wchar_t*>( |
| {L"Sans", L"Arial", L"MS UI Gothic", L"Microsoft Sans Serif", L"Segoe UI", |
| L"Calibri", L"Times New Roman", L"Courier New"}); |
| |
| struct RequiredFontStyle { |
| const char16_t* family_name; |
| DWRITE_FONT_WEIGHT required_weight; |
| DWRITE_FONT_STRETCH required_stretch; |
| DWRITE_FONT_STYLE required_style; |
| }; |
| |
| // Used in tests to allow a known font to masquerade as a locally installed |
| // font. Usually this is the Ahem.ttf font. Leaked at shutdown. |
| std::vector<base::FilePath>* g_sideloaded_fonts = nullptr; |
| |
| const RequiredFontStyle kRequiredStyles[] = { |
| // The regular version of Gill Sans is actually in the Gill Sans MT family, |
| // and the Gill Sans family typically contains just the ultra-bold styles. |
| {u"gill sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, |
| DWRITE_FONT_STYLE_NORMAL}, |
| {u"helvetica", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, |
| DWRITE_FONT_STYLE_NORMAL}, |
| {u"open sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, |
| DWRITE_FONT_STYLE_NORMAL}, |
| }; |
| |
| // As a workaround for crbug.com/635932, refuse to load some common fonts that |
| // do not contain certain styles. We found that sometimes these fonts are |
| // installed only in specialized styles ('Open Sans' might only be available in |
| // the condensed light variant, or Helvetica might only be available in bold). |
| // That results in a poor user experience because websites that use those fonts |
| // usually expect them to be rendered in the regular variant. |
| bool CheckRequiredStylesPresent(IDWriteFontCollection* collection, |
| const std::u16string& family_name, |
| uint32_t family_index) { |
| for (const auto& font_style : kRequiredStyles) { |
| if (base::EqualsCaseInsensitiveASCII(family_name, font_style.family_name)) { |
| mswr::ComPtr<IDWriteFontFamily> family; |
| if (FAILED(collection->GetFontFamily(family_index, &family))) { |
| DCHECK(false); |
| return true; |
| } |
| mswr::ComPtr<IDWriteFont> font; |
| if (FAILED(family->GetFirstMatchingFont( |
| font_style.required_weight, font_style.required_stretch, |
| font_style.required_style, &font))) { |
| DCHECK(false); |
| return true; |
| } |
| |
| // GetFirstMatchingFont doesn't require strict style matching, so check |
| // the actual font that we got. |
| if (font->GetWeight() != font_style.required_weight || |
| font->GetStretch() != font_style.required_stretch || |
| font->GetStyle() != font_style.required_style) { |
| return false; |
| } |
| break; |
| } |
| } |
| return true; |
| } |
| |
| HRESULT GetLocalFontCollection(mswr::ComPtr<IDWriteFactory3>& factory, |
| IDWriteFontCollection** collection) { |
| if (!g_sideloaded_fonts) { |
| // Normal path - use the system's font collection with no sideloading. |
| return factory->GetSystemFontCollection(collection); |
| } |
| // If sideloading - build a font set with sideloads then add the system font |
| // collection. |
| mswr::ComPtr<IDWriteFontSetBuilder> font_set_builder; |
| HRESULT hr = factory->CreateFontSetBuilder(&font_set_builder); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| for (auto& path : *g_sideloaded_fonts) { |
| mswr::ComPtr<IDWriteFontFile> font_file; |
| hr = factory->CreateFontFileReference(path.value().c_str(), nullptr, |
| &font_file); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| BOOL supported; |
| DWRITE_FONT_FILE_TYPE file_type; |
| UINT32 n_fonts; |
| hr = font_file->Analyze(&supported, &file_type, nullptr, &n_fonts); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| for (UINT32 font_index = 0; font_index < n_fonts; ++font_index) { |
| mswr::ComPtr<IDWriteFontFaceReference> font_face; |
| hr = factory->CreateFontFaceReference(font_file.Get(), font_index, |
| DWRITE_FONT_SIMULATIONS_NONE, |
| &font_face); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| hr = font_set_builder->AddFontFaceReference(font_face.Get()); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| } |
| } |
| // Now add the system fonts. |
| mswr::ComPtr<IDWriteFontSet> system_font_set; |
| hr = factory->GetSystemFontSet(&system_font_set); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| hr = font_set_builder->AddFontSet(system_font_set.Get()); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| // Make the set. |
| mswr::ComPtr<IDWriteFontSet> font_set; |
| hr = font_set_builder->CreateFontSet(&font_set); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| // Make the collection. |
| mswr::ComPtr<IDWriteFontCollection1> collection1; |
| hr = factory->CreateFontCollectionFromFontSet(font_set.Get(), &collection1); |
| if (!SUCCEEDED(hr)) { |
| return hr; |
| } |
| hr = collection1->QueryInterface(collection); |
| return hr; |
| } |
| |
| } // namespace |
| |
| DWriteFontProxyImpl::DWriteFontProxyImpl() |
| : windows_fonts_path_(GetWindowsFontsPath()) {} |
| |
| DWriteFontProxyImpl::~DWriteFontProxyImpl() = default; |
| |
| // static |
| void DWriteFontProxyImpl::Create( |
| mojo::PendingReceiver<blink::mojom::DWriteFontProxy> receiver) { |
| mojo::MakeSelfOwnedReceiver(std::make_unique<DWriteFontProxyImpl>(), |
| std::move(receiver)); |
| } |
| |
| // static |
| void DWriteFontProxyImpl::SideLoadFontForTesting(base::FilePath path) { |
| if (!g_sideloaded_fonts) { |
| // Note: this list is leaked. |
| g_sideloaded_fonts = new std::vector<base::FilePath>(); |
| } |
| g_sideloaded_fonts->push_back(path); |
| } |
| |
| void DWriteFontProxyImpl::SetWindowsFontsPathForTesting(std::u16string path) { |
| windows_fonts_path_.swap(path); |
| } |
| |
| void DWriteFontProxyImpl::FindFamily(const std::u16string& family_name, |
| FindFamilyCallback callback) { |
| InitializeDirectWrite(); |
| TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnFindFamily"); |
| UINT32 family_index = UINT32_MAX; |
| if (collection_) { |
| BOOL exists = FALSE; |
| UINT32 index = UINT32_MAX; |
| HRESULT hr = collection_->FindFamilyName(base::as_wcstr(family_name), |
| &index, &exists); |
| if (SUCCEEDED(hr) && exists && |
| CheckRequiredStylesPresent(collection_.Get(), family_name, index)) { |
| family_index = index; |
| } |
| } |
| std::move(callback).Run(family_index); |
| } |
| |
| void DWriteFontProxyImpl::GetFamilyCount(GetFamilyCountCallback callback) { |
| InitializeDirectWrite(); |
| TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFamilyCount"); |
| std::move(callback).Run(collection_ ? collection_->GetFontFamilyCount() : 0); |
| } |
| |
| void DWriteFontProxyImpl::GetFamilyNames(UINT32 family_index, |
| GetFamilyNamesCallback callback) { |
| InitializeDirectWrite(); |
| TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFamilyNames"); |
| callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| std::move(callback), std::vector<blink::mojom::DWriteStringPairPtr>()); |
| if (!collection_) |
| return; |
| |
| TRACE_EVENT0("dwrite,fonts", "FontProxyHost::DoGetFamilyNames"); |
| |
| mswr::ComPtr<IDWriteFontFamily> family; |
| HRESULT hr = collection_->GetFontFamily(family_index, &family); |
| if (FAILED(hr)) { |
| return; |
| } |
| |
| mswr::ComPtr<IDWriteLocalizedStrings> localized_names; |
| hr = family->GetFamilyNames(&localized_names); |
| if (FAILED(hr)) { |
| return; |
| } |
| |
| size_t string_count = localized_names->GetCount(); |
| |
| std::vector<wchar_t> locale; |
| std::vector<wchar_t> name; |
| std::vector<blink::mojom::DWriteStringPairPtr> family_names; |
| for (size_t index = 0; index < string_count; ++index) { |
| UINT32 length = 0; |
| hr = localized_names->GetLocaleNameLength(index, &length); |
| if (FAILED(hr)) { |
| return; |
| } |
| ++length; // Reserve space for the null terminator. |
| locale.resize(length); |
| hr = localized_names->GetLocaleName(index, locale.data(), length); |
| if (FAILED(hr)) { |
| return; |
| } |
| CHECK_EQ(L'\0', locale[length - 1]); |
| |
| length = 0; |
| hr = localized_names->GetStringLength(index, &length); |
| if (FAILED(hr)) { |
| return; |
| } |
| ++length; // Reserve space for the null terminator. |
| name.resize(length); |
| hr = localized_names->GetString(index, name.data(), length); |
| if (FAILED(hr)) { |
| return; |
| } |
| CHECK_EQ(L'\0', name[length - 1]); |
| |
| family_names.emplace_back(std::in_place, base::WideToUTF16(locale.data()), |
| base::WideToUTF16(name.data())); |
| } |
| std::move(callback).Run(std::move(family_names)); |
| } |
| |
| void DWriteFontProxyImpl::GetFontFileHandles( |
| uint32_t family_index, |
| GetFontFileHandlesCallback callback) { |
| InitializeDirectWrite(); |
| TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFontFiles"); |
| callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| std::move(callback), std::vector<base::File>()); |
| if (!collection_) |
| return; |
| |
| base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, |
| base::BlockingType::MAY_BLOCK); |
| mswr::ComPtr<IDWriteFontFamily> family; |
| HRESULT hr = collection_->GetFontFamily(family_index, &family); |
| if (FAILED(hr)) { |
| return; |
| } |
| |
| UINT32 font_count = family->GetFontCount(); |
| |
| std::set<std::wstring> path_set; |
| // Iterate through all the fonts in the family, and all the files for those |
| // fonts. If anything goes wrong, bail on the entire family to avoid having |
| // a partially-loaded font family. |
| for (UINT32 font_index = 0; font_index < font_count; ++font_index) { |
| mswr::ComPtr<IDWriteFont> font; |
| hr = family->GetFont(font_index, &font); |
| if (FAILED(hr)) { |
| return; |
| } |
| |
| std::ignore = AddFilesForFont(font.Get(), windows_fonts_path_, &path_set); |
| } |
| |
| std::vector<base::File> file_handles; |
| // We pass handles for every path as the sandbox blocks direct access to font |
| // files in the renderer. |
| for (const auto& font_path : path_set) { |
| // Specify FLAG_WIN_EXCLUSIVE_WRITE to prevent base::File from opening the |
| // file with FILE_SHARE_WRITE access. FLAG_WIN_EXCLUSIVE_WRITE doesn't |
| // actually open the file for write access. |
| base::File file(base::FilePath(font_path), |
| base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WIN_EXCLUSIVE_WRITE); |
| if (file.IsValid()) { |
| file_handles.push_back(std::move(file)); |
| } |
| } |
| std::move(callback).Run(std::move(file_handles)); |
| } |
| |
| void DWriteFontProxyImpl::MapCharacters( |
| const std::u16string& text, |
| blink::mojom::DWriteFontStylePtr font_style, |
| const std::u16string& locale_name, |
| uint32_t reading_direction, |
| const std::u16string& base_family_name, |
| MapCharactersCallback callback) { |
| InitializeDirectWrite(); |
| callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| std::move(callback), |
| blink::mojom::MapCharactersResult::New( |
| UINT32_MAX, u"", text.length(), 0.0, |
| blink::mojom::DWriteFontStyle::New(DWRITE_FONT_STYLE_NORMAL, |
| DWRITE_FONT_STRETCH_NORMAL, |
| DWRITE_FONT_WEIGHT_NORMAL))); |
| if (factory2_ == nullptr || collection_ == nullptr) |
| return; |
| if (font_fallback_ == nullptr) { |
| if (FAILED(factory2_->GetSystemFontFallback(&font_fallback_))) { |
| return; |
| } |
| } |
| |
| mswr::ComPtr<IDWriteFont> mapped_font; |
| |
| mswr::ComPtr<IDWriteNumberSubstitution> number_substitution; |
| if (FAILED(factory2_->CreateNumberSubstitution( |
| DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, base::as_wcstr(locale_name), |
| TRUE /* ignoreUserOverride */, &number_substitution))) { |
| DCHECK(false); |
| return; |
| } |
| mswr::ComPtr<IDWriteTextAnalysisSource> analysis_source; |
| if (FAILED(gfx::win::TextAnalysisSource::Create( |
| &analysis_source, base::UTF16ToWide(text), |
| base::UTF16ToWide(locale_name), number_substitution.Get(), |
| static_cast<DWRITE_READING_DIRECTION>(reading_direction)))) { |
| DCHECK(false); |
| return; |
| } |
| |
| auto result = blink::mojom::MapCharactersResult::New( |
| UINT32_MAX, u"", text.length(), 0.0, |
| blink::mojom::DWriteFontStyle::New(DWRITE_FONT_STYLE_NORMAL, |
| DWRITE_FONT_STRETCH_NORMAL, |
| DWRITE_FONT_WEIGHT_NORMAL)); |
| if (FAILED(font_fallback_->MapCharacters( |
| analysis_source.Get(), 0, text.length(), collection_.Get(), |
| base::as_wcstr(base_family_name), |
| static_cast<DWRITE_FONT_WEIGHT>(font_style->font_weight), |
| static_cast<DWRITE_FONT_STYLE>(font_style->font_slant), |
| static_cast<DWRITE_FONT_STRETCH>(font_style->font_stretch), |
| &result->mapped_length, &mapped_font, &result->scale))) { |
| DCHECK(false); |
| return; |
| } |
| |
| if (mapped_font == nullptr) { |
| std::move(callback).Run(std::move(result)); |
| return; |
| } |
| |
| mswr::ComPtr<IDWriteFontFamily> mapped_family; |
| if (FAILED(mapped_font->GetFontFamily(&mapped_family))) { |
| DCHECK(false); |
| return; |
| } |
| mswr::ComPtr<IDWriteLocalizedStrings> family_names; |
| if (FAILED(mapped_family->GetFamilyNames(&family_names))) { |
| DCHECK(false); |
| return; |
| } |
| |
| result->font_style->font_slant = mapped_font->GetStyle(); |
| result->font_style->font_stretch = mapped_font->GetStretch(); |
| result->font_style->font_weight = mapped_font->GetWeight(); |
| |
| std::vector<wchar_t> name; |
| size_t name_count = family_names->GetCount(); |
| for (size_t name_index = 0; name_index < name_count; name_index++) { |
| UINT32 name_length = 0; |
| if (FAILED(family_names->GetStringLength(name_index, &name_length))) |
| continue; // Keep trying other names |
| |
| ++name_length; // Reserve space for the null terminator. |
| name.resize(name_length); |
| if (FAILED(family_names->GetString(name_index, name.data(), name_length))) |
| continue; |
| UINT32 index = UINT32_MAX; |
| BOOL exists = false; |
| if (FAILED(collection_->FindFamilyName(name.data(), &index, &exists)) || |
| !exists) |
| continue; |
| |
| // Found a matching family! |
| result->family_index = index; |
| result->family_name = base::as_u16cstr(name.data()); |
| std::move(callback).Run(std::move(result)); |
| return; |
| } |
| |
| // Could not find a matching family |
| DCHECK_EQ(result->family_index, UINT32_MAX); |
| DCHECK_GT(result->mapped_length, 0u); |
| } |
| |
| void DWriteFontProxyImpl::MatchUniqueFont( |
| const std::u16string& unique_font_name, |
| MatchUniqueFontCallback callback) { |
| TRACE_EVENT0("dwrite,fonts", "DWriteFontProxyImpl::MatchUniqueFont"); |
| |
| DCHECK(base::FeatureList::IsEnabled(features::kFontSrcLocalMatching)); |
| callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback), |
| base::File(), 0); |
| InitializeDirectWrite(); |
| |
| // We must not get here if this version of DWrite can't handle performing the |
| // search. |
| DCHECK(factory3_.Get()); |
| DCHECK(collection_); |
| Microsoft::WRL::ComPtr<IDWriteFontCollection1> collection1; |
| HRESULT hr = collection_.As(&collection1); |
| if (FAILED(hr)) { |
| return; |
| } |
| // In non-testing cases this is identical to factory3_->GetSystemFontSet(). |
| mswr::ComPtr<IDWriteFontSet> system_font_set; |
| hr = collection1->GetFontSet(&system_font_set); |
| if (FAILED(hr)) |
| return; |
| |
| DCHECK_GT(system_font_set->GetFontCount(), 0U); |
| |
| mswr::ComPtr<IDWriteFontSet> filtered_set; |
| |
| auto filter_set = [&system_font_set, &filtered_set, |
| &unique_font_name](DWRITE_FONT_PROPERTY_ID property_id) { |
| TRACE_EVENT0("dwrite,fonts", |
| "DWriteFontProxyImpl::MatchUniqueFont::filter_set"); |
| std::wstring unique_font_name_wide = base::UTF16ToWide(unique_font_name); |
| DWRITE_FONT_PROPERTY search_property = {property_id, |
| unique_font_name_wide.c_str(), L""}; |
| // GetMatchingFonts() matches all languages according to: |
| // https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_3/ns-dwrite_3-dwrite_font_property |
| HRESULT hr = |
| system_font_set->GetMatchingFonts(&search_property, 1, &filtered_set); |
| return SUCCEEDED(hr); |
| }; |
| |
| // Search PostScript name first, otherwise try searching for full font name. |
| // Return if filtering failed. |
| if (!filter_set(DWRITE_FONT_PROPERTY_ID_POSTSCRIPT_NAME)) |
| return; |
| |
| if (!filtered_set->GetFontCount() && |
| !filter_set(DWRITE_FONT_PROPERTY_ID_FULL_NAME)) { |
| return; |
| } |
| |
| if (!filtered_set->GetFontCount()) |
| return; |
| |
| mswr::ComPtr<IDWriteFontFaceReference> first_font; |
| hr = filtered_set->GetFontFaceReference(0, &first_font); |
| if (FAILED(hr)) |
| return; |
| |
| mswr::ComPtr<IDWriteFontFace3> first_font_face_3; |
| hr = first_font->CreateFontFace(&first_font_face_3); |
| if (FAILED(hr)) |
| return; |
| |
| mswr::ComPtr<IDWriteFontFace> first_font_face; |
| hr = first_font_face_3.As<IDWriteFontFace>(&first_font_face); |
| if (FAILED(hr)) |
| return; |
| |
| std::wstring font_file_pathname; |
| uint32_t ttc_index; |
| if (FAILED(FontFilePathAndTtcIndex(first_font_face.Get(), font_file_pathname, |
| ttc_index))) { |
| return; |
| } |
| |
| base::FilePath path(font_file_pathname); |
| |
| // Have the Browser process open the font file and send the handle to the |
| // Renderer Process to access the font. Otherwise, user-installed local font |
| // files outside of Windows fonts system directory wouldn't be accessible by |
| // Renderer due to Windows sandboxing rules. |
| |
| // Specify FLAG_WIN_EXCLUSIVE_WRITE to prevent base::File from opening the |
| // file with FILE_SHARE_WRITE access. FLAG_WIN_EXCLUSIVE_WRITE doesn't |
| // actually open the file for write access. |
| base::File font_file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WIN_EXCLUSIVE_WRITE); |
| if (!font_file.IsValid() || !font_file.GetLength()) { |
| return; |
| } |
| std::move(callback).Run(std::move(font_file), ttc_index); |
| } |
| |
| void DWriteFontProxyImpl::InitializeDirectWrite() { |
| if (direct_write_initialized_) |
| return; |
| direct_write_initialized_ = true; |
| |
| TRACE_EVENT0("dwrite,fonts", "DWriteFontProxyImpl::InitializeDirectWrite"); |
| |
| gfx::win::CreateDWriteFactory(&factory_); |
| if (factory_ == nullptr) { |
| // We won't be able to load fonts, but we should still return messages so |
| // renderers don't hang if they for some reason send us a font message. |
| return; |
| } |
| |
| // QueryInterface for IDWriteFactory2. This should succeed since we only |
| // support >= Win10. |
| factory_.As<IDWriteFactory2>(&factory2_); |
| DCHECK(factory2_); |
| |
| // QueryInterface for IDwriteFactory3, needed for MatchUniqueFont on Windows. |
| // This should succeed since we only support >= Win10. |
| factory_.As<IDWriteFactory3>(&factory3_); |
| DCHECK(factory3_); |
| |
| // Normally identical to factory_->GetSystemFontCollection() unless a |
| // sideloaded font has been added using SideLoadFontForTesting(). |
| HRESULT hr = GetLocalFontCollection(factory3_, &collection_); |
| DCHECK(SUCCEEDED(hr)); |
| |
| if (!collection_) { |
| return; |
| } |
| |
| // Temp code to help track down crbug.com/561873 |
| for (const wchar_t* font : kLastResortFontNames) { |
| uint32_t font_index = 0; |
| BOOL exists = FALSE; |
| if (SUCCEEDED(collection_->FindFamilyName(font, &font_index, &exists)) && |
| exists && font_index != UINT32_MAX) { |
| last_resort_fonts_.push_back(font_index); |
| } |
| } |
| } |
| |
| bool DWriteFontProxyImpl::IsLastResortFallbackFont(uint32_t font_index) { |
| for (auto iter = last_resort_fonts_.begin(); iter != last_resort_fonts_.end(); |
| ++iter) { |
| if (*iter == font_index) |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace content |