910e62b5创建于 1月15日历史提交
// Copyright 2024 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/font_data/font_data_manager.h"

#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "base/containers/heap_array.h"
#include "base/files/memory_mapped_file.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "content/common/features.h"
#include "content/public/child/child_thread.h"
#if BUILDFLAG(IS_WIN)
#include "third_party/skia/src/ports/SkTypeface_win_dw.h"  // nogncheck
#endif
#if BUILDFLAG(ENABLE_FREETYPE)
#include "third_party/skia/include/ports/SkFontMgr_empty.h"
#endif
#include "third_party/skia/include/ports/SkTypeface_fontations.h"

namespace font_data_service {

namespace {

const int kTypefaceCacheSize = 128;

// Binds a pending receiver. Must be invoked from the main thread.
void BindHostReceiverOnMainThread(
    mojo::PendingReceiver<font_data_service::mojom::FontDataService>
        pending_receiver) {
  content::ChildThread::Get()->BindHostReceiver(std::move(pending_receiver));
}

mojom::TypefaceSlant ConvertToMojomFontStyle(SkFontStyle::Slant slant) {
  switch (slant) {
    case SkFontStyle::Slant::kUpright_Slant:
      return mojom::TypefaceSlant::kRoman;
    case SkFontStyle::Slant::kItalic_Slant:
      return mojom::TypefaceSlant::kItalic;
    case SkFontStyle::Slant::kOblique_Slant:
      return mojom::TypefaceSlant::kOblique;
  }
  NOTREACHED();
}

UNSAFE_BUFFER_USAGE std::vector<std::string> bcp47ArrayToVector(
    const char* bcp47_array[],
    int bcp47_count) {
  std::vector<std::string> bcp47s;
  bcp47s.reserve(bcp47_count);

  // SAFETY: Skia passes BCP47 language tags as an array of `bcp47_count`
  // null-terminated c-style strings. Generate an equivalent
  // std::vector<std::string> to pass over mojo.
  for (int i = 0; i < bcp47_count; ++i) {
    bcp47s.emplace_back(UNSAFE_BUFFERS(bcp47_array[i]));
  }

  return bcp47s;
}
}  // namespace

FontDataManager::FontDataManager()
    : typeface_cache_(kTypefaceCacheSize),
#if BUILDFLAG(ENABLE_FREETYPE)
      custom_fnt_mgr_(SkFontMgr_New_Custom_Empty()),
#endif
      main_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()) {
}

FontDataManager::~FontDataManager() = default;

int FontDataManager::onCountFamilies() const {
  base::AutoLock locked(family_names_lock_);
  if (family_names_.empty()) {
    GetAllFamilyNamesLockRequired();
  }

  return family_names_.size();
}

void FontDataManager::onGetFamilyName(int index,
                                      SkString* requested_family_name) const {
  if (index < 0) {
    return;
  }

  base::AutoLock locked(family_names_lock_);
  if (family_names_.empty()) {
    GetAllFamilyNamesLockRequired();
  }

  size_t family_index = static_cast<size_t>(index);
  if (family_index >= family_names_.size()) {
    return;
  }

  *requested_family_name = SkString(family_names_[family_index]);
}

sk_sp<SkFontStyleSet> FontDataManager::onCreateStyleSet(int index) const {
  NOTREACHED();
}

sk_sp<SkFontStyleSet> FontDataManager::onMatchFamily(
    const char requested_family_name[]) const {
  NOTREACHED();
}

sk_sp<SkTypeface> FontDataManager::onMatchFamilyStyle(
    const char requested_family_name[],
    const SkFontStyle& requested_style) const {
  // NOTE: requested_family_name can be null to get default font.
  std::string cpp_requested_family_name;
  if (requested_family_name) {
    cpp_requested_family_name = requested_family_name;
  }

  MatchFamilyRequest request(cpp_requested_family_name,
                             requested_style.weight(), requested_style.width(),
                             requested_style.slant());
  std::optional<sk_sp<SkTypeface>> typeface_result = TryGetFromCache(request);
  if (typeface_result) {
    return *typeface_result;
  }

  // Proxy the font request to the font service.
  mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New());
  style->weight = requested_style.weight();
  style->width = requested_style.width();
  style->slant = ConvertToMojomFontStyle(requested_style.slant());

  mojom::MatchFamilyNameResultPtr match_result;
  {
    TRACE_EVENT1("fonts", "FontDataManager::onMatchFamilyStyle", "family_name",
                 cpp_requested_family_name);
    GetRemoteFontDataService().MatchFamilyName(cpp_requested_family_name,
                                               std::move(style), &match_result);
  }

  auto typeface = CreateTypefaceFromMatchResult(std::move(match_result));

  // Update the cache with the resulting typeface even in case of a failure to
  // avoid calling the font service again. Failed typeface will go to the font
  // fallback stack.
  AddToCache(request, typeface);

  return typeface;
}

sk_sp<SkTypeface> FontDataManager::onMatchFamilyStyleCharacter(
    const char requested_family_name[],
    const SkFontStyle& requested_style,
    const char* bcp47[],
    int bcp47_count,
    SkUnichar character) const {
  // NOTE: requested_family_name can be null to get default font.
  std::string cpp_requested_family_name;
  if (requested_family_name) {
    cpp_requested_family_name = requested_family_name;
  }

  // Proxy the font request to the font service.
  mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New());
  style->weight = requested_style.weight();
  style->width = requested_style.width();
  style->slant = ConvertToMojomFontStyle(requested_style.slant());

  mojom::MatchFamilyNameResultPtr match_result;
  {
    TRACE_EVENT1("fonts", "FontDataManager::onMatchFamilyStyleCharacter",
                 "family_name", cpp_requested_family_name);

    // SAFETY: Skia passes BCP47 language tags as an array of `bcp47_count`
    // null-terminated c-style strings. Generate an equivalent
    // std::vector<std::string> to pass over mojo.
    GetRemoteFontDataService().MatchFamilyNameCharacter(
        cpp_requested_family_name, std::move(style),
        UNSAFE_BUFFERS(bcp47ArrayToVector(bcp47, bcp47_count)), character,
        &match_result);
  }

  return CreateTypefaceFromMatchResult(std::move(match_result));
}

sk_sp<SkTypeface> FontDataManager::onMakeFromData(sk_sp<SkData> data,
                                                  int ttc_index) const {
  return makeFromStream(std::make_unique<SkMemoryStream>(std::move(data)),
                        ttc_index);
}

sk_sp<SkTypeface> FontDataManager::onMakeFromStreamIndex(
    std::unique_ptr<SkStreamAsset> stream,
    int ttc_index) const {
  SkFontArguments args;
  args.setCollectionIndex(ttc_index);
  return onMakeFromStreamArgs(std::move(stream), args);
}

sk_sp<SkTypeface> FontDataManager::onMakeFromStreamArgs(
    std::unique_ptr<SkStreamAsset> stream,
    const SkFontArguments& args) const {
  TRACE_EVENT1("fonts", "FontDataManager::onMakeFromStreamArgs", "size",
               stream->getLength());
  // Experiment will test the performance of different SkTypefaces.
  // 'custom_fnt_mgr_' is a wrapper to create an SkFreeType typeface.

  // DWRITE is only an option on Windows. Other platforms must use Freetype or
  // Fontations.
#if BUILDFLAG(IS_WIN)
  if (features::kFontDataServiceTypefaceType.Get() ==
      features::FontDataServiceTypefaceType::kDwrite) {
    return DWriteFontTypeface::MakeFromStream(std::move(stream), args);
  }
#endif
  // Chromium currently always sets ENABLE_FREETYPE, but nonetheless allow
  // falling back to fontations if the param is set to freetype but freetype
  // isn't enabled.
#if BUILDFLAG(ENABLE_FREETYPE)
  if (features::kFontDataServiceTypefaceType.Get() ==
      features::FontDataServiceTypefaceType::kFreetype) {
    return custom_fnt_mgr_->makeFromStream(std::move(stream), args);
  }
#endif

  return SkTypeface_Make_Fontations(std::move(stream), args);
}

sk_sp<SkTypeface> FontDataManager::onMakeFromFile(const char path[],
                                                  int ttc_index) const {
  std::unique_ptr<SkStreamAsset> stream = SkStream::MakeFromFile(path);
  return stream ? makeFromStream(std::move(stream), ttc_index) : nullptr;
}

sk_sp<SkTypeface> FontDataManager::onLegacyMakeTypeface(
    const char requested_family_name[],
    SkFontStyle requested_style) const {
  std::optional<std::string> cpp_requested_family_name;
  if (requested_family_name) {
    cpp_requested_family_name = requested_family_name;
  }

  MatchFamilyRequest request(cpp_requested_family_name,
                             requested_style.weight(), requested_style.width(),
                             requested_style.slant());
  std::optional<sk_sp<SkTypeface>> typeface_result = TryGetFromCache(request);
  if (typeface_result) {
    return *typeface_result;
  }

  // Proxy the font request to the font service.
  mojom::TypefaceStylePtr style(mojom::TypefaceStyle::New());
  style->weight = requested_style.weight();
  style->width = requested_style.width();
  style->slant = ConvertToMojomFontStyle(requested_style.slant());

  mojom::MatchFamilyNameResultPtr match_result;
  {
    TRACE_EVENT1("fonts", "FontDataManager::onLegacyMakeTypeface",
                 "family_name", cpp_requested_family_name);

    GetRemoteFontDataService().LegacyMakeTypeface(
        cpp_requested_family_name, std::move(style), &match_result);
  }

  auto typeface = CreateTypefaceFromMatchResult(std::move(match_result));
  // Update the cache with the resulting typeface even in case of a failure to
  // avoid calling the font service again. Failed typeface will go to the font
  // fallback stack.
  AddToCache(request, typeface);

  return typeface;
}

void FontDataManager::SetFontServiceForTesting(
    mojo::PendingRemote<font_data_service::mojom::FontDataService>
        font_data_service) {
  mojo::Remote<font_data_service::mojom::FontDataService>& remote =
      font_data_service_slot_.GetOrCreateValue();
  remote.Bind(std::move(font_data_service));
}

size_t FontDataManager::GetMappedFilesCountForTesting() const {
  base::AutoLock locked(mapped_files_lock_);
  return mapped_files_.size();
}

font_data_service::mojom::FontDataService&
FontDataManager::GetRemoteFontDataService() const {
  mojo::Remote<font_data_service::mojom::FontDataService>& remote =
      font_data_service_slot_.GetOrCreateValue();

  if (!remote) {
    if (main_task_runner_->RunsTasksInCurrentSequence()) {
      BindHostReceiverOnMainThread(remote.BindNewPipeAndPassReceiver());
    } else {
      main_task_runner_->PostTask(
          FROM_HERE, base::BindOnce(&BindHostReceiverOnMainThread,
                                    remote.BindNewPipeAndPassReceiver()));
    }
  }
  return *remote;
}

sk_sp<SkTypeface> FontDataManager::CreateTypefaceFromMatchResult(
    mojom::MatchFamilyNameResultPtr match_result) const {
  // Create the resulting typeface from the data received from the font
  // service.
  std::unique_ptr<base::MemoryMappedFile> mapped_font_file;
  sk_sp<SkTypeface> typeface;
  if (match_result && match_result->typeface_data) {
    // Attempt to create the SkFontArguments args.
    base::HeapArray<SkFontArguments::VariationPosition::Coordinate>
        typeface_axis;
    SkFontArguments args;
    args.setCollectionIndex(match_result->ttc_index);
    if (match_result->variation_position &&
        match_result->variation_position->coordinateCount > 0) {
      typeface_axis =
          base::HeapArray<SkFontArguments::VariationPosition::Coordinate>::
              Uninit(match_result->variation_position->coordinates.size());
      for (size_t i = 0;
           i < match_result->variation_position->coordinates.size(); i++) {
        const auto& coordinate =
            match_result->variation_position->coordinates[i];
        typeface_axis[i] = {.axis = coordinate->axis,
                            .value = coordinate->value};
      }
      args.setVariationDesignPosition(
          {.coordinates = typeface_axis.data(),
           .coordinateCount = base::checked_cast<int>(typeface_axis.size())});
    }

    // Attempt to create the typeface data based on the match result.
    if (match_result->typeface_data->is_font_file()) {
      TRACE_EVENT("fonts", "FontDataManager - using mapped file");

      // If the file's unique ID is already present in `mapped_files_`, we must
      // reuse it to avoid re-creating a memory mapped file for each match
      // request that resolves to the same font file.
      base::MemoryMappedFile* file_mapping = nullptr;
      {
        base::AutoLock locked(mapped_files_lock_);

        std::unique_ptr<base::MemoryMappedFile>& mapped_files_entry =
            mapped_files_[match_result->typeface_data->get_font_file()->id];
        if (!mapped_files_entry) {
          auto new_mapping = std::make_unique<base::MemoryMappedFile>();
          if (new_mapping->Initialize(std::move(
                  match_result->typeface_data->get_font_file()->file_handle))) {
            mapped_files_entry = std::move(new_mapping);
            base::UmaHistogramCounts1000(
                "Chrome.FontDataManager.NumMappedFiles", mapped_files_.size());
          }
        }
        file_mapping = mapped_files_entry.get();
      }

      if (file_mapping) {
        typeface = onMakeFromStreamArgs(
            SkMemoryStream::MakeDirect(file_mapping->data(),
                                       file_mapping->length()),
            args);
      }
    } else if (match_result->typeface_data->is_region() &&
               match_result->typeface_data->get_region().IsValid()) {
      TRACE_EVENT("fonts", "FontDataManager - using shared memory");
      base::ReadOnlySharedMemoryRegion font_data_memory_region =
          std::move(match_result->typeface_data->get_region());
      const void* mapped_memory = nullptr;
      size_t mapped_size = 0;
      // Map the memory (if needed) and keep it alive for the lifetime of this
      // process. A cache is used to avoid mapping the same memory space
      // multiple time.
      {
        base::AutoLock locked(mapped_regions_lock_);

        const auto iter =
            mapped_regions_.find(font_data_memory_region.GetGUID());

        if (iter != mapped_regions_.end()) {
          mapped_memory = iter->second.memory();
          mapped_size = iter->second.size();
        } else {
          base::ReadOnlySharedMemoryMapping mapping =
              font_data_memory_region.Map();
          if (mapping.IsValid()) {
            mapped_memory = mapping.memory();
            mapped_size = mapping.size();
            mapped_regions_.emplace(font_data_memory_region.GetGUID(),
                                    std::move(mapping));
          }
        }
      }

      // Create the memory stream from the mapped memory.
      if (mapped_memory && mapped_size > 0) {
        typeface = onMakeFromStreamArgs(
            SkMemoryStream::MakeDirect(mapped_memory, mapped_size), args);
      }
    }
  }

  return typeface;
}

void FontDataManager::GetAllFamilyNamesLockRequired() const {
  GetRemoteFontDataService().GetAllFamilyNames(&family_names_);
}

std::optional<sk_sp<SkTypeface>> FontDataManager::TryGetFromCache(
    const FontDataManager::MatchFamilyRequest& request) const {
  base::AutoLock locked(typeface_cache_lock_);
  auto iter = typeface_cache_.Get(request);
  if (iter != typeface_cache_.end()) {
    return iter->second;
  }

  return std::nullopt;
}

void FontDataManager::AddToCache(
    const FontDataManager::MatchFamilyRequest& request,
    sk_sp<SkTypeface> typeface) const {
  base::AutoLock locked(typeface_cache_lock_);
  typeface_cache_.Put(std::move(request), typeface);
}

FontDataManager::MatchFamilyRequest::MatchFamilyRequest(
    std::optional<std::string> name,
    int weight,
    int width,
    SkFontStyle::Slant slant)
    : name(name), weight(weight), width(width), slant(slant) {}
FontDataManager::MatchFamilyRequest::MatchFamilyRequest(
    const MatchFamilyRequest&) = default;
FontDataManager::MatchFamilyRequest::MatchFamilyRequest(
    FontDataManager::MatchFamilyRequest&&) = default;
FontDataManager::MatchFamilyRequest::~MatchFamilyRequest() = default;

}  // namespace font_data_service