// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "third_party/blink/renderer/platform/fonts/mac/font_matcher_mac.h"

#import <AppKit/AppKit.h>
#import <CoreText/CoreText.h>
#include <Foundation/Foundation.h>

#include "base/apple/bridging.h"
#import "base/apple/foundation_util.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/compiler_specific.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/font_family_names.h"
#include "third_party/blink/renderer/platform/fonts/font_selection_types.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"

using base::apple::CFToNSOwnershipCast;
using base::apple::CFToNSPtrCast;
using base::apple::NSToCFOwnershipCast;
using base::apple::ObjCCast;
using base::apple::ScopedCFTypeRef;

namespace blink {

namespace {
struct FontName {
  const char* full_font_name;
  const char* postscript_name;
  const char* family_name;
};

// If these font names are unavailable on future Mac OS versions, please try to
// find replacements or remove individual lines.
const FontName FontNames[] = {
    {"American Typewriter Condensed Light", "AmericanTypewriter-CondensedLight",
     "American Typewriter"},
    {"Apple Braille Outline 6 Dot", "AppleBraille-Outline6Dot",
     "Apple Braille"},
    {"Arial Narrow Bold Italic", "ArialNarrow-BoldItalic", "Arial Narrow"},
    {"Baskerville SemiBold Italic", "Baskerville-SemiBoldItalic",
     "Baskerville"},
    {"Devanagari MT", "DevanagariMT", "Devanagari MT"},
    {"DIN Alternate Bold", "DINAlternate-Bold", "DIN Alternate"},
    {"Gill Sans Light Italic", "GillSans-LightItalic", "Gill Sans"},
    {"Malayalam Sangam MN", "MalayalamSangamMN", "Malayalam Sangam MN"},
    {"Hiragino Maru Gothic ProN W4", "HiraMaruProN-W4",
     "Hiragino Maru Gothic ProN"},
    {"Hiragino Sans W3", "HiraginoSans-W3", "Hiragino Sans"},
};

const FontName CommonFontNames[] = {
    {"Avenir-Roman", "Avenir-Roman", "Avenir"},
    {"CourierNewPS-BoldMT", "CourierNewPS-BoldMT", "Courier New"},
    {"Helvetica-Light", "Helvetica-Light", "Helvetica"},
    {"HelveticaNeue-CondensedBlack", "HelveticaNeue-CondensedBlack",
     "Helvetica Neue"},
    {"Menlo-Bold", "Menlo-Bold", "Menlo"},
    {"Tahoma", "Tahoma", "Tahoma"},
    {"TimesNewRomanPS-BoldItalicMT", "TimesNewRomanPS-BoldItalicMT",
     "Times New Roman"},
};

const char* FamiliesWithBoldItalicFaces[] = {"Baskerville", "Cochin", "Georgia",
                                             "GillSans"};

void TestFontWithBoldAndItalicTraits(const AtomicString& font_name) {
  ScopedCFTypeRef<CTFontRef> font_italic = MatchFontFamily(
      font_name, kNormalWeightValue, kItalicSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font_italic);

  CTFontSymbolicTraits italic_font_traits =
      CTFontGetSymbolicTraits(font_italic.get());
  EXPECT_TRUE(italic_font_traits & kCTFontTraitItalic);

  ScopedCFTypeRef<CTFontRef> font_bold_italic = MatchFontFamily(
      font_name, kBoldWeightValue, kItalicSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font_bold_italic);

  CTFontSymbolicTraits bold_italic_font_traits =
      CTFontGetSymbolicTraits(font_bold_italic.get());
  EXPECT_TRUE(bold_italic_font_traits & kCTFontTraitItalic);
  EXPECT_TRUE(bold_italic_font_traits & kCTFontTraitBold);
}

void TestFontMatchingByFamilyName(const char* font_name) {
  ScopedCFTypeRef<CTFontRef> font =
      MatchFontFamily(AtomicString(font_name), kNormalWeightValue,
                      kNormalSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font);
  ScopedCFTypeRef<CFStringRef> matched_family_name(
      CTFontCopyFamilyName(font.get()));
  ScopedCFTypeRef<CFStringRef> expected_family_name(
      CFStringCreateWithCString(nullptr, font_name, kCFStringEncodingUTF8));
  EXPECT_EQ(
      CFStringCompare(matched_family_name.get(), expected_family_name.get(),
                      kCFCompareCaseInsensitive),
      kCFCompareEqualTo);
}

void TestFontMatchingByPostscriptName(const char* font_name) {
  ScopedCFTypeRef<CTFontRef> font =
      MatchFontFamily(AtomicString(font_name), kNormalWeightValue,
                      kNormalSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font);
  ScopedCFTypeRef<CFStringRef> matched_postscript_name(
      CTFontCopyPostScriptName(font.get()));
  ScopedCFTypeRef<CFStringRef> expected_postscript_name(
      CFStringCreateWithCString(nullptr, font_name, kCFStringEncodingUTF8));
  EXPECT_EQ(CFStringCompare(matched_postscript_name.get(),
                            expected_postscript_name.get(),
                            kCFCompareCaseInsensitive),
            kCFCompareEqualTo);
}

void TestCTAndNSMatchEqual(const char* font_name,
                           float size,
                           int weight,
                           int style,
                           int stretch) {
  ScopedCFTypeRef<CTFontRef> matched_font = MatchFontFamily(
      AtomicString(font_name), FontSelectionValue(weight),
      FontSelectionValue(style), FontSelectionValue(stretch), size);

  NSFontTraitMask traits = (style != kNormalSlopeValue) ? NSFontItalicTrait : 0;
  ScopedCFTypeRef<CTFontRef> matched_ns_font(
      base::apple::NSToCFOwnershipCast(MatchNSFontFamily(
          AtomicString(font_name), traits, FontSelectionValue(weight), size)));

  if (matched_font || matched_ns_font) {
    EXPECT_TRUE(matched_font);
    EXPECT_TRUE(matched_ns_font);

    ScopedCFTypeRef<CFStringRef> matched_font_name(
        CTFontCopyPostScriptName(matched_font.get()));
    EXPECT_TRUE(matched_font_name);

    ScopedCFTypeRef<CFStringRef> matched_ns_font_name(
        CTFontCopyPostScriptName(matched_ns_font.get()));
    EXPECT_TRUE(matched_ns_font_name);

    EXPECT_TRUE(
        CFStringCompare(matched_font_name.get(), matched_ns_font_name.get(),
                        kCFCompareCaseInsensitive) == kCFCompareEqualTo);
  }
}

}  // namespace

TEST(FontMatcherMacTest, MatchSystemFont) {
  ScopedCFTypeRef<CTFontRef> font = MatchSystemUIFont(
      kNormalWeightValue, kNormalSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font);
}

TEST(FontMatcherMacTest, MatchSystemFontItalic) {
  ScopedCFTypeRef<CTFontRef> font = MatchSystemUIFont(
      kNormalWeightValue, kItalicSlopeValue, kNormalWidthValue, 11);
  EXPECT_TRUE(font);
  NSDictionary* traits = CFToNSOwnershipCast(CTFontCopyTraits(font.get()));
  NSNumber* slant_num =
      ObjCCast<NSNumber>(traits[CFToNSPtrCast(kCTFontSlantTrait)]);
  float slant = slant_num.floatValue;
  EXPECT_NE(slant, 0.0);
}

TEST(FontMatcherMacTest, MatchSystemFontWithWeightVariations) {
  // Mac SystemUI font supports weight variations between 1 and 1000.
  int min_weight = 1;
  int max_weight = 1000;
  FourCharCode wght_tag = 'wght';
  for (int weight = min_weight - 1; weight <= max_weight + 1; weight += 50) {
    if (weight != kNormalWeightValue) {
      ScopedCFTypeRef<CTFontRef> font = MatchSystemUIFont(
          FontSelectionValue(weight), kNormalSlopeValue, kNormalWidthValue, 11);
      EXPECT_TRUE(font);

      NSDictionary* variations =
          CFToNSOwnershipCast(CTFontCopyVariation(font.get()));
      NSNumber* actual_weight_num = ObjCCast<NSNumber>(variations[@(wght_tag)]);
      EXPECT_TRUE(actual_weight_num);

      float actual_weight = actual_weight_num.floatValue;
      float expected_weight =
          std::max(min_weight, std::min(max_weight, weight));
      EXPECT_EQ(actual_weight, expected_weight);
    }
  }
}

TEST(FontMatcherMacTest, MatchSystemFontWithWidthVariations) {
  // Mac SystemUI font supports width variations between 30 and 150.
  int min_width = 30;
  int max_width = 150;
  FourCharCode wdth_tag = 'wdth';
  for (int width = min_width - 10; width <= max_width + 10; width += 10) {
    if (width != kNormalWidthValue) {
      ScopedCFTypeRef<CTFontRef> font = MatchSystemUIFont(
          kNormalWidthValue, kNormalSlopeValue, FontSelectionValue(width), 11);
      EXPECT_TRUE(font);

      NSDictionary* variations =
          CFToNSOwnershipCast(CTFontCopyVariation(font.get()));
      NSNumber* actual_width_num = ObjCCast<NSNumber>(variations[@(wdth_tag)]);
      EXPECT_TRUE(actual_width_num);

      float actual_width = actual_width_num.floatValue;
      float expected_width = std::max(min_width, std::min(max_width, width));
      EXPECT_EQ(actual_width, expected_width);
    }
  }
}

TEST(FontMatcherMacTest, FontFamilyMatchingUnavailableFont) {
  ScopedCFTypeRef<CTFontRef> font = MatchFontFamily(
      AtomicString(
          "ThisFontNameDoesNotExist07F444B9-4DDF-4A41-8F30-C80D4ED4CCA2"),
      kNormalWeightValue, kNormalSlopeValue, kNormalWidthValue, 12);
  EXPECT_FALSE(font);
}

TEST(FontMatcherMacTest, FontFamilyMatchingLastResortFont) {
  ScopedCFTypeRef<CTFontRef> last_resort_font =
      MatchFontFamily(AtomicString("lastresort"), kNormalWeightValue,
                      kNormalSlopeValue, kNormalWidthValue, 11);
  EXPECT_FALSE(last_resort_font);

  ScopedCFTypeRef<CTFontRef> last_resort_font_bold =
      MatchFontFamily(AtomicString("lastresort"), kBoldWeightValue,
                      kNormalSlopeValue, kNormalWidthValue, 11);
  EXPECT_FALSE(last_resort_font_bold);
}

TEST(FontMatcherMacTest, MatchUniqueUnavailableFont) {
  ScopedCFTypeRef<CTFontRef> font = MatchUniqueFont(
      AtomicString(
          "ThisFontNameDoesNotExist07F444B9-4DDF-4A41-8F30-C80D4ED4CCA2"),
      12);
  EXPECT_FALSE(font);
}

class TestFontMatchingByName : public testing::TestWithParam<FontName> {};

INSTANTIATE_TEST_SUITE_P(FontMatcherMacTest,
                         TestFontMatchingByName,
                         testing::ValuesIn(FontNames));

INSTANTIATE_TEST_SUITE_P(CommonFontMatcherMacTest,
                         TestFontMatchingByName,
                         testing::ValuesIn(CommonFontNames));

// We perform matching by PostScript name for legacy and compatibility reasons
// (Safari also does it), although CSS specs do not require that, see
// crbug.com/641861.
TEST_P(TestFontMatchingByName, MatchingByFamilyName) {
  const FontName font_name = TestFontMatchingByName::GetParam();
  TestFontMatchingByFamilyName(font_name.family_name);
}

TEST_P(TestFontMatchingByName, MatchingByPostscriptName) {
  const FontName font_name = TestFontMatchingByName::GetParam();
  TestFontMatchingByPostscriptName(font_name.postscript_name);
}

TEST_P(TestFontMatchingByName, MatchUniqueFontByFullFontName) {
  const FontName font_name = TestFontMatchingByName::GetParam();
  ScopedCFTypeRef<CTFontRef> font =
      MatchUniqueFont(AtomicString(font_name.full_font_name), 12);
  EXPECT_TRUE(font);
}

TEST_P(TestFontMatchingByName, MatchUniqueFontByPostscriptName) {
  const FontName font_name = TestFontMatchingByName::GetParam();
  ScopedCFTypeRef<CTFontRef> font =
      MatchUniqueFont(AtomicString(font_name.postscript_name), 12);
  EXPECT_TRUE(font);
}

class TestFontMatchingByNameAndWeight
    : public testing::Test,
      public testing::WithParamInterface<std::tuple<FontName, int, bool>> {};

INSTANTIATE_TEST_SUITE_P(FontMatcherMacTest,
                         TestFontMatchingByNameAndWeight,
                         ::testing::Combine(::testing::ValuesIn(FontNames),
                                            ::testing::Range(100, 900, 100),
                                            ::testing::ValuesIn({true,
                                                                 false})));

INSTANTIATE_TEST_SUITE_P(
    CommonFontMatcherMacTest,
    TestFontMatchingByNameAndWeight,
    ::testing::Combine(::testing::ValuesIn(CommonFontNames),
                       ::testing::Range(100, 900, 100),
                       ::testing::ValuesIn({true, false})));

TEST_P(TestFontMatchingByNameAndWeight, TestCTAndNSMatchEqual) {
  struct FontName font_name;
  int weight;
  bool flag;
  std::tie(font_name, weight, flag) = GetParam();
  ScopedFontFamilyPostscriptMatchingCTMigrationForTest scoped_feature(flag);
  // AppKit computes weight values of some fonts not as discrete as CoreText.
  // This is causing matching results of CoreText approach for some weight
  // values of several font families to be more precise than AppKit's. For
  // instance, if desired weight is 300, with AppKit approach will match
  // "HelveticaNeue-Thin" font, while with CoreText we will match
  // "HelveticaNeue-Light". This fonts should be skipped in this test.
  // This issue is described in the comment under
  // "MatchFamilyWithWeightVariations" test.
  if (UNSAFE_TODO(strcmp(font_name.family_name, "Helvetica Neue")) == 0 ||
      UNSAFE_TODO(strcmp(font_name.family_name, "Hiragino Sans")) == 0) {
    return;
  }
  TestCTAndNSMatchEqual(font_name.family_name, 11, weight, kNormalSlopeValue,
                        kNormalWidthValue);
  TestCTAndNSMatchEqual(font_name.family_name, 11, weight, kItalicSlopeValue,
                        kNormalWidthValue);
  TestCTAndNSMatchEqual(font_name.postscript_name, 11, weight,
                        kNormalSlopeValue, kNormalWidthValue);
  TestCTAndNSMatchEqual(font_name.postscript_name, 11, weight,
                        kItalicSlopeValue, kNormalWidthValue);
}

class TestFontWithTraitsMatching : public testing::TestWithParam<const char*> {
};

INSTANTIATE_TEST_SUITE_P(FontMatcherMacTest,
                         TestFontWithTraitsMatching,
                         testing::ValuesIn(FamiliesWithBoldItalicFaces));

TEST_P(TestFontWithTraitsMatching, FontFamilyMatchingWithBoldItalicTraits) {
  const char* font_name = TestFontWithTraitsMatching::GetParam();
  TestFontWithBoldAndItalicTraits(AtomicString(font_name));
}

TEST(FontMatcherMacTest, FontFamilyMatchingWithBoldCondensedTraits) {
  AtomicString family_name = AtomicString("American Typewriter");
  ScopedCFTypeRef<CTFontRef> font_condensed =
      MatchFontFamily(family_name, kNormalWeightValue, kNormalSlopeValue,
                      kCondensedWidthValue, 11);
  EXPECT_TRUE(font_condensed);

  CTFontSymbolicTraits condensed_font_traits =
      CTFontGetSymbolicTraits(font_condensed.get());
  EXPECT_TRUE(condensed_font_traits & NSFontCondensedTrait);

  ScopedCFTypeRef<CTFontRef> font_bold_condensed =
      MatchFontFamily(family_name, kBoldWeightValue, kNormalSlopeValue,
                      kCondensedWidthValue, 11);
  EXPECT_TRUE(font_bold_condensed.get());

  CTFontSymbolicTraits bold_condensed_font_traits =
      CTFontGetSymbolicTraits(font_bold_condensed.get());
  EXPECT_TRUE(bold_condensed_font_traits & NSFontCondensedTrait);
  EXPECT_TRUE(bold_condensed_font_traits & NSFontCondensedTrait);
}

TEST(FontMatcherMacTest, FontWeightSearchDirection) {
  // CSS font matching algorithm (css-fonts-4 §5.2) requires directional weight
  // search: for weight > 500, prefer heavier faces first. For a family with
  // just normal (400) and bold (700) faces like Times New Roman, any requested
  // weight above 500 should match the bold face, and any weight at or below
  // 500 should match the normal face.
  // This test exercises MatchFontFamily, which uses the CoreText path
  // (BetterChoiceCT). The AppKit path (BetterChoice) uses the same shared
  // weight comparison logic and is exercised only when
  // FontFamilyStyleMatchingCTMigration is disabled.
  AtomicString family_name = AtomicString("Times New Roman");

  // Weights at or below 500 should match the normal (non-bold) face.
  for (int weight : {100, 400, 499, 500}) {
    ScopedCFTypeRef<CTFontRef> font =
        MatchFontFamily(family_name, FontSelectionValue(weight),
                        kNormalSlopeValue, kNormalWidthValue, 11);
    ASSERT_TRUE(font) << "Failed to match font for weight " << weight;
    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font.get());
    EXPECT_FALSE(traits & kCTFontTraitBold)
        << "Weight " << weight << " should match normal, not bold";
  }

  // Weights above 500 should match the bold face (ascending search first).
  for (int weight : {501, 520, 600, 700, 900}) {
    ScopedCFTypeRef<CTFontRef> font =
        MatchFontFamily(family_name, FontSelectionValue(weight),
                        kNormalSlopeValue, kNormalWidthValue, 11);
    ASSERT_TRUE(font) << "Failed to match font for weight " << weight;
    CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font.get());
    EXPECT_TRUE(traits & kCTFontTraitBold)
        << "Weight " << weight << " should match bold";
  }
}

TEST(FontMatcherMacTest, FontWeightSearchDirectionSub400) {
  // crbug.com/467809214: AppKit returns the same numeric weight for several
  // PingFang SC faces (Thin and Light are reported as equal), so the
  // AppKit-only path could not distinguish them. For requested weights in
  // [300, 399] the §5.2 descending search then resolved the tie by
  // iteration order and picked Thin instead of Light. Verified by
  // PostScript name since `CTFontCopyTraits` on an instantiated CTFont can
  // drift from the descriptor weight. 400-700 cases also guard against
  // regressions from the unconditional bold-trait skip in `BetterChoiceCT`.
  AtomicString family_name = AtomicString("PingFang SC");
  struct {
    int requested_weight;
    const char* expected_postscript;
  } kCases[] = {
      {100, "PingFangSC-Ultralight"}, {199, "PingFangSC-Ultralight"},
      {200, "PingFangSC-Thin"},       {299, "PingFangSC-Thin"},
      {300, "PingFangSC-Light"},      {390, "PingFangSC-Light"},
      {400, "PingFangSC-Regular"},    {500, "PingFangSC-Medium"},
      {600, "PingFangSC-Semibold"},   {700, "PingFangSC-Semibold"},
  };
  for (const auto& c : kCases) {
    ScopedCFTypeRef<CTFontRef> font =
        MatchFontFamily(family_name, FontSelectionValue(c.requested_weight),
                        kNormalSlopeValue, kNormalWidthValue, 11);
    ASSERT_TRUE(font) << "Failed to match PingFang SC for weight "
                      << c.requested_weight;
    ScopedCFTypeRef<CFStringRef> actual_postscript(
        CTFontCopyPostScriptName(font.get()));
    ASSERT_TRUE(actual_postscript);
    ScopedCFTypeRef<CFStringRef> expected_postscript(CFStringCreateWithCString(
        nullptr, c.expected_postscript, kCFStringEncodingUTF8));
    EXPECT_EQ(
        CFStringCompare(actual_postscript.get(), expected_postscript.get(),
                        kCFCompareCaseInsensitive),
        kCFCompareEqualTo)
        << "Requested weight " << c.requested_weight << ": expected "
        << c.expected_postscript;
  }
}

TEST(FontMatcherMacTest, ConsistentLightMatchAcrossCJKFamilies) {
  // crbug.com/516316384: At weight 360, the CJK family stack
  // ("Hiragino Sans", "PingFang TC", "PingFang SC") matched mixed faces
  // (W3/W6/Thin) across glyphs. Per CSS Fonts 4 §5.2 each family must pick
  // its Light face.
  struct {
    const char* family;
    const char* expected_postscript;
  } kCases[] = {
      {"Hiragino Sans", "HiraginoSans-W2"},
      {"PingFang TC", "PingFangTC-Light"},
      {"PingFang SC", "PingFangSC-Light"},
  };
  constexpr int kRequestedWeight = 360;
  for (const auto& c : kCases) {
    AtomicString family_name = AtomicString(c.family);
    ScopedCFTypeRef<CTFontRef> font =
        MatchFontFamily(family_name, FontSelectionValue(kRequestedWeight),
                        kNormalSlopeValue, kNormalWidthValue, 11);
    ASSERT_TRUE(font) << "Failed to match " << c.family;
    ScopedCFTypeRef<CFStringRef> actual_postscript(
        CTFontCopyPostScriptName(font.get()));
    ASSERT_TRUE(actual_postscript);
    ScopedCFTypeRef<CFStringRef> expected_postscript(CFStringCreateWithCString(
        nullptr, c.expected_postscript, kCFStringEncodingUTF8));
    EXPECT_EQ(
        CFStringCompare(actual_postscript.get(), expected_postscript.get(),
                        kCFCompareCaseInsensitive),
        kCFCompareEqualTo)
        << c.family << ": expected " << c.expected_postscript;
  }
}

TEST(FontMatcherMacTest, MatchFamilyWithWeightVariations) {
  // For some fonts AppKit returns inconsistent weight values in the font
  // information, retrieved using `availableFontsForFamily`. For instance, both
  // "NotoSansMyanmar-Light" and "NotoSansMyanmar-Thin" have AppKit weight value
  // of 3, while "NotoSansMyanmar-Thin" should be thinner than
  // "NotoSansMyanmar-Light".
  // This behavior is affecting matching results. For instance, in this test, if
  // the "FontFamilyStyleMatchingCTMigration" flag is off, we are using
  // `availableFontsForFamily`, so for `weight=300` we will match
  // "NotoSansMyanmar-Thin" font instead of "NotoSansMyanmar-Light".
  // The same issue might appear with CoreText but less often. For instance, for
  // both "AppleSDGothicNeo-Heavy" and "AppleSDGothicNeo-ExtraBold" CoreText
  // returns font weight value 0.56, although weight value of
  // "AppleSDGothicNeo-Heavy" is higher than weight value of
  // "AppleSDGothicNeo-ExtraBold". However, for fonts in "Noto Sans Myanmar"
  // family CoreText returns the correct weight values.
  // Hence we only run this test with the "FontFamilyStyleMatchingCTMigration"
  // flag on.
  ScopedFontFamilyStyleMatchingCTMigrationForTest scoped_feature(true);
  AtomicString family_name = AtomicString("Noto Sans Myanmar");
  for (int weight = 100; weight <= 900; weight += 100) {
    ScopedCFTypeRef<CTFontRef> font =
        MatchFontFamily(family_name, FontSelectionValue(weight),
                        kNormalSlopeValue, kNormalWidthValue, 11);
    NSDictionary* traits = CFToNSOwnershipCast(CTFontCopyTraits(font.get()));
    NSNumber* actual_weight_num =
        ObjCCast<NSNumber>(traits[CFToNSPtrCast(kCTFontWeightTrait)]);

    float actual_ct_weight = actual_weight_num.floatValue;
    int actual_weight = ToCSSFontWeight(actual_ct_weight);
    EXPECT_EQ(actual_weight, weight);
  }
}

}  // namespace blink
