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