/*
 * Copyright (C) 2007 Nicholas Shanks <contact@nickshanks.com>
 * Copyright (C) 2008 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/platform/fonts/font_description.h"

#include <set>

#include "base/compiler_specific.h"
#include "base/memory/values_equivalent.h"
#include "base/notreached.h"
#include "build/build_config.h"
#include "third_party/blink/public/platform/web_font_description.h"
#include "third_party/blink/renderer/platform/geometry/evaluation_input.h"
#include "third_party/blink/renderer/platform/language.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "third_party/blink/renderer/platform/wtf/size_assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/atomic_string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/format.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hasher.h"

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
#include "third_party/blink/renderer/platform/fonts/font_cache.h"
#endif

namespace blink {

struct SameSizeAsFontDescription {
  DISALLOW_NEW();
  FontFamily family_list;
  scoped_refptr<FontFeatureSettings> feature_settings_;
  scoped_refptr<FontVariationSettings> variation_settings_;
  scoped_refptr<FontPalette> palette_;
  scoped_refptr<FontVariantAlternates> font_variant_alternates_;
  AtomicString locale;
  float sizes[3];
  Length letter_spacing;
  Length word_spacing;
  FontSizeAdjust size_adjust_;
  ResolvedFontFeatures resolved_font_features_;
  FontSelectionRequest selection_request_;
  FontSelectionValue original_slope_;
  FontDescription::StyleSyntax style_syntax_;
  FieldsAsUnsignedType bitfields;
  AtomicString language_override_;
};

ASSERT_SIZE(FontDescription, SameSizeAsFontDescription);

bool FontDescription::use_subpixel_text_positioning_ = false;

// static
FontDescription FontDescription::CreateHashTableEmptyValue() {
  FontDescription result;
  UNSAFE_TODO(memset(&result, 0, sizeof(FontDescription)));
  DCHECK(result.IsHashTableEmptyValue());
  return result;
}

FontDescription::FontDescription(HashTableDeletedValueType) {
  UNSAFE_TODO(memset(this, 0, sizeof(FontDescription)));
  fields_.hash_category_ = kHashDeletedValue;
}

FontDescription::FontDescription()
    : specified_size_(0),
      computed_size_(0),
      adjusted_size_(0),
      letter_spacing_(Length(0, Length::kFixed)),
      word_spacing_(Length(0, Length::kFixed)),
      font_selection_request_(kNormalWeightValue,
                              kNormalWidthValue,
                              kNormalSlopeValue) {
  fields_.orientation_ = static_cast<unsigned>(FontOrientation::kHorizontal);
  fields_.width_variant_ = kRegularWidth;
  fields_.variant_caps_ = kCapsNormal;
  fields_.is_absolute_size_ = false;
  fields_.generic_family_ = kNoFamily;
  fields_.kerning_ = kAutoKerning;
  fields_.common_ligatures_state_ = kNormalLigaturesState;
  fields_.discretionary_ligatures_state_ = kNormalLigaturesState;
  fields_.historical_ligatures_state_ = kNormalLigaturesState;
  fields_.contextual_ligatures_state_ = kNormalLigaturesState;
  fields_.keyword_size_ = 0;
  fields_.font_smoothing_ = kAutoSmoothing;
  fields_.text_rendering_ = kAutoTextRendering;
  fields_.synthetic_bold_ = false;
  fields_.synthetic_italic_ = false;
  fields_.synthetic_oblique_ = false;
  fields_.subpixel_text_position_ = use_subpixel_text_positioning_;
  fields_.typesetting_features_ = 0;
  fields_.variant_numeric_ = FontVariantNumeric().fields_as_unsigned_;
  fields_.variant_east_asian_ = 0;
  fields_.subpixel_ascent_descent_ = false;
  fields_.font_optical_sizing_ = OpticalSizing::kAutoOpticalSizing;
  fields_.has_size_adjust_descriptor_ = false;
  fields_.hash_category_ = kHashRegularValue;
  fields_.font_synthesis_weight_ = kAutoFontSynthesisWeight;
  fields_.font_synthesis_style_ = kAutoFontSynthesisStyle;
  fields_.font_synthesis_small_caps_ = kAutoFontSynthesisSmallCaps;
  fields_.variant_position_ = kNormalVariantPosition;
  fields_.variant_emoji_ = kNormalVariantEmoji;
  fields_.text_spacing_trim_ = static_cast<unsigned>(TextSpacingTrim::kInitial);
}

FontDescription::FontDescription(const FontDescription&) = default;

FontDescription& FontDescription::operator=(const FontDescription&) = default;

bool FontDescription::operator==(const FontDescription& other) const {
  return family_list_ == other.family_list_ && locale_ == other.locale_ &&
         specified_size_ == other.specified_size_ &&
         computed_size_ == other.computed_size_ &&
         adjusted_size_ == other.adjusted_size_ &&
         size_adjust_ == other.size_adjust_ &&
         letter_spacing_ == other.letter_spacing_ &&
         word_spacing_ == other.word_spacing_ &&
         font_selection_request_ == other.font_selection_request_ &&
         style_syntax_ == other.style_syntax_ &&
         fields_as_unsigned_.parts[0] == other.fields_as_unsigned_.parts[0] &&
         fields_as_unsigned_.parts[1] == other.fields_as_unsigned_.parts[1] &&
         (feature_settings_ == other.feature_settings_ ||
          (feature_settings_ && other.feature_settings_ &&
           *feature_settings_ == *other.feature_settings_)) &&
         (variation_settings_ == other.variation_settings_ ||
          (variation_settings_ && other.variation_settings_ &&
           *variation_settings_ == *other.variation_settings_)) &&
         base::ValuesEquivalent(font_palette_, other.font_palette_) &&
         base::ValuesEquivalent(font_variant_alternates_,
                                other.font_variant_alternates_);
}

// Compute a 'lighter' weight per
// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
FontSelectionValue FontDescription::LighterWeight(FontSelectionValue weight) {
  DCHECK(weight >= FontSelectionValue(1) && weight <= FontSelectionValue(1000));
  // [1, 100) => No change
  if (weight < FontSelectionValue(100))
    return weight;
  // [100, 550) => 100
  if (weight < FontSelectionValue(550))
    return FontSelectionValue(100);
  // [550, 750) => 400
  if (weight < FontSelectionValue(750))
    return FontSelectionValue(400);
  // [750, 1000] => 700
  return FontSelectionValue(700);
}

// Compute a 'bolder' weight per
// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
FontSelectionValue FontDescription::BolderWeight(FontSelectionValue weight) {
  DCHECK(weight >= FontSelectionValue(1) && weight <= FontSelectionValue(1000));
  // [1, 350) => 400
  if (weight < FontSelectionValue(350))
    return FontSelectionValue(400);
  // [350, 550) => 700
  if (weight < FontSelectionValue(550))
    return FontSelectionValue(700);
  // [550, 900) => 900
  if (weight < FontSelectionValue(900))
    return FontSelectionValue(900);
  // [900, 1000] => No change
  return weight;
}

FontDescription::Size FontDescription::LargerSize(const Size& size) {
  return Size(0, size.value * 1.2, size.is_absolute);
}

FontDescription::Size FontDescription::SmallerSize(const Size& size) {
  return Size(0, size.value / 1.2, size.is_absolute);
}

FontSelectionRequest FontDescription::GetFontSelectionRequest() const {
  return font_selection_request_;
}

float FontDescription::LetterSpacing() const {
  switch (letter_spacing_.GetType()) {
    case Length::kFixed:
      return letter_spacing_.Pixels();
    case Length::kPercent:
      return letter_spacing_.Percent() / 100 * computed_size_;
    case Length::kCalculated:
      return letter_spacing_.NonNanCalculatedValue(LayoutUnit(computed_size_),
                                                   {});
    default:
      NOTREACHED();
  }
}

float FontDescription::WordSpacing() const {
  switch (word_spacing_.GetType()) {
    case Length::kFixed:
      return word_spacing_.Pixels();
    case Length::kPercent:
      return word_spacing_.Percent() / 100 * computed_size_;
    case Length::kCalculated:
      return word_spacing_.NonNanCalculatedValue(LayoutUnit(computed_size_),
                                                 {});
    default:
      NOTREACHED();
  }
}

FontDescription::VariantLigatures FontDescription::GetVariantLigatures() const {
  VariantLigatures ligatures;

  ligatures.common = CommonLigaturesState();
  ligatures.discretionary = DiscretionaryLigaturesState();
  ligatures.historical = HistoricalLigaturesState();
  ligatures.contextual = ContextualLigaturesState();

  return ligatures;
}

void FontDescription::SetVariantCaps(FontVariantCaps variant_caps) {
  fields_.variant_caps_ = variant_caps;

  UpdateTypesettingFeatures();
}

void FontDescription::SetVariantEastAsian(
    const FontVariantEastAsian variant_east_asian) {
  fields_.variant_east_asian_ = variant_east_asian.fields_as_unsigned_;
}

void FontDescription::SetVariantLigatures(const VariantLigatures& ligatures) {
  fields_.common_ligatures_state_ = ligatures.common;
  fields_.discretionary_ligatures_state_ = ligatures.discretionary;
  fields_.historical_ligatures_state_ = ligatures.historical;
  fields_.contextual_ligatures_state_ = ligatures.contextual;

  UpdateTypesettingFeatures();
}

void FontDescription::SetVariantNumeric(
    const FontVariantNumeric& variant_numeric) {
  fields_.variant_numeric_ = variant_numeric.fields_as_unsigned_;

  UpdateTypesettingFeatures();
}

float FontDescription::EffectiveFontSize() const {
  // Ensure that the effective precision matches the font-cache precision.
  // This guarantees that the same precision is used regardless of cache status.
  // Note: HasSizeAdjust() is for the font-size-adjust property, not for the
  // size-adjust descriptor.
  float computed_or_adjusted_size =
      HasSizeAdjust() || fields_.has_size_adjust_descriptor_ ? AdjustedSize()
                                                             : ComputedSize();
  return floorf(computed_or_adjusted_size *
                FontCacheKey::PrecisionMultiplier()) /
         FontCacheKey::PrecisionMultiplier();
}

float FontDescription::AdjustedSpecifiedSize() const {
  if (HasSizeAdjust() || fields_.has_size_adjust_descriptor_) {
    return SpecifiedSize() * (AdjustedSize() / ComputedSize());
  } else {
    return SpecifiedSize();
  }
}

FontDescription FontDescription::SizeAdjustedFontDescription(
    float size_adjust) const {
  // See note in: https://www.w3.org/TR/css-fonts-5/#font-size-adjust-prop
  // When the font-size-adjust property is applied while a size-adjust
  // descriptor is set, the latter must not have an effect
  if (HasSizeAdjust())
    return *this;

  // size-adjust should be applied at most once.
  DCHECK(!fields_.has_size_adjust_descriptor_);
  FontDescription result(*this);
  result.SetAdjustedSize(ComputedSize() * size_adjust);
  result.fields_.has_size_adjust_descriptor_ = true;
  return result;
}

FontCacheKey FontDescription::CacheKey(
    const FontFaceCreationParams& creation_params,
    bool is_unique_match) const {
  unsigned options =
      static_cast<unsigned>(fields_.variant_emoji_) << 10 |         // bit 11-12
      static_cast<unsigned>(fields_.font_synthesis_style_) << 9 |   // bit 10
      static_cast<unsigned>(fields_.font_synthesis_weight_) << 8 |  // bit 9
      static_cast<unsigned>(fields_.font_optical_sizing_) << 7 |    // bit 8
      static_cast<unsigned>(fields_.synthetic_italic_) << 6 |       // bit 7
      static_cast<unsigned>(fields_.synthetic_bold_) << 5 |         // bit 6
      static_cast<unsigned>(fields_.text_rendering_) << 3 |         // bits 4-5
      static_cast<unsigned>(fields_.orientation_) << 1 |            // bit 2-3
      static_cast<unsigned>(fields_.subpixel_text_position_);       // bit 1

#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  float device_scale_factor_for_key = FontCache::DeviceScaleFactor();
#else
  float device_scale_factor_for_key = 1.0f;
#endif
  FontCacheKey cache_key(creation_params, EffectiveFontSize(),
                         options | font_selection_request_.GetHash() << 13,
                         device_scale_factor_for_key, size_adjust_,
                         variation_settings_, font_palette_,
                         font_variant_alternates_, is_unique_match);
#if BUILDFLAG(IS_ANDROID)
  if (const LayoutLocale* locale = Locale()) {
    if (FontCache::GetLocaleSpecificFamilyName(creation_params.Family()))
      cache_key.SetLocale(AtomicString(locale->LocaleForSkFontMgr()));
  }
#endif  // BUILDFLAG(IS_ANDROID)
  return cache_key;
}

void FontDescription::UpdateTypesettingFeatures() {
  fields_.typesetting_features_ = 0;

  switch (TextRendering()) {
    case kAutoTextRendering:
      break;
    case kOptimizeSpeed:
      fields_.typesetting_features_ &= ~(blink::kKerning | kLigatures);
      break;
    case kGeometricPrecision:
    case kOptimizeLegibility:
      fields_.typesetting_features_ |= blink::kKerning | kLigatures;
      break;
  }

  switch (GetKerning()) {
    case FontDescription::kNoneKerning:
      fields_.typesetting_features_ &= ~blink::kKerning;
      break;
    case FontDescription::kNormalKerning:
      fields_.typesetting_features_ |= blink::kKerning;
      break;
    case FontDescription::kAutoKerning:
      break;
  }

  // As per CSS (https://drafts.csswg.org/css-text/#letter-spacing-property),
  // When the effective letter-spacing between two characters is not zero (due
  // to either justification or non-zero computed letter-spacing), user agents
  // should not apply optional ligatures.
  if (letter_spacing_.IsZero()) {
    switch (CommonLigaturesState()) {
      case FontDescription::kDisabledLigaturesState:
        fields_.typesetting_features_ &= ~blink::kLigatures;
        break;
      case FontDescription::kEnabledLigaturesState:
        fields_.typesetting_features_ |= blink::kLigatures;
        break;
      case FontDescription::kNormalLigaturesState:
        break;
    }

    if (DiscretionaryLigaturesState() ==
            FontDescription::kEnabledLigaturesState ||
        HistoricalLigaturesState() == FontDescription::kEnabledLigaturesState ||
        ContextualLigaturesState() == FontDescription::kEnabledLigaturesState) {
      fields_.typesetting_features_ |= blink::kLigatures;
    }
  }

  if (VariantCaps() != kCapsNormal)
    fields_.typesetting_features_ |= blink::kCaps;
}

unsigned FontDescription::StyleHashWithoutFamilyList() const {
  unsigned hash = 0;
  const FontFeatureSettings* settings = FeatureSettings();
  if (settings) {
    unsigned num_features = settings->size();
    for (unsigned i = 0; i < num_features; ++i) {
      AddIntToHash(hash, settings->at(i).Tag());
      AddIntToHash(hash, settings->at(i).Value());
    }
  }

  if (VariationSettings()) {
    AddIntToHash(hash, VariationSettings()->GetHash());
  }

  if (HasLanguageOverride()) {
    AddIntToHash(hash, language_override_.Hash());
  }

  if (font_palette_) {
    AddIntToHash(hash, font_palette_->GetHash());
  }

  if (locale_) {
    const AtomicString& locale = locale_->LocaleString();
    AddIntToHash(hash, locale.Hash());
  }

  AddFloatToHash(hash, specified_size_);
  AddFloatToHash(hash, computed_size_);
  AddFloatToHash(hash, adjusted_size_);
  AddIntToHash(hash, letter_spacing_.GetHash());
  AddIntToHash(hash, word_spacing_.GetHash());
  AddIntToHash(hash, fields_as_unsigned_.parts[0]);
  AddIntToHash(hash, fields_as_unsigned_.parts[1]);
  AddIntToHash(hash, font_selection_request_.GetHash());
  AddIntToHash(hash, static_cast<unsigned>(style_syntax_));
  AddIntToHash(hash, size_adjust_.GetHash());

  return hash;
}

unsigned FontDescription::GetHash() const {
  unsigned hash = StyleHashWithoutFamilyList();
  for (const FontFamily* family = &family_list_; family;
       family = family->Next()) {
    if (family->FamilyName().empty())
      continue;
    AddIntToHash(hash, family->FamilyIsGeneric());
    AddIntToHash(hash, blink::GetHash(family->FamilyName()));
  }
  return hash;
}

void FontDescription::SetOrientation(FontOrientation orientation) {
  fields_.orientation_ = static_cast<unsigned>(orientation);
  UpdateSyntheticOblique();
}

void FontDescription::SetStyle(FontSelectionValue value) {
  font_selection_request_.slope = value;
  original_slope = value;
  // Since this value did not come from authored CSS, serialization falls
  // back to the slope alone.
  style_syntax_ = StyleSyntax::kImplicitAngle;
  UpdateSyntheticOblique();
}

void FontDescription::UpdateSyntheticOblique() {
  // Doing synthetic oblique for vertical writing mode with upright text
  // orientation when negative angle parameter of "oblique" keyword, e.g.
  // "font-style: oblique -15deg" for simulating "tts:fontShear"[1][2], we
  // need to have normal font style instead of italic/oblique.
  // [1]
  // https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-attribute-fontShear
  // [2] See http://crbug.com/1112923
  fields_.synthetic_oblique_ =
      IsVerticalAnyUpright() && original_slope < FontSelectionValue(0);
  font_selection_request_.slope =
      fields_.synthetic_oblique_ ? kNormalSlopeValue : original_slope;
}

SkFontStyle FontDescription::SkiaFontStyle() const {
  // FIXME(drott): This is a lossy conversion, compare
  // https://bugs.chromium.org/p/skia/issues/detail?id=6844
  int skia_width = SkFontStyle::kNormal_Width;
  if (Stretch() <= kUltraCondensedWidthValue) {
    skia_width = SkFontStyle::kUltraCondensed_Width;
  }
  if (Stretch() <= kExtraCondensedWidthValue) {
    skia_width = SkFontStyle::kExtraCondensed_Width;
  }
  if (Stretch() <= kCondensedWidthValue) {
    skia_width = SkFontStyle::kCondensed_Width;
  }
  if (Stretch() <= kSemiCondensedWidthValue) {
    skia_width = SkFontStyle::kSemiCondensed_Width;
  }
  if (Stretch() >= kSemiExpandedWidthValue) {
    skia_width = SkFontStyle::kSemiExpanded_Width;
  }
  if (Stretch() >= kExpandedWidthValue) {
    skia_width = SkFontStyle::kExpanded_Width;
  }
  if (Stretch() >= kExtraExpandedWidthValue) {
    skia_width = SkFontStyle::kExtraExpanded_Width;
  }
  if (Stretch() >= kUltraExpandedWidthValue) {
    skia_width = SkFontStyle::kUltraExpanded_Width;
  }

  SkFontStyle::Slant slant = SkFontStyle::kUpright_Slant;
  FontSelectionValue style = Style();
  if (style > kNormalSlopeValue && style <= kItalicThreshold) {
    slant = SkFontStyle::kItalic_Slant;
  }
  if (style > kItalicThreshold) {
    slant = SkFontStyle::kOblique_Slant;
  }

  int skia_weight = SkFontStyle::kNormal_Weight;
  if (Weight() >= kMinWeightValue && Weight() <= kMaxWeightValue) {
    skia_weight = static_cast<int>(Weight());
  }

  return SkFontStyle(skia_weight, skia_width, slant);
}

void FontDescription::UpdateFromSkiaFontStyle(const SkFontStyle& font_style) {
  SetWeight(FontSelectionValue(font_style.weight()));

  switch (font_style.width()) {
    case (SkFontStyle::kUltraCondensed_Width):
      SetStretch(kUltraCondensedWidthValue);
      break;
    case (SkFontStyle::kExtraCondensed_Width):
      SetStretch(kExtraCondensedWidthValue);
      break;
    case (SkFontStyle::kCondensed_Width):
      SetStretch(kCondensedWidthValue);
      break;
    case (SkFontStyle::kSemiCondensed_Width):
      SetStretch(kSemiCondensedWidthValue);
      break;
    case (SkFontStyle::kSemiExpanded_Width):
      SetStretch(kSemiExpandedWidthValue);
      break;
    case (SkFontStyle::kExpanded_Width):
      SetStretch(kExpandedWidthValue);
      break;
    case (SkFontStyle::kExtraExpanded_Width):
      SetStretch(kExtraExpandedWidthValue);
      break;
    case (SkFontStyle::kUltraExpanded_Width):
      SetStretch(kUltraExpandedWidthValue);
      break;
  }

  if (font_style.slant() == SkFontStyle::kOblique_Slant)
    SetStyle(kItalicSlopeValue);
  else
    SetStyle(kNormalSlopeValue);
}

ResolvedFontFeatures FontDescription::ResolveFontFeatures() const {
  if (const auto* alternates = GetFontVariantAlternates()) {
    // Per CSS Fonts 4 section 7.2, the font-variant-alternates CSS property
    // takes precedence over the @font-face font-feature-settings descriptor.
    // https://drafts.csswg.org/css-fonts-4/#feature-variation-precedence
    ResolvedFontFeatures merged_features =
        alternates->GetResolvedFontFeatures();
    const wtf_size_t resolved_size = merged_features.size();
    merged_features.reserve(resolved_size + resolved_font_features_.size());
    for (const auto& descriptor_feature : resolved_font_features_) {
      if (!std::ranges::contains(
              base::span{merged_features}.first(resolved_size),
              descriptor_feature.tag, &FontFeatureValue::tag)) {
        merged_features.emplace_back(descriptor_feature);
      }
    }
    return merged_features;
  }
  return resolved_font_features_;
}

void FontDescription::MergeFontFeatureSettingsWithDescriptor(
    const FontFeatureSettings* feature_settings_descriptor) {
  ResolvedFontFeatures resolved_font_features =
      ResolveFontFeatureSettingsDescriptor(FeatureSettings(),
                                           feature_settings_descriptor);
  SetResolvedFontFeatures(std::move(resolved_font_features));
}

void FontDescription::MergeFontVariationSettingsWithDescriptor(
    const FontVariationSettings* variation_settings_descriptor) {
  scoped_refptr<FontVariationSettings> font_variation_settings =
      FontVariationSettings::Create();

  if ((!variation_settings_ || variation_settings_->size() == 0) &&
      (!variation_settings_descriptor ||
       variation_settings_descriptor->size() == 0)) {
    SetVariationSettings(font_variation_settings);
    return;
  }

  std::set<uint32_t> existing_tags;
  // Store the existing axis settings
  if (variation_settings_) {
    for (const FontVariationAxis& axis : *variation_settings_) {
      existing_tags.insert(axis.Tag());
      font_variation_settings->Append(axis);
    }
  }
  for (const FontVariationAxis& axis : *variation_settings_descriptor) {
    if (existing_tags.find(axis.Tag()) == existing_tags.end()) {
      font_variation_settings->Append(
          FontVariationAxis(axis.Tag(), axis.Value()));
    }
  }
  std::sort(font_variation_settings->begin(), font_variation_settings->end());
  SetVariationSettings(font_variation_settings);
}

String FontDescription::ToString(GenericFamilyType familyType) {
  switch (familyType) {
    case GenericFamilyType::kNoFamily:
      return "None";
    case GenericFamilyType::kStandardFamily:
      return "Standard";
    case GenericFamilyType::kWebkitBodyFamily:
      return "WebkitBody";
    case GenericFamilyType::kSerifFamily:
      return "Serif";
    case GenericFamilyType::kSansSerifFamily:
      return "SansSerif";
    case GenericFamilyType::kMonospaceFamily:
      return "Monospace";
    case GenericFamilyType::kCursiveFamily:
      return "Cursive";
    case GenericFamilyType::kFantasyFamily:
      return "Fantasy";
  }
  return "Unknown";
}

String FontDescription::ToString(LigaturesState state) {
  switch (state) {
    case LigaturesState::kNormalLigaturesState:
      return "Normal";
    case LigaturesState::kDisabledLigaturesState:
      return "Disabled";
    case LigaturesState::kEnabledLigaturesState:
      return "Enabled";
  }
  return "Unknown";
}

String FontDescription::ToString(Kerning kerning) {
  switch (kerning) {
    case Kerning::kAutoKerning:
      return "Auto";
    case Kerning::kNormalKerning:
      return "Normal";
    case Kerning::kNoneKerning:
      return "None";
  }
  return "Unknown";
}

String FontDescription::ToString(FontSelectionValue selection_value) {
  if (selection_value == kUltraCondensedWidthValue) {
    return "Ultra-Condensed";
  } else if (selection_value == kExtraCondensedWidthValue) {
    return "Extra-Condensed";
  } else if (selection_value == kCondensedWidthValue) {
    return "Condensed";
  } else if (selection_value == kSemiCondensedWidthValue) {
    return "Semi-Condensed";
  } else if (selection_value == kNormalWidthValue) {
    return "Normal";
  } else if (selection_value == kSemiExpandedWidthValue) {
    return "Semi-Expanded";
  } else if (selection_value == kExpandedWidthValue) {
    return "Expanded";
  } else if (selection_value == kExtraExpandedWidthValue) {
    return "Extra-Expanded";
  } else if (selection_value == kUltraExpandedWidthValue) {
    return "Ultra-Expanded";
  }

  return "Unknown";
}

String FontDescription::ToString(FontVariantCaps variant) {
  switch (variant) {
    case FontVariantCaps::kCapsNormal:
      return "Normal";
    case FontVariantCaps::kSmallCaps:
      return "SmallCaps";
    case FontVariantCaps::kAllSmallCaps:
      return "AllSmallCaps";
    case FontVariantCaps::kPetiteCaps:
      return "PetiteCaps";
    case FontVariantCaps::kAllPetiteCaps:
      return "AllPetiteCaps";
    case FontVariantCaps::kUnicase:
      return "Unicase";
    case FontVariantCaps::kTitlingCaps:
      return "TitlingCaps";
  }
  return "Unknown";
}

String FontDescription::ToStringForIdl(FontVariantCaps variant) {
  switch (variant) {
    case FontVariantCaps::kCapsNormal:
      return "normal";
    case FontVariantCaps::kSmallCaps:
      return "small-caps";
    case FontVariantCaps::kAllSmallCaps:
      return "all-small-caps";
    case FontVariantCaps::kPetiteCaps:
      return "petite-caps";
    case FontVariantCaps::kAllPetiteCaps:
      return "all-petite-caps";
    case FontVariantCaps::kUnicase:
      return "unicase";
    case FontVariantCaps::kTitlingCaps:
      return "titling-caps";
  }
  return "Unknown";
}

String FontDescription::ToString(FontSynthesisWeight font_synthesis_weight) {
  switch (font_synthesis_weight) {
    case FontSynthesisWeight::kAutoFontSynthesisWeight:
      return "Auto";
    case FontSynthesisWeight::kNoneFontSynthesisWeight:
      return "None";
  }
  return "Unknown";
}

String FontDescription::ToString(FontSynthesisStyle font_synthesis_style) {
  switch (font_synthesis_style) {
    case FontSynthesisStyle::kAutoFontSynthesisStyle:
      return "Auto";
    case FontSynthesisStyle::kNoneFontSynthesisStyle:
      return "None";
  }
  return "Unknown";
}

String FontDescription::ToString(
    FontSynthesisSmallCaps font_synthesis_small_caps) {
  switch (font_synthesis_small_caps) {
    case FontSynthesisSmallCaps::kAutoFontSynthesisSmallCaps:
      return "Auto";
    case FontSynthesisSmallCaps::kNoneFontSynthesisSmallCaps:
      return "None";
  }
  return "Unknown";
}

String FontDescription::VariantLigatures::ToString() const {
  return StrCat(
      {"common=",
       FontDescription::ToString(static_cast<LigaturesState>(common)),
       ", discretionary=",
       FontDescription::ToString(static_cast<LigaturesState>(discretionary)),
       ", historical=",
       FontDescription::ToString(static_cast<LigaturesState>(historical)),
       ", contextual=",
       FontDescription::ToString(static_cast<LigaturesState>(contextual))});
}

String FontDescription::Size::ToString() const {
  return Format("keyword_size={}, specified_size={:f}, is_absolute_size={}",
                keyword, value, is_absolute);
}

String FontDescription::FamilyDescription::ToString() const {
  return StrCat({"generic_family=", FontDescription::ToString(generic_family),
                 ", family=[", family.ToString(), "]"});
}

String FontDescription::ToString(FontVariantPosition variant_position) {
  switch (variant_position) {
    case FontVariantPosition::kNormalVariantPosition:
      return "Normal";
    case FontVariantPosition::kSubVariantPosition:
      return "Sub";
    case FontVariantPosition::kSuperVariantPosition:
      return "Super";
  }
  return "Unknown";
}

String FontDescription::ToString() const {
  return Format(
      "family_list=[{}], feature_settings=[{}], variation_settings=[{}], "
      "locale={}, specified_size={:f}, computed_size={:f}, adjusted_size={:f}, "
      "size_adjust={}, letter_spacing={:f}, word_spacing={:f}, "
      "font_selection_request=[{}], typesetting_features=[{}], orientation={}, "
      "width_variant={}, variant_caps={}, is_absolute_size={}, "
      "generic_family={}, kerning={}, variant_ligatures=[{}], keyword_size={}, "
      "font_smoothing={}, text_rendering={}, synthetic_bold={}, "
      "synthetic_italic={}, subpixel_positioning={}, "
      "subpixel_ascent_descent={}, variant_numeric=[{}], "
      "variant_east_asian=[{}], font_optical_sizing={}, "
      "font_synthesis_weight={}, font_synthesis_style={}, "
      "font_synthesis_small_caps={}, font_variant_position={}, "
      "font_variant_emoji={}",
      family_list_.ToString(),
      (feature_settings_ ? feature_settings_->ToString() : ""),
      (variation_settings_ ? variation_settings_->ToString() : ""),
      // TODO(wkorman): Locale has additional internal fields such as
      // hyphenation and script. Consider adding a more detailed
      // string method.
      (locale_ ? locale_->LocaleString() : g_empty_atom), specified_size_,
      computed_size_, adjusted_size_, size_adjust_.ToString(), LetterSpacing(),
      WordSpacing(), font_selection_request_.ToString(),
      blink::ToString(
          static_cast<TypesettingFeatures>(fields_.typesetting_features_)),
      blink::ToString(Orientation()), blink::ToString(WidthVariant()),
      FontDescription::ToString(VariantCaps()), IsAbsoluteSize(),
      FontDescription::ToString(GenericFamily()),
      FontDescription::ToString(Kerning()), GetVariantLigatures().ToString(),
      KeywordSize(), blink::ToString(FontSmoothing()),
      blink::ToString(TextRendering()), IsSyntheticBold(), IsSyntheticItalic(),
      UseSubpixelPositioning(), SubpixelAscentDescent(),
      VariantNumeric().ToString(), VariantEastAsian().ToString(),
      blink::ToString(FontOpticalSizing()),
      FontDescription::ToString(GetFontSynthesisWeight()),
      FontDescription::ToString(GetFontSynthesisStyle()),
      FontDescription::ToString(GetFontSynthesisSmallCaps()),
      FontDescription::ToString(VariantPosition()),
      blink::ToString(VariantEmoji()));
}

}  // namespace blink
