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