1 /*
   2  * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
   3  *           (C) 1999 Antti Koivisto (koivisto@kde.org)
   4  * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
   5  * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net)
   6  * Copyright (C) 2010 Daniel Bates (dbates@intudata.com)
   7  *
   8  * This library is free software; you can redistribute it and/or
   9  * modify it under the terms of the GNU Library General Public
  10  * License as published by the Free Software Foundation; either
  11  * version 2 of the License, or (at your option) any later version.
  12  *
  13  * This library is distributed in the hope that it will be useful,
  14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16  * Library General Public License for more details.
  17  *
  18  * You should have received a copy of the GNU Library General Public License
  19  * along with this library; see the file COPYING.LIB.  If not, write to
  20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  21  * Boston, MA 02110-1301, USA.
  22  *
  23  */
  24 
  25 #include "config.h"
  26 #include "RenderListMarker.h"
  27 
  28 #include "Document.h"
  29 #include "FontCascade.h"
  30 #include "GraphicsContext.h"
  31 #include "InlineElementBox.h"
  32 #include "RenderLayer.h"
  33 #include "RenderListItem.h"
  34 #include "RenderView.h"
  35 #include <wtf/IsoMallocInlines.h>
  36 #include <wtf/StackStats.h>
  37 #include <wtf/text/StringBuilder.h>
  38 #include <wtf/unicode/CharacterNames.h>
  39 
  40 
  41 namespace WebCore {
  42 using namespace WTF;
  43 using namespace Unicode;
  44 
  45 WTF_MAKE_ISO_ALLOCATED_IMPL(RenderListMarker);
  46 
  47 const int cMarkerPadding = 7;
  48 
  49 enum SequenceType { NumericSequence, AlphabeticSequence };
  50 
  51 static NEVER_INLINE void toRoman(StringBuilder& builder, int number, bool upper)
  52 {
  53     // FIXME: CSS3 describes how to make this work for much larger numbers,
  54     // using overbars and special characters. It also specifies the characters
  55     // in the range U+2160 to U+217F instead of standard ASCII ones.
  56     ASSERT(number >= 1 && number <= 3999);
  57 
  58     // Big enough to store largest roman number less than 3999 which
  59     // is 3888 (MMMDCCCLXXXVIII)
  60     const int lettersSize = 15;
  61     LChar letters[lettersSize];
  62 
  63     int length = 0;
  64     const LChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' };
  65     const LChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' };
  66     const LChar* digits = upper ? udigits : ldigits;
  67     int d = 0;
  68     do {
  69         int num = number % 10;
  70         if (num % 5 < 4)
  71             for (int i = num % 5; i > 0; i--)
  72                 letters[lettersSize - ++length] = digits[d];
  73         if (num >= 4 && num <= 8)
  74             letters[lettersSize - ++length] = digits[d + 1];
  75         if (num == 9)
  76             letters[lettersSize - ++length] = digits[d + 2];
  77         if (num % 5 == 4)
  78             letters[lettersSize - ++length] = digits[d];
  79         number /= 10;
  80         d += 2;
  81     } while (number);
  82 
  83     ASSERT(length <= lettersSize);
  84     builder.append(&letters[lettersSize - length], length);
  85 }
  86 
  87 template <typename CharacterType>
  88 static inline void toAlphabeticOrNumeric(StringBuilder& builder, int number, const CharacterType* sequence, unsigned sequenceSize, SequenceType type)
  89 {
  90     ASSERT(sequenceSize >= 2);
  91 
  92     // Taking sizeof(number) in the expression below doesn't work with some compilers.
  93     const int lettersSize = sizeof(int) * 8 + 1; // Binary is the worst case; requires one character per bit plus a minus sign.
  94 
  95     CharacterType letters[lettersSize];
  96 
  97     bool isNegativeNumber = false;
  98     unsigned numberShadow = number;
  99     if (type == AlphabeticSequence) {
 100         ASSERT(number > 0);
 101         --numberShadow;
 102     } else if (number < 0) {
 103         numberShadow = -number;
 104         isNegativeNumber = true;
 105     }
 106     letters[lettersSize - 1] = sequence[numberShadow % sequenceSize];
 107     int length = 1;
 108 
 109     if (type == AlphabeticSequence) {
 110         while ((numberShadow /= sequenceSize) > 0) {
 111             --numberShadow;
 112             letters[lettersSize - ++length] = sequence[numberShadow % sequenceSize];
 113         }
 114     } else {
 115         while ((numberShadow /= sequenceSize) > 0)
 116             letters[lettersSize - ++length] = sequence[numberShadow % sequenceSize];
 117     }
 118     if (isNegativeNumber)
 119         letters[lettersSize - ++length] = hyphenMinus;
 120 
 121     ASSERT(length <= lettersSize);
 122     builder.append(&letters[lettersSize - length], length);
 123 }
 124 
 125 template <typename CharacterType>
 126 static NEVER_INLINE void toSymbolic(StringBuilder& builder, int number, const CharacterType* symbols, unsigned symbolsSize)
 127 {
 128     ASSERT(number > 0);
 129     ASSERT(symbolsSize >= 1);
 130     unsigned numberShadow = number;
 131     --numberShadow;
 132 
 133     // The asterisks list-style-type is the worst case; we show |numberShadow| asterisks.
 134     builder.append(symbols[numberShadow % symbolsSize]);
 135     unsigned numSymbols = numberShadow / symbolsSize;
 136     while (numSymbols--)
 137         builder.append(symbols[numberShadow % symbolsSize]);
 138 }
 139 
 140 template <typename CharacterType>
 141 static NEVER_INLINE void toAlphabetic(StringBuilder& builder, int number, const CharacterType* alphabet, unsigned alphabetSize)
 142 {
 143     toAlphabeticOrNumeric(builder, number, alphabet, alphabetSize, AlphabeticSequence);
 144 }
 145 
 146 template <typename CharacterType>
 147 static NEVER_INLINE void toNumeric(StringBuilder& builder, int number, const CharacterType* numerals, unsigned numeralsSize)
 148 {
 149     toAlphabeticOrNumeric(builder, number, numerals, numeralsSize, NumericSequence);
 150 }
 151 
 152 template <typename CharacterType, size_t size>
 153 static inline void toAlphabetic(StringBuilder& builder, int number, const CharacterType(&alphabet)[size])
 154 {
 155     toAlphabetic(builder, number, alphabet, size);
 156 }
 157 
 158 template <typename CharacterType, size_t size>
 159 static inline void toNumeric(StringBuilder& builder, int number, const CharacterType(&alphabet)[size])
 160 {
 161     toNumeric(builder, number, alphabet, size);
 162 }
 163 
 164 template <typename CharacterType, size_t size>
 165 static inline void toSymbolic(StringBuilder& builder, int number, const CharacterType(&alphabet)[size])
 166 {
 167     toSymbolic(builder, number, alphabet, size);
 168 }
 169 
 170 static NEVER_INLINE int toHebrewUnder1000(int number, UChar letters[5])
 171 {
 172     // FIXME: CSS3 mentions various refinements not implemented here.
 173     // FIXME: Should take a look at Mozilla's HebrewToText function (in nsBulletFrame).
 174     ASSERT(number >= 0 && number < 1000);
 175     int length = 0;
 176     int fourHundreds = number / 400;
 177     for (int i = 0; i < fourHundreds; i++)
 178         letters[length++] = 1511 + 3;
 179     number %= 400;
 180     if (number / 100)
 181         letters[length++] = 1511 + (number / 100) - 1;
 182     number %= 100;
 183     if (number == 15 || number == 16) {
 184         letters[length++] = 1487 + 9;
 185         letters[length++] = 1487 + number - 9;
 186     } else {
 187         if (int tens = number / 10) {
 188             static const UChar hebrewTens[9] = { 1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510 };
 189             letters[length++] = hebrewTens[tens - 1];
 190         }
 191         if (int ones = number % 10)
 192             letters[length++] = 1487 + ones;
 193     }
 194     ASSERT(length <= 5);
 195     return length;
 196 }
 197 
 198 static NEVER_INLINE void toHebrew(StringBuilder& builder, int number)
 199 {
 200     // FIXME: CSS3 mentions ways to make this work for much larger numbers.
 201     ASSERT(number >= 0 && number <= 999999);
 202 
 203     if (number == 0) {
 204         static const UChar hebrewZero[3] = { 0x05D0, 0x05E4, 0x05E1 };
 205         builder.append(hebrewZero, 3);
 206         return;
 207     }
 208 
 209     const int lettersSize = 11; // big enough for two 5-digit sequences plus a quote mark between
 210     UChar letters[lettersSize];
 211 
 212     int length;
 213     if (number < 1000)
 214         length = 0;
 215     else {
 216         length = toHebrewUnder1000(number / 1000, letters);
 217         letters[length++] = '\'';
 218         number = number % 1000;
 219     }
 220     length += toHebrewUnder1000(number, letters + length);
 221 
 222     ASSERT(length <= lettersSize);
 223     builder.append(letters, length);
 224 }
 225 
 226 static NEVER_INLINE int toArmenianUnder10000(int number, bool upper, bool addCircumflex, UChar letters[9])
 227 {
 228     ASSERT(number >= 0 && number < 10000);
 229     int length = 0;
 230 
 231     int lowerOffset = upper ? 0 : 0x0030;
 232 
 233     if (int thousands = number / 1000) {
 234         if (thousands == 7) {
 235             letters[length++] = 0x0552 + lowerOffset;
 236             if (addCircumflex)
 237                 letters[length++] = 0x0302;
 238         } else {
 239             letters[length++] = (0x054C - 1 + lowerOffset) + thousands;
 240             if (addCircumflex)
 241                 letters[length++] = 0x0302;
 242         }
 243     }
 244 
 245     if (int hundreds = (number / 100) % 10) {
 246         letters[length++] = (0x0543 - 1 + lowerOffset) + hundreds;
 247         if (addCircumflex)
 248             letters[length++] = 0x0302;
 249     }
 250 
 251     if (int tens = (number / 10) % 10) {
 252         letters[length++] = (0x053A - 1 + lowerOffset) + tens;
 253         if (addCircumflex)
 254             letters[length++] = 0x0302;
 255     }
 256 
 257     if (int ones = number % 10) {
 258         letters[length++] = (0x531 - 1 + lowerOffset) + ones;
 259         if (addCircumflex)
 260             letters[length++] = 0x0302;
 261     }
 262 
 263     return length;
 264 }
 265 
 266 static NEVER_INLINE void toArmenian(StringBuilder& builder, int number, bool upper)
 267 {
 268     ASSERT(number >= 1 && number <= 99999999);
 269 
 270     const int lettersSize = 18; // twice what toArmenianUnder10000 needs
 271     UChar letters[lettersSize];
 272 
 273     int length = toArmenianUnder10000(number / 10000, upper, true, letters);
 274     length += toArmenianUnder10000(number % 10000, upper, false, letters + length);
 275 
 276     ASSERT(length <= lettersSize);
 277     builder.append(letters, length);
 278 }
 279 
 280 static NEVER_INLINE void toGeorgian(StringBuilder& builder, int number)
 281 {
 282     ASSERT(number >= 1 && number <= 19999);
 283 
 284     const int lettersSize = 5;
 285     UChar letters[lettersSize];
 286 
 287     int length = 0;
 288 
 289     if (number > 9999)
 290         letters[length++] = 0x10F5;
 291 
 292     if (int thousands = (number / 1000) % 10) {
 293         static const UChar georgianThousands[9] = {
 294             0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10F4, 0x10EF, 0x10F0
 295         };
 296         letters[length++] = georgianThousands[thousands - 1];
 297     }
 298 
 299     if (int hundreds = (number / 100) % 10) {
 300         static const UChar georgianHundreds[9] = {
 301             0x10E0, 0x10E1, 0x10E2, 0x10F3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, 0x10E8
 302         };
 303         letters[length++] = georgianHundreds[hundreds - 1];
 304     }
 305 
 306     if (int tens = (number / 10) % 10) {
 307         static const UChar georgianTens[9] = {
 308             0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10F2, 0x10DD, 0x10DE, 0x10DF
 309         };
 310         letters[length++] = georgianTens[tens - 1];
 311     }
 312 
 313     if (int ones = number % 10) {
 314         static const UChar georgianOnes[9] = {
 315             0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10F1, 0x10D7
 316         };
 317         letters[length++] = georgianOnes[ones - 1];
 318     }
 319 
 320     ASSERT(length <= lettersSize);
 321     builder.append(letters, length);
 322 }
 323 
 324 // The table uses the order from the CSS3 specification:
 325 // first 3 group markers, then 3 digit markers, then ten digits.
 326 static NEVER_INLINE void toCJKIdeographic(StringBuilder& builder, int number, const UChar table[16])
 327 {
 328     ASSERT(number >= 0);
 329 
 330     enum AbstractCJKChar {
 331         noChar,
 332         secondGroupMarker, thirdGroupMarker, fourthGroupMarker,
 333         secondDigitMarker, thirdDigitMarker, fourthDigitMarker,
 334         digit0, digit1, digit2, digit3, digit4,
 335         digit5, digit6, digit7, digit8, digit9
 336     };
 337 
 338     if (number == 0) {
 339         builder.append(table[digit0 - 1]);
 340         return;
 341     }
 342 
 343     const int groupLength = 8; // 4 digits, 3 digit markers, and a group marker
 344     const int bufferLength = 4 * groupLength;
 345     AbstractCJKChar buffer[bufferLength] = { noChar };
 346 
 347     for (int i = 0; i < 4; ++i) {
 348         int groupValue = number % 10000;
 349         number /= 10000;
 350 
 351         // Process least-significant group first, but put it in the buffer last.
 352         AbstractCJKChar* group = &buffer[(3 - i) * groupLength];
 353 
 354         if (groupValue && i)
 355             group[7] = static_cast<AbstractCJKChar>(secondGroupMarker - 1 + i);
 356 
 357         // Put in the four digits and digit markers for any non-zero digits.
 358         group[6] = static_cast<AbstractCJKChar>(digit0 + (groupValue % 10));
 359         if (number != 0 || groupValue > 9) {
 360             int digitValue = ((groupValue / 10) % 10);
 361             group[4] = static_cast<AbstractCJKChar>(digit0 + digitValue);
 362             if (digitValue)
 363                 group[5] = secondDigitMarker;
 364         }
 365         if (number != 0 || groupValue > 99) {
 366             int digitValue = ((groupValue / 100) % 10);
 367             group[2] = static_cast<AbstractCJKChar>(digit0 + digitValue);
 368             if (digitValue)
 369                 group[3] = thirdDigitMarker;
 370         }
 371         if (number != 0 || groupValue > 999) {
 372             int digitValue = groupValue / 1000;
 373             group[0] = static_cast<AbstractCJKChar>(digit0 + digitValue);
 374             if (digitValue)
 375                 group[1] = fourthDigitMarker;
 376         }
 377 
 378         // Remove the tens digit, but leave the marker, for any group that has
 379         // a value of less than 20.
 380         if (groupValue < 20) {
 381             ASSERT(group[4] == noChar || group[4] == digit0 || group[4] == digit1);
 382             group[4] = noChar;
 383         }
 384 
 385         if (number == 0)
 386             break;
 387     }
 388 
 389     // Convert into characters, omitting consecutive runs of digit0 and
 390     // any trailing digit0.
 391     int length = 0;
 392     UChar characters[bufferLength];
 393     AbstractCJKChar last = noChar;
 394     for (int i = 0; i < bufferLength; ++i) {
 395         AbstractCJKChar a = buffer[i];
 396         if (a != noChar) {
 397             if (a != digit0 || last != digit0)
 398                 characters[length++] = table[a - 1];
 399             last = a;
 400         }
 401     }
 402     if (last == digit0)
 403         --length;
 404 
 405     builder.append(characters, length);
 406 }
 407 
 408 static EListStyleType effectiveListMarkerType(EListStyleType type, int value)
 409 {
 410     // Note, the following switch statement has been explicitly grouped
 411     // by list-style-type ordinal range.
 412     switch (type) {
 413     case ArabicIndic:
 414     case Bengali:
 415     case BinaryListStyle:
 416     case Cambodian:
 417     case Circle:
 418     case DecimalLeadingZero:
 419     case DecimalListStyle:
 420     case Devanagari:
 421     case Disc:
 422     case Gujarati:
 423     case Gurmukhi:
 424     case Kannada:
 425     case Khmer:
 426     case Lao:
 427     case LowerHexadecimal:
 428     case Malayalam:
 429     case Mongolian:
 430     case Myanmar:
 431     case NoneListStyle:
 432     case Octal:
 433     case Oriya:
 434     case Persian:
 435     case Square:
 436     case Telugu:
 437     case Thai:
 438     case Tibetan:
 439     case UpperHexadecimal:
 440     case Urdu:
 441         return type; // Can represent all ordinals.
 442     case Armenian:
 443         return (value < 1 || value > 99999999) ? DecimalListStyle : type;
 444     case CJKIdeographic:
 445         return (value < 0) ? DecimalListStyle : type;
 446     case Georgian:
 447         return (value < 1 || value > 19999) ? DecimalListStyle : type;
 448     case Hebrew:
 449         return (value < 0 || value > 999999) ? DecimalListStyle : type;
 450     case LowerRoman:
 451     case UpperRoman:
 452         return (value < 1 || value > 3999) ? DecimalListStyle : type;
 453     case Afar:
 454     case Amharic:
 455     case AmharicAbegede:
 456     case Asterisks:
 457     case CjkEarthlyBranch:
 458     case CjkHeavenlyStem:
 459     case Ethiopic:
 460     case EthiopicAbegede:
 461     case EthiopicAbegedeAmEt:
 462     case EthiopicAbegedeGez:
 463     case EthiopicAbegedeTiEr:
 464     case EthiopicAbegedeTiEt:
 465     case EthiopicHalehameAaEr:
 466     case EthiopicHalehameAaEt:
 467     case EthiopicHalehameAmEt:
 468     case EthiopicHalehameGez:
 469     case EthiopicHalehameOmEt:
 470     case EthiopicHalehameSidEt:
 471     case EthiopicHalehameSoEt:
 472     case EthiopicHalehameTiEr:
 473     case EthiopicHalehameTiEt:
 474     case EthiopicHalehameTig:
 475     case Footnotes:
 476     case Hangul:
 477     case HangulConsonant:
 478     case Hiragana:
 479     case HiraganaIroha:
 480     case Katakana:
 481     case KatakanaIroha:
 482     case LowerAlpha:
 483     case LowerArmenian:
 484     case LowerGreek:
 485     case LowerLatin:
 486     case LowerNorwegian:
 487     case Oromo:
 488     case Sidama:
 489     case Somali:
 490     case Tigre:
 491     case TigrinyaEr:
 492     case TigrinyaErAbegede:
 493     case TigrinyaEt:
 494     case TigrinyaEtAbegede:
 495     case UpperAlpha:
 496     case UpperArmenian:
 497     case UpperGreek:
 498     case UpperLatin:
 499     case UpperNorwegian:
 500         return (value < 1) ? DecimalListStyle : type;
 501     }
 502 
 503     ASSERT_NOT_REACHED();
 504     return type;
 505 }
 506 
 507 static UChar listMarkerSuffix(EListStyleType type, int value)
 508 {
 509     // If the list-style-type cannot represent |value| because it's outside its
 510     // ordinal range then we fall back to some list style that can represent |value|.
 511     EListStyleType effectiveType = effectiveListMarkerType(type, value);
 512 
 513     // Note, the following switch statement has been explicitly
 514     // grouped by list-style-type suffix.
 515     switch (effectiveType) {
 516     case Asterisks:
 517     case Circle:
 518     case Disc:
 519     case Footnotes:
 520     case NoneListStyle:
 521     case Square:
 522         return ' ';
 523     case Afar:
 524     case Amharic:
 525     case AmharicAbegede:
 526     case Ethiopic:
 527     case EthiopicAbegede:
 528     case EthiopicAbegedeAmEt:
 529     case EthiopicAbegedeGez:
 530     case EthiopicAbegedeTiEr:
 531     case EthiopicAbegedeTiEt:
 532     case EthiopicHalehameAaEr:
 533     case EthiopicHalehameAaEt:
 534     case EthiopicHalehameAmEt:
 535     case EthiopicHalehameGez:
 536     case EthiopicHalehameOmEt:
 537     case EthiopicHalehameSidEt:
 538     case EthiopicHalehameSoEt:
 539     case EthiopicHalehameTiEr:
 540     case EthiopicHalehameTiEt:
 541     case EthiopicHalehameTig:
 542     case Oromo:
 543     case Sidama:
 544     case Somali:
 545     case Tigre:
 546     case TigrinyaEr:
 547     case TigrinyaErAbegede:
 548     case TigrinyaEt:
 549     case TigrinyaEtAbegede:
 550         return ethiopicPrefaceColon;
 551     case Armenian:
 552     case ArabicIndic:
 553     case Bengali:
 554     case BinaryListStyle:
 555     case Cambodian:
 556     case CJKIdeographic:
 557     case CjkEarthlyBranch:
 558     case CjkHeavenlyStem:
 559     case DecimalLeadingZero:
 560     case DecimalListStyle:
 561     case Devanagari:
 562     case Georgian:
 563     case Gujarati:
 564     case Gurmukhi:
 565     case Hangul:
 566     case HangulConsonant:
 567     case Hebrew:
 568     case Hiragana:
 569     case HiraganaIroha:
 570     case Kannada:
 571     case Katakana:
 572     case KatakanaIroha:
 573     case Khmer:
 574     case Lao:
 575     case LowerAlpha:
 576     case LowerArmenian:
 577     case LowerGreek:
 578     case LowerHexadecimal:
 579     case LowerLatin:
 580     case LowerNorwegian:
 581     case LowerRoman:
 582     case Malayalam:
 583     case Mongolian:
 584     case Myanmar:
 585     case Octal:
 586     case Oriya:
 587     case Persian:
 588     case Telugu:
 589     case Thai:
 590     case Tibetan:
 591     case UpperAlpha:
 592     case UpperArmenian:
 593     case UpperGreek:
 594     case UpperHexadecimal:
 595     case UpperLatin:
 596     case UpperNorwegian:
 597     case UpperRoman:
 598     case Urdu:
 599         return '.';
 600     }
 601 
 602     ASSERT_NOT_REACHED();
 603     return '.';
 604 }
 605 
 606 String listMarkerText(EListStyleType type, int value)
 607 {
 608     StringBuilder builder;
 609 
 610     // If the list-style-type, say hebrew, cannot represent |value| because it's outside
 611     // its ordinal range then we fallback to some list style that can represent |value|.
 612     switch (effectiveListMarkerType(type, value)) {
 613         case NoneListStyle:
 614             return emptyString();
 615 
 616         case Asterisks: {
 617             static const LChar asterisksSymbols[1] = { 0x2A };
 618             toSymbolic(builder, value, asterisksSymbols);
 619             break;
 620         }
 621         // We use the same characters for text security.
 622         // See RenderText::setInternalString.
 623         case Circle:
 624             builder.append(whiteBullet);
 625             break;
 626         case Disc:
 627             builder.append(bullet);
 628             break;
 629         case Footnotes: {
 630             static const UChar footnotesSymbols[4] = { 0x002A, 0x2051, 0x2020, 0x2021 };
 631             toSymbolic(builder, value, footnotesSymbols);
 632             break;
 633         }
 634         case Square:
 635             // The CSS 2.1 test suite uses U+25EE BLACK MEDIUM SMALL SQUARE
 636             // instead, but I think this looks better.
 637             builder.append(blackSquare);
 638             break;
 639 
 640         case DecimalListStyle:
 641             builder.appendNumber(value);
 642             break;
 643 
 644         case DecimalLeadingZero:
 645             if (value < -9 || value > 9) {
 646                 builder.appendNumber(value);
 647                 break;
 648             }
 649             if (value < 0) {
 650                 builder.appendLiteral("-0");
 651                 builder.appendNumber(-value); // -01 to -09
 652                 break;
 653             }
 654             builder.append('0');
 655             builder.appendNumber(value); // 00 to 09
 656             break;
 657 
 658         case ArabicIndic: {
 659             static const UChar arabicIndicNumerals[10] = {
 660                 0x0660, 0x0661, 0x0662, 0x0663, 0x0664, 0x0665, 0x0666, 0x0667, 0x0668, 0x0669
 661             };
 662             toNumeric(builder, value, arabicIndicNumerals);
 663             break;
 664         }
 665 
 666         case BinaryListStyle: {
 667             static const LChar binaryNumerals[2] = { '0', '1' };
 668             toNumeric(builder, value, binaryNumerals);
 669             break;
 670         }
 671 
 672         case Bengali: {
 673             static const UChar bengaliNumerals[10] = {
 674                 0x09E6, 0x09E7, 0x09E8, 0x09E9, 0x09EA, 0x09EB, 0x09EC, 0x09ED, 0x09EE, 0x09EF
 675             };
 676             toNumeric(builder, value, bengaliNumerals);
 677             break;
 678         }
 679 
 680         case Cambodian:
 681         case Khmer: {
 682             static const UChar khmerNumerals[10] = {
 683                 0x17E0, 0x17E1, 0x17E2, 0x17E3, 0x17E4, 0x17E5, 0x17E6, 0x17E7, 0x17E8, 0x17E9
 684             };
 685             toNumeric(builder, value, khmerNumerals);
 686             break;
 687         }
 688         case Devanagari: {
 689             static const UChar devanagariNumerals[10] = {
 690                 0x0966, 0x0967, 0x0968, 0x0969, 0x096A, 0x096B, 0x096C, 0x096D, 0x096E, 0x096F
 691             };
 692             toNumeric(builder, value, devanagariNumerals);
 693             break;
 694         }
 695         case Gujarati: {
 696             static const UChar gujaratiNumerals[10] = {
 697                 0x0AE6, 0x0AE7, 0x0AE8, 0x0AE9, 0x0AEA, 0x0AEB, 0x0AEC, 0x0AED, 0x0AEE, 0x0AEF
 698             };
 699             toNumeric(builder, value, gujaratiNumerals);
 700             break;
 701         }
 702         case Gurmukhi: {
 703             static const UChar gurmukhiNumerals[10] = {
 704                 0x0A66, 0x0A67, 0x0A68, 0x0A69, 0x0A6A, 0x0A6B, 0x0A6C, 0x0A6D, 0x0A6E, 0x0A6F
 705             };
 706             toNumeric(builder, value, gurmukhiNumerals);
 707             break;
 708         }
 709         case Kannada: {
 710             static const UChar kannadaNumerals[10] = {
 711                 0x0CE6, 0x0CE7, 0x0CE8, 0x0CE9, 0x0CEA, 0x0CEB, 0x0CEC, 0x0CED, 0x0CEE, 0x0CEF
 712             };
 713             toNumeric(builder, value, kannadaNumerals);
 714             break;
 715         }
 716         case LowerHexadecimal: {
 717             static const LChar lowerHexadecimalNumerals[16] = {
 718                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
 719             };
 720             toNumeric(builder, value, lowerHexadecimalNumerals);
 721             break;
 722         }
 723         case Lao: {
 724             static const UChar laoNumerals[10] = {
 725                 0x0ED0, 0x0ED1, 0x0ED2, 0x0ED3, 0x0ED4, 0x0ED5, 0x0ED6, 0x0ED7, 0x0ED8, 0x0ED9
 726             };
 727             toNumeric(builder, value, laoNumerals);
 728             break;
 729         }
 730         case Malayalam: {
 731             static const UChar malayalamNumerals[10] = {
 732                 0x0D66, 0x0D67, 0x0D68, 0x0D69, 0x0D6A, 0x0D6B, 0x0D6C, 0x0D6D, 0x0D6E, 0x0D6F
 733             };
 734             toNumeric(builder, value, malayalamNumerals);
 735             break;
 736         }
 737         case Mongolian: {
 738             static const UChar mongolianNumerals[10] = {
 739                 0x1810, 0x1811, 0x1812, 0x1813, 0x1814, 0x1815, 0x1816, 0x1817, 0x1818, 0x1819
 740             };
 741             toNumeric(builder, value, mongolianNumerals);
 742             break;
 743         }
 744         case Myanmar: {
 745             static const UChar myanmarNumerals[10] = {
 746                 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, 0x1048, 0x1049
 747             };
 748             toNumeric(builder, value, myanmarNumerals);
 749             break;
 750         }
 751         case Octal: {
 752             static const LChar octalNumerals[8] = {
 753                 '0', '1', '2', '3', '4', '5', '6', '7'
 754             };
 755             toNumeric(builder, value, octalNumerals);
 756             break;
 757         }
 758         case Oriya: {
 759             static const UChar oriyaNumerals[10] = {
 760                 0x0B66, 0x0B67, 0x0B68, 0x0B69, 0x0B6A, 0x0B6B, 0x0B6C, 0x0B6D, 0x0B6E, 0x0B6F
 761             };
 762             toNumeric(builder, value, oriyaNumerals);
 763             break;
 764         }
 765         case Persian:
 766         case Urdu: {
 767             static const UChar urduNumerals[10] = {
 768                 0x06F0, 0x06F1, 0x06F2, 0x06F3, 0x06F4, 0x06F5, 0x06F6, 0x06F7, 0x06F8, 0x06F9
 769             };
 770             toNumeric(builder, value, urduNumerals);
 771             break;
 772         }
 773         case Telugu: {
 774             static const UChar teluguNumerals[10] = {
 775                 0x0C66, 0x0C67, 0x0C68, 0x0C69, 0x0C6A, 0x0C6B, 0x0C6C, 0x0C6D, 0x0C6E, 0x0C6F
 776             };
 777             toNumeric(builder, value, teluguNumerals);
 778             break;
 779         }
 780         case Tibetan: {
 781             static const UChar tibetanNumerals[10] = {
 782                 0x0F20, 0x0F21, 0x0F22, 0x0F23, 0x0F24, 0x0F25, 0x0F26, 0x0F27, 0x0F28, 0x0F29
 783             };
 784             toNumeric(builder, value, tibetanNumerals);
 785             break;
 786         }
 787         case Thai: {
 788             static const UChar thaiNumerals[10] = {
 789                 0x0E50, 0x0E51, 0x0E52, 0x0E53, 0x0E54, 0x0E55, 0x0E56, 0x0E57, 0x0E58, 0x0E59
 790             };
 791             toNumeric(builder, value, thaiNumerals);
 792             break;
 793         }
 794         case UpperHexadecimal: {
 795             static const LChar upperHexadecimalNumerals[16] = {
 796                 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
 797             };
 798             toNumeric(builder, value, upperHexadecimalNumerals);
 799             break;
 800         }
 801 
 802         case LowerAlpha:
 803         case LowerLatin: {
 804             static const LChar lowerLatinAlphabet[26] = {
 805                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
 806                 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
 807             };
 808             toAlphabetic(builder, value, lowerLatinAlphabet);
 809             break;
 810         }
 811         case UpperAlpha:
 812         case UpperLatin: {
 813             static const LChar upperLatinAlphabet[26] = {
 814                 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
 815                 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
 816             };
 817             toAlphabetic(builder, value, upperLatinAlphabet);
 818             break;
 819         }
 820         case LowerGreek: {
 821             static const UChar lowerGreekAlphabet[24] = {
 822                 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8,
 823                 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0,
 824                 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9
 825             };
 826             toAlphabetic(builder, value, lowerGreekAlphabet);
 827             break;
 828         }
 829 
 830         case Hiragana: {
 831             // FIXME: This table comes from the CSS3 draft, and is probably
 832             // incorrect, given the comments in that draft.
 833             static const UChar hiraganaAlphabet[48] = {
 834                 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F,
 835                 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F,
 836                 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D,
 837                 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, 0x307F,
 838                 0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A,
 839                 0x308B, 0x308C, 0x308D, 0x308F, 0x3090, 0x3091, 0x3092, 0x3093
 840             };
 841             toAlphabetic(builder, value, hiraganaAlphabet);
 842             break;
 843         }
 844         case HiraganaIroha: {
 845             // FIXME: This table comes from the CSS3 draft, and is probably
 846             // incorrect, given the comments in that draft.
 847             static const UChar hiraganaIrohaAlphabet[47] = {
 848                 0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068, 0x3061,
 849                 0x308A, 0x306C, 0x308B, 0x3092, 0x308F, 0x304B, 0x3088, 0x305F,
 850                 0x308C, 0x305D, 0x3064, 0x306D, 0x306A, 0x3089, 0x3080, 0x3046,
 851                 0x3090, 0x306E, 0x304A, 0x304F, 0x3084, 0x307E, 0x3051, 0x3075,
 852                 0x3053, 0x3048, 0x3066, 0x3042, 0x3055, 0x304D, 0x3086, 0x3081,
 853                 0x307F, 0x3057, 0x3091, 0x3072, 0x3082, 0x305B, 0x3059
 854             };
 855             toAlphabetic(builder, value, hiraganaIrohaAlphabet);
 856             break;
 857         }
 858         case Katakana: {
 859             // FIXME: This table comes from the CSS3 draft, and is probably
 860             // incorrect, given the comments in that draft.
 861             static const UChar katakanaAlphabet[48] = {
 862                 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF,
 863                 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, 0x30BF,
 864                 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD,
 865                 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, 0x30DF,
 866                 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA,
 867                 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F0, 0x30F1, 0x30F2, 0x30F3
 868             };
 869             toAlphabetic(builder, value, katakanaAlphabet);
 870             break;
 871         }
 872         case KatakanaIroha: {
 873             // FIXME: This table comes from the CSS3 draft, and is probably
 874             // incorrect, given the comments in that draft.
 875             static const UChar katakanaIrohaAlphabet[47] = {
 876                 0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8, 0x30C1,
 877                 0x30EA, 0x30CC, 0x30EB, 0x30F2, 0x30EF, 0x30AB, 0x30E8, 0x30BF,
 878                 0x30EC, 0x30BD, 0x30C4, 0x30CD, 0x30CA, 0x30E9, 0x30E0, 0x30A6,
 879                 0x30F0, 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE, 0x30B1, 0x30D5,
 880                 0x30B3, 0x30A8, 0x30C6, 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1,
 881                 0x30DF, 0x30B7, 0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x30B9
 882             };
 883             toAlphabetic(builder, value, katakanaIrohaAlphabet);
 884             break;
 885         }
 886 
 887         case Afar:
 888         case EthiopicHalehameAaEt:
 889         case EthiopicHalehameAaEr: {
 890             static const UChar ethiopicHalehameAaErAlphabet[18] = {
 891                 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1260, 0x1270, 0x1290,
 892                 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12E8, 0x12F0, 0x1308, 0x1338, 0x1348
 893             };
 894             toAlphabetic(builder, value, ethiopicHalehameAaErAlphabet);
 895             break;
 896         }
 897         case Amharic:
 898         case EthiopicHalehameAmEt: {
 899             static const UChar ethiopicHalehameAmEtAlphabet[33] = {
 900                 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240,
 901                 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12B8,
 902                 0x12C8, 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320,
 903                 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350
 904             };
 905             toAlphabetic(builder, value, ethiopicHalehameAmEtAlphabet);
 906             break;
 907         }
 908         case AmharicAbegede:
 909         case EthiopicAbegedeAmEt: {
 910             static const UChar ethiopicAbegedeAmEtAlphabet[33] = {
 911                 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0,
 912                 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290,
 913                 0x1298, 0x1220, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1228, 0x1230, 0x1238,
 914                 0x1270, 0x1278, 0x1280, 0x1340, 0x1330, 0x1350
 915             };
 916             toAlphabetic(builder, value, ethiopicAbegedeAmEtAlphabet);
 917             break;
 918         }
 919         case CjkEarthlyBranch: {
 920             static const UChar cjkEarthlyBranchAlphabet[12] = {
 921                 0x5B50, 0x4E11, 0x5BC5, 0x536F, 0x8FB0, 0x5DF3, 0x5348, 0x672A, 0x7533,
 922                 0x9149, 0x620C, 0x4EA5
 923             };
 924             toAlphabetic(builder, value, cjkEarthlyBranchAlphabet);
 925             break;
 926         }
 927         case CjkHeavenlyStem: {
 928             static const UChar cjkHeavenlyStemAlphabet[10] = {
 929                 0x7532, 0x4E59, 0x4E19, 0x4E01, 0x620A, 0x5DF1, 0x5E9A, 0x8F9B, 0x58EC,
 930                 0x7678
 931             };
 932             toAlphabetic(builder, value, cjkHeavenlyStemAlphabet);
 933             break;
 934         }
 935         case Ethiopic:
 936         case EthiopicHalehameGez: {
 937             static const UChar ethiopicHalehameGezAlphabet[26] = {
 938                 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1240, 0x1260,
 939                 0x1270, 0x1280, 0x1290, 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12D8, 0x12E8,
 940                 0x12F0, 0x1308, 0x1320, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350
 941             };
 942             toAlphabetic(builder, value, ethiopicHalehameGezAlphabet);
 943             break;
 944         }
 945         case EthiopicAbegede:
 946         case EthiopicAbegedeGez: {
 947             static const UChar ethiopicAbegedeGezAlphabet[26] = {
 948                 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1200, 0x12C8, 0x12D8, 0x1210, 0x1320,
 949                 0x12E8, 0x12A8, 0x1208, 0x1218, 0x1290, 0x1220, 0x12D0, 0x1348, 0x1338,
 950                 0x1240, 0x1228, 0x1230, 0x1270, 0x1280, 0x1340, 0x1330, 0x1350
 951             };
 952             toAlphabetic(builder, value, ethiopicAbegedeGezAlphabet);
 953             break;
 954         }
 955         case HangulConsonant: {
 956             static const UChar hangulConsonantAlphabet[14] = {
 957                 0x3131, 0x3134, 0x3137, 0x3139, 0x3141, 0x3142, 0x3145, 0x3147, 0x3148,
 958                 0x314A, 0x314B, 0x314C, 0x314D, 0x314E
 959             };
 960             toAlphabetic(builder, value, hangulConsonantAlphabet);
 961             break;
 962         }
 963         case Hangul: {
 964             static const UChar hangulAlphabet[14] = {
 965                 0xAC00, 0xB098, 0xB2E4, 0xB77C, 0xB9C8, 0xBC14, 0xC0AC, 0xC544, 0xC790,
 966                 0xCC28, 0xCE74, 0xD0C0, 0xD30C, 0xD558
 967             };
 968             toAlphabetic(builder, value, hangulAlphabet);
 969             break;
 970         }
 971         case Oromo:
 972         case EthiopicHalehameOmEt: {
 973             static const UChar ethiopicHalehameOmEtAlphabet[25] = {
 974                 0x1200, 0x1208, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260, 0x1270,
 975                 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12C8, 0x12E8, 0x12F0, 0x12F8,
 976                 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348
 977             };
 978             toAlphabetic(builder, value, ethiopicHalehameOmEtAlphabet);
 979             break;
 980         }
 981         case Sidama:
 982         case EthiopicHalehameSidEt: {
 983             static const UChar ethiopicHalehameSidEtAlphabet[26] = {
 984                 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260,
 985                 0x1270, 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12C8, 0x12E8, 0x12F0,
 986                 0x12F8, 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348
 987             };
 988             toAlphabetic(builder, value, ethiopicHalehameSidEtAlphabet);
 989             break;
 990         }
 991         case Somali:
 992         case EthiopicHalehameSoEt: {
 993             static const UChar ethiopicHalehameSoEtAlphabet[22] = {
 994                 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260,
 995                 0x1270, 0x1290, 0x12A0, 0x12A8, 0x12B8, 0x12C8, 0x12D0, 0x12E8, 0x12F0,
 996                 0x1300, 0x1308, 0x1338, 0x1348
 997             };
 998             toAlphabetic(builder, value, ethiopicHalehameSoEtAlphabet);
 999             break;
1000         }
1001         case Tigre:
1002         case EthiopicHalehameTig: {
1003             static const UChar ethiopicHalehameTigAlphabet[27] = {
1004                 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1260,
1005                 0x1270, 0x1278, 0x1290, 0x12A0, 0x12A8, 0x12C8, 0x12D0, 0x12D8, 0x12E8,
1006                 0x12F0, 0x1300, 0x1308, 0x1320, 0x1328, 0x1338, 0x1330, 0x1348, 0x1350
1007             };
1008             toAlphabetic(builder, value, ethiopicHalehameTigAlphabet);
1009             break;
1010         }
1011         case TigrinyaEr:
1012         case EthiopicHalehameTiEr: {
1013             static const UChar ethiopicHalehameTiErAlphabet[31] = {
1014                 0x1200, 0x1208, 0x1210, 0x1218, 0x1228, 0x1230, 0x1238, 0x1240, 0x1250,
1015                 0x1260, 0x1270, 0x1278, 0x1290, 0x1298, 0x12A0, 0x12A8, 0x12B8, 0x12C8,
1016                 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308, 0x1320, 0x1328,
1017                 0x1330, 0x1338, 0x1348, 0x1350
1018             };
1019             toAlphabetic(builder, value, ethiopicHalehameTiErAlphabet);
1020             break;
1021         }
1022         case TigrinyaErAbegede:
1023         case EthiopicAbegedeTiEr: {
1024             static const UChar ethiopicAbegedeTiErAlphabet[31] = {
1025                 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0,
1026                 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290,
1027                 0x1298, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1250, 0x1228, 0x1230, 0x1238,
1028                 0x1270, 0x1278, 0x1330, 0x1350
1029             };
1030             toAlphabetic(builder, value, ethiopicAbegedeTiErAlphabet);
1031             break;
1032         }
1033         case TigrinyaEt:
1034         case EthiopicHalehameTiEt: {
1035             static const UChar ethiopicHalehameTiEtAlphabet[34] = {
1036                 0x1200, 0x1208, 0x1210, 0x1218, 0x1220, 0x1228, 0x1230, 0x1238, 0x1240,
1037                 0x1250, 0x1260, 0x1270, 0x1278, 0x1280, 0x1290, 0x1298, 0x12A0, 0x12A8,
1038                 0x12B8, 0x12C8, 0x12D0, 0x12D8, 0x12E0, 0x12E8, 0x12F0, 0x1300, 0x1308,
1039                 0x1320, 0x1328, 0x1330, 0x1338, 0x1340, 0x1348, 0x1350
1040             };
1041             toAlphabetic(builder, value, ethiopicHalehameTiEtAlphabet);
1042             break;
1043         }
1044         case TigrinyaEtAbegede:
1045         case EthiopicAbegedeTiEt: {
1046             static const UChar ethiopicAbegedeTiEtAlphabet[34] = {
1047                 0x12A0, 0x1260, 0x1308, 0x12F0, 0x1300, 0x1200, 0x12C8, 0x12D8, 0x12E0,
1048                 0x1210, 0x1320, 0x1328, 0x12E8, 0x12A8, 0x12B8, 0x1208, 0x1218, 0x1290,
1049                 0x1298, 0x1220, 0x12D0, 0x1348, 0x1338, 0x1240, 0x1250, 0x1228, 0x1230,
1050                 0x1238, 0x1270, 0x1278, 0x1280, 0x1340, 0x1330, 0x1350
1051             };
1052             toAlphabetic(builder, value, ethiopicAbegedeTiEtAlphabet);
1053             break;
1054         }
1055         case UpperGreek: {
1056             static const UChar upperGreekAlphabet[24] = {
1057                 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, 0x0398, 0x0399,
1058                 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, 0x03A0, 0x03A1, 0x03A3,
1059                 0x03A4, 0x03A5, 0x03A6, 0x03A7, 0x03A8, 0x03A9
1060             };
1061             toAlphabetic(builder, value, upperGreekAlphabet);
1062             break;
1063         }
1064         case LowerNorwegian: {
1065             static const LChar lowerNorwegianAlphabet[29] = {
1066                 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
1067                 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70, 0x71, 0x72,
1068                 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0xE6,
1069                 0xF8, 0xE5
1070             };
1071             toAlphabetic(builder, value, lowerNorwegianAlphabet);
1072             break;
1073         }
1074         case UpperNorwegian: {
1075             static const LChar upperNorwegianAlphabet[29] = {
1076                 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
1077                 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52,
1078                 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0xC6,
1079                 0xD8, 0xC5
1080             };
1081             toAlphabetic(builder, value, upperNorwegianAlphabet);
1082             break;
1083         }
1084         case CJKIdeographic: {
1085             static const UChar traditionalChineseInformalTable[16] = {
1086                 0x842C, 0x5104, 0x5146,
1087                 0x5341, 0x767E, 0x5343,
1088                 0x96F6, 0x4E00, 0x4E8C, 0x4E09, 0x56DB,
1089                 0x4E94, 0x516D, 0x4E03, 0x516B, 0x4E5D
1090             };
1091             toCJKIdeographic(builder, value, traditionalChineseInformalTable);
1092             break;
1093         }
1094 
1095         case LowerRoman:
1096             toRoman(builder, value, false);
1097             break;
1098         case UpperRoman:
1099             toRoman(builder, value, true);
1100             break;
1101 
1102         case Armenian:
1103         case UpperArmenian:
1104             // CSS3 says "armenian" means "lower-armenian".
1105             // But the CSS2.1 test suite contains uppercase test results for "armenian",
1106             // so we'll match the test suite.
1107             toArmenian(builder, value, true);
1108             break;
1109         case LowerArmenian:
1110             toArmenian(builder, value, false);
1111             break;
1112         case Georgian:
1113             toGeorgian(builder, value);
1114             break;
1115         case Hebrew:
1116             toHebrew(builder, value);
1117             break;
1118     }
1119 
1120     return builder.toString();
1121 }
1122 
1123 RenderListMarker::RenderListMarker(RenderListItem& listItem, RenderStyle&& style)
1124     : RenderBox(listItem.document(), WTFMove(style), 0)
1125     , m_listItem(listItem)
1126 {
1127     // init RenderObject attributes
1128     setInline(true);   // our object is Inline
1129     setReplaced(true); // pretend to be replaced
1130 }
1131 
1132 RenderListMarker::~RenderListMarker()
1133 {
1134     // Do not add any code here. Add it to willBeDestroyed() instead.
1135 }
1136 
1137 void RenderListMarker::willBeDestroyed()
1138 {
1139     if (m_image)
1140         m_image->removeClient(this);
1141     RenderBox::willBeDestroyed();
1142 }
1143 
1144 void RenderListMarker::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
1145 {
1146     RenderBox::styleDidChange(diff, oldStyle);
1147 
1148     if (oldStyle && (style().listStylePosition() != oldStyle->listStylePosition() || style().listStyleType() != oldStyle->listStyleType()))
1149         setNeedsLayoutAndPrefWidthsRecalc();
1150 
1151     if (m_image != style().listStyleImage()) {
1152         if (m_image)
1153             m_image->removeClient(this);
1154         m_image = style().listStyleImage();
1155         if (m_image)
1156             m_image->addClient(this);
1157     }
1158 }
1159 
1160 std::unique_ptr<InlineElementBox> RenderListMarker::createInlineBox()
1161 {
1162     auto box = RenderBox::createInlineBox();
1163     box->setBehavesLikeText(isText());
1164     return box;
1165 }
1166 
1167 bool RenderListMarker::isImage() const
1168 {
1169     return m_image && !m_image->errorOccurred();
1170 }
1171 
1172 LayoutRect RenderListMarker::localSelectionRect()
1173 {
1174     InlineBox* box = inlineBoxWrapper();
1175     if (!box)
1176         return LayoutRect(LayoutPoint(), size());
1177     const RootInlineBox& rootBox = m_inlineBoxWrapper->root();
1178     LayoutUnit newLogicalTop = rootBox.blockFlow().style().isFlippedBlocksWritingMode() ? m_inlineBoxWrapper->logicalBottom() - rootBox.selectionBottom() : rootBox.selectionTop() - m_inlineBoxWrapper->logicalTop();
1179     if (rootBox.blockFlow().style().isHorizontalWritingMode())
1180         return LayoutRect(0, newLogicalTop, width(), rootBox.selectionHeight());
1181     return LayoutRect(newLogicalTop, 0, rootBox.selectionHeight(), height());
1182 }
1183 
1184 void RenderListMarker::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
1185 {
1186     if (paintInfo.phase != PaintPhaseForeground)
1187         return;
1188 
1189     if (style().visibility() != VISIBLE)
1190         return;
1191 
1192     LayoutPoint boxOrigin(paintOffset + location());
1193     LayoutRect overflowRect(visualOverflowRect());
1194     overflowRect.moveBy(boxOrigin);
1195     if (!paintInfo.rect.intersects(overflowRect))
1196         return;
1197 
1198     LayoutRect box(boxOrigin, size());
1199 
1200     auto markerRect = getRelativeMarkerRect();
1201     markerRect.moveBy(boxOrigin);
1202     if (markerRect.isEmpty())
1203         return;
1204 
1205     GraphicsContext& context = paintInfo.context();
1206 
1207     if (isImage()) {
1208         if (RefPtr<Image> markerImage = m_image->image(this, markerRect.size()))
1209             context.drawImage(*markerImage, markerRect);
1210         if (selectionState() != SelectionNone) {
1211             LayoutRect selRect = localSelectionRect();
1212             selRect.moveBy(boxOrigin);
1213             context.fillRect(snappedIntRect(selRect), m_listItem.selectionBackgroundColor());
1214         }
1215         return;
1216     }
1217 
1218     if (selectionState() != SelectionNone) {
1219         LayoutRect selRect = localSelectionRect();
1220         selRect.moveBy(boxOrigin);
1221         context.fillRect(snappedIntRect(selRect), m_listItem.selectionBackgroundColor());
1222     }
1223 
1224     const Color color(style().visitedDependentColor(CSSPropertyColor));
1225     context.setStrokeColor(color);
1226     context.setStrokeStyle(SolidStroke);
1227     context.setStrokeThickness(1.0f);
1228     context.setFillColor(color);
1229 
1230     EListStyleType type = style().listStyleType();
1231     switch (type) {
1232         case Disc:
1233             context.drawEllipse(markerRect);
1234             return;
1235         case Circle:
1236             context.setFillColor(Color::transparent);
1237             context.drawEllipse(markerRect);
1238             return;
1239         case Square:
1240             context.drawRect(markerRect);
1241             return;
1242         case NoneListStyle:
1243             return;
1244         case Afar:
1245         case Amharic:
1246         case AmharicAbegede:
1247         case ArabicIndic:
1248         case Armenian:
1249         case BinaryListStyle:
1250         case Bengali:
1251         case Cambodian:
1252         case CJKIdeographic:
1253         case CjkEarthlyBranch:
1254         case CjkHeavenlyStem:
1255         case DecimalLeadingZero:
1256         case DecimalListStyle:
1257         case Devanagari:
1258         case Ethiopic:
1259         case EthiopicAbegede:
1260         case EthiopicAbegedeAmEt:
1261         case EthiopicAbegedeGez:
1262         case EthiopicAbegedeTiEr:
1263         case EthiopicAbegedeTiEt:
1264         case EthiopicHalehameAaEr:
1265         case EthiopicHalehameAaEt:
1266         case EthiopicHalehameAmEt:
1267         case EthiopicHalehameGez:
1268         case EthiopicHalehameOmEt:
1269         case EthiopicHalehameSidEt:
1270         case EthiopicHalehameSoEt:
1271         case EthiopicHalehameTiEr:
1272         case EthiopicHalehameTiEt:
1273         case EthiopicHalehameTig:
1274         case Georgian:
1275         case Gujarati:
1276         case Gurmukhi:
1277         case Hangul:
1278         case HangulConsonant:
1279         case Hebrew:
1280         case Hiragana:
1281         case HiraganaIroha:
1282         case Kannada:
1283         case Katakana:
1284         case KatakanaIroha:
1285         case Khmer:
1286         case Lao:
1287         case LowerAlpha:
1288         case LowerArmenian:
1289         case LowerGreek:
1290         case LowerHexadecimal:
1291         case LowerLatin:
1292         case LowerNorwegian:
1293         case LowerRoman:
1294         case Malayalam:
1295         case Mongolian:
1296         case Myanmar:
1297         case Octal:
1298         case Oriya:
1299         case Oromo:
1300         case Persian:
1301         case Sidama:
1302         case Somali:
1303         case Telugu:
1304         case Thai:
1305         case Tibetan:
1306         case Tigre:
1307         case TigrinyaEr:
1308         case TigrinyaErAbegede:
1309         case TigrinyaEt:
1310         case TigrinyaEtAbegede:
1311         case UpperAlpha:
1312         case UpperArmenian:
1313         case UpperGreek:
1314         case UpperHexadecimal:
1315         case UpperLatin:
1316         case UpperNorwegian:
1317         case UpperRoman:
1318         case Urdu:
1319         case Asterisks:
1320         case Footnotes:
1321             break;
1322     }
1323     if (m_text.isEmpty())
1324         return;
1325 
1326     const FontCascade& font = style().fontCascade();
1327     TextRun textRun = RenderBlock::constructTextRun(m_text, style());
1328 
1329     GraphicsContextStateSaver stateSaver(context, false);
1330     if (!style().isHorizontalWritingMode()) {
1331         markerRect.moveBy(-boxOrigin);
1332         markerRect = markerRect.transposedRect();
1333         markerRect.moveBy(FloatPoint(box.x(), box.y() - logicalHeight()));
1334         stateSaver.save();
1335         context.translate(markerRect.x(), markerRect.maxY());
1336         context.rotate(static_cast<float>(deg2rad(90.)));
1337         context.translate(-markerRect.x(), -markerRect.maxY());
1338     }
1339 
1340     FloatPoint textOrigin = FloatPoint(markerRect.x(), markerRect.y() + style().fontMetrics().ascent());
1341     textOrigin = roundPointToDevicePixels(LayoutPoint(textOrigin), document().deviceScaleFactor(), style().isLeftToRightDirection());
1342 
1343     if (type == Asterisks || type == Footnotes)
1344         context.drawText(font, textRun, textOrigin);
1345     else {
1346         const UChar suffix = listMarkerSuffix(type, m_listItem.value());
1347 
1348         // Text is not arbitrary. We can judge whether it's RTL from the first character,
1349         // and we only need to handle the direction U_RIGHT_TO_LEFT for now.
1350         bool textNeedsReversing = u_charDirection(m_text[0]) == U_RIGHT_TO_LEFT;
1351         String toDraw;
1352         if (textNeedsReversing) {
1353             unsigned length = m_text.length();
1354             StringBuilder buffer;
1355             buffer.reserveCapacity(length + 2);
1356             if (!style().isLeftToRightDirection()) {
1357                 buffer.append(space);
1358                 buffer.append(suffix);
1359             }
1360             for (unsigned i = 0; i < length; ++i)
1361                 buffer.append(m_text[length - i - 1]);
1362             if (style().isLeftToRightDirection()) {
1363                 buffer.append(suffix);
1364                 buffer.append(space);
1365             }
1366             toDraw = buffer.toString();
1367         } else {
1368             if (style().isLeftToRightDirection())
1369                 toDraw = m_text + String(&suffix, 1) + String(&space, 1);
1370             else
1371                 toDraw = String(&space, 1) + String(&suffix, 1) + m_text;
1372         }
1373         textRun.setText(StringView(toDraw));
1374 
1375         context.drawText(font, textRun, textOrigin);
1376     }
1377 }
1378 
1379 void RenderListMarker::layout()
1380 {
1381     StackStats::LayoutCheckPoint layoutCheckPoint;
1382     ASSERT(needsLayout());
1383 
1384     LayoutUnit blockOffset;
1385     for (auto* ancestor = parentBox(); ancestor && ancestor != &m_listItem; ancestor = ancestor->parentBox())
1386         blockOffset += ancestor->logicalTop();
1387     if (style().isLeftToRightDirection())
1388         m_lineOffsetForListItem = m_listItem.logicalLeftOffsetForLine(blockOffset, DoNotIndentText, LayoutUnit());
1389     else
1390         m_lineOffsetForListItem = m_listItem.logicalRightOffsetForLine(blockOffset, DoNotIndentText, LayoutUnit());
1391 
1392     if (isImage()) {
1393         updateMarginsAndContent();
1394         setWidth(m_image->imageSize(this, style().effectiveZoom()).width());
1395         setHeight(m_image->imageSize(this, style().effectiveZoom()).height());
1396     } else {
1397         setLogicalWidth(minPreferredLogicalWidth());
1398         setLogicalHeight(style().fontMetrics().height());
1399     }
1400 
1401     setMarginStart(0);
1402     setMarginEnd(0);
1403 
1404     Length startMargin = style().marginStart();
1405     Length endMargin = style().marginEnd();
1406     if (startMargin.isFixed())
1407         setMarginStart(startMargin.value());
1408     if (endMargin.isFixed())
1409         setMarginEnd(endMargin.value());
1410 
1411     clearNeedsLayout();
1412 }
1413 
1414 void RenderListMarker::imageChanged(WrappedImagePtr o, const IntRect*)
1415 {
1416     // A list marker can't have a background or border image, so no need to call the base class method.
1417     if (o != m_image->data())
1418         return;
1419 
1420     if (width() != m_image->imageSize(this, style().effectiveZoom()).width() || height() != m_image->imageSize(this, style().effectiveZoom()).height() || m_image->errorOccurred())
1421         setNeedsLayoutAndPrefWidthsRecalc();
1422     else
1423         repaint();
1424 }
1425 
1426 void RenderListMarker::updateMarginsAndContent()
1427 {
1428     updateContent();
1429     updateMargins();
1430 }
1431 
1432 void RenderListMarker::updateContent()
1433 {
1434     // FIXME: This if-statement is just a performance optimization, but it's messy to use the preferredLogicalWidths dirty bit for this.
1435     // It's unclear if this is a premature optimization.
1436     if (!preferredLogicalWidthsDirty())
1437         return;
1438 
1439     m_text = emptyString();
1440 
1441     if (isImage()) {
1442         // FIXME: This is a somewhat arbitrary width.  Generated images for markers really won't become particularly useful
1443         // until we support the CSS3 marker pseudoclass to allow control over the width and height of the marker box.
1444         LayoutUnit bulletWidth = style().fontMetrics().ascent() / LayoutUnit(2);
1445         LayoutSize defaultBulletSize(bulletWidth, bulletWidth);
1446         LayoutSize imageSize = calculateImageIntrinsicDimensions(m_image.get(), defaultBulletSize, DoNotScaleByEffectiveZoom);
1447         m_image->setContainerContextForRenderer(*this, imageSize, style().effectiveZoom());
1448         return;
1449     }
1450 
1451     EListStyleType type = style().listStyleType();
1452     switch (type) {
1453     case NoneListStyle:
1454         break;
1455     case Circle:
1456     case Disc:
1457     case Square:
1458         m_text = listMarkerText(type, 0); // value is ignored for these types
1459         break;
1460     case Asterisks:
1461     case Footnotes:
1462     case Afar:
1463     case Amharic:
1464     case AmharicAbegede:
1465     case ArabicIndic:
1466     case Armenian:
1467     case BinaryListStyle:
1468     case Bengali:
1469     case Cambodian:
1470     case CJKIdeographic:
1471     case CjkEarthlyBranch:
1472     case CjkHeavenlyStem:
1473     case DecimalLeadingZero:
1474     case DecimalListStyle:
1475     case Devanagari:
1476     case Ethiopic:
1477     case EthiopicAbegede:
1478     case EthiopicAbegedeAmEt:
1479     case EthiopicAbegedeGez:
1480     case EthiopicAbegedeTiEr:
1481     case EthiopicAbegedeTiEt:
1482     case EthiopicHalehameAaEr:
1483     case EthiopicHalehameAaEt:
1484     case EthiopicHalehameAmEt:
1485     case EthiopicHalehameGez:
1486     case EthiopicHalehameOmEt:
1487     case EthiopicHalehameSidEt:
1488     case EthiopicHalehameSoEt:
1489     case EthiopicHalehameTiEr:
1490     case EthiopicHalehameTiEt:
1491     case EthiopicHalehameTig:
1492     case Georgian:
1493     case Gujarati:
1494     case Gurmukhi:
1495     case Hangul:
1496     case HangulConsonant:
1497     case Hebrew:
1498     case Hiragana:
1499     case HiraganaIroha:
1500     case Kannada:
1501     case Katakana:
1502     case KatakanaIroha:
1503     case Khmer:
1504     case Lao:
1505     case LowerAlpha:
1506     case LowerArmenian:
1507     case LowerGreek:
1508     case LowerHexadecimal:
1509     case LowerLatin:
1510     case LowerNorwegian:
1511     case LowerRoman:
1512     case Malayalam:
1513     case Mongolian:
1514     case Myanmar:
1515     case Octal:
1516     case Oriya:
1517     case Oromo:
1518     case Persian:
1519     case Sidama:
1520     case Somali:
1521     case Telugu:
1522     case Thai:
1523     case Tibetan:
1524     case Tigre:
1525     case TigrinyaEr:
1526     case TigrinyaErAbegede:
1527     case TigrinyaEt:
1528     case TigrinyaEtAbegede:
1529     case UpperAlpha:
1530     case UpperArmenian:
1531     case UpperGreek:
1532     case UpperHexadecimal:
1533     case UpperLatin:
1534     case UpperNorwegian:
1535     case UpperRoman:
1536     case Urdu:
1537         m_text = listMarkerText(type, m_listItem.value());
1538         break;
1539     }
1540 }
1541 
1542 void RenderListMarker::computePreferredLogicalWidths()
1543 {
1544     ASSERT(preferredLogicalWidthsDirty());
1545     updateContent();
1546 
1547     if (isImage()) {
1548         LayoutSize imageSize = LayoutSize(m_image->imageSize(this, style().effectiveZoom()));
1549         m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth = style().isHorizontalWritingMode() ? imageSize.width() : imageSize.height();
1550         setPreferredLogicalWidthsDirty(false);
1551         updateMargins();
1552         return;
1553     }
1554 
1555     const FontCascade& font = style().fontCascade();
1556 
1557     LayoutUnit logicalWidth = 0;
1558     EListStyleType type = style().listStyleType();
1559     switch (type) {
1560         case NoneListStyle:
1561             break;
1562         case Asterisks:
1563         case Footnotes: {
1564             TextRun run = RenderBlock::constructTextRun(m_text, style());
1565             logicalWidth = font.width(run); // no suffix for these types
1566         }
1567             break;
1568         case Circle:
1569         case Disc:
1570         case Square:
1571             logicalWidth = (font.fontMetrics().ascent() * 2 / 3 + 1) / 2 + 2;
1572             break;
1573         case Afar:
1574         case Amharic:
1575         case AmharicAbegede:
1576         case ArabicIndic:
1577         case Armenian:
1578         case BinaryListStyle:
1579         case Bengali:
1580         case Cambodian:
1581         case CJKIdeographic:
1582         case CjkEarthlyBranch:
1583         case CjkHeavenlyStem:
1584         case DecimalLeadingZero:
1585         case DecimalListStyle:
1586         case Devanagari:
1587         case Ethiopic:
1588         case EthiopicAbegede:
1589         case EthiopicAbegedeAmEt:
1590         case EthiopicAbegedeGez:
1591         case EthiopicAbegedeTiEr:
1592         case EthiopicAbegedeTiEt:
1593         case EthiopicHalehameAaEr:
1594         case EthiopicHalehameAaEt:
1595         case EthiopicHalehameAmEt:
1596         case EthiopicHalehameGez:
1597         case EthiopicHalehameOmEt:
1598         case EthiopicHalehameSidEt:
1599         case EthiopicHalehameSoEt:
1600         case EthiopicHalehameTiEr:
1601         case EthiopicHalehameTiEt:
1602         case EthiopicHalehameTig:
1603         case Georgian:
1604         case Gujarati:
1605         case Gurmukhi:
1606         case Hangul:
1607         case HangulConsonant:
1608         case Hebrew:
1609         case Hiragana:
1610         case HiraganaIroha:
1611         case Kannada:
1612         case Katakana:
1613         case KatakanaIroha:
1614         case Khmer:
1615         case Lao:
1616         case LowerAlpha:
1617         case LowerArmenian:
1618         case LowerGreek:
1619         case LowerHexadecimal:
1620         case LowerLatin:
1621         case LowerNorwegian:
1622         case LowerRoman:
1623         case Malayalam:
1624         case Mongolian:
1625         case Myanmar:
1626         case Octal:
1627         case Oriya:
1628         case Oromo:
1629         case Persian:
1630         case Sidama:
1631         case Somali:
1632         case Telugu:
1633         case Thai:
1634         case Tibetan:
1635         case Tigre:
1636         case TigrinyaEr:
1637         case TigrinyaErAbegede:
1638         case TigrinyaEt:
1639         case TigrinyaEtAbegede:
1640         case UpperAlpha:
1641         case UpperArmenian:
1642         case UpperGreek:
1643         case UpperHexadecimal:
1644         case UpperLatin:
1645         case UpperNorwegian:
1646         case UpperRoman:
1647         case Urdu:
1648             if (m_text.isEmpty())
1649                 logicalWidth = 0;
1650             else {
1651                 TextRun run = RenderBlock::constructTextRun(m_text, style());
1652                 LayoutUnit itemWidth = font.width(run);
1653                 UChar suffixSpace[2] = { listMarkerSuffix(type, m_listItem.value()), ' ' };
1654                 LayoutUnit suffixSpaceWidth = font.width(RenderBlock::constructTextRun(suffixSpace, 2, style()));
1655                 logicalWidth = itemWidth + suffixSpaceWidth;
1656             }
1657             break;
1658     }
1659 
1660     m_minPreferredLogicalWidth = logicalWidth;
1661     m_maxPreferredLogicalWidth = logicalWidth;
1662 
1663     setPreferredLogicalWidthsDirty(false);
1664 
1665     updateMargins();
1666 }
1667 
1668 void RenderListMarker::updateMargins()
1669 {
1670     const FontMetrics& fontMetrics = style().fontMetrics();
1671 
1672     LayoutUnit marginStart = 0;
1673     LayoutUnit marginEnd = 0;
1674 
1675     if (isInside()) {
1676         if (isImage())
1677             marginEnd = cMarkerPadding;
1678         else switch (style().listStyleType()) {
1679             case Disc:
1680             case Circle:
1681             case Square:
1682                 marginStart = -1;
1683                 marginEnd = fontMetrics.ascent() - minPreferredLogicalWidth() + 1;
1684                 break;
1685             default:
1686                 break;
1687         }
1688     } else {
1689         if (style().isLeftToRightDirection()) {
1690             if (isImage())
1691                 marginStart = -minPreferredLogicalWidth() - cMarkerPadding;
1692             else {
1693                 int offset = fontMetrics.ascent() * 2 / 3;
1694                 switch (style().listStyleType()) {
1695                     case Disc:
1696                     case Circle:
1697                     case Square:
1698                         marginStart = -offset - cMarkerPadding - 1;
1699                         break;
1700                     case NoneListStyle:
1701                         break;
1702                     default:
1703                         marginStart = m_text.isEmpty() ? LayoutUnit() : -minPreferredLogicalWidth() - offset / 2;
1704                 }
1705             }
1706             marginEnd = -marginStart - minPreferredLogicalWidth();
1707         } else {
1708             if (isImage())
1709                 marginEnd = cMarkerPadding;
1710             else {
1711                 int offset = fontMetrics.ascent() * 2 / 3;
1712                 switch (style().listStyleType()) {
1713                     case Disc:
1714                     case Circle:
1715                     case Square:
1716                         marginEnd = offset + cMarkerPadding + 1 - minPreferredLogicalWidth();
1717                         break;
1718                     case NoneListStyle:
1719                         break;
1720                     default:
1721                         marginEnd = m_text.isEmpty() ? 0 : offset / 2;
1722                 }
1723             }
1724             marginStart = -marginEnd - minPreferredLogicalWidth();
1725         }
1726 
1727     }
1728 
1729     mutableStyle().setMarginStart(Length(marginStart, Fixed));
1730     mutableStyle().setMarginEnd(Length(marginEnd, Fixed));
1731 }
1732 
1733 LayoutUnit RenderListMarker::lineHeight(bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
1734 {
1735     if (!isImage())
1736         return m_listItem.lineHeight(firstLine, direction, PositionOfInteriorLineBoxes);
1737     return RenderBox::lineHeight(firstLine, direction, linePositionMode);
1738 }
1739 
1740 int RenderListMarker::baselinePosition(FontBaseline baselineType, bool firstLine, LineDirectionMode direction, LinePositionMode linePositionMode) const
1741 {
1742     if (!isImage())
1743         return m_listItem.baselinePosition(baselineType, firstLine, direction, PositionOfInteriorLineBoxes);
1744     return RenderBox::baselinePosition(baselineType, firstLine, direction, linePositionMode);
1745 }
1746 
1747 String RenderListMarker::suffix() const
1748 {
1749     EListStyleType type = style().listStyleType();
1750     const UChar suffix = listMarkerSuffix(type, m_listItem.value());
1751 
1752     if (suffix == ' ')
1753         return String(" ");
1754 
1755     // If the suffix is not ' ', an extra space is needed
1756     UChar data[2];
1757     if (style().isLeftToRightDirection()) {
1758         data[0] = suffix;
1759         data[1] = ' ';
1760     } else {
1761         data[0] = ' ';
1762         data[1] = suffix;
1763     }
1764 
1765     return String(data, 2);
1766 }
1767 
1768 bool RenderListMarker::isInside() const
1769 {
1770     return m_listItem.notInList() || style().listStylePosition() == INSIDE;
1771 }
1772 
1773 FloatRect RenderListMarker::getRelativeMarkerRect()
1774 {
1775     if (isImage())
1776         return FloatRect(0, 0, m_image->imageSize(this, style().effectiveZoom()).width(), m_image->imageSize(this, style().effectiveZoom()).height());
1777 
1778     FloatRect relativeRect;
1779     EListStyleType type = style().listStyleType();
1780     switch (type) {
1781         case Asterisks:
1782         case Footnotes: {
1783             const FontCascade& font = style().fontCascade();
1784             TextRun run = RenderBlock::constructTextRun(m_text, style());
1785             relativeRect = FloatRect(0, 0, font.width(run), font.fontMetrics().height());
1786             break;
1787         }
1788         case Disc:
1789         case Circle:
1790         case Square: {
1791             // FIXME: Are these particular rounding rules necessary?
1792             const FontMetrics& fontMetrics = style().fontMetrics();
1793             int ascent = fontMetrics.ascent();
1794             int bulletWidth = (ascent * 2 / 3 + 1) / 2;
1795             relativeRect = FloatRect(1, 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth);
1796             break;
1797         }
1798         case NoneListStyle:
1799             return FloatRect();
1800         case Afar:
1801         case Amharic:
1802         case AmharicAbegede:
1803         case ArabicIndic:
1804         case Armenian:
1805         case BinaryListStyle:
1806         case Bengali:
1807         case Cambodian:
1808         case CJKIdeographic:
1809         case CjkEarthlyBranch:
1810         case CjkHeavenlyStem:
1811         case DecimalLeadingZero:
1812         case DecimalListStyle:
1813         case Devanagari:
1814         case Ethiopic:
1815         case EthiopicAbegede:
1816         case EthiopicAbegedeAmEt:
1817         case EthiopicAbegedeGez:
1818         case EthiopicAbegedeTiEr:
1819         case EthiopicAbegedeTiEt:
1820         case EthiopicHalehameAaEr:
1821         case EthiopicHalehameAaEt:
1822         case EthiopicHalehameAmEt:
1823         case EthiopicHalehameGez:
1824         case EthiopicHalehameOmEt:
1825         case EthiopicHalehameSidEt:
1826         case EthiopicHalehameSoEt:
1827         case EthiopicHalehameTiEr:
1828         case EthiopicHalehameTiEt:
1829         case EthiopicHalehameTig:
1830         case Georgian:
1831         case Gujarati:
1832         case Gurmukhi:
1833         case Hangul:
1834         case HangulConsonant:
1835         case Hebrew:
1836         case Hiragana:
1837         case HiraganaIroha:
1838         case Kannada:
1839         case Katakana:
1840         case KatakanaIroha:
1841         case Khmer:
1842         case Lao:
1843         case LowerAlpha:
1844         case LowerArmenian:
1845         case LowerGreek:
1846         case LowerHexadecimal:
1847         case LowerLatin:
1848         case LowerNorwegian:
1849         case LowerRoman:
1850         case Malayalam:
1851         case Mongolian:
1852         case Myanmar:
1853         case Octal:
1854         case Oriya:
1855         case Oromo:
1856         case Persian:
1857         case Sidama:
1858         case Somali:
1859         case Telugu:
1860         case Thai:
1861         case Tibetan:
1862         case Tigre:
1863         case TigrinyaEr:
1864         case TigrinyaErAbegede:
1865         case TigrinyaEt:
1866         case TigrinyaEtAbegede:
1867         case UpperAlpha:
1868         case UpperArmenian:
1869         case UpperGreek:
1870         case UpperHexadecimal:
1871         case UpperLatin:
1872         case UpperNorwegian:
1873         case UpperRoman:
1874         case Urdu:
1875             if (m_text.isEmpty())
1876                 return FloatRect();
1877             const FontCascade& font = style().fontCascade();
1878             TextRun run = RenderBlock::constructTextRun(m_text, style());
1879             float itemWidth = font.width(run);
1880             UChar suffixSpace[2] = { listMarkerSuffix(type, m_listItem.value()), ' ' };
1881             float suffixSpaceWidth = font.width(RenderBlock::constructTextRun(suffixSpace, 2, style()));
1882             relativeRect = FloatRect(0, 0, itemWidth + suffixSpaceWidth, font.fontMetrics().height());
1883     }
1884 
1885     if (!style().isHorizontalWritingMode()) {
1886         relativeRect = relativeRect.transposedRect();
1887         relativeRect.setX(width() - relativeRect.x() - relativeRect.width());
1888     }
1889 
1890     return relativeRect;
1891 }
1892 
1893 void RenderListMarker::setSelectionState(SelectionState state)
1894 {
1895     // The selection state for our containing block hierarchy is updated by the base class call.
1896     RenderBox::setSelectionState(state);
1897 
1898     if (m_inlineBoxWrapper && canUpdateSelectionOnRootLineBoxes())
1899         m_inlineBoxWrapper->root().setHasSelectedChildren(state != SelectionNone);
1900 }
1901 
1902 LayoutRect RenderListMarker::selectionRectForRepaint(const RenderLayerModelObject* repaintContainer, bool clipToVisibleContent)
1903 {
1904     ASSERT(!needsLayout());
1905 
1906     if (selectionState() == SelectionNone || !inlineBoxWrapper())
1907         return LayoutRect();
1908 
1909     RootInlineBox& rootBox = inlineBoxWrapper()->root();
1910     LayoutRect rect(0, rootBox.selectionTop() - y(), width(), rootBox.selectionHeight());
1911 
1912     if (clipToVisibleContent)
1913         return computeRectForRepaint(rect, repaintContainer);
1914     return localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
1915 }
1916 
1917 } // namespace WebCore