text: /*
   * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
   *           (C) 1999 Antti Koivisto (koivisto@kde.org)
   * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights
   * reserved.
   * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
   * Copyright (C) 2010 Daniel Bates (dbates@intudata.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Library General Public
   * License as published by the Free Software Foundation; either
   * version 2 of the License, or (at your option) any later version.
   *
   * This library is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   * Library General Public License for more details.
   *
   * You should have received a copy of the GNU Library General Public License
   * along with this library; see the file COPYING.LIB.  If not, write to
   * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   * Boston, MA 02110-1301, USA.
   *
   */
  
  #include "third_party/blink/renderer/core/layout/list_marker_text.h"
  
  #include "third_party/blink/renderer/core/layout/text_run_constructor.h"
  #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
  #include "third_party/blink/renderer/platform/wtf/text/character_names.h"
  #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
  
  namespace blink {
  
  namespace list_marker_text {
  
  enum SequenceType { kNumericSequence, kAlphabeticSequence };
  
  static String ToRoman(int number, bool upper) {
    // FIXME: CSS3 describes how to make this work for much larger numbers,
    // using overbars and special characters. It also specifies the characters
    // in the range U+2160 to U+217F instead of standard ASCII ones.
    DCHECK_GE(number, 1);
    DCHECK_LE(number, 3999);
  
    // Big enough to store largest roman number less than 3999 which
    // is 3888 (MMMDCCCLXXXVIII)
    const int kLettersSize = 15;
    LChar letters[kLettersSize];
  
    int length = 0;
    const LChar kLdigits[] = {'i', 'v', 'x', 'l', 'c', 'd', 'm'};
    const LChar kUdigits[] = {'I', 'V', 'X', 'L', 'C', 'D', 'M'};
    const LChar* digits = upper ? kUdigits : kLdigits;
    int d = 0;
    do {
      int num = number % 10;
      if (num % 5 < 4)
        for (int i = num % 5; i > 0; i--)
          letters[kLettersSize - ++length] = digits[d];
      if (num >= 4 && num <= 8)
        letters[kLettersSize - ++length] = digits[d + 1];
      if (num == 9)
        letters[kLettersSize - ++length] = digits[d + 2];
      if (num % 5 == 4)
        letters[kLettersSize - ++length] = digits[d];
      number /= 10;
      d += 2;
    } while (number);
  
    DCHECK_LE(length, kLettersSize);
    return String(&letters[kLettersSize - length], length);
  }
  
  // The typedef is needed because taking sizeof(number) in the const expression
  // below doesn't work with some compilers. This is likely the case because of
  // the template.
  typedef int numberType;
  
  template <typename CharacterType>
  static inline String ToAlphabeticOrNumeric(numberType number,
                                             const CharacterType* sequence,
                                             unsigned sequence_size,
                                             SequenceType type) {
    DCHECK_GE(sequence_size, 2u);
  
    // Binary is the worst case; requires one character per bit plus a minus sign.
    const int kLettersSize = sizeof(numberType) * 8 + 1;
  
    CharacterType letters[kLettersSize];
  
    bool is_negative_number = false;
    unsigned number_shadow = number;
    if (type == kAlphabeticSequence) {
      DCHECK_GT(number, 0);
      --number_shadow;
    } else if (number < 0) {
      number_shadow = -number;
      is_negative_number = true;
    }
    letters[kLettersSize - 1] = sequence[number_shadow % sequence_size];
    int length = 1;
  
    if (type == kAlphabeticSequence) {
      while ((number_shadow /= sequence_size) > 0) {
        --number_shadow;
        letters[kLettersSize - ++length] =
            sequence[number_shadow % sequence_size];
      }
    } else {
      while ((number_shadow /= sequence_size) > 0)
        letters[kLettersSize - ++length] =
            sequence[number_shadow % sequence_size];
    }
    if (is_negative_number)
      letters[kLettersSize - ++length] = kHyphenMinusCharacter;
  
    DCHECK_LE(length, kLettersSize);
    return String(&letters[kLettersSize - length], length);
  }
  
  template <typename CharacterType>
  static String ToAlphabetic(int number,
                             const CharacterType* alphabet,
                             unsigned alphabet_size) {
    return ToAlphabeticOrNumeric(number, alphabet, alphabet_size,
                                 kAlphabeticSequence);
  }
  
  template <typename CharacterType>
  static String ToNumeric(int number,
                          const CharacterType* numerals,
                          unsigned numerals_size) {
    return ToAlphabeticOrNumeric(number, numerals, numerals_size,
                                 kNumericSequence);
  }
  
  template <typename CharacterType, size_t size>
  static inline String ToAlphabetic(int number,
                                    const CharacterType (&alphabet)[size]) {
    return ToAlphabetic(number, alphabet, size);
  }
  
  template <typename CharacterType, size_t size>
  static inline String ToNumeric(int number,
                                 const CharacterType (&alphabet)[size]) {
    return ToNumeric(number, alphabet, size);
  }
  
  static void ToHebrewUnder1000(int number, Vector<UChar>& letters) {
    // FIXME: CSS3 mentions various refinements not implemented here.
    // FIXME: Should take a look at Mozilla's HebrewToText function (in
    // nsBulletFrame).
    DCHECK_GE(number, 0);
    DCHECK_LT(number, 1000);
    int four_hundreds = number / 400;
    for (int i = 0; i < four_hundreds; i++)
      letters.push_front(1511 + 3);
    number %= 400;
    if (number / 100)
      letters.push_front(1511 + (number / 100) - 1);
    number %= 100;
    if (number == 15 || number == 16) {
      letters.push_front(1487 + 9);
      letters.push_front(1487 + number - 9);
    } else {
      if (int tens = number / 10) {
        static const UChar kHebrewTens[9] = {1497, 1499, 1500, 1502, 1504,
                                             1505, 1506, 1508, 1510};
        letters.push_front(kHebrewTens[tens - 1]);
      }
      if (int ones = number % 10)
        letters.push_front(1487 + ones);
    }
  }
  
  static String ToHebrew(int number) {
    // FIXME: CSS3 mentions ways to make this work for much larger numbers.
    DCHECK_GE(number, 0);
    DCHECK_LE(number, 999999);
  
    Vector<UChar> letters;
  
    if (number == 0) {
      static const UChar kHebrewZero[3] = {0x05E1, 0x05E4, 0x05D0};
      letters.Append(kHebrewZero, 3);
    } else {
      if (number > 999) {
        ToHebrewUnder1000(number / 1000, letters);
        letters.push_front(kHebrewPunctuationGereshCharacter);
        number = number % 1000;
      }
      ToHebrewUnder1000(number, letters);
    }
  
    // Since Hebrew is RTL, legacy implementation generates letters in the
    // reversed ordering, which is actually wrong because characters in a String
    // should always be in the logical ordering. We re-reverse it so that the
    // output ordering is correct.
    std::reverse(letters.begin(), letters.end());
  
    return String(letters);
  }
  
  static int ToArmenianUnder10000(int number,
                                  bool upper,
                                  bool add_circumflex,
                                  UChar letters[9]) {
    DCHECK_GE(number, 0);
    DCHECK_LT(number, 10000);
    int length = 0;
  
    int lower_offset = upper ? 0 : 0x0030;
  
    if (int thousands = number / 1000) {
      if (thousands == 7) {
        letters[length++] = 0x0552 + lower_offset;
        if (add_circumflex)
          letters[length++] = 0x0302;
      } else {
        letters[length++] = (0x054C - 1 + lower_offset) + thousands;
        if (add_circumflex)
          letters[length++] = 0x0302;
      }
    }
  
    if (int hundreds = (number / 100) % 10) {
      letters[length++] = (0x0543 - 1 + lower_offset) + hundreds;
      if (add_circumflex)
        letters[length++] = 0x0302;
    }
  
    if (int tens = (number / 10) % 10) {
      letters[length++] = (0x053A - 1 + lower_offset) + tens;
      if (add_circumflex)
        letters[length++] = 0x0302;
    }
  
    if (int ones = number % 10) {
      letters[length++] = (0x531 - 1 + lower_offset) + ones;
      if (add_circumflex)
        letters[length++] = 0x0302;
    }
  
    return length;
  }
  
  static String ToArmenian(int number, bool upper) {
    DCHECK_GE(number, 1);
    DCHECK_LE(number, 99999999);
  
    const int kLettersSize = 18;  // twice what toArmenianUnder10000 needs
    UChar letters[kLettersSize];
  
    int length = ToArmenianUnder10000(number / 10000, upper, true, letters);
    length +=
        ToArmenianUnder10000(number % 10000, upper, false, letters + length);
  
    DCHECK_LE(length, kLettersSize);
    return String(letters, length);
  }
  
  static String ToGeorgian(int number) {
    DCHECK_GE(number, 1);
    DCHECK_LE(number, 19999);
  
    const int kLettersSize = 5;
    UChar letters[kLettersSize];
  
    int length = 0;
  
    if (number > 9999)
      letters[length++] = 0x10F5;
  
    if (int thousands = (number / 1000) % 10) {
      static const UChar kGeorgianThousands[9] = {
          0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10F4, 0x10EF, 0x10F0};
      letters[length++] = kGeorgianThousands[thousands - 1];
    }
  
    if (int hundreds = (number / 100) % 10) {
      static const UChar kGeorgianHundreds[9] = {
          0x10E0, 0x10E1, 0x10E2, 0x10F3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, 0x10E8};
      letters[length++] = kGeorgianHundreds[hundreds - 1];
    }
  
    if (int tens = (number / 10) % 10) {
      static const UChar kGeorgianTens[9] = {
          0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10F2, 0x10DD, 0x10DE, 0x10DF};
      letters[length++] = kGeorgianTens[tens - 1];
    }
  
    if (int ones = number % 10) {
      static const UChar kGeorgianOnes[9] = {
          0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10F1, 0x10D7};
      letters[length++] = kGeorgianOnes[ones - 1];
    }
  
    DCHECK_LE(length, kLettersSize);
    return String(letters, length);
  }
  
  enum CJKLang { kChinese = 1, kKorean, kJapanese };
  
  enum CJKStyle { kFormal, kInformal };
  
  // The table uses the order from the CSS3 specification:
  // first 3 group markers, then 3 digit markers, then ten digits, then negative
  // symbols.
  static String ToCJKIdeographic(int number,
                                 const UChar table[26],
                                 CJKStyle cjk_style) {
    enum AbstractCJKChar {
      kNoChar = 0,
      kLang = 0,
      // FourthGroupMarker for simplified chinese has two codepoints, to simplify
      // the main algorithm below use two codepoints for all group markers.
      kSecondGroupMarker = 1,
      kThirdGroupMarker = 3,
      kFourthGroupMarker = 5,
      kSecondDigitMarker = 7,
      kThirdDigitMarker,
      kFourthDigitMarker,
      kDigit0,
      kDigit1,
      kDigit2,
      kDigit3,
      kDigit4,
      kDigit5,
      kDigit6,
      kDigit7,
      kDigit8,
      kDigit9,
      kNeg1,
      kNeg2,
      kNeg3,
      kNeg4,
      kNeg5
    };
  
    if (number == 0)
      return String(&table[kDigit0], 1);
  
    const bool negative = number < 0;
    if (negative) {
      // Negating the most negative integer (INT_MIN) doesn't work, since it has
      // no positive counterpart. Deal with that here, manually.
      if (UNLIKELY(number == INT_MIN))
        number = INT_MAX;
      else
        number = -number;
    }
  
    const int kGroupLength =
        9;  // 4 digits, 3 digit markers, group marker of size 2.
    const int kBufferLength = 4 * kGroupLength;
    AbstractCJKChar buffer[kBufferLength] = {kNoChar};
  
    for (int i = 0; i < 4; ++i) {
      int group_value = number % 10000;
      number /= 10000;
  
      // Process least-significant group first, but put it in the buffer last.
      AbstractCJKChar* group = &buffer[(3 - i) * kGroupLength];
  
      if (group_value && i) {
        group[8] = static_cast<AbstractCJKChar>(kSecondGroupMarker + i);
        group[7] = static_cast<AbstractCJKChar>(kSecondGroupMarker - 1 + i);
      }
  
      // Put in the four digits and digit markers for any non-zero digits.
      int digit_value = (group_value % 10);
      bool trailing_zero = table[kLang] == kChinese && !digit_value;
      if (digit_value) {
        bool drop_one = table[kLang] == kKorean && cjk_style == kInformal &&
                        digit_value == 1 && i > 0;
        if (!drop_one)
          group[6] = static_cast<AbstractCJKChar>(kDigit0 + (group_value % 10));
      }
      if (number != 0 || group_value > 9) {
        digit_value = ((group_value / 10) % 10);
        bool drop_one =
            table[kLang] == kKorean && cjk_style == kInformal && digit_value == 1;
        if ((digit_value && !drop_one) || (!digit_value && !trailing_zero))
          group[4] = static_cast<AbstractCJKChar>(kDigit0 + digit_value);
        trailing_zero &= !digit_value;
        if (digit_value)
          group[5] = kSecondDigitMarker;
      }
      if (number != 0 || group_value > 99) {
        digit_value = ((group_value / 100) % 10);
        bool drop_one =
            table[kLang] == kKorean && cjk_style == kInformal && digit_value == 1;
        if ((digit_value && !drop_one) || (!digit_value && !trailing_zero))
          group[2] = static_cast<AbstractCJKChar>(kDigit0 + digit_value);
        trailing_zero &= !digit_value;
        if (digit_value)
          group[3] = kThirdDigitMarker;
      }
      if (number != 0 || group_value > 999) {
        digit_value = group_value / 1000;
        bool drop_one =
            table[kLang] == kKorean && cjk_style == kInformal && digit_value == 1;
        if ((digit_value && !drop_one) || (!digit_value && !trailing_zero))
          group[0] = static_cast<AbstractCJKChar>(kDigit0 + digit_value);
        if (digit_value)
          group[1] = kFourthDigitMarker;
      }
  
      if (trailing_zero && i > 0) {
        group[6] = group[7];
        group[7] = group[8];
        group[8] = kDigit0;
      }
  
      // Remove the tens digit, but leave the marker, for any group that has
      // a value of less than 20.
      if (table[kLang] == kChinese && cjk_style == kInformal &&
          group_value < 20) {
        DCHECK(group[4] == kNoChar || group[4] == kDigit0 || group[4] == kDigit1);
        group[4] = kNoChar;
      }
  
      if (number == 0)
        break;
    }
  
    // Convert into characters, omitting consecutive runs of Digit0 and
    // any trailing Digit0.
    int length = 0;
    const int kMaxLengthForNegativeSymbols = 5;
    UChar characters[kBufferLength + kMaxLengthForNegativeSymbols];
    AbstractCJKChar last = kNoChar;
    if (negative) {
      while (UChar a = table[kNeg1 + length])
        characters[length++] = a;
    }
    for (int i = 0; i < kBufferLength; ++i) {
      AbstractCJKChar a = buffer[i];
      if (a != kNoChar) {
        if (a != kDigit0 || (table[kLang] == kChinese && last != kDigit0)) {
          UChar new_char = table[a];
          if (new_char != kNoChar) {
            characters[length++] = table[a];
            if (table[kLang] == kKorean &&
                (a == kSecondGroupMarker || a == kThirdGroupMarker ||
                 a == kFourthGroupMarker))
              characters[length++] = ' ';
          }
        }
        last = a;
      }
    }
    if ((table[kLang] == kChinese && last == kDigit0) ||
        characters[length - 1] == ' ')
      --length;
  
    return String(characters, length);
  }
  
  static EListStyleType EffectiveListMarkerType(EListStyleType type, int count) {
    // Note, the following switch statement has been explicitly grouped
    // by list-style-type ordinal range.
    switch (type) {
      case EListStyleType::kArabicIndic:
      case EListStyleType::kBengali:
      case EListStyleType::kCambodian:
      case EListStyleType::kCircle:
      case EListStyleType::kDecimalLeadingZero:
      case EListStyleType::kDecimal:
      case EListStyleType::kDevanagari:
      case EListStyleType::kDisc:
      case EListStyleType::kDisclosureClosed:
      case EListStyleType::kDisclosureOpen:
      case EListStyleType::kGujarati:
      case EListStyleType::kGurmukhi:
      case EListStyleType::kKannada:
      case EListStyleType::kKhmer:
      case EListStyleType::kLao:
      case EListStyleType::kMalayalam:
      case EListStyleType::kMongolian:
      case EListStyleType::kMyanmar:
      case EListStyleType::kNone:
      case EListStyleType::kOriya:
      case EListStyleType::kPersian:
      case EListStyleType::kSquare:
      case EListStyleType::kTelugu:
      case EListStyleType::kThai:
      case EListStyleType::kTibetan:
      case EListStyleType::kUrdu:
      case EListStyleType::kKoreanHangulFormal:
      case EListStyleType::kKoreanHanjaFormal:
      case EListStyleType::kKoreanHanjaInformal:
      case EListStyleType::kCjkIdeographic:
      case EListStyleType::kSimpChineseFormal:
      case EListStyleType::kSimpChineseInformal:
      case EListStyleType::kTradChineseFormal:
      case EListStyleType::kTradChineseInformal:
        return type;  // Can represent all ordinals.
      case EListStyleType::kArmenian:
      case EListStyleType::kLowerArmenian:
      case EListStyleType::kUpperArmenian:
        return (count < 1 || count > 99999999) ? EListStyleType::kDecimal : type;
      case EListStyleType::kGeorgian:
        return (count < 1 || count > 19999) ? EListStyleType::kDecimal : type;
      case EListStyleType::kHebrew:
        return (count < 0 || count > 999999) ? EListStyleType::kDecimal : type;
      case EListStyleType::kLowerRoman:
      case EListStyleType::kUpperRoman:
        return (count < 1 || count > 3999) ? EListStyleType::kDecimal : type;
      case EListStyleType::kCjkEarthlyBranch:
      case EListStyleType::kCjkHeavenlyStem:
      case EListStyleType::kEthiopicHalehameAm:
      case EListStyleType::kEthiopicHalehame:
      case EListStyleType::kEthiopicHalehameTiEr:
      case EListStyleType::kEthiopicHalehameTiEt:
      case EListStyleType::kHangul:
      case EListStyleType::kHangulConsonant:
      case EListStyleType::kHiragana:
      case EListStyleType::kHiraganaIroha:
      case EListStyleType::kKatakana:
      case EListStyleType::kKatakanaIroha:
      case EListStyleType::kLowerAlpha:
      case EListStyleType::kLowerGreek:
      case EListStyleType::kLowerLatin:
      case EListStyleType::kUpperAlpha:
      case EListStyleType::kUpperLatin:
        return (count < 1) ? EListStyleType::kDecimal : type;
      case EListStyleType::kString:
        NOTREACHED();
        break;
    }
  
    NOTREACHED();
    return type;
  }
  
  UChar Suffix(EListStyleType type, int count) {
    // If the list-style-type cannot represent |count| because it's outside its
    // ordinal range then we fall back to some list style that can represent
    // |count|.
    EListStyleType effective_type = EffectiveListMarkerType(type, count);
  
    // Note, the following switch statement has been explicitly
    // grouped by list-style-type suffix.
    switch (effective_type) {
      case EListStyleType::kCircle:
      case EListStyleType::kDisc:
      case EListStyleType::kDisclosureClosed:
      case EListStyleType::kDisclosureOpen:
      case EListStyleType::kNone:
      case EListStyleType::kSquare:
        return ' ';
      case EListStyleType::kEthiopicHalehame:
      case EListStyleType::kEthiopicHalehameAm:
      case EListStyleType::kEthiopicHalehameTiEr:
      case EListStyleType::kEthiopicHalehameTiEt:
        return kEthiopicPrefaceColonCharacter;
      case EListStyleType::kArmenian:
      case EListStyleType::kArabicIndic:
      case EListStyleType::kBengali:
      case EListStyleType::kCambodian:
      case EListStyleType::kCjkIdeographic:
      case EListStyleType::kCjkEarthlyBranch:
      case EListStyleType::kCjkHeavenlyStem:
      case EListStyleType::kDecimalLeadingZero:
      case EListStyleType::kDecimal:
      case EListStyleType::kDevanagari:
      case EListStyleType::kGeorgian:
      case EListStyleType::kGujarati:
      case EListStyleType::kGurmukhi:
      case EListStyleType::kHangul:
      case EListStyleType::kHangulConsonant:
      case EListStyleType::kHebrew:
      case EListStyleType::kHiragana:
      case EListStyleType::kHiraganaIroha:
      case EListStyleType::kKannada:
      case EListStyleType::kKatakana:
      case EListStyleType::kKatakanaIroha:
      case EListStyleType::kKhmer:
      case EListStyleType::kLao:
      case EListStyleType::kLowerAlpha:
      case EListStyleType::kLowerArmenian:
      case EListStyleType::kLowerGreek:
      case EListStyleType::kLowerLatin:
      case EListStyleType::kLowerRoman:
      case EListStyleType::kMalayalam:
      case EListStyleType::kMongolian:
      case EListStyleType::kMyanmar:
      case EListStyleType::kOriya:
      case EListStyleType::kPersian:
      case EListStyleType::kTelugu:
      case EListStyleType::kThai:
      case EListStyleType::kTibetan:
      case EListStyleType::kUpperAlpha:
      case EListStyleType::kUpperArmenian:
      case EListStyleType::kUpperLatin:
      case EListStyleType::kUpperRoman:
      case EListStyleType::kUrdu:
        return '.';
      case EListStyleType::kSimpChineseFormal:
      case EListStyleType::kSimpChineseInformal:
      case EListStyleType::kTradChineseFormal:
      case EListStyleType::kTradChineseInformal:
      case EListStyleType::kKoreanHangulFormal:
      case EListStyleType::kKoreanHanjaFormal:
      case EListStyleType::kKoreanHanjaInformal:
        return 0x3001;
      case EListStyleType::kString:
        NOTREACHED();
        break;
    }
  
    NOTREACHED();
    return '.';
  }
  
  String GetText(EListStyleType type, int count) {
    // If the list-style-type, say hebrew, cannot represent |count| because it's
    // outside its ordinal range then we fallback to some list style that can
    // represent |count|.
    switch (EffectiveListMarkerType(type, count)) {
      case EListStyleType::kNone:
        return "";
  
      // We use the same characters for text security.
      // See LayoutText::setInternalString.
      case EListStyleType::kCircle:
        return String(&kWhiteBulletCharacter, 1);
      case EListStyleType::kDisc:
        return String(&kBulletCharacter, 1);
  
      // TODO(layout-dev): Text for disclosure-* should not depend on physical
      // shape?
      case EListStyleType::kDisclosureClosed:
        return String(&kBlackRightPointingSmallTriangle, 1);
      case EListStyleType::kDisclosureOpen:
        return String(&kBlackDownPointingSmallTriangle, 1);
  
      case EListStyleType::kSquare:
        // The CSS 2.1 test suite uses U+25EE BLACK MEDIUM SMALL SQUARE
        // instead, but I think this looks better.
        return String(&kBlackSquareCharacter, 1);
  
      case EListStyleType::kDecimal:
        return String::Number(count);
      case EListStyleType::kDecimalLeadingZero:
        if (count < -9 || count > 9)
          return String::Number(count);
        if (count < 0)
          return "-0" + String::Number(-count);  // -01 to -09
        return "0" + String::Number(count);      // 00 to 09
  
      case EListStyleType::kArabicIndic: {
        static const UChar kArabicIndicNumerals[10] = {
            0x0660, 0x0661, 0x0662, 0x0663, 0x0664,
            0x0665, 0x0666, 0x0667, 0x0668, 0x0669};
        return ToNumeric(count, kArabicIndicNumerals);
      }
      case EListStyleType::kBengali: {
        static const UChar kBengaliNumerals[10] = {0x09E6, 0x09E7, 0x09E8, 0x09E9,
                                                   0x09EA, 0x09EB, 0x09EC, 0x09ED,
                                                   0x09EE, 0x09EF};
        return ToNumeric(count, kBengaliNumerals);
      }
      case EListStyleType::kCambodian:
      case EListStyleType::kKhmer: {
        static const UChar kKhmerNumerals[10] = {0x17E0, 0x17E1, 0x17E2, 0x17E3,
                                                 0x17E4, 0x17E5, 0x17E6, 0x17E7,
                                                 0x17E8, 0x17E9};
        return ToNumeric(count, kKhmerNumerals);
      }
      case EListStyleType::kDevanagari: {
        static const UChar kDevanagariNumerals[10] = {
            0x0966, 0x0967, 0x0968, 0x0969, 0x096A,
            0x096B, 0x096C, 0x096D, 0x096E, 0x096F};
        return ToNumeric(count, kDevanagariNumerals);
      }
      case EListStyleType::kGujarati: {
        static const UChar kGujaratiNumerals[10] = {
            0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA,
            0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF};
        return ToNumeric(count, kGujaratiNumerals);
      }
      case EListStyleType::kGurmukhi: {
        static const UChar kGurmukhiNumerals[10] = {
            0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A,
            0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F};
        return ToNumeric(count, kGurmukhiNumerals);
      }
      case EListStyleType::kKannada: {
        static const UChar kKannadaNumerals[10] = {0x0CE6, 0x0CE7, 0x0CE8, 0x0CE9,
                                                   0x0CEA, 0x0CEB, 0x0CEC, 0x0CED,
                                                   0x0CEE, 0x0CEF};
        return ToNumeric(count, kKannadaNumerals);
      }
      case EListStyleType::kLao: {
        static const UChar kLaoNumerals[10] = {0x0ED0, 0x0ED1, 0x0ED2, 0x0ED3,
                                               0x0ED4, 0x0ED5, 0x0ED6, 0x0ED7,
                                               0x0ED8, 0x0ED9};
        return ToNumeric(count, kLaoNumerals);
      }
      case EListStyleType::kMalayalam: {
        static const UChar kMalayalamNumerals[10] = {
            0x0D66, 0x0D67, 0x0D68, 0x0D69, 0x0D6A,
            0x0D6B, 0x0D6C, 0x0D6D, 0x0D6E, 0x0D6F};
        return ToNumeric(count, kMalayalamNumerals);
      }
      case EListStyleType::kMongolian: {
        static const UChar kMongolianNumerals[10] = {
            0x1810, 0x1811, 0x1812, 0x1813, 0x1814,
            0x1815, 0x1816, 0x1817, 0x1818, 0x1819};
        return ToNumeric(count, kMongolianNumerals);
      }
      case EListStyleType::kMyanmar: {
        static const UChar kMyanmarNumerals[10] = {0x1040, 0x1041, 0x1042, 0x1043,
                                                   0x1044, 0x1045, 0x1046, 0x1047,
                                                   0x1048, 0x1049};
        return ToNumeric(count, kMyanmarNumerals);
      }
      case EListStyleType::kOriya: {
        static const UChar kOriyaNumerals[10] = {0x0B66, 0x0B67, 0x0B68, 0x0B69,
                                                 0x0B6A, 0x0B6B, 0x0B6C, 0x0B6D,
                                                 0x0B6E, 0x0B6F};
        return ToNumeric(count, kOriyaNumerals);
      }
      case EListStyleType::kPersian:
      case EListStyleType::kUrdu: {
        static const UChar kUrduNumerals[10] = {0x06F0, 0x06F1, 0x06F2, 0x06F3,
                                                0x06F4, 0x06F5, 0x06F6, 0x06F7,
                                                0x06F8, 0x06F9};
        return ToNumeric(count, kUrduNumerals);
      }
      case EListStyleType::kTelugu: {
        static const UChar kTeluguNumerals[10] = {0x0C66, 0x0C67, 0x0C68, 0x0C69,
                                                  0x0C6A, 0x0C6B, 0x0C6C, 0x0C6D,
                                                  0x0C6E, 0x0C6F};
        return ToNumeric(count, kTeluguNumerals);
      }
      case EListStyleType::kTibetan: {
        static const UChar kTibetanNumerals[10] = {0x0F20, 0x0F21, 0x0F22, 0x0F23,
                                                   0x0F24, 0x0F25, 0x0F26, 0x0F27,
                                                   0x0F28, 0x0F29};
        return ToNumeric(count, kTibetanNumerals);
      }
      case EListStyleType::kThai: {
        static const UChar kThaiNumerals[10] = {0x0E50, 0x0E51, 0x0E52, 0x0E53,
                                                0x0E54, 0x0E55, 0x0E56, 0x0E57,
                                                0x0E58, 0x0E59};
        return ToNumeric(count, kThaiNumerals);
      }
  
      case EListStyleType::kLowerAlpha:
      case EListStyleType::kLowerLatin: {
        static const LChar kLowerLatinAlphabet[26] = {
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
        return ToAlphabetic(count, kLowerLatinAlphabet);
      }
      case EListStyleType::kUpperAlpha:
      case EListStyleType::kUpperLatin: {
        static const LChar kUpperLatinAlphabet[26] = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
        return ToAlphabetic(count, kUpperLatinAlphabet);
      }
      case EListStyleType::kLowerGreek: {
        static const UChar kLowerGreekAlphabet[24] = {
            0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8,
            0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0,
            0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9};
        return ToAlphabetic(count, kLowerGreekAlphabet);
      }
  
      case EListStyleType::kHiragana: {
        // FIXME: This table comes from the CSS3 draft, and is probably
        // incorrect, given the comments in that draft.
        static const UChar kHiraganaAlphabet[48] = {
            0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F,
            0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F,
            0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D,
            0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, 0x307F,
            0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A,
            0x308B, 0x308C, 0x308D, 0x308F, 0x3090, 0x3091, 0x3092, 0x3093};
        return ToAlphabetic(count, kHiraganaAlphabet);
      }
      case EListStyleType::kHiraganaIroha: {
        // FIXME: This table comes from the CSS3 draft, and is probably
        // incorrect, given the comments in that draft.
        static const UChar kHiraganaIrohaAlphabet[47] = {
            0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068, 0x3061,
            0x308A, 0x306C, 0x308B, 0x3092, 0x308F, 0x304B, 0x3088, 0x305F,
            0x308C, 0x305D, 0x3064, 0x306D, 0x306A, 0x3089, 0x3080, 0x3046,
            0x3090, 0x306E, 0x304A, 0x304F, 0x3084, 0x307E, 0x3051, 0x3075,
            0x3053, 0x3048, 0x3066, 0x3042, 0x3055, 0x304D, 0x3086, 0x3081,
            0x307F, 0x3057, 0x3091, 0x3072, 0x3082, 0x305B, 0x3059};
        return ToAlphabetic(count, kHiraganaIrohaAlphabet);
      }
      case EListStyleType::kKatakana: {
        // FIXME: This table comes from the CSS3 draft, and is probably
        // incorrect, given the comments in that draft.
        static const UChar kKatakanaAlphabet[48] = {
            0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF,
            0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, 0x30BF,
            0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD,
            0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, 0x30DF,
            0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA,
            0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F0, 0x30F1, 0x30F2, 0x30F3};
        return ToAlphabetic(count, kKatakanaAlphabet);
      }
      case EListStyleType::kKatakanaIroha: {
        // FIXME: This table comes from the CSS3 draft, and is probably
        // incorrect, given the comments in that draft.
        static const UChar kKatakanaIrohaAlphabet[47] = {
            0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8, 0x30C1,
            0x30EA, 0x30CC, 0x30EB, 0x30F2, 0x30EF, 0x30AB, 0x30E8, 0x30BF,
            0x30EC, 0x30BD, 0x30C4, 0x30CD, 0x30CA, 0x30E9, 0x30E0, 0x30A6,
            0x30F0, 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE, 0x30B1, 0x30D5,
            0x30B3, 0x30A8, 0x30C6, 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1,
            0x30DF, 0x30B7, 0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x30B9};
        return ToAlphabetic(count, kKatakanaIrohaAlphabet);
      }
  
      case EListStyleType::kCjkEarthlyBranch: {
        static const UChar kCjkEarthlyBranchAlphabet[12] = {
            0x5B50, 0x4E11, 0x5BC5, 0x536F, 0x8FB0, 0x5DF3,
            0x5348, 0x672A, 0x7533, 0x9149, 0x620C, 0x4EA5};
        return ToAlphabetic(count, kCjkEarthlyBranchAlphabet);
      }
      case EListStyleType::kCjkHeavenlyStem: {
        static const UChar kCjkHeavenlyStemAlphabet[10] = {
            0x7532, 0x4E59, 0x4E19, 0x4E01, 0x620A,
            0x5DF1, 0x5E9A, 0x8F9B, 0x58EC, 0x7678};
        return ToAlphabetic(count, kCjkHeavenlyStemAlphabet);
      }
      case EListStyleType::kHangulConsonant: {
        static const UChar kHangulConsonantAlphabet[14] = {
            0x3131, 0x3134, 0x3137, 0x3139, 0x3141, 0x3142, 0x3145,
            0x3147, 0x3148, 0x314A, 0x314B, 0x314C, 0x314D, 0x314E};
        return ToAlphabetic(count, kHangulConsonantAlphabet);
      }
      case EListStyleType::kHangul: {
        static const UChar kHangulAlphabet[14] = {
            0xAC00, 0xB098, 0xB2E4, 0xB77C, 0xB9C8, 0xBC14, 0xC0AC,
            0xC544, 0xC790, 0xCC28, 0xCE74, 0xD0C0, 0xD30C, 0xD558};
        return ToAlphabetic(count, kHangulAlphabet);
      }
      case EListStyleType::kEthiopicHalehame: {
        static const UChar kEthiopicHalehameGezAlphabet[26] = {
            0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230,
            0x1240, 0x1260, 0x1270, 0x1280, 0x1290, 0x12A0, 0x12A8,
            0x12C8, 0x12D0, 0x12D8, 0x12E8, 0x12F0, 0x1308, 0x1320,
            0x1330, 0x1338, 0x1340, 0x1348, 0x1350};
        return ToAlphabetic(count, kEthiopicHalehameGezAlphabet);
      }
      case EListStyleType::kEthiopicHalehameAm: {
        static const UChar kEthiopicHalehameAmAlphabet[33] = {
            0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230,
            0x1238, 0x1240, 0x1260, 0x1270, 0x1278, 0x1280, 0x1290,
            0x1298, 0x12A0, 0x12A8, 0x12B8, 0x12C8, 0x12D0, 0x12D8,
            0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320, 0x1328,
            0x1330, 0x1338, 0x1340, 0x1348, 0x1350};
        return ToAlphabetic(count, kEthiopicHalehameAmAlphabet);
      }
      case EListStyleType::kEthiopicHalehameTiEr: {
        static const UChar kEthiopicHalehameTiErAlphabet[31] = {
            0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240,
            0x1250, 0x1260, 0x1270, 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8,
            0x12B8, 0x12C8, 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300,
            0x1308, 0x1320, 0x1328, 0x1330, 0x1338, 0x1348, 0x1350};
        return ToAlphabetic(count, kEthiopicHalehameTiErAlphabet);
      }
      case EListStyleType::kEthiopicHalehameTiEt: {
        static const UChar kEthiopicHalehameTiEtAlphabet[34] = {
            0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230,
            0x1238, 0x1240, 0x1250, 0x1260, 0x1270, 0x1278, 0x1280,
            0x1290, 0x1298, 0x12A0, 0x12A8, 0x12B8, 0x12C8, 0x12D0,
            0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320,
            0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350};
        return ToAlphabetic(count, kEthiopicHalehameTiEtAlphabet);
      }
      case EListStyleType::kKoreanHangulFormal: {
        static const UChar kKoreanHangulFormalTable[26] = {
            kKorean, 0xB9CC, 0x0000, 0xC5B5, 0x0000, 0xC870, 0x0000,
            0xC2ED,  0xBC31, 0xCC9C, 0xC601, 0xC77C, 0xC774, 0xC0BC,
            0xC0AC,  0xC624, 0xC721, 0xCE60, 0xD314, 0xAD6C, 0xB9C8,
            0xC774,  0xB108, 0xC2A4, 0x0020, 0x0000};
        return ToCJKIdeographic(count, kKoreanHangulFormalTable, kFormal);
      }
      case EListStyleType::kKoreanHanjaFormal: {
        static const UChar kKoreanHanjaFormalTable[26] = {
            kKorean, 0x842C, 0x0000, 0x5104, 0x0000, 0x5146, 0x0000,
            0x62FE,  0x767E, 0x4EDF, 0x96F6, 0x58F9, 0x8CB3, 0x53C3,
            0x56DB,  0x4E94, 0x516D, 0x4E03, 0x516B, 0x4E5D, 0xB9C8,
            0xC774,  0xB108, 0xC2A4, 0x0020, 0x0000};
        return ToCJKIdeographic(count, kKoreanHanjaFormalTable, kFormal);
      }
      case EListStyleType::kKoreanHanjaInformal: {
        static const UChar kKoreanHanjaInformalTable[26] = {
            kKorean, 0x842C, 0x0000, 0x5104, 0x0000, 0x5146, 0x0000,
            0x5341,  0x767E, 0x5343, 0x96F6, 0x4E00, 0x4E8C, 0x4E09,
            0x56DB,  0x4E94, 0x516D, 0x4E03, 0x516B, 0x4E5D, 0xB9C8,
            0xC774,  0xB108, 0xC2A4, 0x0020, 0x0000};
        return ToCJKIdeographic(count, kKoreanHanjaInformalTable, kInformal);
      }
      case EListStyleType::kCjkIdeographic:
      case EListStyleType::kTradChineseInformal: {
        static const UChar kTraditionalChineseInformalTable[22] = {
            kChinese, 0x842C, 0x0000, 0x5104, 0x0000, 0x5146, 0x0000, 0x5341,
            0x767E,   0x5343, 0x96F6, 0x4E00, 0x4E8C, 0x4E09, 0x56DB, 0x4E94,
            0x516D,   0x4E03, 0x516B, 0x4E5D, 0x8CA0, 0x0000};
        return ToCJKIdeographic(count, kTraditionalChineseInformalTable,
                                kInformal);
      }
      case EListStyleType::kSimpChineseInformal: {
        static const UChar kSimpleChineseInformalTable[22] = {
            kChinese, 0x4E07, 0x0000, 0x4EBF, 0x0000, 0x4E07, 0x4EBF, 0x5341,
            0x767E,   0x5343, 0x96F6, 0x4E00, 0x4E8C, 0x4E09, 0x56DB, 0x4E94,
            0x516D,   0x4E03, 0x516B, 0x4E5D, 0x8D1F, 0x0000};
        return ToCJKIdeographic(count, kSimpleChineseInformalTable, kInformal);
      }
      case EListStyleType::kTradChineseFormal: {
        static const UChar kTraditionalChineseFormalTable[22] = {
            kChinese, 0x842C, 0x0000, 0x5104, 0x0000, 0x5146, 0x0000, 0x62FE,
            0x4F70,   0x4EDF, 0x96F6, 0x58F9, 0x8CB3, 0x53C3, 0x8086, 0x4F0D,
            0x9678,   0x67D2, 0x634C, 0x7396, 0x8CA0, 0x0000};
        return ToCJKIdeographic(count, kTraditionalChineseFormalTable, kFormal);
      }
      case EListStyleType::kSimpChineseFormal: {
        static const UChar kSimpleChineseFormalTable[22] = {
            kChinese, 0x4E07, 0x0000, 0x4EBF, 0x0000, 0x4E07, 0x4EBF, 0x62FE,
            0x4F70,   0x4EDF, 0x96F6, 0x58F9, 0x8D30, 0x53C1, 0x8086, 0x4F0D,
            0x9646,   0x67D2, 0x634C, 0x7396, 0x8D1F, 0x0000};
        return ToCJKIdeographic(count, kSimpleChineseFormalTable, kFormal);
      }
  
      case EListStyleType::kLowerRoman:
        return ToRoman(count, false);
      case EListStyleType::kUpperRoman:
        return ToRoman(count, true);
  
      case EListStyleType::kArmenian:
      case EListStyleType::kUpperArmenian:
        // CSS3 says "armenian" means "lower-armenian".
        // But the CSS2.1 test suite contains uppercase test results for
        // "armenian", so we'll match the test suite.
        return ToArmenian(count, true);
      case EListStyleType::kLowerArmenian:
        return ToArmenian(count, false);
      case EListStyleType::kGeorgian:
        return ToGeorgian(count);
      case EListStyleType::kHebrew:
        return ToHebrew(count);
  
      case EListStyleType::kString:
        NOTREACHED();
        break;
    }
  
    NOTREACHED();
    return "";
  }
  
  }  // namespace list_marker_text
  
  }  // namespace blink
  