chromium/ui/gfx/font_fallback_skia_impl.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/354829279): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "ui/gfx/font_fallback_skia_impl.h"

#include <set>
#include <string>
#include <string_view>

#include "skia/ext/font_utils.h"
#include "third_party/icu/source/common/unicode/normalizer2.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "third_party/icu/source/common/unicode/utf16.h"
#include "third_party/skia/include/core/SkFontMgr.h"
#include "third_party/skia/include/core/SkTypeface.h"

namespace gfx {

namespace {

// Returns true when the codepoint has an unicode decomposition and store
// the decomposed string into |output|.
bool UnicodeDecomposeCodepoint(UChar32 codepoint, icu::UnicodeString* output) {
  static const icu::Normalizer2* normalizer = nullptr;

  UErrorCode error = U_ZERO_ERROR;
  if (!normalizer) {
    normalizer = icu::Normalizer2::getNFDInstance(error);
    if (U_FAILURE(error))
      return false;
    DCHECK(normalizer);
  }

  return normalizer->getDecomposition(codepoint, *output);
}

// Extracts every codepoint and its decomposed codepoints from unicode
// decomposition. Inserts in |codepoints| the set of codepoints in |text|.
void RetrieveCodepointsAndDecomposedCodepoints(std::u16string_view text,
                                               std::set<UChar32>* codepoints) {
  size_t offset = 0;
  while (offset < text.length()) {
    UChar32 codepoint;
    U16_NEXT(text.data(), offset, text.length(), codepoint);

    if (codepoints->insert(codepoint).second) {
      // For each codepoint, add the decomposed codepoints.
      icu::UnicodeString decomposed_text;
      if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text)) {
        for (int i = 0; i < decomposed_text.length(); ++i) {
          codepoints->insert(decomposed_text[i]);
        }
      }
    }
  }
}

// Returns the amount of codepoint in |text| without a glyph representation in
// |typeface|. A codepoint is present if there is a corresponding glyph in
// typeface, or if there are glyphs for each of its decomposed codepoints.
size_t ComputeMissingGlyphsForGivenTypeface(std::u16string_view text,
                                            sk_sp<SkTypeface> typeface) {
  // Validate that every character has a known glyph in the font.
  size_t missing_glyphs = 0;
  size_t i = 0;
  while (i < text.length()) {
    UChar32 codepoint;
    U16_NEXT(text.data(), i, text.length(), codepoint);

    // The glyph is present in the font.
    if (typeface->unicharToGlyph(codepoint) != 0)
      continue;

    // Do not count missing codepoints when they are ignorable as they will be
    // ignored by the shaping engine.
    if (u_hasBinaryProperty(codepoint, UCHAR_DEFAULT_IGNORABLE_CODE_POINT))
      continue;

    // No glyph is present in the font for the codepoint. Try the decomposed
    // codepoints instead.
    icu::UnicodeString decomposed_text;
    if (UnicodeDecomposeCodepoint(codepoint, &decomposed_text) &&
        !decomposed_text.isEmpty()) {
      // Check that every decomposed codepoint is in the font.
      bool every_codepoint_found = true;
      for (int offset = 0; offset < decomposed_text.length(); ++offset) {
        if (typeface->unicharToGlyph(decomposed_text[offset]) == 0) {
          every_codepoint_found = false;
          break;
        }
      }

      // The decomposed codepoints can be mapped to glyphs by the font.
      if (every_codepoint_found)
        continue;
    }

    // The current glyphs can't be find.
    ++missing_glyphs;
  }

  return missing_glyphs;
}

}  // namespace

sk_sp<SkTypeface> GetSkiaFallbackTypeface(const Font& template_font,
                                          const std::string& locale,
                                          std::u16string_view text) {
  if (text.empty())
    return nullptr;

  sk_sp<SkFontMgr> font_mgr(skia::DefaultFontMgr());

  const char* bcp47_locales[] = {locale.c_str()};
  int num_locales = locale.empty() ? 0 : 1;
  const char** locales = locale.empty() ? nullptr : bcp47_locales;

  const int font_weight = (template_font.GetWeight() == Font::Weight::INVALID)
                              ? static_cast<int>(Font::Weight::NORMAL)
                              : static_cast<int>(template_font.GetWeight());
  const bool italic = (template_font.GetStyle() & Font::ITALIC) != 0;
  SkFontStyle skia_style(
      font_weight, SkFontStyle::kNormal_Width,
      italic ? SkFontStyle::kItalic_Slant : SkFontStyle::kUpright_Slant);

  std::set<SkTypefaceID> tested_typeface;
  sk_sp<SkTypeface> fallback_typeface;
  size_t fewest_missing_glyphs = text.length() + 1;

  // Retrieve the set of codepoints (or unicode decomposed codepoints) from
  // the input text.
  std::set<UChar32> codepoints;
  RetrieveCodepointsAndDecomposedCodepoints(text, &codepoints);

  // Determine which fallback font is given the fewer missing glyphs.
  for (UChar32 codepoint : codepoints) {
    sk_sp<SkTypeface> typeface(font_mgr->matchFamilyStyleCharacter(
        template_font.GetFontName().c_str(), skia_style, locales, num_locales,
        codepoint));
    // If the typeface is not found or was already tested, skip it.
    if (!typeface || !tested_typeface.insert(typeface->uniqueID()).second)
      continue;

    // Validate that every codepoint has a known glyph in the font.
    size_t missing_glyphs =
        ComputeMissingGlyphsForGivenTypeface(text, typeface);
    if (missing_glyphs < fewest_missing_glyphs) {
      fewest_missing_glyphs = missing_glyphs;
      fallback_typeface = typeface;
    }

    // The font is a valid fallback font for the given text.
    if (missing_glyphs == 0)
      break;
  }

  return fallback_typeface;
}

}  // namespace gfx