// Copyright 2020 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/font_access/font_enumeration_data_source_mac.h"

#import <CoreFoundation/CoreFoundation.h>
#import <CoreText/CoreText.h>

#include <string>

#include "base/apple/bridging.h"
#include "base/apple/scoped_cftyperef.h"
#include "base/notreached.h"
#include "base/strings/sys_string_conversions.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/font_access/font_enumeration_table.pb.h"

namespace content {

namespace {

base::apple::ScopedCFTypeRef<CFStringRef> GetLocalizedString(
    CTFontDescriptorRef fd,
    CFStringRef attribute) {
  return base::apple::ScopedCFTypeRef<CFStringRef>(
      base::apple::CFCast<CFStringRef>(CTFontDescriptorCopyLocalizedAttribute(
          fd, attribute, /*language=*/nullptr)));
}

base::apple::ScopedCFTypeRef<CFStringRef> GetString(CTFontDescriptorRef fd,
                                                    CFStringRef attribute) {
  return base::apple::ScopedCFTypeRef<CFStringRef>(
      base::apple::CFCast<CFStringRef>(
          CTFontDescriptorCopyAttribute(fd, attribute)));
}

}  // namespace

FontEnumerationDataSourceMac::FontEnumerationDataSourceMac() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
}

FontEnumerationDataSourceMac::~FontEnumerationDataSourceMac() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

bool FontEnumerationDataSourceMac::IsValidFontMac(
    const CTFontDescriptorRef& fd) {
  base::apple::ScopedCFTypeRef<CFStringRef> cf_postscript_name =
      GetString(fd, kCTFontNameAttribute);
  base::apple::ScopedCFTypeRef<CFStringRef> cf_full_name =
      GetLocalizedString(fd, kCTFontDisplayNameAttribute);
  base::apple::ScopedCFTypeRef<CFStringRef> cf_family =
      GetString(fd, kCTFontFamilyNameAttribute);
  base::apple::ScopedCFTypeRef<CFStringRef> cf_style =
      GetString(fd, kCTFontStyleNameAttribute);

  if (!cf_postscript_name || !cf_full_name || !cf_family || !cf_style) {
    // Check for invalid attribute returns as MacOS may allow
    // OS-level installation of fonts for some of these.
    return false;
  }
  this->cf_postscript_name_ = cf_postscript_name;
  this->cf_full_name_ = cf_full_name;
  this->cf_family_ = cf_family;
  this->cf_style_ = cf_style;
  return true;
}

blink::FontEnumerationTable FontEnumerationDataSourceMac::GetFonts(
    const std::string& locale) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  blink::FontEnumerationTable font_enumeration_table;

  @autoreleasepool {
    NSDictionary* options = @{
      base::apple::CFToNSPtrCast(kCTFontCollectionRemoveDuplicatesOption) : @YES
    };

    base::apple::ScopedCFTypeRef<CTFontCollectionRef> collection(
        CTFontCollectionCreateFromAvailableFonts(
            base::apple::NSToCFPtrCast(options)));

    base::apple::ScopedCFTypeRef<CFArrayRef> font_descs(
        CTFontCollectionCreateMatchingFontDescriptors(collection));

    // Used to filter duplicates.
    std::set<std::string> fonts_seen;

    for (CFIndex i = 0; i < CFArrayGetCount(font_descs); ++i) {
      CTFontDescriptorRef fd = base::apple::CFCast<CTFontDescriptorRef>(
          CFArrayGetValueAtIndex(font_descs, i));
      if (!IsValidFontMac(fd)) {
        // Skip invalid fonts.
        continue;
      }

      std::string postscript_name =
          base::SysCFStringRefToUTF8(cf_postscript_name_.get());

      auto it_and_success = fonts_seen.emplace(postscript_name);
      if (!it_and_success.second) {
        // Skip duplicate.
        continue;
      }

      blink::FontEnumerationTable_FontData* data =
          font_enumeration_table.add_fonts();
      data->set_postscript_name(std::move(postscript_name));
      data->set_full_name(base::SysCFStringRefToUTF8(cf_full_name_.get()));
      data->set_family(base::SysCFStringRefToUTF8(cf_family_.get()));
      data->set_style(base::SysCFStringRefToUTF8(cf_style_.get()));
    }

    return font_enumeration_table;
  }
}

}  // namespace content
