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

#include <math.h>

#include <algorithm>

#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h"
#include "content/common/dwrite_font_proxy_messages.h"
#include "content/public/child/child_thread.h"
#include "ipc/ipc_sender.h"

namespace mswr = Microsoft::WRL;

namespace content {

namespace {

const size_t kMaxFamilyCacheSize = 10;

// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
//   (a) existing enumerated constants should never be deleted or reordered, and
//   (b) new constants should only be appended at the end of the enumeration.
enum DirectWriteFontFallbackResult {
  FAILED_NO_FONT = 0,
  SUCCESS_CACHE = 1,
  SUCCESS_IPC = 2,

  FONT_FALLBACK_RESULT_MAX_VALUE
};

void LogFallbackResult(DirectWriteFontFallbackResult fallback_result) {
  UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.FallbackResult",
                            fallback_result, FONT_FALLBACK_RESULT_MAX_VALUE);
}

}  // namespace

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;
  }
  base::string16 text_chunk(text, std::min(chunk_length, text_length));

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

  base_family_name = base_family_name ? base_family_name : L"";

  if (GetCachedFont(text_chunk, base_family_name, base_weight, base_style,
                    base_stretch, mapped_font, mapped_length)) {
    DCHECK(*mapped_font);
    DCHECK_GT(*mapped_length, 0u);
    LogFallbackResult(SUCCESS_CACHE);
    return S_OK;
  }

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

  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"";

  DWriteFontStyle style;
  style.font_weight = base_weight;
  style.font_slant = base_style;
  style.font_stretch = base_stretch;

  MapCharactersResult result;

  IPC::Sender* sender =
      sender_override_ ? sender_override_ : ChildThread::Get();
  if (!sender->Send(new DWriteFontProxyMsg_MapCharacters(
          text_chunk, style, locale, source->GetParagraphReadingDirection(),
          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) {
    LogFallbackResult(FAILED_NO_FONT);
    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);
  LogFallbackResult(SUCCESS_IPC);
  return S_OK;
}

HRESULT STDMETHODCALLTYPE
FontFallback::RuntimeClassInitialize(DWriteFontCollectionProxy* collection,
                                     IPC::Sender* sender_override) {
  sender_override_ = sender_override;
  collection_ = collection;
  return S_OK;
}

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

  TRACE_EVENT0("dwrite", "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;
    (*family_iterator)->GetFirstMatchingFont(base_weight, base_stretch,
                                             base_style, &matched_font);

    // |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).
    int32_t character_index = 0;
    uint32_t length = 0;  // How much of the text can actually be mapped.
    while (static_cast<uint32_t>(character_index) < text.length()) {
      BOOL exists = false;
      uint32_t 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) {
  std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list =
      fallback_family_cache_[base_family_name];
  family_list.push_front(std::move(family));

  UMA_HISTOGRAM_COUNTS_100("DirectWrite.Fonts.Proxy.Fallback.CacheSize",
                           family_list.size());

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

}  // namespace content
