chromium/content/browser/renderer_host/dwrite_font_file_util_win.cc

// Copyright 2019 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_file_util_win.h"

#include <shlobj.h>
#include <wrl.h>
#include <vector>

#include "base/i18n/case_conversion.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"

namespace content {

HRESULT FontFilePathAndTtcIndex(IDWriteFont* font,
                                std::wstring& file_path,
                                uint32_t& ttc_index) {
  Microsoft::WRL::ComPtr<IDWriteFontFace> font_face;
  HRESULT hr;
  hr = font->CreateFontFace(&font_face);
  if (FAILED(hr)) {
    return hr;
  }
  return FontFilePathAndTtcIndex(font_face.Get(), file_path, ttc_index);
}

HRESULT FontFilePathAndTtcIndex(IDWriteFontFace* font_face,
                                std::wstring& file_path,
                                uint32_t& ttc_index) {
  TRACE_EVENT0("dwrite,fonts",
               "dwrite_font_file_util::FontFilePathAndTtcIndex");
  UINT32 file_count;
  HRESULT hr;
  hr = font_face->GetFiles(&file_count, nullptr);
  if (FAILED(hr)) {
    return hr;
  }

  // We've learned from the DirectWrite team at MS that the number of font files
  // retrieved per IDWriteFontFile can only ever be 1. Other font formats such
  // as Type 1, which represent one font in multiple files, are currently not
  // supported in the API (as of December 2018, Windows 10). In Chrome we do not
  // plan to support Type 1 fonts, or generally other font formats different
  // from OpenType, hence no need to loop over file_count or retrieve multiple
  // files.
  DCHECK_EQ(file_count, 1u);
  if (file_count > 1) {
    return kErrorFontFileUtilTooManyFilesPerFace;
  }

  Microsoft::WRL::ComPtr<IDWriteFontFile> font_file;
  hr = font_face->GetFiles(&file_count, &font_file);
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IDWriteFontFileLoader> loader;
  hr = font_file->GetLoader(&loader);
  if (FAILED(hr)) {
    return hr;
  }

  Microsoft::WRL::ComPtr<IDWriteLocalFontFileLoader> local_loader;
  hr = loader.As(&local_loader);

  if (hr == E_NOINTERFACE) {
    // We could get here if the system font collection contains fonts that
    // are backed by something other than files in the system fonts folder.
    // I don't think that is actually possible, so for now we'll just
    // ignore it (result will be that we'll be unable to match any styles
    // for this font, forcing blink/skia to fall back to whatever font is
    // next). If we get telemetry indicating that this case actually
    // happens, we can implement this by exposing the loader via ipc. That
    // will likely be by loading the font data into shared memory, although
    // we could proxy the stream reads directly instead.
    DCHECK(false);
    return hr;
  } else if (FAILED(hr)) {
    return hr;
  }

  const void* key;
  UINT32 key_size;
  hr = font_file->GetReferenceKey(&key, &key_size);
  if (FAILED(hr)) {
    return hr;
  }

  UINT32 path_length = 0;
  hr = local_loader->GetFilePathLengthFromKey(key, key_size, &path_length);
  if (FAILED(hr)) {
    return hr;
  }
  std::wstring retrieve_file_path;
  retrieve_file_path.resize(
      ++path_length);  // Reserve space for the null terminator.
  hr = local_loader->GetFilePathFromKey(key, key_size, &retrieve_file_path[0],
                                        path_length);
  if (FAILED(hr)) {
    return hr;
  }
  // No need for the null-terminator in std::u16string.
  retrieve_file_path.resize(--path_length);

  uint32_t retrieve_ttc_index = font_face->GetIndex();
  if (FAILED(hr)) {
    return hr;
  }

  file_path = retrieve_file_path;
  ttc_index = retrieve_ttc_index;

  return S_OK;
}

HRESULT AddFilesForFont(IDWriteFont* font,
                        const std::u16string& windows_fonts_path,
                        std::set<std::wstring>* path_set) {
  std::wstring file_path;
  uint32_t dummy_ttc_index;
  HRESULT hr = FontFilePathAndTtcIndex(font, file_path, dummy_ttc_index);
  if (FAILED(hr)) {
    return hr;
  }

  std::u16string file_path_folded =
      base::i18n::FoldCase(base::WideToUTF16(file_path));

  if (!file_path_folded.size())
    return kErrorFontFileUtilEmptyFilePath;

  if (!base::StartsWith(file_path_folded, windows_fonts_path,
                        base::CompareCase::SENSITIVE)) {
    path_set->insert(file_path);
  } else {
    path_set->insert(file_path);
  }
  return S_OK;
}

std::u16string GetWindowsFontsPath() {
  std::vector<wchar_t> font_path_chars;
  // SHGetSpecialFolderPath requires at least MAX_PATH characters.
  font_path_chars.resize(MAX_PATH);
  BOOL result = SHGetSpecialFolderPath(nullptr /* hwndOwner - reserved */,
                                       font_path_chars.data(), CSIDL_FONTS,
                                       FALSE /* fCreate */);
  DCHECK(result);
  return base::i18n::FoldCase(base::AsStringPiece16(font_path_chars.data()));
}

}  // namespace content