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