910e62b5创建于 1月15日历史提交
// 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 "pdf/pdfium/pdfium_font_linux.h"

#include <stddef.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <utility>

#include "base/check_op.h"
#include "base/containers/heap_array.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/i18n/encoding_detection.h"
#include "base/i18n/icu_string_conversions.h"
#include "base/no_destructor.h"
#include "base/numerics/byte_conversions.h"
#include "base/numerics/safe_conversions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/sequence_checker.h"
#include "components/services/font/public/cpp/font_loader.h"
#include "pdf/pdf_features.h"
#include "pdf/pdfium/pdfium_engine.h"
#include "pdf/pdfium/pdfium_font_helpers.h"
#include "third_party/blink/public/platform/web_font_description.h"
#include "third_party/pdfium/public/fpdf_sysfontinfo.h"

namespace chrome_pdf {

namespace {

// Enables enumerating all system fonts via FontConfig instead of just Arial
// and PDFium's default fonts. This sends all font family names over IPC,
// which could be large on systems with many fonts.
// TODO(crbug.com/449573621): Remove this kill switch after a safe rollout.
BASE_FEATURE(kPdfEnumerateAllSystemFonts, base::FEATURE_ENABLED_BY_DEFAULT);

// GetFontTable loads a specified font table from an open SFNT file.
//   fd: a file descriptor to the SFNT file. The position doesn't matter.
//   table_tag: the table tag in *big-endian* format, or 0 for the entire font.
//   output: a buffer of size output_length that gets the data.  can be 0, in
//     which case output_length will be set to the required size in bytes.
//   output_length: size of output, if it's not 0.
//
//   returns: true on success.
//
// TODO(drott): This should be should be replaced with using FreeType for the
// purpose instead of reimplementing table parsing.
bool GetFontTable(int fd,
                  uint32_t table_tag,
                  uint8_t* output,
                  size_t* output_length) {
  size_t data_length = 0u;  // the length of the file data.
  off_t data_offset = 0;    // the offset of the data in the file.
  if (table_tag == 0) {
    // Get the entire font file.
    struct stat st;
    if (fstat(fd, &st) < 0) {
      return false;
    }
    data_length = base::checked_cast<size_t>(st.st_size);
  } else {
    // Get a font table. Read the header to find its offset in the file.
    uint8_t bytes[2];
    ssize_t n = HANDLE_EINTR(
        pread(fd, bytes, sizeof(bytes), 4 /* skip the font type */));
    if (n != sizeof(bytes)) {
      return false;
    }
    // Font data is stored in net (big-endian) order.
    uint16_t num_tables = base::U16FromBigEndian(bytes);

    // Read the table directory.
    static const size_t kTableEntrySize = 16u;
    auto table_entries =
        base::HeapArray<uint8_t>::WithSize(num_tables * kTableEntrySize);

    n = HANDLE_EINTR(pread(fd, table_entries.data(), table_entries.size(),
                           12 /* skip the SFNT header */));
    if (n != base::checked_cast<ssize_t>(table_entries.size())) {
      return false;
    }

    for (uint16_t i = 0u; i < num_tables; ++i) {
      auto entry = table_entries.subspan(i * kTableEntrySize, kTableEntrySize);
      // The `table_tag` is encoded in the same endian as the tag in the table.
      auto tag = base::U32FromNativeEndian(entry.first<4u>());
      if (tag == table_tag) {
        // Font data is stored in net (big-endian) order.
        data_offset = base::U32FromBigEndian(entry.subspan<8u, 4u>());
        data_length = base::U32FromBigEndian(entry.subspan<12u, 4u>());
        break;
      }
    }
  }

  if (!data_length) {
    return false;
  }

  if (output) {
    // 'output_length' holds the maximum amount of data the caller can accept.
    data_length = std::min(data_length, *output_length);
    ssize_t n = HANDLE_EINTR(pread(fd, output, data_length, data_offset));
    if (n != base::checked_cast<ssize_t>(data_length)) {
      return false;
    }
  }
  *output_length = data_length;

  return true;
}

// Maps font description and charset to `FontId` as requested by PDFium, with
// `FontId` as an opaque type that PDFium works with. Based on the `FontId`,
// PDFium can read from the font files using GetFontData(). Properly frees the
// underlying resource type when PDFium is done with the mapped font.
class BlinkFontMapper {
 public:
  // Defined as the type most convenient for use with PDFium's
  // `FPDF_SYSFONTINFO` functions.
  using FontId = void*;

  BlinkFontMapper() = default;
  ~BlinkFontMapper() = delete;

  // Returns a handle to the font mapped based on `desc` and `charset`, for use
  // as the `font_id` in GetFontData() and DeleteFont() below. Returns nullptr
  // on failure.
  // TODO(thestig): Document how this handles TTC files.
  FontId MapFont(const blink::WebFontDescription& desc, int charset) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    // If there was never a SkFontConfigInterface::SetGlobal() call, then `fci`
    // defaults to the direct interface, which is not suitable, as it does not
    // provide MatchFontWithFallback(). This only happens in unit tests, so just
    // refuse to map fonts there.
    sk_sp<SkFontConfigInterface> fci = SkFontConfigInterface::RefGlobal();
    if (fci.get() == SkFontConfigInterface::GetSingletonDirectInterface()) {
      return nullptr;
    }

    auto font_file = std::make_unique<base::File>();
    // In RendererBlinkPlatform, SkFontConfigInterface::SetGlobal() only ever
    // sets the global to a FontLoader. Thus it is safe to assume the returned
    // result is just that.
    auto* font_loader = reinterpret_cast<font_service::FontLoader*>(fci.get());
    font_loader->MatchFontWithFallback(
        desc.family.Utf8(),
        desc.weight >= blink::WebFontDescription::kWeightBold, desc.italic,
        charset, desc.generic_family, font_file.get());
    if (!font_file->IsValid()) {
      return nullptr;
    }

    // Release to PDFium. PDFium will free `font_file` in DeleteFont() below.
    return font_file.release();
  }

  // Releases the font file that `font_id` points to.
  void DeleteFont(FontId font_id) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    delete FileFromFontId(font_id);
  }

  // Reads data from the `font_id` handle for `table` into a `buffer` of
  // `buf_size`. Returns the amount of data read on success, or 0 on failure.
  // If `buffer` is null, then just return the required size for the buffer.
  // See content::GetFontTable() for information on the `table_tag` parameter.
  unsigned long GetFontData(FontId font_id,
                            unsigned int table_tag,
                            unsigned char* buffer,
                            unsigned long buf_size) const {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

    // TODO(thestig): cache?
    base::PlatformFile platform_file =
        FileFromFontId(font_id)->GetPlatformFile();
    size_t size = buf_size;
    if (!GetFontTable(platform_file, table_tag, buffer, &size)) {
      return 0;
    }
    return size;
  }

  // This list is for CPWL_FontMap::GetDefaultFontByCharset().
  // We pretend to have these font natively and let the browser (or underlying
  // fontconfig) pick the proper font on the system.
  //
  // NOTE: When version 2 is enabled (features::kPdfiumPerRequestFontMatching),
  // PDFium does NOT call this function. Instead, it calls MapFont() directly
  // for each font request, eliminating the need for upfront enumeration.
  void EnumFonts(FPDF_SYSFONTINFO* sysfontinfo, void* mapper) {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    CHECK(
        !base::FeatureList::IsEnabled(features::kPdfiumPerRequestFontMatching));

    if (!base::FeatureList::IsEnabled(kPdfEnumerateAllSystemFonts)) {
      FPDF_AddInstalledFont(mapper, "Arial", FXFONT_DEFAULT_CHARSET);

      size_t count = FPDF_GetDefaultTTFMapCount();
      for (size_t i = 0; i < count; ++i) {
        const FPDF_CharsetFontMap* font_map = FPDF_GetDefaultTTFMapEntry(i);
        if (font_map) {
          FPDF_AddInstalledFont(mapper, font_map->fontname, font_map->charset);
        }
      }
      return;
    }

    std::set<std::pair<std::string, int>> seen_fonts;

    if (sk_sp<SkFontConfigInterface> fci = SkFontConfigInterface::RefGlobal();
        fci &&
        fci.get() != SkFontConfigInterface::GetSingletonDirectInterface()) {
      auto* font_loader = static_cast<font_service::FontLoader*>(fci.get());
      for (const std::string& family_name : font_loader->ListFamilies()) {
        CHECK(!family_name.empty());
        if (bool inserted =
                seen_fonts.emplace(family_name, FXFONT_DEFAULT_CHARSET).second;
            inserted) {
          FPDF_AddInstalledFont(mapper, family_name.c_str(),
                                FXFONT_DEFAULT_CHARSET);
        }
      }
    }

    size_t count = FPDF_GetDefaultTTFMapCount();
    for (size_t i = 0; i < count; ++i) {
      const FPDF_CharsetFontMap* font_map = FPDF_GetDefaultTTFMapEntry(i);
      if (font_map) {
        if (bool inserted =
                seen_fonts.emplace(font_map->fontname, font_map->charset)
                    .second;
            inserted) {
          FPDF_AddInstalledFont(mapper, font_map->fontname, font_map->charset);
        }
      }
    }
  }

 private:
  static base::File* FileFromFontId(FontId font_id) {
    return reinterpret_cast<base::File*>(font_id);
  }

  SEQUENCE_CHECKER(sequence_checker_);
};

BlinkFontMapper& GetBlinkFontMapper() {
  static base::NoDestructor<BlinkFontMapper> mapper;
  return *mapper;
}

void EnumFonts(FPDF_SYSFONTINFO* sysfontinfo, void* mapper) {
  if (PDFiumEngine::GetFontMappingMode() != FontMappingMode::kBlink) {
    CHECK_EQ(PDFiumEngine::GetFontMappingMode(), FontMappingMode::kNoMapping);
    return;
  }

  GetBlinkFontMapper().EnumFonts(sysfontinfo, mapper);
}

void* MapFont(FPDF_SYSFONTINFO*,
              int weight,
              int italic,
              int charset,
              int pitch_family,
              const char* face,
              int* exact) {
  // The code below is Blink-specific, return early in non-Blink mode to avoid
  // crashing.
  if (PDFiumEngine::GetFontMappingMode() != FontMappingMode::kBlink) {
    DCHECK_EQ(PDFiumEngine::GetFontMappingMode(), FontMappingMode::kNoMapping);
    return nullptr;
  }

  std::optional<blink::WebFontDescription> desc =
      PdfFontToBlinkFontMapping(weight, italic, charset, pitch_family, face);
  if (!desc.has_value()) {
    return nullptr;
  }

  return GetBlinkFontMapper().MapFont(desc.value(), charset);
}

unsigned long GetFontData(FPDF_SYSFONTINFO*,
                          void* font_id,
                          unsigned int table,
                          unsigned char* buffer,
                          unsigned long buf_size) {
  DCHECK_EQ(PDFiumEngine::GetFontMappingMode(), FontMappingMode::kBlink);
  return GetBlinkFontMapper().GetFontData(font_id, table, buffer, buf_size);
}

void DeleteFont(FPDF_SYSFONTINFO*, void* font_id) {
  DCHECK_EQ(PDFiumEngine::GetFontMappingMode(), FontMappingMode::kBlink);
  GetBlinkFontMapper().DeleteFont(font_id);
}

FPDF_SYSFONTINFO g_font_info = {.version = 1,
                                .Release = nullptr,
                                .EnumFonts = EnumFonts,
                                .MapFont = MapFont,
                                .GetFont = nullptr,
                                .GetFontData = GetFontData,
                                .GetFaceName = nullptr,
                                .GetFontCharset = nullptr,
                                .DeleteFont = DeleteFont};

}  // namespace

void InitializeLinuxFontMapper() {
  // Set version based on feature flag. Version 2 uses per-request font
  // matching (MapFont called directly) instead of upfront enumeration
  // (EnumFonts). This eliminates the expensive ListFamilies() IPC.
  if (base::FeatureList::IsEnabled(features::kPdfiumPerRequestFontMatching)) {
    g_font_info.version = 2;
  } else {
    g_font_info.version = 1;
  }

  FPDF_SetSystemFontInfo(&g_font_info);
}

}  // namespace chrome_pdf