1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CounterStyleManager.h"
11 #include "mozilla/ArenaObjectID.h"
12 #include "mozilla/ArrayUtils.h"
13 #include "mozilla/CheckedInt.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/PresShell.h"
16 #include "mozilla/Types.h"
17 #include "mozilla/WritingModes.h"
18 #include "nsPresContext.h"
19 #include "nsPresContextInlines.h"
22 #include "nsTHashtable.h"
23 #include "nsUnicodeProperties.h"
24 #include "mozilla/ServoBindings.h"
25 #include "mozilla/ServoStyleSet.h"
29 using AdditiveSymbol
= StyleAdditiveSymbol
;
32 nsString before
, after
;
40 // This limitation will be applied to some systems, and pad descriptor.
41 // Any initial representation generated by symbolic or additive which is
42 // longer than this limitation will be dropped. If any pad is longer
43 // than this, the whole counter text will be dropped as well.
44 // The spec requires user agents to support at least 60 Unicode code-
45 // points for counter text. However, this constant only limits the
46 // length in 16-bit units. So it has to be at least 120, since code-
47 // points outside the BMP will need 2 16-bit units.
48 #define LENGTH_LIMIT 150
50 static bool GetCyclicCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
51 Span
<const nsString
> aSymbols
) {
52 MOZ_ASSERT(aSymbols
.Length() >= 1, "No symbol available for cyclic counter.");
53 auto n
= CounterValue(aSymbols
.Length());
54 CounterValue index
= (aOrdinal
- 1) % n
;
55 aResult
= aSymbols
[index
>= 0 ? index
: index
+ n
];
59 static bool GetFixedCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
61 Span
<const nsString
> aSymbols
) {
62 CounterValue index
= aOrdinal
- aStart
;
63 if (index
>= 0 && index
< CounterValue(aSymbols
.Length())) {
64 aResult
= aSymbols
[index
];
71 static bool GetSymbolicCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
72 Span
<const nsString
> aSymbols
) {
73 MOZ_ASSERT(aSymbols
.Length() >= 1,
74 "No symbol available for symbolic counter.");
75 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
81 auto n
= aSymbols
.Length();
82 const nsString
& symbol
= aSymbols
[(aOrdinal
- 1) % n
];
83 size_t len
= (aOrdinal
+ n
- 1) / n
;
84 auto symbolLength
= symbol
.Length();
85 if (symbolLength
> 0) {
86 if (len
> LENGTH_LIMIT
|| symbolLength
> LENGTH_LIMIT
||
87 len
* symbolLength
> LENGTH_LIMIT
) {
90 for (size_t i
= 0; i
< len
; ++i
) {
91 aResult
.Append(symbol
);
97 static bool GetAlphabeticCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
98 Span
<const nsString
> aSymbols
) {
99 MOZ_ASSERT(aSymbols
.Length() >= 2, "Too few symbols for alphabetic counter.");
100 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
105 auto n
= aSymbols
.Length();
106 // The precise length of this array should be
107 // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
108 // The max length is slightly smaller than which defined below.
109 AutoTArray
<int32_t, std::numeric_limits
<CounterValue
>::digits
> indexes
;
110 while (aOrdinal
> 0) {
112 indexes
.AppendElement(aOrdinal
% n
);
117 for (auto i
= indexes
.Length(); i
> 0; --i
) {
118 aResult
.Append(aSymbols
[indexes
[i
- 1]]);
123 static bool GetNumericCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
124 Span
<const nsString
> aSymbols
) {
125 MOZ_ASSERT(aSymbols
.Length() >= 2, "Too few symbols for numeric counter.");
126 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
129 aResult
= aSymbols
[0];
133 auto n
= aSymbols
.Length();
134 AutoTArray
<int32_t, std::numeric_limits
<CounterValue
>::digits
> indexes
;
135 while (aOrdinal
> 0) {
136 indexes
.AppendElement(aOrdinal
% n
);
141 for (auto i
= indexes
.Length(); i
> 0; --i
) {
142 aResult
.Append(aSymbols
[indexes
[i
- 1]]);
147 static bool GetAdditiveCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
148 Span
<const AdditiveSymbol
> aSymbols
) {
149 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
152 const AdditiveSymbol
& last
= aSymbols
[aSymbols
.Length() - 1];
153 if (last
.weight
== 0) {
154 aResult
= last
.symbol
;
162 for (size_t i
= 0, iEnd
= aSymbols
.Length(); i
< iEnd
; ++i
) {
163 const AdditiveSymbol
& symbol
= aSymbols
[i
];
164 if (symbol
.weight
== 0) {
167 CounterValue times
= aOrdinal
/ symbol
.weight
;
169 auto symbolLength
= symbol
.symbol
.Length();
170 if (symbolLength
> 0) {
171 length
+= times
* symbolLength
;
172 if (times
> LENGTH_LIMIT
|| symbolLength
> LENGTH_LIMIT
||
173 length
> LENGTH_LIMIT
) {
176 for (CounterValue j
= 0; j
< times
; ++j
) {
177 aResult
.Append(symbol
.symbol
);
180 aOrdinal
-= times
* symbol
.weight
;
183 return aOrdinal
== 0;
186 static bool DecimalToText(CounterValue aOrdinal
, nsAString
& aResult
) {
187 aResult
.AppendInt(aOrdinal
);
191 // We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
192 // georgian needs 6 at most
193 // armenian needs 12 at most
194 // hebrew may need more...
196 #define NUM_BUF_SIZE 34
198 enum CJKIdeographicLang
{ CHINESE
, KOREAN
, JAPANESE
};
199 struct CJKIdeographicData
{
206 static const CJKIdeographicData gDataJapaneseInformal
= {
207 {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
209 {0x5341, 0x767e, 0x5343}, // unit
210 {0x4e07, 0x5104}, // unit10K
214 static const CJKIdeographicData gDataJapaneseFormal
= {
215 {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
217 {0x62fe, 0x767e, 0x9621}, // unit
218 {0x842c, 0x5104}, // unit10K
222 static const CJKIdeographicData gDataKoreanHangulFormal
= {
223 {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
225 {0xc2ed, 0xbc31, 0xcc9c}, // unit
226 {0xb9cc, 0xc5b5}, // unit10K
230 static const CJKIdeographicData gDataKoreanHanjaInformal
= {
231 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
233 {0x5341, 0x767e, 0x5343}, // unit
234 {0x842c, 0x5104}, // unit10K
238 static const CJKIdeographicData gDataKoreanHanjaFormal
= {
239 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
241 {0x62fe, 0x767e, 0x4edf}, // unit
242 {0x842c, 0x5104}, // unit10K
246 static const CJKIdeographicData gDataSimpChineseInformal
= {
247 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
249 {0x5341, 0x767e, 0x5343}, // unit
250 {0x4e07, 0x4ebf}, // unit10K
254 static const CJKIdeographicData gDataSimpChineseFormal
= {
255 {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
257 {0x62fe, 0x4f70, 0x4edf}, // unit
258 {0x4e07, 0x4ebf}, // unit10K
262 static const CJKIdeographicData gDataTradChineseInformal
= {
263 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
265 {0x5341, 0x767e, 0x5343}, // unit
266 {0x842c, 0x5104}, // unit10K
270 static const CJKIdeographicData gDataTradChineseFormal
= {
271 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
273 {0x62fe, 0x4f70, 0x4edf}, // unit
274 {0x842c, 0x5104}, // unit10K
279 static bool CJKIdeographicToText(CounterValue aOrdinal
, nsAString
& aResult
,
280 const CJKIdeographicData
& data
) {
281 NS_ASSERTION(aOrdinal
>= 0, "Only accept non-negative ordinal");
282 char16_t buf
[NUM_BUF_SIZE
];
283 int32_t idx
= NUM_BUF_SIZE
;
285 bool needZero
= (aOrdinal
== 0);
286 int32_t unitidx
= 0, unit10Kidx
= 0;
290 unit10Kidx
= pos
/ 4;
292 auto cur
= static_cast<std::make_unsigned_t
<CounterValue
>>(aOrdinal
) % 10;
296 buf
[--idx
] = data
.digit
[0];
299 if (data
.lang
== CHINESE
) {
302 if (unit10Kidx
!= 0) {
303 if (data
.lang
== KOREAN
&& idx
!= NUM_BUF_SIZE
) {
306 buf
[--idx
] = data
.unit10K
[unit10Kidx
- 1];
309 buf
[--idx
] = data
.unit
[unitidx
- 1];
312 buf
[--idx
] = data
.digit
[cur
];
319 (aOrdinal
== 1 || (pos
> 4 && aOrdinal
% 1000 == 1))) {
325 (unitidx
!= 3 || (pos
== 3 && aOrdinal
== 1))) {
330 if (unitidx
> 0 || (pos
== 4 && (aOrdinal
% 1000) == 1)) {
337 buf
[--idx
] = data
.digit
[1];
344 } while (aOrdinal
> 0);
345 aResult
.Assign(buf
+ idx
, NUM_BUF_SIZE
- idx
);
349 #define HEBREW_GERESH 0x05F3
350 static const char16_t gHebrewDigit
[22] = {
352 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
353 // 10 20 30 40 50 60 70 80 90
354 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
356 0x05E7, 0x05E8, 0x05E9, 0x05EA};
358 static bool HebrewToText(CounterValue aOrdinal
, nsAString
& aResult
) {
359 if (aOrdinal
< 1 || aOrdinal
> 999999) {
363 bool outputSep
= false;
364 nsAutoString allText
, thousandsGroup
;
366 thousandsGroup
.Truncate();
367 int32_t n3
= aOrdinal
% 1000;
368 // Process digit for 100 - 900
369 for (int32_t n1
= 400; n1
> 0;) {
372 thousandsGroup
.Append(gHebrewDigit
[(n1
/ 100) - 1 + 18]);
378 // Process digit for 10 - 90
381 // Special process for 15 and 16
382 if ((15 == n3
) || (16 == n3
)) {
383 // Special rule for religious reason...
384 // 15 is represented by 9 and 6, not 10 and 5
385 // 16 is represented by 9 and 7, not 10 and 6
387 thousandsGroup
.Append(gHebrewDigit
[n2
- 1]);
390 thousandsGroup
.Append(gHebrewDigit
[(n2
/ 10) - 1 + 9]);
395 // Process digit for 1 - 9
396 if (n3
> 0) thousandsGroup
.Append(gHebrewDigit
[n3
- 1]);
397 if (outputSep
) thousandsGroup
.Append((char16_t
)HEBREW_GERESH
);
398 if (allText
.IsEmpty())
399 allText
= thousandsGroup
;
401 allText
= thousandsGroup
+ allText
;
404 } while (aOrdinal
>= 1);
410 // Convert ordinal to Ethiopic numeric representation.
411 // The detail is available at http://www.ethiopic.org/Numerals/
412 // The algorithm used here is based on the pseudo-code put up there by
413 // Daniel Yacob <yacob@geez.org>.
414 // Another reference is Unicode 3.0 standard section 11.1.
415 #define ETHIOPIC_ONE 0x1369
416 #define ETHIOPIC_TEN 0x1372
417 #define ETHIOPIC_HUNDRED 0x137B
418 #define ETHIOPIC_TEN_THOUSAND 0x137C
420 static bool EthiopicToText(CounterValue aOrdinal
, nsAString
& aResult
) {
425 nsAutoString asciiNumberString
; // decimal string representation of ordinal
426 DecimalToText(aOrdinal
, asciiNumberString
);
427 uint8_t asciiStringLength
= asciiNumberString
.Length();
429 // If number length is odd, add a leading "0"
430 // the leading "0" preconditions the string to always have the
431 // leading tens place populated, this avoids a check within the loop.
432 // If we didn't add the leading "0", decrement asciiStringLength so
433 // it will be equivalent to a zero-based index in both cases.
434 if (asciiStringLength
& 1) {
435 asciiNumberString
.InsertLiteral(u
"0", 0);
441 // Iterate from the highest digits to lowest
442 // indexFromLeft indexes digits (0 = most significant)
443 // groupIndexFromRight indexes pairs of digits (0 = least significant)
444 for (uint8_t indexFromLeft
= 0, groupIndexFromRight
= asciiStringLength
>> 1;
445 indexFromLeft
<= asciiStringLength
;
446 indexFromLeft
+= 2, groupIndexFromRight
--) {
447 uint8_t tensValue
= asciiNumberString
.CharAt(indexFromLeft
) & 0x0F;
448 uint8_t unitsValue
= asciiNumberString
.CharAt(indexFromLeft
+ 1) & 0x0F;
449 uint8_t groupValue
= tensValue
* 10 + unitsValue
;
451 bool oddGroup
= (groupIndexFromRight
& 1);
453 // we want to clear ETHIOPIC_ONE when it is superfluous
454 if (aOrdinal
> 1 && groupValue
== 1 && // one without a leading ten
456 indexFromLeft
== 0)) { // preceding (100) or leading the sequence
460 // put it all together...
462 // map onto Ethiopic "tens":
463 aResult
.Append((char16_t
)(tensValue
+ ETHIOPIC_TEN
- 1));
466 // map onto Ethiopic "units":
467 aResult
.Append((char16_t
)(unitsValue
+ ETHIOPIC_ONE
- 1));
469 // Add a separator for all even groups except the last,
470 // and for odd groups with non-zero value.
473 aResult
.Append((char16_t
)ETHIOPIC_HUNDRED
);
476 if (groupIndexFromRight
) {
477 aResult
.Append((char16_t
)ETHIOPIC_TEN_THOUSAND
);
484 static SpeakAs
GetDefaultSpeakAsForSystem(StyleCounterSystem aSystem
) {
485 MOZ_ASSERT(aSystem
!= StyleCounterSystem::Extends
,
486 "Extends system does not have static default speak-as");
488 case StyleCounterSystem::Alphabetic
:
489 return SpeakAs::Spellout
;
490 case StyleCounterSystem::Cyclic
:
491 return SpeakAs::Bullets
;
493 return SpeakAs::Numbers
;
497 static bool SystemUsesNegativeSign(StyleCounterSystem aSystem
) {
498 MOZ_ASSERT(aSystem
!= StyleCounterSystem::Extends
,
499 "Cannot check this for extending style");
501 case StyleCounterSystem::Symbolic
:
502 case StyleCounterSystem::Alphabetic
:
503 case StyleCounterSystem::Numeric
:
504 case StyleCounterSystem::Additive
:
511 class BuiltinCounterStyle
: public CounterStyle
{
513 constexpr BuiltinCounterStyle(ListStyle aStyle
, nsStaticAtom
* aName
)
514 : CounterStyle(aStyle
), mName(aName
) {}
516 nsStaticAtom
* GetStyleName() const { return mName
; }
518 virtual void GetPrefix(nsAString
& aResult
) override
;
519 virtual void GetSuffix(nsAString
& aResult
) override
;
520 virtual void GetSpokenCounterText(CounterValue aOrdinal
,
521 WritingMode aWritingMode
,
523 bool& aIsBullet
) override
;
524 virtual bool IsBullet() override
;
526 virtual void GetNegative(NegativeType
& aResult
) override
;
527 virtual bool IsOrdinalInRange(CounterValue aOrdinal
) override
;
528 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal
) override
;
529 virtual void GetPad(PadType
& aResult
) override
;
530 virtual CounterStyle
* GetFallback() override
;
531 virtual SpeakAs
GetSpeakAs() override
;
532 virtual bool UseNegativeSign() override
;
534 virtual bool GetInitialCounterText(CounterValue aOrdinal
,
535 WritingMode aWritingMode
,
536 nsAString
& aResult
, bool& aIsRTL
) override
;
539 constexpr BuiltinCounterStyle(const BuiltinCounterStyle
& aOther
)
540 : CounterStyle(aOther
.mStyle
), mName(aOther
.mName
) {}
547 void BuiltinCounterStyle::GetPrefix(nsAString
& aResult
) { aResult
.Truncate(); }
550 void BuiltinCounterStyle::GetSuffix(nsAString
& aResult
) {
552 case ListStyle::None
:
556 case ListStyle::Disc
:
557 case ListStyle::Circle
:
558 case ListStyle::Square
:
559 case ListStyle::DisclosureClosed
:
560 case ListStyle::DisclosureOpen
:
561 case ListStyle::EthiopicNumeric
:
565 case ListStyle::TradChineseInformal
:
566 case ListStyle::TradChineseFormal
:
567 case ListStyle::SimpChineseInformal
:
568 case ListStyle::SimpChineseFormal
:
569 case ListStyle::JapaneseInformal
:
570 case ListStyle::JapaneseFormal
:
574 case ListStyle::KoreanHangulFormal
:
575 case ListStyle::KoreanHanjaInformal
:
576 case ListStyle::KoreanHanjaFormal
:
577 aResult
.AssignLiteral(u
", ");
581 aResult
.AssignLiteral(u
". ");
586 static const char16_t kDiscCharacter
= 0x2022;
587 static const char16_t kCircleCharacter
= 0x25e6;
588 static const char16_t kSquareCharacter
= 0x25aa;
589 static const char16_t kRightPointingCharacter
= 0x25b8;
590 static const char16_t kLeftPointingCharacter
= 0x25c2;
591 static const char16_t kDownPointingCharacter
= 0x25be;
594 void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
595 WritingMode aWritingMode
,
599 case ListStyle::None
:
600 case ListStyle::Disc
:
601 case ListStyle::Circle
:
602 case ListStyle::Square
:
603 case ListStyle::DisclosureClosed
:
604 case ListStyle::DisclosureOpen
: {
605 // Same as the initial representation
607 GetInitialCounterText(aOrdinal
, aWritingMode
, aResult
, isRTL
);
612 CounterStyle::GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
619 bool BuiltinCounterStyle::IsBullet() {
621 case ListStyle::Disc
:
622 case ListStyle::Circle
:
623 case ListStyle::Square
:
624 case ListStyle::DisclosureClosed
:
625 case ListStyle::DisclosureOpen
:
632 static const char16_t gJapaneseNegative
[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
634 static const char16_t gKoreanNegative
[] = {0xb9c8, 0xc774, 0xb108,
635 0xc2a4, 0x0020, 0x0000};
636 static const char16_t gSimpChineseNegative
[] = {0x8d1f, 0x0000};
637 static const char16_t gTradChineseNegative
[] = {0x8ca0, 0x0000};
640 void BuiltinCounterStyle::GetNegative(NegativeType
& aResult
) {
642 case ListStyle::JapaneseFormal
:
643 case ListStyle::JapaneseInformal
:
644 aResult
.before
= gJapaneseNegative
;
647 case ListStyle::KoreanHangulFormal
:
648 case ListStyle::KoreanHanjaInformal
:
649 case ListStyle::KoreanHanjaFormal
:
650 aResult
.before
= gKoreanNegative
;
653 case ListStyle::SimpChineseFormal
:
654 case ListStyle::SimpChineseInformal
:
655 aResult
.before
= gSimpChineseNegative
;
658 case ListStyle::TradChineseFormal
:
659 case ListStyle::TradChineseInformal
:
660 aResult
.before
= gTradChineseNegative
;
664 aResult
.before
.AssignLiteral(u
"-");
666 aResult
.after
.Truncate();
670 bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
674 case ListStyle::None
:
675 case ListStyle::Disc
:
676 case ListStyle::Circle
:
677 case ListStyle::Square
:
678 case ListStyle::DisclosureClosed
:
679 case ListStyle::DisclosureOpen
:
681 case ListStyle::Decimal
:
682 // use CJKIdeographicToText
683 case ListStyle::JapaneseFormal
:
684 case ListStyle::JapaneseInformal
:
685 case ListStyle::KoreanHanjaFormal
:
686 case ListStyle::KoreanHanjaInformal
:
687 case ListStyle::KoreanHangulFormal
:
688 case ListStyle::TradChineseFormal
:
689 case ListStyle::TradChineseInformal
:
690 case ListStyle::SimpChineseFormal
:
691 case ListStyle::SimpChineseInformal
:
694 // use EthiopicToText
695 case ListStyle::EthiopicNumeric
:
696 return aOrdinal
>= 1;
699 case ListStyle::Hebrew
:
700 return aOrdinal
>= 1 && aOrdinal
<= 999999;
705 bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
708 case ListStyle::None
:
709 case ListStyle::Disc
:
710 case ListStyle::Circle
:
711 case ListStyle::Square
:
712 case ListStyle::DisclosureClosed
:
713 case ListStyle::DisclosureOpen
:
715 case ListStyle::Decimal
:
719 case ListStyle::Hebrew
:
720 return aOrdinal
>= 0;
722 // complex predefined:
723 case ListStyle::JapaneseFormal
:
724 case ListStyle::JapaneseInformal
:
725 case ListStyle::KoreanHanjaFormal
:
726 case ListStyle::KoreanHanjaInformal
:
727 case ListStyle::KoreanHangulFormal
:
728 case ListStyle::TradChineseFormal
:
729 case ListStyle::TradChineseInformal
:
730 case ListStyle::SimpChineseFormal
:
731 case ListStyle::SimpChineseInformal
:
732 case ListStyle::EthiopicNumeric
:
733 return IsOrdinalInRange(aOrdinal
);
736 MOZ_ASSERT_UNREACHABLE("Unknown counter style");
742 void BuiltinCounterStyle::GetPad(PadType
& aResult
) {
744 aResult
.symbol
.Truncate();
748 CounterStyle
* BuiltinCounterStyle::GetFallback() {
749 // Fallback of dependent builtin counter styles are handled in class
750 // DependentBuiltinCounterStyle.
751 return CounterStyleManager::GetDecimalStyle();
755 SpeakAs
BuiltinCounterStyle::GetSpeakAs() {
757 case ListStyle::None
:
758 case ListStyle::Disc
:
759 case ListStyle::Circle
:
760 case ListStyle::Square
:
761 case ListStyle::DisclosureClosed
:
762 case ListStyle::DisclosureOpen
:
763 return SpeakAs::Bullets
;
765 return SpeakAs::Numbers
;
770 bool BuiltinCounterStyle::UseNegativeSign() {
772 case ListStyle::None
:
773 case ListStyle::Disc
:
774 case ListStyle::Circle
:
775 case ListStyle::Square
:
776 case ListStyle::DisclosureClosed
:
777 case ListStyle::DisclosureOpen
:
785 bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
786 WritingMode aWritingMode
,
791 // used by counters & extends counter-style code only
792 // XXX We really need to do this the same way we do list bullets.
793 case ListStyle::None
:
796 case ListStyle::Disc
:
797 aResult
.Assign(kDiscCharacter
);
799 case ListStyle::Circle
:
800 aResult
.Assign(kCircleCharacter
);
802 case ListStyle::Square
:
803 aResult
.Assign(kSquareCharacter
);
805 case ListStyle::DisclosureClosed
:
806 if (aWritingMode
.IsVertical()) {
807 aResult
.Assign(kDownPointingCharacter
);
808 } else if (aWritingMode
.IsBidiLTR()) {
809 aResult
.Assign(kRightPointingCharacter
);
811 aResult
.Assign(kLeftPointingCharacter
);
814 case ListStyle::DisclosureOpen
:
815 if (!aWritingMode
.IsVertical()) {
816 aResult
.Assign(kDownPointingCharacter
);
817 } else if (aWritingMode
.IsVerticalLR()) {
818 aResult
.Assign(kRightPointingCharacter
);
820 aResult
.Assign(kLeftPointingCharacter
);
824 case ListStyle::Decimal
:
825 return DecimalToText(aOrdinal
, aResult
);
827 case ListStyle::TradChineseInformal
:
828 return CJKIdeographicToText(aOrdinal
, aResult
, gDataTradChineseInformal
);
829 case ListStyle::TradChineseFormal
:
830 return CJKIdeographicToText(aOrdinal
, aResult
, gDataTradChineseFormal
);
831 case ListStyle::SimpChineseInformal
:
832 return CJKIdeographicToText(aOrdinal
, aResult
, gDataSimpChineseInformal
);
833 case ListStyle::SimpChineseFormal
:
834 return CJKIdeographicToText(aOrdinal
, aResult
, gDataSimpChineseFormal
);
835 case ListStyle::JapaneseInformal
:
836 return CJKIdeographicToText(aOrdinal
, aResult
, gDataJapaneseInformal
);
837 case ListStyle::JapaneseFormal
:
838 return CJKIdeographicToText(aOrdinal
, aResult
, gDataJapaneseFormal
);
839 case ListStyle::KoreanHangulFormal
:
840 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHangulFormal
);
841 case ListStyle::KoreanHanjaInformal
:
842 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHanjaInformal
);
843 case ListStyle::KoreanHanjaFormal
:
844 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHanjaFormal
);
846 case ListStyle::Hebrew
:
848 return HebrewToText(aOrdinal
, aResult
);
850 case ListStyle::EthiopicNumeric
:
851 return EthiopicToText(aOrdinal
, aResult
);
854 MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
859 static constexpr BuiltinCounterStyle gBuiltinStyleTable
[] = {
860 #define BUILTIN_COUNTER_STYLE(value_, atom_) \
861 {ListStyle::value_, nsGkAtoms::atom_},
862 #include "BuiltinCounterStyleList.h"
863 #undef BUILTIN_COUNTER_STYLE
866 #define BUILTIN_COUNTER_STYLE(value_, atom_) \
868 gBuiltinStyleTable[static_cast<size_t>(ListStyle::value_)].GetStyle() == \
870 "Builtin counter style " #atom_ " has unmatched index and value.");
871 #include "BuiltinCounterStyleList.h"
872 #undef BUILTIN_COUNTER_STYLE
874 class DependentBuiltinCounterStyle final
: public BuiltinCounterStyle
{
876 DependentBuiltinCounterStyle(ListStyle aStyle
, CounterStyleManager
* aManager
)
877 : BuiltinCounterStyle(gBuiltinStyleTable
[static_cast<size_t>(aStyle
)]),
879 NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
880 MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
883 virtual CounterStyle
* GetFallback() override
;
885 void* operator new(size_t sz
, nsPresContext
* aPresContext
) {
886 return aPresContext
->PresShell()->AllocateByObjectID(
887 eArenaObjectID_DependentBuiltinCounterStyle
, sz
);
891 PresShell
* presShell
= mManager
->PresContext()->PresShell();
892 this->~DependentBuiltinCounterStyle();
893 presShell
->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle
,
898 ~DependentBuiltinCounterStyle() = default;
900 CounterStyleManager
* mManager
;
904 CounterStyle
* DependentBuiltinCounterStyle::GetFallback() {
905 switch (GetStyle()) {
906 case ListStyle::JapaneseInformal
:
907 case ListStyle::JapaneseFormal
:
908 case ListStyle::KoreanHangulFormal
:
909 case ListStyle::KoreanHanjaInformal
:
910 case ListStyle::KoreanHanjaFormal
:
911 case ListStyle::SimpChineseInformal
:
912 case ListStyle::SimpChineseFormal
:
913 case ListStyle::TradChineseInformal
:
914 case ListStyle::TradChineseFormal
:
915 // These styles all have a larger range than cjk-decimal, so the
916 // only case fallback is accessed is that they are extended.
917 // Since extending styles will cache the data themselves, we need
918 // not cache it here.
919 return mManager
->ResolveCounterStyle(nsGkAtoms::cjk_decimal
);
921 MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
922 return BuiltinCounterStyle::GetFallback();
926 class CustomCounterStyle final
: public CounterStyle
{
928 CustomCounterStyle(CounterStyleManager
* aManager
,
929 const StyleLockedCounterStyleRule
* aRule
)
930 : CounterStyle(ListStyle::Custom
),
933 mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule
)),
934 mSystem(Servo_CounterStyleRule_GetSystem(aRule
)),
937 mSpeakAsCounter(nullptr),
939 mExtendsRoot(nullptr) {}
941 // This method will clear all cached data in the style and update the
942 // generation number of the rule. It should be called when the rule of
943 // this style is changed.
944 void ResetCachedData();
946 // This method will reset all cached data which may depend on other
947 // counter style. It will reset all pointers to other counter styles.
948 // For counter style extends other, in addition, all fields will be
949 // reset to uninitialized state. This method should be called when any
950 // other counter style is added, removed, or changed.
951 void ResetDependentData();
953 const StyleLockedCounterStyleRule
* GetRule() const { return mRule
; }
954 uint32_t GetRuleGeneration() const { return mRuleGeneration
; }
956 virtual void GetPrefix(nsAString
& aResult
) override
;
957 virtual void GetSuffix(nsAString
& aResult
) override
;
958 virtual void GetSpokenCounterText(CounterValue aOrdinal
,
959 WritingMode aWritingMode
,
961 bool& aIsBullet
) override
;
962 virtual bool IsBullet() override
;
964 virtual void GetNegative(NegativeType
& aResult
) override
;
965 virtual bool IsOrdinalInRange(CounterValue aOrdinal
) override
;
966 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal
) override
;
967 virtual void GetPad(PadType
& aResult
) override
;
968 virtual CounterStyle
* GetFallback() override
;
969 virtual SpeakAs
GetSpeakAs() override
;
970 virtual bool UseNegativeSign() override
;
972 virtual void CallFallbackStyle(CounterValue aOrdinal
,
973 WritingMode aWritingMode
, nsAString
& aResult
,
974 bool& aIsRTL
) override
;
975 virtual bool GetInitialCounterText(CounterValue aOrdinal
,
976 WritingMode aWritingMode
,
977 nsAString
& aResult
, bool& aIsRTL
) override
;
979 bool IsExtendsSystem() { return mSystem
== StyleCounterSystem::Extends
; }
981 void* operator new(size_t sz
, nsPresContext
* aPresContext
) {
982 return aPresContext
->PresShell()->AllocateByObjectID(
983 eArenaObjectID_CustomCounterStyle
, sz
);
987 PresShell
* presShell
= mManager
->PresContext()->PresShell();
988 this->~CustomCounterStyle();
989 presShell
->FreeByObjectID(eArenaObjectID_CustomCounterStyle
, this);
993 ~CustomCounterStyle() = default;
995 Span
<const nsString
> GetSymbols();
996 Span
<const AdditiveSymbol
> GetAdditiveSymbols();
998 // The speak-as values of counter styles may form a loop, and the
999 // loops may have complex interaction with the loop formed by
1000 // extending. To solve this problem, the computation of speak-as is
1001 // divided into two phases:
1002 // 1. figure out the raw value, by ComputeRawSpeakAs, and
1003 // 2. eliminate loop, by ComputeSpeakAs.
1004 // See comments before the definitions of these methods for details.
1005 SpeakAs
GetSpeakAsAutoValue();
1006 void ComputeRawSpeakAs(SpeakAs
& aSpeakAs
, CounterStyle
*& aSpeakAsCounter
);
1007 CounterStyle
* ComputeSpeakAs();
1009 CounterStyle
* ComputeExtends();
1010 CounterStyle
* GetExtends();
1011 CounterStyle
* GetExtendsRoot();
1013 // CounterStyleManager should always overlive any CounterStyle as it
1014 // is owned by nsPresContext, and will be released after all nodes and
1015 // frames are released.
1016 CounterStyleManager
* mManager
;
1018 RefPtr
<const StyleLockedCounterStyleRule
> mRule
;
1019 uint32_t mRuleGeneration
;
1021 StyleCounterSystem mSystem
;
1022 // GetSpeakAs will ensure that private member mSpeakAs is initialized before
1024 MOZ_INIT_OUTSIDE_CTOR SpeakAs mSpeakAs
;
1028 FLAG_EXTENDS_VISITED
= 1 << 0,
1029 FLAG_EXTENDS_LOOP
= 1 << 1,
1030 FLAG_SPEAKAS_VISITED
= 1 << 2,
1031 FLAG_SPEAKAS_LOOP
= 1 << 3,
1033 FLAG_NEGATIVE_INITED
= 1 << 4,
1034 FLAG_PREFIX_INITED
= 1 << 5,
1035 FLAG_SUFFIX_INITED
= 1 << 6,
1036 FLAG_PAD_INITED
= 1 << 7,
1037 FLAG_SPEAKAS_INITED
= 1 << 8,
1041 // Fields below will be initialized when necessary.
1042 StyleOwnedSlice
<nsString
> mSymbols
;
1043 StyleOwnedSlice
<AdditiveSymbol
> mAdditiveSymbols
;
1044 NegativeType mNegative
;
1045 nsString mPrefix
, mSuffix
;
1048 // CounterStyleManager will guarantee that none of the pointers below
1049 // refers to a freed CounterStyle. There are two possible cases where
1050 // the manager will release its reference to a CounterStyle: 1. the
1051 // manager itself is released, 2. a rule is invalidated. In the first
1052 // case, all counter style are removed from the manager, and should
1053 // also have been dereferenced from other objects. All styles will be
1054 // released all together. In the second case, CounterStyleManager::
1055 // NotifyRuleChanged will guarantee that all pointers will be reset
1056 // before any CounterStyle is released.
1058 CounterStyle
* mFallback
;
1059 // This field refers to the last counter in a speak-as chain.
1060 // That counter must not speak as another counter.
1061 CounterStyle
* mSpeakAsCounter
;
1063 CounterStyle
* mExtends
;
1064 // This field refers to the last counter in the extends chain. The
1065 // counter must be either a builtin style or a style whose system is
1067 CounterStyle
* mExtendsRoot
;
1070 void CustomCounterStyle::ResetCachedData() {
1072 mAdditiveSymbols
.Clear();
1073 mFlags
&= ~(FLAG_NEGATIVE_INITED
| FLAG_PREFIX_INITED
| FLAG_SUFFIX_INITED
|
1074 FLAG_PAD_INITED
| FLAG_SPEAKAS_INITED
);
1075 mFallback
= nullptr;
1076 mSpeakAsCounter
= nullptr;
1078 mExtendsRoot
= nullptr;
1079 mRuleGeneration
= Servo_CounterStyleRule_GetGeneration(mRule
);
1082 void CustomCounterStyle::ResetDependentData() {
1083 mFlags
&= ~FLAG_SPEAKAS_INITED
;
1084 mSpeakAsCounter
= nullptr;
1085 mFallback
= nullptr;
1087 mExtendsRoot
= nullptr;
1088 if (IsExtendsSystem()) {
1089 mFlags
&= ~(FLAG_NEGATIVE_INITED
| FLAG_PREFIX_INITED
| FLAG_SUFFIX_INITED
|
1095 void CustomCounterStyle::GetPrefix(nsAString
& aResult
) {
1096 if (!(mFlags
& FLAG_PREFIX_INITED
)) {
1097 mFlags
|= FLAG_PREFIX_INITED
;
1099 if (!Servo_CounterStyleRule_GetPrefix(mRule
, &mPrefix
)) {
1100 if (IsExtendsSystem()) {
1101 GetExtends()->GetPrefix(mPrefix
);
1111 void CustomCounterStyle::GetSuffix(nsAString
& aResult
) {
1112 if (!(mFlags
& FLAG_SUFFIX_INITED
)) {
1113 mFlags
|= FLAG_SUFFIX_INITED
;
1115 if (!Servo_CounterStyleRule_GetSuffix(mRule
, &mSuffix
)) {
1116 if (IsExtendsSystem()) {
1117 GetExtends()->GetSuffix(mSuffix
);
1119 mSuffix
.AssignLiteral(u
". ");
1127 void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
1128 WritingMode aWritingMode
,
1131 if (GetSpeakAs() != SpeakAs::Other
) {
1132 CounterStyle::GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
1135 MOZ_ASSERT(mSpeakAsCounter
,
1136 "mSpeakAsCounter should have been initialized.");
1137 mSpeakAsCounter
->GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
1143 bool CustomCounterStyle::IsBullet() {
1145 case StyleCounterSystem::Cyclic
:
1146 // Only use ::-moz-list-bullet for cyclic system
1148 case StyleCounterSystem::Extends
:
1149 return GetExtendsRoot()->IsBullet();
1156 void CustomCounterStyle::GetNegative(NegativeType
& aResult
) {
1157 if (!(mFlags
& FLAG_NEGATIVE_INITED
)) {
1158 mFlags
|= FLAG_NEGATIVE_INITED
;
1159 if (!Servo_CounterStyleRule_GetNegative(mRule
, &mNegative
.before
,
1160 &mNegative
.after
)) {
1161 if (IsExtendsSystem()) {
1162 GetExtends()->GetNegative(mNegative
);
1164 mNegative
.before
.AssignLiteral(u
"-");
1165 mNegative
.after
.Truncate();
1169 aResult
= mNegative
;
1173 bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
1174 auto inRange
= Servo_CounterStyleRule_IsInRange(mRule
, aOrdinal
);
1176 case StyleIsOrdinalInRange::InRange
:
1178 case StyleIsOrdinalInRange::NotInRange
:
1180 case StyleIsOrdinalInRange::NoOrdinalSpecified
:
1181 if (IsExtendsSystem()) {
1182 return GetExtends()->IsOrdinalInRange(aOrdinal
);
1185 case StyleIsOrdinalInRange::Auto
:
1188 MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
1190 return IsOrdinalInAutoRange(aOrdinal
);
1194 bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
1196 case StyleCounterSystem::Cyclic
:
1197 case StyleCounterSystem::Numeric
:
1198 case StyleCounterSystem::Fixed
:
1200 case StyleCounterSystem::Alphabetic
:
1201 case StyleCounterSystem::Symbolic
:
1202 return aOrdinal
>= 1;
1203 case StyleCounterSystem::Additive
:
1204 return aOrdinal
>= 0;
1205 case StyleCounterSystem::Extends
:
1206 return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal
);
1208 MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
1214 void CustomCounterStyle::GetPad(PadType
& aResult
) {
1215 if (!(mFlags
& FLAG_PAD_INITED
)) {
1216 mFlags
|= FLAG_PAD_INITED
;
1217 if (!Servo_CounterStyleRule_GetPad(mRule
, &mPad
.width
, &mPad
.symbol
)) {
1218 if (IsExtendsSystem()) {
1219 GetExtends()->GetPad(mPad
);
1222 mPad
.symbol
.Truncate();
1230 CounterStyle
* CustomCounterStyle::GetFallback() {
1232 mFallback
= CounterStyleManager::GetDecimalStyle();
1233 if (nsAtom
* fallback
= Servo_CounterStyleRule_GetFallback(mRule
)) {
1234 mFallback
= mManager
->ResolveCounterStyle(fallback
);
1235 } else if (IsExtendsSystem()) {
1236 mFallback
= GetExtends()->GetFallback();
1243 SpeakAs
CustomCounterStyle::GetSpeakAs() {
1244 if (!(mFlags
& FLAG_SPEAKAS_INITED
)) {
1251 bool CustomCounterStyle::UseNegativeSign() {
1252 if (mSystem
== StyleCounterSystem::Extends
) {
1253 return GetExtendsRoot()->UseNegativeSign();
1255 return SystemUsesNegativeSign(mSystem
);
1259 void CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal
,
1260 WritingMode aWritingMode
,
1261 nsAString
& aResult
, bool& aIsRTL
) {
1262 CounterStyle
* fallback
= GetFallback();
1263 // If it recursively falls back to this counter style again,
1264 // it will then fallback to decimal to break the loop.
1265 mFallback
= CounterStyleManager::GetDecimalStyle();
1266 fallback
->GetCounterText(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1267 mFallback
= fallback
;
1271 bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
1272 WritingMode aWritingMode
,
1276 case StyleCounterSystem::Cyclic
:
1277 return GetCyclicCounterText(aOrdinal
, aResult
, GetSymbols());
1278 case StyleCounterSystem::Fixed
: {
1279 int32_t start
= Servo_CounterStyleRule_GetFixedFirstValue(mRule
);
1280 return GetFixedCounterText(aOrdinal
, aResult
, start
, GetSymbols());
1282 case StyleCounterSystem::Symbolic
:
1283 return GetSymbolicCounterText(aOrdinal
, aResult
, GetSymbols());
1284 case StyleCounterSystem::Alphabetic
:
1285 return GetAlphabeticCounterText(aOrdinal
, aResult
, GetSymbols());
1286 case StyleCounterSystem::Numeric
:
1287 return GetNumericCounterText(aOrdinal
, aResult
, GetSymbols());
1288 case StyleCounterSystem::Additive
:
1289 return GetAdditiveCounterText(aOrdinal
, aResult
, GetAdditiveSymbols());
1290 case StyleCounterSystem::Extends
:
1291 return GetExtendsRoot()->GetInitialCounterText(aOrdinal
, aWritingMode
,
1294 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1299 Span
<const nsString
> CustomCounterStyle::GetSymbols() {
1300 if (mSymbols
.IsEmpty()) {
1301 Servo_CounterStyleRule_GetSymbols(mRule
, &mSymbols
);
1303 return mSymbols
.AsSpan();
1306 Span
<const AdditiveSymbol
> CustomCounterStyle::GetAdditiveSymbols() {
1307 if (mAdditiveSymbols
.IsEmpty()) {
1308 Servo_CounterStyleRule_GetAdditiveSymbols(mRule
, &mAdditiveSymbols
);
1310 return mAdditiveSymbols
.AsSpan();
1313 // This method is used to provide the computed value for 'auto'.
1314 SpeakAs
CustomCounterStyle::GetSpeakAsAutoValue() {
1315 auto system
= mSystem
;
1316 if (IsExtendsSystem()) {
1317 CounterStyle
* root
= GetExtendsRoot();
1318 if (!root
->IsCustomStyle()) {
1319 // It is safe to call GetSpeakAs on non-custom style.
1320 return root
->GetSpeakAs();
1322 system
= static_cast<CustomCounterStyle
*>(root
)->mSystem
;
1324 return GetDefaultSpeakAsForSystem(system
);
1327 // This method corresponds to the first stage of computation of the
1328 // value of speak-as. It will extract the value from the rule and
1329 // possibly recursively call itself on the extended style to figure
1330 // out the raw value. To keep things clear, this method is designed to
1331 // have no side effects (but functions it calls may still affect other
1332 // fields in the style.)
1333 void CustomCounterStyle::ComputeRawSpeakAs(SpeakAs
& aSpeakAs
,
1334 CounterStyle
*& aSpeakAsCounter
) {
1335 NS_ASSERTION(!(mFlags
& FLAG_SPEAKAS_INITED
),
1336 "ComputeRawSpeakAs is called with speak-as inited.");
1338 auto speakAs
= StyleCounterSpeakAs::None();
1339 Servo_CounterStyleRule_GetSpeakAs(mRule
, &speakAs
);
1340 switch (speakAs
.tag
) {
1341 case StyleCounterSpeakAs::Tag::Auto
:
1342 aSpeakAs
= GetSpeakAsAutoValue();
1344 case StyleCounterSpeakAs::Tag::Bullets
:
1345 aSpeakAs
= SpeakAs::Bullets
;
1347 case StyleCounterSpeakAs::Tag::Numbers
:
1348 aSpeakAs
= SpeakAs::Numbers
;
1350 case StyleCounterSpeakAs::Tag::Words
:
1351 aSpeakAs
= SpeakAs::Words
;
1353 case StyleCounterSpeakAs::Tag::Ident
:
1354 aSpeakAs
= SpeakAs::Other
;
1355 aSpeakAsCounter
= mManager
->ResolveCounterStyle(speakAs
.AsIdent());
1357 case StyleCounterSpeakAs::Tag::None
: {
1358 if (!IsExtendsSystem()) {
1359 aSpeakAs
= GetSpeakAsAutoValue();
1361 CounterStyle
* extended
= GetExtends();
1362 if (!extended
->IsCustomStyle()) {
1363 // It is safe to call GetSpeakAs on non-custom style.
1364 aSpeakAs
= extended
->GetSpeakAs();
1366 CustomCounterStyle
* custom
=
1367 static_cast<CustomCounterStyle
*>(extended
);
1368 if (!(custom
->mFlags
& FLAG_SPEAKAS_INITED
)) {
1369 custom
->ComputeRawSpeakAs(aSpeakAs
, aSpeakAsCounter
);
1371 aSpeakAs
= custom
->mSpeakAs
;
1372 aSpeakAsCounter
= custom
->mSpeakAsCounter
;
1379 MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
1383 // This method corresponds to the second stage of getting speak-as
1384 // related values. It will recursively figure out the final value of
1385 // mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
1386 // caller is in a loop, and the root counter style in the chain
1387 // otherwise. It use the same loop detection algorithm as
1388 // CustomCounterStyle::ComputeExtends, see comments before that
1389 // method for more details.
1390 CounterStyle
* CustomCounterStyle::ComputeSpeakAs() {
1391 if (mFlags
& FLAG_SPEAKAS_INITED
) {
1392 if (mSpeakAs
== SpeakAs::Other
) {
1393 return mSpeakAsCounter
;
1398 if (mFlags
& FLAG_SPEAKAS_VISITED
) {
1400 mFlags
|= FLAG_SPEAKAS_LOOP
;
1404 CounterStyle
* speakAsCounter
;
1405 ComputeRawSpeakAs(mSpeakAs
, speakAsCounter
);
1407 bool inLoop
= false;
1408 if (mSpeakAs
!= SpeakAs::Other
) {
1409 mSpeakAsCounter
= nullptr;
1410 } else if (!speakAsCounter
->IsCustomStyle()) {
1411 mSpeakAsCounter
= speakAsCounter
;
1413 mFlags
|= FLAG_SPEAKAS_VISITED
;
1414 CounterStyle
* target
=
1415 static_cast<CustomCounterStyle
*>(speakAsCounter
)->ComputeSpeakAs();
1416 mFlags
&= ~FLAG_SPEAKAS_VISITED
;
1419 NS_ASSERTION(!(mFlags
& FLAG_SPEAKAS_LOOP
),
1420 "Invalid state for speak-as loop detecting");
1421 mSpeakAsCounter
= target
;
1423 mSpeakAs
= GetSpeakAsAutoValue();
1424 mSpeakAsCounter
= nullptr;
1425 if (mFlags
& FLAG_SPEAKAS_LOOP
) {
1426 mFlags
&= ~FLAG_SPEAKAS_LOOP
;
1433 mFlags
|= FLAG_SPEAKAS_INITED
;
1437 return mSpeakAsCounter
? mSpeakAsCounter
: this;
1440 // This method will recursively figure out mExtends in the whole chain.
1441 // It will return nullptr if the caller is in a loop, and return this
1442 // otherwise. To detect the loop, this method marks the style VISITED
1443 // before the recursive call. When a VISITED style is reached again, the
1444 // loop is detected, and flag LOOP will be marked on the first style in
1445 // loop. mExtends of all counter styles in loop will be set to decimal
1446 // according to the spec.
1447 CounterStyle
* CustomCounterStyle::ComputeExtends() {
1448 if (!IsExtendsSystem() || mExtends
) {
1451 if (mFlags
& FLAG_EXTENDS_VISITED
) {
1453 mFlags
|= FLAG_EXTENDS_LOOP
;
1457 nsAtom
* extended
= Servo_CounterStyleRule_GetExtended(mRule
);
1458 CounterStyle
* nextCounter
= mManager
->ResolveCounterStyle(extended
);
1459 CounterStyle
* target
= nextCounter
;
1460 if (nextCounter
->IsCustomStyle()) {
1461 mFlags
|= FLAG_EXTENDS_VISITED
;
1462 target
= static_cast<CustomCounterStyle
*>(nextCounter
)->ComputeExtends();
1463 mFlags
&= ~FLAG_EXTENDS_VISITED
;
1467 NS_ASSERTION(!(mFlags
& FLAG_EXTENDS_LOOP
),
1468 "Invalid state for extends loop detecting");
1469 mExtends
= nextCounter
;
1472 mExtends
= CounterStyleManager::GetDecimalStyle();
1473 if (mFlags
& FLAG_EXTENDS_LOOP
) {
1474 mFlags
&= ~FLAG_EXTENDS_LOOP
;
1482 CounterStyle
* CustomCounterStyle::GetExtends() {
1484 // Any extends loop will be eliminated in the method below.
1490 CounterStyle
* CustomCounterStyle::GetExtendsRoot() {
1491 if (!mExtendsRoot
) {
1492 CounterStyle
* extended
= GetExtends();
1493 mExtendsRoot
= extended
;
1494 if (extended
->IsCustomStyle()) {
1495 CustomCounterStyle
* custom
= static_cast<CustomCounterStyle
*>(extended
);
1496 if (custom
->IsExtendsSystem()) {
1497 // This will make mExtendsRoot in the whole extends chain be
1498 // set recursively, which could save work when part of a chain
1499 // is shared by multiple counter styles.
1500 mExtendsRoot
= custom
->GetExtendsRoot();
1504 return mExtendsRoot
;
1507 AnonymousCounterStyle::AnonymousCounterStyle(const nsAString
& aContent
)
1508 : CounterStyle(ListStyle::Custom
),
1509 mSingleString(true),
1510 mSymbolsType(StyleSymbolsType::Cyclic
) {
1511 mSymbols
.SetCapacity(1);
1512 mSymbols
.AppendElement(aContent
);
1515 AnonymousCounterStyle::AnonymousCounterStyle(StyleSymbolsType aType
,
1516 nsTArray
<nsString
> aSymbols
)
1517 : CounterStyle(ListStyle::Custom
),
1518 mSingleString(false),
1519 mSymbolsType(aType
),
1520 mSymbols(std::move(aSymbols
)) {}
1523 void AnonymousCounterStyle::GetPrefix(nsAString
& aResult
) {
1528 void AnonymousCounterStyle::GetSuffix(nsAString
& aResult
) {
1529 if (IsSingleString()) {
1537 bool AnonymousCounterStyle::IsBullet() {
1538 // Only use ::-moz-list-bullet for cyclic system
1539 return mSymbolsType
== StyleSymbolsType::Cyclic
;
1543 void AnonymousCounterStyle::GetNegative(NegativeType
& aResult
) {
1544 aResult
.before
.AssignLiteral(u
"-");
1545 aResult
.after
.Truncate();
1549 bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
1550 switch (mSymbolsType
) {
1551 case StyleSymbolsType::Cyclic
:
1552 case StyleSymbolsType::Numeric
:
1553 case StyleSymbolsType::Fixed
:
1555 case StyleSymbolsType::Alphabetic
:
1556 case StyleSymbolsType::Symbolic
:
1557 return aOrdinal
>= 1;
1559 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1565 bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
1566 return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal
);
1570 void AnonymousCounterStyle::GetPad(PadType
& aResult
) {
1572 aResult
.symbol
.Truncate();
1576 CounterStyle
* AnonymousCounterStyle::GetFallback() {
1577 return CounterStyleManager::GetDecimalStyle();
1580 StyleCounterSystem
AnonymousCounterStyle::GetSystem() const {
1581 switch (mSymbolsType
) {
1582 case StyleSymbolsType::Cyclic
:
1583 return StyleCounterSystem::Cyclic
;
1584 case StyleSymbolsType::Numeric
:
1585 return StyleCounterSystem::Numeric
;
1586 case StyleSymbolsType::Fixed
:
1587 return StyleCounterSystem::Fixed
;
1588 case StyleSymbolsType::Alphabetic
:
1589 return StyleCounterSystem::Alphabetic
;
1590 case StyleSymbolsType::Symbolic
:
1591 return StyleCounterSystem::Symbolic
;
1593 MOZ_ASSERT_UNREACHABLE("Unknown symbols() type");
1594 return StyleCounterSystem::Cyclic
;
1598 SpeakAs
AnonymousCounterStyle::GetSpeakAs() {
1599 return GetDefaultSpeakAsForSystem(GetSystem());
1603 bool AnonymousCounterStyle::UseNegativeSign() {
1604 return SystemUsesNegativeSign(GetSystem());
1608 bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
1609 WritingMode aWritingMode
,
1612 switch (mSymbolsType
) {
1613 case StyleSymbolsType::Cyclic
:
1614 return GetCyclicCounterText(aOrdinal
, aResult
, mSymbols
);
1615 case StyleSymbolsType::Numeric
:
1616 return GetNumericCounterText(aOrdinal
, aResult
, mSymbols
);
1617 case StyleSymbolsType::Fixed
:
1618 return GetFixedCounterText(aOrdinal
, aResult
, 1, mSymbols
);
1619 case StyleSymbolsType::Alphabetic
:
1620 return GetAlphabeticCounterText(aOrdinal
, aResult
, mSymbols
);
1621 case StyleSymbolsType::Symbolic
:
1622 return GetSymbolicCounterText(aOrdinal
, aResult
, mSymbols
);
1624 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1628 bool CounterStyle::IsDependentStyle() const {
1630 // CustomCounterStyle
1631 case ListStyle::Custom
:
1632 // DependentBuiltinCounterStyle
1633 case ListStyle::JapaneseInformal
:
1634 case ListStyle::JapaneseFormal
:
1635 case ListStyle::KoreanHangulFormal
:
1636 case ListStyle::KoreanHanjaInformal
:
1637 case ListStyle::KoreanHanjaFormal
:
1638 case ListStyle::SimpChineseInformal
:
1639 case ListStyle::SimpChineseFormal
:
1640 case ListStyle::TradChineseInformal
:
1641 case ListStyle::TradChineseFormal
:
1644 // BuiltinCounterStyle
1650 void CounterStyle::GetCounterText(CounterValue aOrdinal
,
1651 WritingMode aWritingMode
, nsAString
& aResult
,
1653 bool success
= IsOrdinalInRange(aOrdinal
);
1657 // generate initial representation
1658 bool useNegativeSign
= UseNegativeSign();
1659 nsAutoString initialText
;
1660 CounterValue ordinal
;
1661 if (!useNegativeSign
) {
1664 CheckedInt
<CounterValue
> absolute(Abs(aOrdinal
));
1665 ordinal
= absolute
.isValid() ? absolute
.value()
1666 : std::numeric_limits
<CounterValue
>::max();
1668 success
= GetInitialCounterText(ordinal
, aWritingMode
, initialText
, aIsRTL
);
1670 // add pad & negative, build the final result
1673 if (useNegativeSign
&& aOrdinal
< 0) {
1674 NegativeType negative
;
1675 GetNegative(negative
);
1676 aResult
.Append(negative
.before
);
1677 // There is nothing between the suffix part of negative and initial
1678 // representation, so we append it directly here.
1679 initialText
.Append(negative
.after
);
1685 narrow_cast
<int32_t>(unicode::CountGraphemeClusters(initialText
) +
1686 unicode::CountGraphemeClusters(aResult
));
1688 auto length
= pad
.symbol
.Length();
1689 if (diff
> LENGTH_LIMIT
|| length
> LENGTH_LIMIT
||
1690 diff
* length
> LENGTH_LIMIT
) {
1692 } else if (length
> 0) {
1693 for (int32_t i
= 0; i
< diff
; ++i
) {
1694 aResult
.Append(pad
.symbol
);
1699 aResult
.Append(initialText
);
1705 CallFallbackStyle(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1710 void CounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
1711 WritingMode aWritingMode
,
1712 nsAString
& aResult
, bool& aIsBullet
) {
1713 bool isRTL
; // we don't care about direction for spoken text
1715 switch (GetSpeakAs()) {
1716 case SpeakAs::Bullets
:
1717 aResult
.Assign(kDiscCharacter
);
1720 case SpeakAs::Numbers
:
1721 DecimalToText(aOrdinal
, aResult
);
1723 case SpeakAs::Spellout
:
1724 // we currently do not actually support 'spell-out',
1725 // so 'words' is used instead.
1726 case SpeakAs::Words
:
1727 GetCounterText(aOrdinal
, WritingMode(), aResult
, isRTL
);
1729 case SpeakAs::Other
:
1730 // This should be processed by CustomCounterStyle
1731 MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
1734 MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
1740 void CounterStyle::CallFallbackStyle(CounterValue aOrdinal
,
1741 WritingMode aWritingMode
,
1742 nsAString
& aResult
, bool& aIsRTL
) {
1743 GetFallback()->GetCounterText(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1746 CounterStyleManager::CounterStyleManager(nsPresContext
* aPresContext
)
1747 : mPresContext(aPresContext
) {
1748 // Insert the static styles into cache table
1749 mStyles
.InsertOrUpdate(nsGkAtoms::none
, GetNoneStyle());
1750 mStyles
.InsertOrUpdate(nsGkAtoms::decimal
, GetDecimalStyle());
1751 mStyles
.InsertOrUpdate(nsGkAtoms::disc
, GetDiscStyle());
1754 CounterStyleManager::~CounterStyleManager() {
1755 MOZ_ASSERT(!mPresContext
, "Disconnect should have been called");
1758 void CounterStyleManager::DestroyCounterStyle(CounterStyle
* aCounterStyle
) {
1759 if (aCounterStyle
->IsCustomStyle()) {
1760 MOZ_ASSERT(!aCounterStyle
->AsAnonymous(),
1761 "Anonymous counter styles "
1762 "are not managed by CounterStyleManager");
1763 static_cast<CustomCounterStyle
*>(aCounterStyle
)->Destroy();
1764 } else if (aCounterStyle
->IsDependentStyle()) {
1765 static_cast<DependentBuiltinCounterStyle
*>(aCounterStyle
)->Destroy();
1767 MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
1771 void CounterStyleManager::Disconnect() {
1772 CleanRetiredStyles();
1773 for (CounterStyle
* style
: mStyles
.Values()) {
1774 if (style
->IsDependentStyle()) {
1775 DestroyCounterStyle(style
);
1779 mPresContext
= nullptr;
1782 CounterStyle
* CounterStyleManager::ResolveCounterStyle(nsAtom
* aName
) {
1783 MOZ_ASSERT(NS_IsMainThread());
1784 CounterStyle
* data
= GetCounterStyle(aName
);
1789 // Names are compared case-sensitively here. Predefined names should
1790 // have been lowercased by the parser.
1791 ServoStyleSet
* styleSet
= mPresContext
->StyleSet();
1792 auto* rule
= styleSet
->CounterStyleRuleForName(aName
);
1794 MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule
) == aName
);
1795 data
= new (mPresContext
) CustomCounterStyle(this, rule
);
1797 for (const BuiltinCounterStyle
& item
: gBuiltinStyleTable
) {
1798 if (item
.GetStyleName() == aName
) {
1799 const auto style
= item
.GetStyle();
1800 data
= item
.IsDependentStyle()
1801 ? new (mPresContext
)
1802 DependentBuiltinCounterStyle(style
, this)
1803 : GetBuiltinStyle(style
);
1809 data
= GetDecimalStyle();
1811 mStyles
.InsertOrUpdate(aName
, data
);
1816 CounterStyle
* CounterStyleManager::GetBuiltinStyle(ListStyle aStyle
) {
1817 MOZ_ASSERT(size_t(aStyle
) < ArrayLength(gBuiltinStyleTable
),
1818 "Require a valid builtin style constant");
1819 MOZ_ASSERT(!gBuiltinStyleTable
[size_t(aStyle
)].IsDependentStyle(),
1820 "Cannot get dependent builtin style");
1821 // No method of BuiltinCounterStyle mutates the struct itself, so it
1822 // should be fine to cast const away.
1823 return const_cast<BuiltinCounterStyle
*>(&gBuiltinStyleTable
[size_t(aStyle
)]);
1826 bool CounterStyleManager::NotifyRuleChanged() {
1827 bool changed
= false;
1828 for (auto iter
= mStyles
.Iter(); !iter
.Done(); iter
.Next()) {
1829 CounterStyle
* style
= iter
.Data();
1830 bool toBeUpdated
= false;
1831 bool toBeRemoved
= false;
1832 ServoStyleSet
* styleSet
= mPresContext
->StyleSet();
1833 auto* newRule
= styleSet
->CounterStyleRuleForName(iter
.Key());
1835 if (style
->IsCustomStyle()) {
1839 if (!style
->IsCustomStyle()) {
1842 auto custom
= static_cast<CustomCounterStyle
*>(style
);
1843 if (custom
->GetRule() != newRule
) {
1846 auto generation
= Servo_CounterStyleRule_GetGeneration(newRule
);
1847 if (custom
->GetRuleGeneration() != generation
) {
1849 custom
->ResetCachedData();
1854 changed
= changed
|| toBeUpdated
|| toBeRemoved
;
1856 if (style
->IsDependentStyle()) {
1857 // Add object to retired list so we can clean them up later.
1858 mRetiredStyles
.AppendElement(style
);
1865 for (CounterStyle
* style
: mStyles
.Values()) {
1866 if (style
->IsCustomStyle()) {
1867 CustomCounterStyle
* custom
= static_cast<CustomCounterStyle
*>(style
);
1868 custom
->ResetDependentData();
1870 // There is no dependent data cached in DependentBuiltinCounterStyle
1871 // instances, so we don't need to reset their data.
1877 void CounterStyleManager::CleanRetiredStyles() {
1878 nsTArray
<CounterStyle
*> list(std::move(mRetiredStyles
));
1879 for (CounterStyle
* style
: list
) {
1880 DestroyCounterStyle(style
);
1884 } // namespace mozilla