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