chromium/content/child/dwrite_font_proxy/font_fallback_win.cc

// Copyright 2016 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/child/dwrite_font_proxy/font_fallback_win.h"

#include <math.h>

#include <algorithm>
#include <string>

#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h"

namespace mswr = Microsoft::WRL;

namespace content {

namespace {

const size_t kMaxFamilyCacheSize = 10;

std::wstring MakeCacheKey(const wchar_t* base_family_name,
                          const wchar_t* locale) {
  std::wstring cache_key(base_family_name);
  return cache_key + L"_" + locale;
}

}  // namespace

HRESULT FontFallback::Create(FontFallback** font_fallback_out,
                             DWriteFontCollectionProxy* collection) {
  return Microsoft::WRL::MakeAndInitialize<FontFallback>(font_fallback_out,
                                                         collection);
}

FontFallback::FontFallback() = default;
FontFallback::~FontFallback() = default;

HRESULT FontFallback::MapCharacters(IDWriteTextAnalysisSource* source,
                                    UINT32 text_position,
                                    UINT32 text_length,
                                    IDWriteFontCollection* base_font_collection,
                                    const wchar_t* base_family_name,
                                    DWRITE_FONT_WEIGHT base_weight,
                                    DWRITE_FONT_STYLE base_style,
                                    DWRITE_FONT_STRETCH base_stretch,
                                    UINT32* mapped_length,
                                    IDWriteFont** mapped_font,
                                    FLOAT* scale) {
  *mapped_font = nullptr;
  *mapped_length = 1;
  *scale = 1.0;

  const WCHAR* text = nullptr;
  UINT32 chunk_length = 0;
  if (FAILED(source->GetTextAtPosition(text_position, &text, &chunk_length))) {
    DCHECK(false);
    return E_FAIL;
  }
  std::u16string text_chunk;
  base::WideToUTF16(text, std::min(chunk_length, text_length), &text_chunk);

  if (text_chunk.size() == 0) {
    DCHECK(false);
    return E_INVALIDARG;
  }

  base_family_name = base_family_name ? base_family_name : L"";

  const WCHAR* locale = nullptr;
  // |locale_text_length| is actually the length of text with the locale, not
  // the length of the locale string itself.
  UINT32 locale_text_length = 0;
  source->GetLocaleName(text_position /*textPosition*/, &locale_text_length,
                        &locale);

  locale = locale ? locale : L"";

  size_t mapped_length_size_t = *mapped_length;
  if (GetCachedFont(text_chunk, base_family_name, locale, base_weight,
                    base_style, base_stretch, mapped_font,
                    &mapped_length_size_t)) {
    DCHECK(*mapped_font);
    DCHECK_GT(mapped_length_size_t, 0u);
    *mapped_length = base::checked_cast<UINT32>(mapped_length_size_t);
    return S_OK;
  }

  TRACE_EVENT0("dwrite,fonts", "FontFallback::MapCharacters (IPC)");

  blink::mojom::MapCharactersResultPtr result;

  if (!GetFontProxy().MapCharacters(
          text_chunk,
          blink::mojom::DWriteFontStyle::New(base_weight, base_style,
                                             base_stretch),
          base::WideToUTF16(locale), source->GetParagraphReadingDirection(),
          base::WideToUTF16(base_family_name), &result)) {
    DCHECK(false);
    return E_FAIL;
  }

  // We don't cache scale in the fallback cache, and Skia ignores scale anyway.
  // If we ever get a result that's significantly different from 1 we may need
  // to consider whether it's worth doing the work to plumb it through.
  DCHECK(fabs(*scale - 1.0f) < 0.00001);

  *mapped_length = result->mapped_length;
  *scale = result->scale;

  if (result->family_index == UINT32_MAX) {
    return S_OK;
  }

  mswr::ComPtr<IDWriteFontFamily> family;
  // It would be nice to find a way to determine at runtime if |collection_| is
  // a proxy collection, or just a generic IDWriteFontCollection. Unfortunately
  // I can't find a way to get QueryInterface to return the actual class when
  // using mswr::RuntimeClass. If we could use QI, we can fallback on
  // FindFontFamily if the proxy is not available.
  if (!collection_->GetFontFamily(result->family_index, result->family_name,
                                  &family)) {
    DCHECK(false);
    return E_FAIL;
  }

  if (FAILED(family->GetFirstMatchingFont(
          static_cast<DWRITE_FONT_WEIGHT>(result->font_style->font_weight),
          static_cast<DWRITE_FONT_STRETCH>(result->font_style->font_stretch),
          static_cast<DWRITE_FONT_STYLE>(result->font_style->font_slant),
          mapped_font))) {
    DCHECK(false);
    return E_FAIL;
  }

  DCHECK(*mapped_font);
  AddCachedFamily(std::move(family), base_family_name, locale);
  return S_OK;
}

HRESULT STDMETHODCALLTYPE
FontFallback::RuntimeClassInitialize(DWriteFontCollectionProxy* collection) {
  collection_ = collection;
  return S_OK;
}

bool FontFallback::GetCachedFont(const std::u16string& text,
                                 const wchar_t* base_family_name,
                                 const wchar_t* locale,
                                 DWRITE_FONT_WEIGHT base_weight,
                                 DWRITE_FONT_STYLE base_style,
                                 DWRITE_FONT_STRETCH base_stretch,
                                 IDWriteFont** font,
                                 size_t* mapped_length) {
  base::AutoLock guard(lock_);
  std::map<std::wstring, std::list<mswr::ComPtr<IDWriteFontFamily>>>::iterator
      it = fallback_family_cache_.find(MakeCacheKey(base_family_name, locale));
  if (it == fallback_family_cache_.end())
    return false;

  TRACE_EVENT0("dwrite,fonts", "FontFallback::GetCachedFont");

  std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list = it->second;
  std::list<mswr::ComPtr<IDWriteFontFamily>>::iterator family_iterator;
  for (family_iterator = family_list.begin();
       family_iterator != family_list.end(); ++family_iterator) {
    mswr::ComPtr<IDWriteFont> matched_font;

    if (FAILED((*family_iterator)
                   ->GetFirstMatchingFont(base_weight, base_stretch, base_style,
                                          &matched_font))) {
      continue;
    }

    // |character_index| tracks how much of the string we have read. This is
    // different from |mapped_length| because ReadUnicodeCharacter can advance
    // |character_index| even if the character cannot be mapped (invalid
    // surrogate pair or font does not contain a matching glyph).
    size_t character_index = 0;
    size_t length = 0;  // How much of the text can actually be mapped.
    while (character_index < text.length()) {
      BOOL exists = false;
      base_icu::UChar32 character = 0;
      if (!base::ReadUnicodeCharacter(text.c_str(), text.length(),
                                      &character_index, &character))
        break;
      if (FAILED(matched_font->HasCharacter(character, &exists)) || !exists)
        break;
      character_index++;
      length = character_index;
    }

    if (length > 0) {
      // Move the current family to the front of the list
      family_list.splice(family_list.begin(), family_list, family_iterator);

      matched_font.CopyTo(font);
      *mapped_length = length;
      return true;
    }
  }

  return false;
}

void FontFallback::AddCachedFamily(
    Microsoft::WRL::ComPtr<IDWriteFontFamily> family,
    const wchar_t* base_family_name,
    const wchar_t* locale) {
  base::AutoLock guard(lock_);
  // Note: If the requested locale does not disambiguate Han ideographs, caching
  // by locale may prime the cache with one CJK font for the first request,
  // which may be unsuitable for the next request. For example: While specifying
  // an ambiguous locale, requesting certain Chinese characters first, DWrite
  // will give us a simplified Chinese font, then requesting a Korean character
  // later may return a Chinese font for the Korean character. This is prevented
  // on the Blink side by passing a disambiguating locale.
  std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list =
      fallback_family_cache_[MakeCacheKey(base_family_name, locale)];
  family_list.push_front(std::move(family));

  while (family_list.size() > kMaxFamilyCacheSize)
    family_list.pop_back();
}

blink::mojom::DWriteFontProxy& FontFallback::GetFontProxy() {
  return collection_->GetFontProxy();
}

}  // namespace content