• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  */
23 
24 #include "config.h"
25 #include "RenderListMarker.h"
26 
27 #include "CachedImage.h"
28 #include "CharacterNames.h"
29 #include "Document.h"
30 #include "GraphicsContext.h"
31 #include "RenderLayer.h"
32 #include "RenderListItem.h"
33 #include "RenderView.h"
34 
35 using namespace std;
36 using namespace WTF;
37 using namespace Unicode;
38 
39 namespace WebCore {
40 
41 const int cMarkerPadding = 7;
42 
toRoman(int number,bool upper)43 static String toRoman(int number, bool upper)
44 {
45     // FIXME: CSS3 describes how to make this work for much larger numbers,
46     // using overbars and special characters. It also specifies the characters
47     // in the range U+2160 to U+217F instead of standard ASCII ones.
48     if (number < 1 || number > 3999)
49         return String::number(number);
50 
51     const int lettersSize = 12; // big enough for three each of I, X, C, and M
52     UChar letters[lettersSize];
53 
54     int length = 0;
55     const UChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' };
56     const UChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' };
57     const UChar* 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 
toAlphabetic(int number,const UChar * alphabet,int alphabetSize)78 static String toAlphabetic(int number, const UChar* alphabet, int alphabetSize)
79 {
80     ASSERT(alphabetSize >= 10);
81 
82     if (number < 1)
83         return String::number(number);
84 
85     const int lettersSize = 10; // big enough for a 32-bit int, with a 10-letter alphabet
86     UChar letters[lettersSize];
87 
88     --number;
89     letters[lettersSize - 1] = alphabet[number % alphabetSize];
90     int length = 1;
91     while ((number /= alphabetSize) > 0)
92         letters[lettersSize - ++length] = alphabet[number % alphabetSize - 1];
93 
94     ASSERT(length <= lettersSize);
95     return String(&letters[lettersSize - length], length);
96 }
97 
toHebrewUnder1000(int number,UChar letters[5])98 static int toHebrewUnder1000(int number, UChar letters[5])
99 {
100     // FIXME: CSS3 mentions various refinements not implemented here.
101     // FIXME: Should take a look at Mozilla's HebrewToText function (in nsBulletFrame).
102     ASSERT(number >= 0 && number < 1000);
103     int length = 0;
104     int fourHundreds = number / 400;
105     for (int i = 0; i < fourHundreds; i++)
106         letters[length++] = 1511 + 3;
107     number %= 400;
108     if (number / 100)
109         letters[length++] = 1511 + (number / 100) - 1;
110     number %= 100;
111     if (number == 15 || number == 16) {
112         letters[length++] = 1487 + 9;
113         letters[length++] = 1487 + number - 9;
114     } else {
115         if (int tens = number / 10) {
116             static const UChar hebrewTens[9] = { 1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510 };
117             letters[length++] = hebrewTens[tens - 1];
118         }
119         if (int ones = number % 10)
120             letters[length++] = 1487 + ones;
121     }
122     ASSERT(length <= 5);
123     return length;
124 }
125 
toHebrew(int number)126 static String toHebrew(int number)
127 {
128     // FIXME: CSS3 mentions ways to make this work for much larger numbers.
129     if (number < 0 || number > 999999)
130         return String::number(number);
131 
132     if (number == 0) {
133         static const UChar hebrewZero[3] = { 0x05D0, 0x05E4, 0x05E1 };
134         return String(hebrewZero, 3);
135     }
136 
137     const int lettersSize = 11; // big enough for two 5-digit sequences plus a quote mark between
138     UChar letters[lettersSize];
139 
140     int length;
141     if (number < 1000)
142         length = 0;
143     else {
144         length = toHebrewUnder1000(number / 1000, letters);
145         letters[length++] = '\'';
146         number = number % 1000;
147     }
148     length += toHebrewUnder1000(number, letters + length);
149 
150     ASSERT(length <= lettersSize);
151     return String(letters, length);
152 }
153 
toArmenianUnder10000(int number,bool upper,bool addCircumflex,UChar letters[9])154 static int toArmenianUnder10000(int number, bool upper, bool addCircumflex, UChar letters[9])
155 {
156     ASSERT(number >= 0 && number < 10000);
157     int length = 0;
158 
159     int lowerOffset = upper ? 0 : 0x0030;
160 
161     if (int thousands = number / 1000) {
162         if (thousands == 7) {
163             letters[length++] = 0x0548 + lowerOffset;
164             letters[length++] = 0x0552 + lowerOffset;
165             if (addCircumflex)
166                 letters[length++] = 0x0302;
167         } else {
168             letters[length++] = (0x054C - 1 + lowerOffset) + thousands;
169             if (addCircumflex)
170                 letters[length++] = 0x0302;
171         }
172     }
173 
174     if (int hundreds = (number / 100) % 10) {
175         letters[length++] = (0x0543 - 1 + lowerOffset) + hundreds;
176         if (addCircumflex)
177             letters[length++] = 0x0302;
178     }
179 
180     if (int tens = (number / 10) % 10) {
181         letters[length++] = (0x053A - 1 + lowerOffset) + tens;
182         if (addCircumflex)
183             letters[length++] = 0x0302;
184     }
185 
186     if (int ones = number % 10) {
187         letters[length++] = (0x531 - 1 + lowerOffset) + ones;
188         if (addCircumflex)
189             letters[length++] = 0x0302;
190     }
191 
192     return length;
193 }
194 
toArmenian(int number,bool upper)195 static String toArmenian(int number, bool upper)
196 {
197     if (number < 1 || number > 99999999)
198         return String::number(number);
199 
200     const int lettersSize = 18; // twice what toArmenianUnder10000 needs
201     UChar letters[lettersSize];
202 
203     int length = toArmenianUnder10000(number / 10000, upper, true, letters);
204     length += toArmenianUnder10000(number % 10000, upper, false, letters + length);
205 
206     ASSERT(length <= lettersSize);
207     return String(letters, length);
208 }
209 
toGeorgian(int number)210 static String toGeorgian(int number)
211 {
212     if (number < 1 || number > 19999)
213         return String::number(number);
214 
215     const int lettersSize = 5;
216     UChar letters[lettersSize];
217 
218     int length = 0;
219 
220     if (number > 9999)
221         letters[length++] = 0x10F5;
222 
223     if (int thousands = (number / 1000) % 10) {
224         static const UChar georgianThousands[9] = {
225             0x10E9, 0x10EA, 0x10EB, 0x10EC, 0x10ED, 0x10EE, 0x10F4, 0x10EF, 0x10F0
226         };
227         letters[length++] = georgianThousands[thousands - 1];
228     }
229 
230     if (int hundreds = (number / 100) % 10) {
231         static const UChar georgianHundreds[9] = {
232             0x10E0, 0x10E1, 0x10E2, 0x10F3, 0x10E4, 0x10E5, 0x10E6, 0x10E7, 0x10E8
233         };
234         letters[length++] = georgianHundreds[hundreds - 1];
235     }
236 
237     if (int tens = (number / 10) % 10) {
238         static const UChar georgianTens[9] = {
239             0x10D8, 0x10D9, 0x10DA, 0x10DB, 0x10DC, 0x10F2, 0x10DD, 0x10DE, 0x10DF
240         };
241         letters[length++] = georgianTens[tens - 1];
242     }
243 
244     if (int ones = number % 10) {
245         static const UChar georgianOnes[9] = {
246             0x10D0, 0x10D1, 0x10D2, 0x10D3, 0x10D4, 0x10D5, 0x10D6, 0x10F1, 0x10D7
247         };
248         letters[length++] = georgianOnes[ones - 1];
249     }
250 
251     ASSERT(length <= lettersSize);
252     return String(letters, length);
253 }
254 
255 // The table uses the order from the CSS3 specification:
256 // first 3 group markers, then 3 digit markers, then ten digits.
toCJKIdeographic(int number,const UChar table[16])257 static String toCJKIdeographic(int number, const UChar table[16])
258 {
259     if (number < 0)
260         return String::number(number);
261 
262     enum AbstractCJKChar {
263         noChar,
264         secondGroupMarker, thirdGroupMarker, fourthGroupMarker,
265         secondDigitMarker, thirdDigitMarker, fourthDigitMarker,
266         digit0, digit1, digit2, digit3, digit4,
267         digit5, digit6, digit7, digit8, digit9
268     };
269 
270     if (number == 0)
271         return String(&table[digit0 - 1], 1);
272 
273     const int groupLength = 8; // 4 digits, 3 digit markers, and a group marker
274     const int bufferLength = 4 * groupLength;
275     AbstractCJKChar buffer[bufferLength] = { noChar };
276 
277     for (int i = 0; i < 4; ++i) {
278         int groupValue = number % 10000;
279         number /= 10000;
280 
281         // Process least-significant group first, but put it in the buffer last.
282         AbstractCJKChar* group = &buffer[(3 - i) * groupLength];
283 
284         if (groupValue && i)
285             group[7] = static_cast<AbstractCJKChar>(secondGroupMarker - 1 + i);
286 
287         // Put in the four digits and digit markers for any non-zero digits.
288         group[6] = static_cast<AbstractCJKChar>(digit0 + (groupValue % 10));
289         if (number != 0 || groupValue > 9) {
290             int digitValue = ((groupValue / 10) % 10);
291             group[4] = static_cast<AbstractCJKChar>(digit0 + digitValue);
292             if (digitValue)
293                 group[5] = secondDigitMarker;
294         }
295         if (number != 0 || groupValue > 99) {
296             int digitValue = ((groupValue / 100) % 10);
297             group[2] = static_cast<AbstractCJKChar>(digit0 + digitValue);
298             if (digitValue)
299                 group[3] = thirdDigitMarker;
300         }
301         if (number != 0 || groupValue > 999) {
302             int digitValue = groupValue / 1000;
303             group[0] = static_cast<AbstractCJKChar>(digit0 + digitValue);
304             if (digitValue)
305                 group[1] = fourthDigitMarker;
306         }
307 
308         // Remove the tens digit, but leave the marker, for any group that has
309         // a value of less than 20.
310         if (groupValue < 20) {
311             ASSERT(group[4] == noChar || group[4] == digit0 || group[4] == digit1);
312             group[4] = noChar;
313         }
314 
315         if (number == 0)
316             break;
317     }
318 
319     // Convert into characters, omitting consecutive runs of digit0 and
320     // any trailing digit0.
321     int length = 0;
322     UChar characters[bufferLength];
323     AbstractCJKChar last = noChar;
324     for (int i = 0; i < bufferLength; ++i) {
325         AbstractCJKChar a = buffer[i];
326         if (a != noChar) {
327             if (a != digit0 || last != digit0)
328                 characters[length++] = table[a - 1];
329             last = a;
330         }
331     }
332     if (last == digit0)
333         --length;
334 
335     return String(characters, length);
336 }
337 
listMarkerText(EListStyleType type,int value)338 String listMarkerText(EListStyleType type, int value)
339 {
340     switch (type) {
341         case LNONE:
342             return "";
343 
344         // We use the same characters for text security.
345         // See RenderText::setInternalString.
346         case CIRCLE:
347             return String(&whiteBullet, 1);
348         case DISC:
349             return String(&bullet, 1);
350         case SQUARE:
351             // The CSS 2.1 test suite uses U+25EE BLACK MEDIUM SMALL SQUARE
352             // instead, but I think this looks better.
353             return String(&blackSquare, 1);
354 
355         case LDECIMAL:
356             return String::number(value);
357         case DECIMAL_LEADING_ZERO:
358             if (value < -9 || value > 9)
359                 return String::number(value);
360             if (value < 0)
361                 return "-0" + String::number(-value); // -01 to -09
362             return "0" + String::number(value); // 00 to 09
363 
364         case LOWER_ALPHA:
365         case LOWER_LATIN: {
366             static const UChar lowerLatinAlphabet[26] = {
367                 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
368                 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
369             };
370             return toAlphabetic(value, lowerLatinAlphabet, 26);
371         }
372         case UPPER_ALPHA:
373         case UPPER_LATIN: {
374             static const UChar upperLatinAlphabet[26] = {
375                 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
376                 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
377             };
378             return toAlphabetic(value, upperLatinAlphabet, 26);
379         }
380         case LOWER_GREEK: {
381             static const UChar lowerGreekAlphabet[24] = {
382                 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8,
383                 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, 0x03C0,
384                 0x03C1, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9
385             };
386             return toAlphabetic(value, lowerGreekAlphabet, 24);
387         }
388 
389         case HIRAGANA: {
390             // FIXME: This table comes from the CSS3 draft, and is probably
391             // incorrect, given the comments in that draft.
392             static const UChar hiraganaAlphabet[48] = {
393                 0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, 0x304F,
394                 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, 0x305F,
395                 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, 0x306C, 0x306D,
396                 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, 0x307B, 0x307E, 0x307F,
397                 0x3080, 0x3081, 0x3082, 0x3084, 0x3086, 0x3088, 0x3089, 0x308A,
398                 0x308B, 0x308C, 0x308D, 0x308F, 0x3090, 0x3091, 0x3092, 0x3093
399             };
400             return toAlphabetic(value, hiraganaAlphabet, 48);
401         }
402         case HIRAGANA_IROHA: {
403             // FIXME: This table comes from the CSS3 draft, and is probably
404             // incorrect, given the comments in that draft.
405             static const UChar hiraganaIrohaAlphabet[47] = {
406                 0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068, 0x3061,
407                 0x308A, 0x306C, 0x308B, 0x3092, 0x308F, 0x304B, 0x3088, 0x305F,
408                 0x308C, 0x305D, 0x3064, 0x306D, 0x306A, 0x3089, 0x3080, 0x3046,
409                 0x3090, 0x306E, 0x304A, 0x304F, 0x3084, 0x307E, 0x3051, 0x3075,
410                 0x3053, 0x3048, 0x3066, 0x3042, 0x3055, 0x304D, 0x3086, 0x3081,
411                 0x307F, 0x3057, 0x3091, 0x3072, 0x3082, 0x305B, 0x3059
412             };
413             return toAlphabetic(value, hiraganaIrohaAlphabet, 47);
414         }
415         case KATAKANA: {
416             // FIXME: This table comes from the CSS3 draft, and is probably
417             // incorrect, given the comments in that draft.
418             static const UChar katakanaAlphabet[48] = {
419                 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, 0x30AF,
420                 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, 0x30BF,
421                 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, 0x30CD,
422                 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, 0x30DF,
423                 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA,
424                 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F0, 0x30F1, 0x30F2, 0x30F3
425             };
426             return toAlphabetic(value, katakanaAlphabet, 48);
427         }
428         case KATAKANA_IROHA: {
429             // FIXME: This table comes from the CSS3 draft, and is probably
430             // incorrect, given the comments in that draft.
431             static const UChar katakanaIrohaAlphabet[47] = {
432                 0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8, 0x30C1,
433                 0x30EA, 0x30CC, 0x30EB, 0x30F2, 0x30EF, 0x30AB, 0x30E8, 0x30BF,
434                 0x30EC, 0x30BD, 0x30C4, 0x30CD, 0x30CA, 0x30E9, 0x30E0, 0x30A6,
435                 0x30F0, 0x30CE, 0x30AA, 0x30AF, 0x30E4, 0x30DE, 0x30B1, 0x30D5,
436                 0x30B3, 0x30A8, 0x30C6, 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1,
437                 0x30DF, 0x30B7, 0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x30B9
438             };
439             return toAlphabetic(value, katakanaIrohaAlphabet, 47);
440         }
441 
442         case CJK_IDEOGRAPHIC: {
443             static const UChar traditionalChineseInformalTable[16] = {
444                 0x842C, 0x5104, 0x5146,
445                 0x5341, 0x767E, 0x5343,
446                 0x96F6, 0x4E00, 0x4E8C, 0x4E09, 0x56DB,
447                 0x4E94, 0x516D, 0x4E03, 0x516B, 0x4E5D
448             };
449             return toCJKIdeographic(value, traditionalChineseInformalTable);
450         }
451 
452         case LOWER_ROMAN:
453             return toRoman(value, false);
454         case UPPER_ROMAN:
455             return toRoman(value, true);
456 
457         case ARMENIAN:
458             // CSS3 says "armenian" means "lower-armenian".
459             // But the CSS2.1 test suite contains uppercase test results for "armenian",
460             // so we'll match the test suite.
461             return toArmenian(value, true);
462         case GEORGIAN:
463             return toGeorgian(value);
464         case HEBREW:
465             return toHebrew(value);
466     }
467 
468     ASSERT_NOT_REACHED();
469     return "";
470 }
471 
RenderListMarker(RenderListItem * item)472 RenderListMarker::RenderListMarker(RenderListItem* item)
473     : RenderBox(item->document())
474     , m_listItem(item)
475 {
476     // init RenderObject attributes
477     setInline(true);   // our object is Inline
478     setReplaced(true); // pretend to be replaced
479 }
480 
~RenderListMarker()481 RenderListMarker::~RenderListMarker()
482 {
483     if (m_image)
484         m_image->removeClient(this);
485 }
486 
styleWillChange(StyleDifference diff,const RenderStyle * newStyle)487 void RenderListMarker::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
488 {
489     if (style() && (newStyle->listStylePosition() != style()->listStylePosition() || newStyle->listStyleType() != style()->listStyleType()))
490         setNeedsLayoutAndPrefWidthsRecalc();
491 
492     RenderBox::styleWillChange(diff, newStyle);
493 }
494 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)495 void RenderListMarker::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
496 {
497     RenderBox::styleDidChange(diff, oldStyle);
498 
499     if (m_image != style()->listStyleImage()) {
500         if (m_image)
501             m_image->removeClient(this);
502         m_image = style()->listStyleImage();
503         if (m_image)
504             m_image->addClient(this);
505     }
506 }
507 
createInlineBox()508 InlineBox* RenderListMarker::createInlineBox()
509 {
510     InlineBox* result = RenderBox::createInlineBox();
511     result->setIsText(isText());
512     return result;
513 }
514 
isImage() const515 bool RenderListMarker::isImage() const
516 {
517     return m_image && !m_image->errorOccurred();
518 }
519 
paint(PaintInfo & paintInfo,int tx,int ty)520 void RenderListMarker::paint(PaintInfo& paintInfo, int tx, int ty)
521 {
522     if (paintInfo.phase != PaintPhaseForeground)
523         return;
524 
525     if (style()->visibility() != VISIBLE)
526         return;
527 
528     IntRect marker = getRelativeMarkerRect();
529     marker.move(tx, ty);
530 
531     IntRect box(tx + x(), ty + y(), width(), height());
532 
533     if (box.y() > paintInfo.rect.bottom() || box.y() + box.height() < paintInfo.rect.y())
534         return;
535 
536     if (hasBoxDecorations())
537         paintBoxDecorations(paintInfo, box.x(), box.y());
538 
539     GraphicsContext* context = paintInfo.context;
540 
541     if (isImage()) {
542 #if PLATFORM(MAC)
543         if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
544             paintCustomHighlight(tx, ty, style()->highlight(), true);
545 #endif
546         context->drawImage(m_image->image(this, marker.size()), marker.location());
547         if (selectionState() != SelectionNone) {
548             // FIXME: selectionRect() is in absolute, not painting coordinates.
549             context->fillRect(selectionRect(), selectionBackgroundColor());
550         }
551         return;
552     }
553 
554 #if PLATFORM(MAC)
555     // FIXME: paint gap between marker and list item proper
556     if (style()->highlight() != nullAtom && !paintInfo.context->paintingDisabled())
557         paintCustomHighlight(tx, ty, style()->highlight(), true);
558 #endif
559 
560     if (selectionState() != SelectionNone) {
561         // FIXME: selectionRect() is in absolute, not painting coordinates.
562         context->fillRect(selectionRect(), selectionBackgroundColor());
563     }
564 
565     const Color color(style()->color());
566     context->setStrokeColor(color);
567     context->setStrokeStyle(SolidStroke);
568     context->setStrokeThickness(1.0f);
569     context->setFillColor(color);
570 
571     switch (style()->listStyleType()) {
572         case DISC:
573             context->drawEllipse(marker);
574             return;
575         case CIRCLE:
576             context->setFillColor(Color::transparent);
577             context->drawEllipse(marker);
578             return;
579         case SQUARE:
580             context->drawRect(marker);
581             return;
582         case LNONE:
583             return;
584         case ARMENIAN:
585         case CJK_IDEOGRAPHIC:
586         case DECIMAL_LEADING_ZERO:
587         case GEORGIAN:
588         case HEBREW:
589         case HIRAGANA:
590         case HIRAGANA_IROHA:
591         case KATAKANA:
592         case KATAKANA_IROHA:
593         case LDECIMAL:
594         case LOWER_ALPHA:
595         case LOWER_GREEK:
596         case LOWER_LATIN:
597         case LOWER_ROMAN:
598         case UPPER_ALPHA:
599         case UPPER_LATIN:
600         case UPPER_ROMAN:
601             break;
602     }
603     if (m_text.isEmpty())
604         return;
605 
606     TextRun textRun(m_text);
607 
608     // Text is not arbitrary. We can judge whether it's RTL from the first character,
609     // and we only need to handle the direction RightToLeft for now.
610     bool textNeedsReversing = direction(m_text[0]) == RightToLeft;
611     Vector<UChar> reversedText;
612     if (textNeedsReversing) {
613         int length = m_text.length();
614         reversedText.grow(length);
615         for (int i = 0; i < length; ++i)
616             reversedText[length - i - 1] = m_text[i];
617         textRun = TextRun(reversedText.data(), length);
618     }
619 
620     const Font& font = style()->font();
621     if (style()->direction() == LTR) {
622         int width = font.width(textRun);
623         context->drawText(style()->font(), textRun, marker.location());
624         const UChar periodSpace[2] = { '.', ' ' };
625         context->drawText(style()->font(), TextRun(periodSpace, 2), marker.location() + IntSize(width, 0));
626     } else {
627         const UChar spacePeriod[2] = { ' ', '.' };
628         TextRun spacePeriodRun(spacePeriod, 2);
629         int width = font.width(spacePeriodRun);
630         context->drawText(style()->font(), spacePeriodRun, marker.location());
631         context->drawText(style()->font(), textRun, marker.location() + IntSize(width, 0));
632     }
633 }
634 
layout()635 void RenderListMarker::layout()
636 {
637     ASSERT(needsLayout());
638     ASSERT(!prefWidthsDirty());
639 
640     if (isImage()) {
641         setWidth(m_image->imageSize(this, style()->effectiveZoom()).width());
642         setHeight(m_image->imageSize(this, style()->effectiveZoom()).height());
643     } else {
644         setWidth(minPrefWidth());
645         setHeight(style()->font().height());
646     }
647 
648     m_marginLeft = m_marginRight = 0;
649 
650     Length leftMargin = style()->marginLeft();
651     Length rightMargin = style()->marginRight();
652     if (leftMargin.isFixed())
653         m_marginLeft = leftMargin.value();
654     if (rightMargin.isFixed())
655         m_marginRight = rightMargin.value();
656 
657     setNeedsLayout(false);
658 }
659 
imageChanged(WrappedImagePtr o,const IntRect *)660 void RenderListMarker::imageChanged(WrappedImagePtr o, const IntRect*)
661 {
662     // A list marker can't have a background or border image, so no need to call the base class method.
663     if (o != m_image->data())
664         return;
665 
666     if (width() != m_image->imageSize(this, style()->effectiveZoom()).width() || height() != m_image->imageSize(this, style()->effectiveZoom()).height() || m_image->errorOccurred())
667         setNeedsLayoutAndPrefWidthsRecalc();
668     else
669         repaint();
670 }
671 
calcPrefWidths()672 void RenderListMarker::calcPrefWidths()
673 {
674     ASSERT(prefWidthsDirty());
675 
676     m_text = "";
677 
678     const Font& font = style()->font();
679 
680     if (isImage()) {
681         // FIXME: This is a somewhat arbitrary width.  Generated images for markers really won't become particularly useful
682         // until we support the CSS3 marker pseudoclass to allow control over the width and height of the marker box.
683         int bulletWidth = font.ascent() / 2;
684         m_image->setImageContainerSize(IntSize(bulletWidth, bulletWidth));
685         m_minPrefWidth = m_maxPrefWidth = m_image->imageSize(this, style()->effectiveZoom()).width();
686         setPrefWidthsDirty(false);
687         updateMargins();
688         return;
689     }
690 
691     int width = 0;
692     EListStyleType type = style()->listStyleType();
693     switch (type) {
694         case LNONE:
695             break;
696         case CIRCLE:
697         case DISC:
698         case SQUARE:
699             m_text = listMarkerText(type, 0); // value is ignored for these types
700             width = (font.ascent() * 2 / 3 + 1) / 2 + 2;
701             break;
702         case ARMENIAN:
703         case CJK_IDEOGRAPHIC:
704         case DECIMAL_LEADING_ZERO:
705         case GEORGIAN:
706         case HEBREW:
707         case HIRAGANA:
708         case HIRAGANA_IROHA:
709         case KATAKANA:
710         case KATAKANA_IROHA:
711         case LDECIMAL:
712         case LOWER_ALPHA:
713         case LOWER_GREEK:
714         case LOWER_LATIN:
715         case LOWER_ROMAN:
716         case UPPER_ALPHA:
717         case UPPER_LATIN:
718         case UPPER_ROMAN:
719             m_text = listMarkerText(type, m_listItem->value());
720             if (m_text.isEmpty())
721                 width = 0;
722             else {
723                 int itemWidth = font.width(m_text);
724                 const UChar periodSpace[2] = { '.', ' ' };
725                 int periodSpaceWidth = font.width(TextRun(periodSpace, 2));
726                 width = itemWidth + periodSpaceWidth;
727             }
728             break;
729     }
730 
731     m_minPrefWidth = width;
732     m_maxPrefWidth = width;
733 
734     setPrefWidthsDirty(false);
735 
736     updateMargins();
737 }
738 
updateMargins()739 void RenderListMarker::updateMargins()
740 {
741     const Font& font = style()->font();
742 
743     int marginLeft = 0;
744     int marginRight = 0;
745 
746     if (isInside()) {
747         if (isImage()) {
748             if (style()->direction() == LTR)
749                 marginRight = cMarkerPadding;
750             else
751                 marginLeft = cMarkerPadding;
752         } else switch (style()->listStyleType()) {
753             case DISC:
754             case CIRCLE:
755             case SQUARE:
756                 if (style()->direction() == LTR) {
757                     marginLeft = -1;
758                     marginRight = font.ascent() - minPrefWidth() + 1;
759                 } else {
760                     marginLeft = font.ascent() - minPrefWidth() + 1;
761                     marginRight = -1;
762                 }
763                 break;
764             default:
765                 break;
766         }
767     } else {
768         if (style()->direction() == LTR) {
769             if (isImage())
770                 marginLeft = -minPrefWidth() - cMarkerPadding;
771             else {
772                 int offset = font.ascent() * 2 / 3;
773                 switch (style()->listStyleType()) {
774                     case DISC:
775                     case CIRCLE:
776                     case SQUARE:
777                         marginLeft = -offset - cMarkerPadding - 1;
778                         break;
779                     case LNONE:
780                         break;
781                     default:
782                         marginLeft = m_text.isEmpty() ? 0 : -minPrefWidth() - offset / 2;
783                 }
784             }
785         } else {
786             if (isImage())
787                 marginLeft = cMarkerPadding;
788             else {
789                 int offset = font.ascent() * 2 / 3;
790                 switch (style()->listStyleType()) {
791                     case DISC:
792                     case CIRCLE:
793                     case SQUARE:
794                         marginLeft = offset + cMarkerPadding + 1 - minPrefWidth();
795                         break;
796                     case LNONE:
797                         break;
798                     default:
799                         marginLeft = m_text.isEmpty() ? 0 : offset / 2;
800                 }
801             }
802         }
803         marginRight = -marginLeft - minPrefWidth();
804     }
805 
806     style()->setMarginLeft(Length(marginLeft, Fixed));
807     style()->setMarginRight(Length(marginRight, Fixed));
808 }
809 
lineHeight(bool,bool) const810 int RenderListMarker::lineHeight(bool, bool) const
811 {
812     if (!isImage())
813         return m_listItem->lineHeight(false, true);
814     return height();
815 }
816 
baselinePosition(bool,bool) const817 int RenderListMarker::baselinePosition(bool, bool) const
818 {
819     if (!isImage()) {
820         const Font& font = style()->font();
821         return font.ascent() + (lineHeight(false) - font.height())/2;
822     }
823     return height();
824 }
825 
isInside() const826 bool RenderListMarker::isInside() const
827 {
828     return m_listItem->notInList() || style()->listStylePosition() == INSIDE;
829 }
830 
getRelativeMarkerRect()831 IntRect RenderListMarker::getRelativeMarkerRect()
832 {
833     if (isImage())
834         return IntRect(x(), y(), m_image->imageSize(this, style()->effectiveZoom()).width(), m_image->imageSize(this, style()->effectiveZoom()).height());
835 
836     switch (style()->listStyleType()) {
837         case DISC:
838         case CIRCLE:
839         case SQUARE: {
840             // FIXME: Are these particular rounding rules necessary?
841             const Font& font = style()->font();
842             int ascent = font.ascent();
843             int bulletWidth = (ascent * 2 / 3 + 1) / 2;
844             return IntRect(x() + 1, y() + 3 * (ascent - ascent * 2 / 3) / 2, bulletWidth, bulletWidth);
845         }
846         case LNONE:
847             return IntRect();
848         case ARMENIAN:
849         case CJK_IDEOGRAPHIC:
850         case DECIMAL_LEADING_ZERO:
851         case GEORGIAN:
852         case HEBREW:
853         case HIRAGANA:
854         case HIRAGANA_IROHA:
855         case KATAKANA:
856         case KATAKANA_IROHA:
857         case LDECIMAL:
858         case LOWER_ALPHA:
859         case LOWER_GREEK:
860         case LOWER_LATIN:
861         case LOWER_ROMAN:
862         case UPPER_ALPHA:
863         case UPPER_LATIN:
864         case UPPER_ROMAN:
865             if (m_text.isEmpty())
866                 return IntRect();
867             const Font& font = style()->font();
868             int itemWidth = font.width(m_text);
869             const UChar periodSpace[2] = { '.', ' ' };
870             int periodSpaceWidth = font.width(TextRun(periodSpace, 2));
871             return IntRect(x(), y() + font.ascent(), itemWidth + periodSpaceWidth, font.height());
872     }
873 
874     return IntRect();
875 }
876 
setSelectionState(SelectionState state)877 void RenderListMarker::setSelectionState(SelectionState state)
878 {
879     RenderBox::setSelectionState(state);
880     if (InlineBox* box = inlineBoxWrapper())
881         if (RootInlineBox* root = box->root())
882             root->setHasSelectedChildren(state != SelectionNone);
883     containingBlock()->setSelectionState(state);
884 }
885 
selectionRectForRepaint(RenderBoxModelObject * repaintContainer,bool clipToVisibleContent)886 IntRect RenderListMarker::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent)
887 {
888     ASSERT(!needsLayout());
889 
890     if (selectionState() == SelectionNone || !inlineBoxWrapper())
891         return IntRect();
892 
893     RootInlineBox* root = inlineBoxWrapper()->root();
894     IntRect rect(0, root->selectionTop() - y(), width(), root->selectionHeight());
895 
896     if (clipToVisibleContent)
897         computeRectForRepaint(repaintContainer, rect);
898     else
899         rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox();
900 
901     return rect;
902 }
903 
904 } // namespace WebCore
905