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"
9 #include "mozilla/ArenaObjectID.h"
10 #include "mozilla/ArrayUtils.h"
11 #include "mozilla/CheckedInt.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/PresShell.h"
14 #include "mozilla/Types.h"
15 #include "mozilla/WritingModes.h"
16 #include "nsPresContext.h"
17 #include "nsPresContextInlines.h"
20 #include "nsTHashtable.h"
21 #include "nsUnicodeProperties.h"
22 #include "mozilla/ServoBindings.h"
23 #include "mozilla/ServoStyleSet.h"
27 using AdditiveSymbol
= StyleAdditiveSymbol
;
30 nsString before
, after
;
38 // This limitation will be applied to some systems, and pad descriptor.
39 // Any initial representation generated by symbolic or additive which is
40 // longer than this limitation will be dropped. If any pad is longer
41 // than this, the whole counter text will be dropped as well.
42 // The spec requires user agents to support at least 60 Unicode code-
43 // points for counter text. However, this constant only limits the
44 // length in 16-bit units. So it has to be at least 120, since code-
45 // points outside the BMP will need 2 16-bit units.
46 #define LENGTH_LIMIT 150
48 static bool GetCyclicCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
49 Span
<const nsString
> aSymbols
) {
50 MOZ_ASSERT(aSymbols
.Length() >= 1, "No symbol available for cyclic counter.");
51 auto n
= aSymbols
.Length();
52 CounterValue index
= (aOrdinal
- 1) % n
;
53 aResult
= aSymbols
[index
>= 0 ? index
: index
+ n
];
57 static bool GetFixedCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
59 Span
<const nsString
> aSymbols
) {
60 CounterValue index
= aOrdinal
- aStart
;
61 if (index
>= 0 && index
< CounterValue(aSymbols
.Length())) {
62 aResult
= aSymbols
[index
];
69 static bool GetSymbolicCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
70 Span
<const nsString
> aSymbols
) {
71 MOZ_ASSERT(aSymbols
.Length() >= 1,
72 "No symbol available for symbolic counter.");
73 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
79 auto n
= aSymbols
.Length();
80 const nsString
& symbol
= aSymbols
[(aOrdinal
- 1) % n
];
81 size_t len
= (aOrdinal
+ n
- 1) / n
;
82 auto symbolLength
= symbol
.Length();
83 if (symbolLength
> 0) {
84 if (len
> LENGTH_LIMIT
|| symbolLength
> LENGTH_LIMIT
||
85 len
* symbolLength
> LENGTH_LIMIT
) {
88 for (size_t i
= 0; i
< len
; ++i
) {
89 aResult
.Append(symbol
);
95 static bool GetAlphabeticCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
96 Span
<const nsString
> aSymbols
) {
97 MOZ_ASSERT(aSymbols
.Length() >= 2, "Too few symbols for alphabetic counter.");
98 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
103 auto n
= aSymbols
.Length();
104 // The precise length of this array should be
105 // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
106 // The max length is slightly smaller than which defined below.
107 AutoTArray
<int32_t, std::numeric_limits
<CounterValue
>::digits
> indexes
;
108 while (aOrdinal
> 0) {
110 indexes
.AppendElement(aOrdinal
% n
);
115 for (auto i
= indexes
.Length(); i
> 0; --i
) {
116 aResult
.Append(aSymbols
[indexes
[i
- 1]]);
121 static bool GetNumericCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
122 Span
<const nsString
> aSymbols
) {
123 MOZ_ASSERT(aSymbols
.Length() >= 2, "Too few symbols for numeric counter.");
124 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
127 aResult
= aSymbols
[0];
131 auto n
= aSymbols
.Length();
132 AutoTArray
<int32_t, std::numeric_limits
<CounterValue
>::digits
> indexes
;
133 while (aOrdinal
> 0) {
134 indexes
.AppendElement(aOrdinal
% n
);
139 for (auto i
= indexes
.Length(); i
> 0; --i
) {
140 aResult
.Append(aSymbols
[indexes
[i
- 1]]);
145 static bool GetAdditiveCounterText(CounterValue aOrdinal
, nsAString
& aResult
,
146 Span
<const AdditiveSymbol
> aSymbols
) {
147 MOZ_ASSERT(aOrdinal
>= 0, "Invalid ordinal.");
150 const AdditiveSymbol
& last
= aSymbols
[aSymbols
.Length() - 1];
151 if (last
.weight
== 0) {
152 aResult
= last
.symbol
;
160 for (size_t i
= 0, iEnd
= aSymbols
.Length(); i
< iEnd
; ++i
) {
161 const AdditiveSymbol
& symbol
= aSymbols
[i
];
162 if (symbol
.weight
== 0) {
165 CounterValue times
= aOrdinal
/ symbol
.weight
;
167 auto symbolLength
= symbol
.symbol
.Length();
168 if (symbolLength
> 0) {
169 length
+= times
* symbolLength
;
170 if (times
> LENGTH_LIMIT
|| symbolLength
> LENGTH_LIMIT
||
171 length
> LENGTH_LIMIT
) {
174 for (CounterValue j
= 0; j
< times
; ++j
) {
175 aResult
.Append(symbol
.symbol
);
178 aOrdinal
-= times
* symbol
.weight
;
181 return aOrdinal
== 0;
184 static bool DecimalToText(CounterValue aOrdinal
, nsAString
& aResult
) {
185 aResult
.AppendInt(aOrdinal
);
189 // We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
190 // georgian needs 6 at most
191 // armenian needs 12 at most
192 // hebrew may need more...
194 #define NUM_BUF_SIZE 34
196 enum CJKIdeographicLang
{ CHINESE
, KOREAN
, JAPANESE
};
197 struct CJKIdeographicData
{
204 static const CJKIdeographicData gDataJapaneseInformal
= {
205 {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
207 {0x5341, 0x767e, 0x5343}, // unit
208 {0x4e07, 0x5104}, // unit10K
212 static const CJKIdeographicData gDataJapaneseFormal
= {
213 {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
215 {0x62fe, 0x767e, 0x9621}, // unit
216 {0x842c, 0x5104}, // unit10K
220 static const CJKIdeographicData gDataKoreanHangulFormal
= {
221 {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
223 {0xc2ed, 0xbc31, 0xcc9c}, // unit
224 {0xb9cc, 0xc5b5}, // unit10K
228 static const CJKIdeographicData gDataKoreanHanjaInformal
= {
229 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
231 {0x5341, 0x767e, 0x5343}, // unit
232 {0x842c, 0x5104}, // unit10K
236 static const CJKIdeographicData gDataKoreanHanjaFormal
= {
237 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
239 {0x62fe, 0x767e, 0x4edf}, // unit
240 {0x842c, 0x5104}, // unit10K
244 static const CJKIdeographicData gDataSimpChineseInformal
= {
245 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
247 {0x5341, 0x767e, 0x5343}, // unit
248 {0x4e07, 0x4ebf}, // unit10K
252 static const CJKIdeographicData gDataSimpChineseFormal
= {
253 {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
255 {0x62fe, 0x4f70, 0x4edf}, // unit
256 {0x4e07, 0x4ebf}, // unit10K
260 static const CJKIdeographicData gDataTradChineseInformal
= {
261 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
263 {0x5341, 0x767e, 0x5343}, // unit
264 {0x842c, 0x5104}, // unit10K
268 static const CJKIdeographicData gDataTradChineseFormal
= {
269 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
271 {0x62fe, 0x4f70, 0x4edf}, // unit
272 {0x842c, 0x5104}, // unit10K
277 static bool CJKIdeographicToText(CounterValue aOrdinal
, nsAString
& aResult
,
278 const CJKIdeographicData
& data
) {
279 NS_ASSERTION(aOrdinal
>= 0, "Only accept non-negative ordinal");
280 char16_t buf
[NUM_BUF_SIZE
];
281 int32_t idx
= NUM_BUF_SIZE
;
283 bool needZero
= (aOrdinal
== 0);
284 int32_t unitidx
= 0, unit10Kidx
= 0;
288 unit10Kidx
= pos
/ 4;
290 auto cur
= static_cast<MakeUnsigned
<CounterValue
>::Type
>(aOrdinal
) % 10;
294 buf
[--idx
] = data
.digit
[0];
297 if (data
.lang
== CHINESE
) {
300 if (unit10Kidx
!= 0) {
301 if (data
.lang
== KOREAN
&& idx
!= NUM_BUF_SIZE
) {
304 buf
[--idx
] = data
.unit10K
[unit10Kidx
- 1];
307 buf
[--idx
] = data
.unit
[unitidx
- 1];
310 buf
[--idx
] = data
.digit
[cur
];
317 (aOrdinal
== 1 || (pos
> 4 && aOrdinal
% 1000 == 1))) {
323 (unitidx
!= 3 || (pos
== 3 && aOrdinal
== 1))) {
328 if (unitidx
> 0 || (pos
== 4 && (aOrdinal
% 1000) == 1)) {
335 buf
[--idx
] = data
.digit
[1];
342 } while (aOrdinal
> 0);
343 aResult
.Assign(buf
+ idx
, NUM_BUF_SIZE
- idx
);
347 #define HEBREW_GERESH 0x05F3
348 static const char16_t gHebrewDigit
[22] = {
350 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
351 // 10 20 30 40 50 60 70 80 90
352 0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
354 0x05E7, 0x05E8, 0x05E9, 0x05EA};
356 static bool HebrewToText(CounterValue aOrdinal
, nsAString
& aResult
) {
357 if (aOrdinal
< 1 || aOrdinal
> 999999) {
361 bool outputSep
= false;
362 nsAutoString allText
, thousandsGroup
;
364 thousandsGroup
.Truncate();
365 int32_t n3
= aOrdinal
% 1000;
366 // Process digit for 100 - 900
367 for (int32_t n1
= 400; n1
> 0;) {
370 thousandsGroup
.Append(gHebrewDigit
[(n1
/ 100) - 1 + 18]);
376 // Process digit for 10 - 90
379 // Special process for 15 and 16
380 if ((15 == n3
) || (16 == n3
)) {
381 // Special rule for religious reason...
382 // 15 is represented by 9 and 6, not 10 and 5
383 // 16 is represented by 9 and 7, not 10 and 6
385 thousandsGroup
.Append(gHebrewDigit
[n2
- 1]);
388 thousandsGroup
.Append(gHebrewDigit
[(n2
/ 10) - 1 + 9]);
393 // Process digit for 1 - 9
394 if (n3
> 0) thousandsGroup
.Append(gHebrewDigit
[n3
- 1]);
395 if (outputSep
) thousandsGroup
.Append((char16_t
)HEBREW_GERESH
);
396 if (allText
.IsEmpty())
397 allText
= thousandsGroup
;
399 allText
= thousandsGroup
+ allText
;
402 } while (aOrdinal
>= 1);
408 // Convert ordinal to Ethiopic numeric representation.
409 // The detail is available at http://www.ethiopic.org/Numerals/
410 // The algorithm used here is based on the pseudo-code put up there by
411 // Daniel Yacob <yacob@geez.org>.
412 // Another reference is Unicode 3.0 standard section 11.1.
413 #define ETHIOPIC_ONE 0x1369
414 #define ETHIOPIC_TEN 0x1372
415 #define ETHIOPIC_HUNDRED 0x137B
416 #define ETHIOPIC_TEN_THOUSAND 0x137C
418 static bool EthiopicToText(CounterValue aOrdinal
, nsAString
& aResult
) {
423 nsAutoString asciiNumberString
; // decimal string representation of ordinal
424 DecimalToText(aOrdinal
, asciiNumberString
);
425 uint8_t asciiStringLength
= asciiNumberString
.Length();
427 // If number length is odd, add a leading "0"
428 // the leading "0" preconditions the string to always have the
429 // leading tens place populated, this avoids a check within the loop.
430 // If we didn't add the leading "0", decrement asciiStringLength so
431 // it will be equivalent to a zero-based index in both cases.
432 if (asciiStringLength
& 1) {
433 asciiNumberString
.InsertLiteral(u
"0", 0);
439 // Iterate from the highest digits to lowest
440 // indexFromLeft indexes digits (0 = most significant)
441 // groupIndexFromRight indexes pairs of digits (0 = least significant)
442 for (uint8_t indexFromLeft
= 0, groupIndexFromRight
= asciiStringLength
>> 1;
443 indexFromLeft
<= asciiStringLength
;
444 indexFromLeft
+= 2, groupIndexFromRight
--) {
445 uint8_t tensValue
= asciiNumberString
.CharAt(indexFromLeft
) & 0x0F;
446 uint8_t unitsValue
= asciiNumberString
.CharAt(indexFromLeft
+ 1) & 0x0F;
447 uint8_t groupValue
= tensValue
* 10 + unitsValue
;
449 bool oddGroup
= (groupIndexFromRight
& 1);
451 // we want to clear ETHIOPIC_ONE when it is superfluous
452 if (aOrdinal
> 1 && groupValue
== 1 && // one without a leading ten
454 indexFromLeft
== 0)) { // preceding (100) or leading the sequence
458 // put it all together...
460 // map onto Ethiopic "tens":
461 aResult
.Append((char16_t
)(tensValue
+ ETHIOPIC_TEN
- 1));
464 // map onto Ethiopic "units":
465 aResult
.Append((char16_t
)(unitsValue
+ ETHIOPIC_ONE
- 1));
467 // Add a separator for all even groups except the last,
468 // and for odd groups with non-zero value.
471 aResult
.Append((char16_t
)ETHIOPIC_HUNDRED
);
474 if (groupIndexFromRight
) {
475 aResult
.Append((char16_t
)ETHIOPIC_TEN_THOUSAND
);
482 static uint8_t GetDefaultSpeakAsForSystem(uint8_t aSystem
) {
483 MOZ_ASSERT(aSystem
!= NS_STYLE_COUNTER_SYSTEM_EXTENDS
,
484 "Extends system does not have static default speak-as");
486 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
487 return NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT
;
488 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
489 return NS_STYLE_COUNTER_SPEAKAS_BULLETS
;
491 return NS_STYLE_COUNTER_SPEAKAS_NUMBERS
;
495 static bool SystemUsesNegativeSign(uint8_t aSystem
) {
496 MOZ_ASSERT(aSystem
!= NS_STYLE_COUNTER_SYSTEM_EXTENDS
,
497 "Cannot check this for extending style");
499 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC
:
500 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
501 case NS_STYLE_COUNTER_SYSTEM_NUMERIC
:
502 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE
:
509 class BuiltinCounterStyle
: public CounterStyle
{
511 constexpr BuiltinCounterStyle(int32_t aStyle
, nsStaticAtom
* aName
)
512 : CounterStyle(aStyle
), mName(aName
) {}
514 nsStaticAtom
* GetStyleName() const { return mName
; }
516 virtual void GetPrefix(nsAString
& aResult
) override
;
517 virtual void GetSuffix(nsAString
& aResult
) override
;
518 virtual void GetSpokenCounterText(CounterValue aOrdinal
,
519 WritingMode aWritingMode
,
521 bool& aIsBullet
) override
;
522 virtual bool IsBullet() override
;
524 virtual void GetNegative(NegativeType
& aResult
) override
;
525 virtual bool IsOrdinalInRange(CounterValue aOrdinal
) override
;
526 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal
) override
;
527 virtual void GetPad(PadType
& aResult
) override
;
528 virtual CounterStyle
* GetFallback() override
;
529 virtual uint8_t GetSpeakAs() override
;
530 virtual bool UseNegativeSign() override
;
532 virtual bool GetInitialCounterText(CounterValue aOrdinal
,
533 WritingMode aWritingMode
,
534 nsAString
& aResult
, bool& aIsRTL
) override
;
537 constexpr BuiltinCounterStyle(const BuiltinCounterStyle
& aOther
)
538 : CounterStyle(aOther
.mStyle
), mName(aOther
.mName
) {}
545 void BuiltinCounterStyle::GetPrefix(nsAString
& aResult
) { aResult
.Truncate(); }
548 void BuiltinCounterStyle::GetSuffix(nsAString
& aResult
) {
550 case NS_STYLE_LIST_STYLE_NONE
:
554 case NS_STYLE_LIST_STYLE_DISC
:
555 case NS_STYLE_LIST_STYLE_CIRCLE
:
556 case NS_STYLE_LIST_STYLE_SQUARE
:
557 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
558 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
559 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC
:
563 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
564 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
565 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
566 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
567 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
568 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
572 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
573 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
574 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
575 aResult
.AssignLiteral(u
", ");
579 aResult
.AssignLiteral(u
". ");
584 static const char16_t kDiscCharacter
= 0x2022;
585 static const char16_t kCircleCharacter
= 0x25e6;
586 static const char16_t kSquareCharacter
= 0x25fe;
587 static const char16_t kRightPointingCharacter
= 0x25b8;
588 static const char16_t kLeftPointingCharacter
= 0x25c2;
589 static const char16_t kDownPointingCharacter
= 0x25be;
592 void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
593 WritingMode aWritingMode
,
597 case NS_STYLE_LIST_STYLE_NONE
:
598 case NS_STYLE_LIST_STYLE_DISC
:
599 case NS_STYLE_LIST_STYLE_CIRCLE
:
600 case NS_STYLE_LIST_STYLE_SQUARE
:
601 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
602 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
: {
603 // Same as the initial representation
605 GetInitialCounterText(aOrdinal
, aWritingMode
, aResult
, isRTL
);
610 CounterStyle::GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
617 bool BuiltinCounterStyle::IsBullet() {
619 case NS_STYLE_LIST_STYLE_DISC
:
620 case NS_STYLE_LIST_STYLE_CIRCLE
:
621 case NS_STYLE_LIST_STYLE_SQUARE
:
622 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
623 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
630 static const char16_t gJapaneseNegative
[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
632 static const char16_t gKoreanNegative
[] = {0xb9c8, 0xc774, 0xb108,
633 0xc2a4, 0x0020, 0x0000};
634 static const char16_t gSimpChineseNegative
[] = {0x8d1f, 0x0000};
635 static const char16_t gTradChineseNegative
[] = {0x8ca0, 0x0000};
638 void BuiltinCounterStyle::GetNegative(NegativeType
& aResult
) {
640 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
641 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
642 aResult
.before
= gJapaneseNegative
;
645 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
646 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
647 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
648 aResult
.before
= gKoreanNegative
;
651 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
652 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
653 aResult
.before
= gSimpChineseNegative
;
656 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
657 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
658 aResult
.before
= gTradChineseNegative
;
662 aResult
.before
.AssignLiteral(u
"-");
664 aResult
.after
.Truncate();
668 bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
672 case NS_STYLE_LIST_STYLE_NONE
:
673 case NS_STYLE_LIST_STYLE_DISC
:
674 case NS_STYLE_LIST_STYLE_CIRCLE
:
675 case NS_STYLE_LIST_STYLE_SQUARE
:
676 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
677 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
679 case NS_STYLE_LIST_STYLE_DECIMAL
:
680 // use CJKIdeographicToText
681 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
682 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
683 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
684 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
685 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
686 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
687 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
688 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
689 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
692 // use EthiopicToText
693 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC
:
694 return aOrdinal
>= 1;
697 case NS_STYLE_LIST_STYLE_HEBREW
:
698 return aOrdinal
>= 1 && aOrdinal
<= 999999;
703 bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
706 case NS_STYLE_LIST_STYLE_NONE
:
707 case NS_STYLE_LIST_STYLE_DISC
:
708 case NS_STYLE_LIST_STYLE_CIRCLE
:
709 case NS_STYLE_LIST_STYLE_SQUARE
:
710 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
711 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
713 case NS_STYLE_LIST_STYLE_DECIMAL
:
717 case NS_STYLE_LIST_STYLE_HEBREW
:
718 return aOrdinal
>= 0;
720 // complex predefined:
721 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
722 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
723 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
724 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
725 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
726 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
727 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
728 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
729 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
730 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC
:
731 return IsOrdinalInRange(aOrdinal
);
734 MOZ_ASSERT_UNREACHABLE("Unknown counter style");
740 void BuiltinCounterStyle::GetPad(PadType
& aResult
) {
742 aResult
.symbol
.Truncate();
746 CounterStyle
* BuiltinCounterStyle::GetFallback() {
747 // Fallback of dependent builtin counter styles are handled in class
748 // DependentBuiltinCounterStyle.
749 return CounterStyleManager::GetDecimalStyle();
753 uint8_t BuiltinCounterStyle::GetSpeakAs() {
755 case NS_STYLE_LIST_STYLE_NONE
:
756 case NS_STYLE_LIST_STYLE_DISC
:
757 case NS_STYLE_LIST_STYLE_CIRCLE
:
758 case NS_STYLE_LIST_STYLE_SQUARE
:
759 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
760 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
761 return NS_STYLE_COUNTER_SPEAKAS_BULLETS
;
763 return NS_STYLE_COUNTER_SPEAKAS_NUMBERS
;
768 bool BuiltinCounterStyle::UseNegativeSign() {
770 case NS_STYLE_LIST_STYLE_NONE
:
771 case NS_STYLE_LIST_STYLE_DISC
:
772 case NS_STYLE_LIST_STYLE_CIRCLE
:
773 case NS_STYLE_LIST_STYLE_SQUARE
:
774 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
775 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
783 bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
784 WritingMode aWritingMode
,
789 // used by counters & extends counter-style code only
790 // XXX We really need to do this the same way we do list bullets.
791 case NS_STYLE_LIST_STYLE_NONE
:
794 case NS_STYLE_LIST_STYLE_DISC
:
795 aResult
.Assign(kDiscCharacter
);
797 case NS_STYLE_LIST_STYLE_CIRCLE
:
798 aResult
.Assign(kCircleCharacter
);
800 case NS_STYLE_LIST_STYLE_SQUARE
:
801 aResult
.Assign(kSquareCharacter
);
803 case NS_STYLE_LIST_STYLE_DISCLOSURE_CLOSED
:
804 if (aWritingMode
.IsVertical()) {
805 aResult
.Assign(kDownPointingCharacter
);
806 } else if (aWritingMode
.IsBidiLTR()) {
807 aResult
.Assign(kRightPointingCharacter
);
809 aResult
.Assign(kLeftPointingCharacter
);
812 case NS_STYLE_LIST_STYLE_DISCLOSURE_OPEN
:
813 if (!aWritingMode
.IsVertical()) {
814 aResult
.Assign(kDownPointingCharacter
);
815 } else if (aWritingMode
.IsVerticalLR()) {
816 aResult
.Assign(kRightPointingCharacter
);
818 aResult
.Assign(kLeftPointingCharacter
);
822 case NS_STYLE_LIST_STYLE_DECIMAL
:
823 return DecimalToText(aOrdinal
, aResult
);
825 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
826 return CJKIdeographicToText(aOrdinal
, aResult
, gDataTradChineseInformal
);
827 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
828 return CJKIdeographicToText(aOrdinal
, aResult
, gDataTradChineseFormal
);
829 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
830 return CJKIdeographicToText(aOrdinal
, aResult
, gDataSimpChineseInformal
);
831 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
832 return CJKIdeographicToText(aOrdinal
, aResult
, gDataSimpChineseFormal
);
833 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
834 return CJKIdeographicToText(aOrdinal
, aResult
, gDataJapaneseInformal
);
835 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
836 return CJKIdeographicToText(aOrdinal
, aResult
, gDataJapaneseFormal
);
837 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
838 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHangulFormal
);
839 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
840 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHanjaInformal
);
841 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
842 return CJKIdeographicToText(aOrdinal
, aResult
, gDataKoreanHanjaFormal
);
844 case NS_STYLE_LIST_STYLE_HEBREW
:
846 return HebrewToText(aOrdinal
, aResult
);
848 case NS_STYLE_LIST_STYLE_ETHIOPIC_NUMERIC
:
849 return EthiopicToText(aOrdinal
, aResult
);
852 MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
857 static constexpr BuiltinCounterStyle gBuiltinStyleTable
[] = {
858 #define BUILTIN_COUNTER_STYLE(value_, atom_) \
859 {NS_STYLE_LIST_STYLE_##value_, nsGkAtoms::atom_},
860 #include "BuiltinCounterStyleList.h"
861 #undef BUILTIN_COUNTER_STYLE
864 #define BUILTIN_COUNTER_STYLE(value_, atom_) \
865 static_assert(gBuiltinStyleTable[NS_STYLE_LIST_STYLE_##value_].GetStyle() == \
866 NS_STYLE_LIST_STYLE_##value_, \
867 "Builtin counter style " #atom_ \
868 " has unmatched index and value.");
869 #include "BuiltinCounterStyleList.h"
870 #undef BUILTIN_COUNTER_STYLE
872 class DependentBuiltinCounterStyle final
: public BuiltinCounterStyle
{
874 DependentBuiltinCounterStyle(int32_t aStyle
, CounterStyleManager
* aManager
)
875 : BuiltinCounterStyle(gBuiltinStyleTable
[aStyle
]), mManager(aManager
) {
876 NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
877 MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
880 virtual CounterStyle
* GetFallback() override
;
882 void* operator new(size_t sz
, nsPresContext
* aPresContext
) {
883 return aPresContext
->PresShell()->AllocateByObjectID(
884 eArenaObjectID_DependentBuiltinCounterStyle
, sz
);
888 PresShell
* presShell
= mManager
->PresContext()->PresShell();
889 this->~DependentBuiltinCounterStyle();
890 presShell
->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle
,
895 ~DependentBuiltinCounterStyle() {}
897 CounterStyleManager
* mManager
;
901 CounterStyle
* DependentBuiltinCounterStyle::GetFallback() {
902 switch (GetStyle()) {
903 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
904 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
905 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
906 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
907 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
908 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
909 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
910 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
911 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
912 // These styles all have a larger range than cjk-decimal, so the
913 // only case fallback is accessed is that they are extended.
914 // Since extending styles will cache the data themselves, we need
915 // not cache it here.
916 return mManager
->ResolveCounterStyle(nsGkAtoms::cjk_decimal
);
918 MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
919 return BuiltinCounterStyle::GetFallback();
923 class CustomCounterStyle final
: public CounterStyle
{
925 CustomCounterStyle(CounterStyleManager
* aManager
,
926 const RawServoCounterStyleRule
* aRule
)
927 : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM
),
930 mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule
)),
931 mSystem(Servo_CounterStyleRule_GetSystem(aRule
)),
934 mSpeakAsCounter(nullptr),
936 mExtendsRoot(nullptr) {}
938 // This method will clear all cached data in the style and update the
939 // generation number of the rule. It should be called when the rule of
940 // this style is changed.
941 void ResetCachedData();
943 // This method will reset all cached data which may depend on other
944 // counter style. It will reset all pointers to other counter styles.
945 // For counter style extends other, in addition, all fields will be
946 // reset to uninitialized state. This method should be called when any
947 // other counter style is added, removed, or changed.
948 void ResetDependentData();
950 const RawServoCounterStyleRule
* GetRule() const { return mRule
; }
951 uint32_t GetRuleGeneration() const { return mRuleGeneration
; }
953 virtual void GetPrefix(nsAString
& aResult
) override
;
954 virtual void GetSuffix(nsAString
& aResult
) override
;
955 virtual void GetSpokenCounterText(CounterValue aOrdinal
,
956 WritingMode aWritingMode
,
958 bool& aIsBullet
) override
;
959 virtual bool IsBullet() override
;
961 virtual void GetNegative(NegativeType
& aResult
) override
;
962 virtual bool IsOrdinalInRange(CounterValue aOrdinal
) override
;
963 virtual bool IsOrdinalInAutoRange(CounterValue aOrdinal
) override
;
964 virtual void GetPad(PadType
& aResult
) override
;
965 virtual CounterStyle
* GetFallback() override
;
966 virtual uint8_t GetSpeakAs() override
;
967 virtual bool UseNegativeSign() override
;
969 virtual void CallFallbackStyle(CounterValue aOrdinal
,
970 WritingMode aWritingMode
, nsAString
& aResult
,
971 bool& aIsRTL
) override
;
972 virtual bool GetInitialCounterText(CounterValue aOrdinal
,
973 WritingMode aWritingMode
,
974 nsAString
& aResult
, bool& aIsRTL
) override
;
976 bool IsExtendsSystem() { return mSystem
== NS_STYLE_COUNTER_SYSTEM_EXTENDS
; }
978 void* operator new(size_t sz
, nsPresContext
* aPresContext
) {
979 return aPresContext
->PresShell()->AllocateByObjectID(
980 eArenaObjectID_CustomCounterStyle
, sz
);
984 PresShell
* presShell
= mManager
->PresContext()->PresShell();
985 this->~CustomCounterStyle();
986 presShell
->FreeByObjectID(eArenaObjectID_CustomCounterStyle
, this);
990 ~CustomCounterStyle() {}
992 Span
<const nsString
> GetSymbols();
993 Span
<const AdditiveSymbol
> GetAdditiveSymbols();
995 // The speak-as values of counter styles may form a loop, and the
996 // loops may have complex interaction with the loop formed by
997 // extending. To solve this problem, the computation of speak-as is
998 // divided into two phases:
999 // 1. figure out the raw value, by ComputeRawSpeakAs, and
1000 // 2. eliminate loop, by ComputeSpeakAs.
1001 // See comments before the definitions of these methods for details.
1002 uint8_t GetSpeakAsAutoValue();
1003 void ComputeRawSpeakAs(uint8_t& aSpeakAs
, CounterStyle
*& aSpeakAsCounter
);
1004 CounterStyle
* ComputeSpeakAs();
1006 CounterStyle
* ComputeExtends();
1007 CounterStyle
* GetExtends();
1008 CounterStyle
* GetExtendsRoot();
1010 // CounterStyleManager should always overlive any CounterStyle as it
1011 // is owned by nsPresContext, and will be released after all nodes and
1012 // frames are released.
1013 CounterStyleManager
* mManager
;
1015 RefPtr
<const RawServoCounterStyleRule
> mRule
;
1016 uint32_t mRuleGeneration
;
1019 // GetSpeakAs will ensure that private member mSpeakAs is initialized before
1021 MOZ_INIT_OUTSIDE_CTOR
uint8_t mSpeakAs
;
1025 FLAG_EXTENDS_VISITED
= 1 << 0,
1026 FLAG_EXTENDS_LOOP
= 1 << 1,
1027 FLAG_SPEAKAS_VISITED
= 1 << 2,
1028 FLAG_SPEAKAS_LOOP
= 1 << 3,
1030 FLAG_NEGATIVE_INITED
= 1 << 4,
1031 FLAG_PREFIX_INITED
= 1 << 5,
1032 FLAG_SUFFIX_INITED
= 1 << 6,
1033 FLAG_PAD_INITED
= 1 << 7,
1034 FLAG_SPEAKAS_INITED
= 1 << 8,
1038 // Fields below will be initialized when necessary.
1039 StyleOwnedSlice
<nsString
> mSymbols
;
1040 StyleOwnedSlice
<AdditiveSymbol
> mAdditiveSymbols
;
1041 NegativeType mNegative
;
1042 nsString mPrefix
, mSuffix
;
1045 // CounterStyleManager will guarantee that none of the pointers below
1046 // refers to a freed CounterStyle. There are two possible cases where
1047 // the manager will release its reference to a CounterStyle: 1. the
1048 // manager itself is released, 2. a rule is invalidated. In the first
1049 // case, all counter style are removed from the manager, and should
1050 // also have been dereferenced from other objects. All styles will be
1051 // released all together. In the second case, CounterStyleManager::
1052 // NotifyRuleChanged will guarantee that all pointers will be reset
1053 // before any CounterStyle is released.
1055 CounterStyle
* mFallback
;
1056 // This field refers to the last counter in a speak-as chain.
1057 // That counter must not speak as another counter.
1058 CounterStyle
* mSpeakAsCounter
;
1060 CounterStyle
* mExtends
;
1061 // This field refers to the last counter in the extends chain. The
1062 // counter must be either a builtin style or a style whose system is
1064 CounterStyle
* mExtendsRoot
;
1067 void CustomCounterStyle::ResetCachedData() {
1069 mAdditiveSymbols
.Clear();
1070 mFlags
&= ~(FLAG_NEGATIVE_INITED
| FLAG_PREFIX_INITED
| FLAG_SUFFIX_INITED
|
1071 FLAG_PAD_INITED
| FLAG_SPEAKAS_INITED
);
1072 mFallback
= nullptr;
1073 mSpeakAsCounter
= nullptr;
1075 mExtendsRoot
= nullptr;
1076 mRuleGeneration
= Servo_CounterStyleRule_GetGeneration(mRule
);
1079 void CustomCounterStyle::ResetDependentData() {
1080 mFlags
&= ~FLAG_SPEAKAS_INITED
;
1081 mSpeakAsCounter
= nullptr;
1082 mFallback
= nullptr;
1084 mExtendsRoot
= nullptr;
1085 if (IsExtendsSystem()) {
1086 mFlags
&= ~(FLAG_NEGATIVE_INITED
| FLAG_PREFIX_INITED
| FLAG_SUFFIX_INITED
|
1092 void CustomCounterStyle::GetPrefix(nsAString
& aResult
) {
1093 if (!(mFlags
& FLAG_PREFIX_INITED
)) {
1094 mFlags
|= FLAG_PREFIX_INITED
;
1096 if (!Servo_CounterStyleRule_GetPrefix(mRule
, &mPrefix
)) {
1097 if (IsExtendsSystem()) {
1098 GetExtends()->GetPrefix(mPrefix
);
1108 void CustomCounterStyle::GetSuffix(nsAString
& aResult
) {
1109 if (!(mFlags
& FLAG_SUFFIX_INITED
)) {
1110 mFlags
|= FLAG_SUFFIX_INITED
;
1112 if (!Servo_CounterStyleRule_GetSuffix(mRule
, &mSuffix
)) {
1113 if (IsExtendsSystem()) {
1114 GetExtends()->GetSuffix(mSuffix
);
1116 mSuffix
.AssignLiteral(u
". ");
1124 void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
1125 WritingMode aWritingMode
,
1128 if (GetSpeakAs() != NS_STYLE_COUNTER_SPEAKAS_OTHER
) {
1129 CounterStyle::GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
1132 MOZ_ASSERT(mSpeakAsCounter
,
1133 "mSpeakAsCounter should have been initialized.");
1134 mSpeakAsCounter
->GetSpokenCounterText(aOrdinal
, aWritingMode
, aResult
,
1140 bool CustomCounterStyle::IsBullet() {
1142 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1143 // Only use ::-moz-list-bullet for cyclic system
1145 case NS_STYLE_COUNTER_SYSTEM_EXTENDS
:
1146 return GetExtendsRoot()->IsBullet();
1153 void CustomCounterStyle::GetNegative(NegativeType
& aResult
) {
1154 if (!(mFlags
& FLAG_NEGATIVE_INITED
)) {
1155 mFlags
|= FLAG_NEGATIVE_INITED
;
1156 if (!Servo_CounterStyleRule_GetNegative(mRule
, &mNegative
.before
,
1157 &mNegative
.after
)) {
1158 if (IsExtendsSystem()) {
1159 GetExtends()->GetNegative(mNegative
);
1161 mNegative
.before
.AssignLiteral(u
"-");
1162 mNegative
.after
.Truncate();
1166 aResult
= mNegative
;
1170 bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
1171 auto inRange
= Servo_CounterStyleRule_IsInRange(mRule
, aOrdinal
);
1173 case StyleIsOrdinalInRange::InRange
:
1175 case StyleIsOrdinalInRange::NotInRange
:
1177 case StyleIsOrdinalInRange::NoOrdinalSpecified
:
1178 if (IsExtendsSystem()) {
1179 return GetExtends()->IsOrdinalInRange(aOrdinal
);
1182 case StyleIsOrdinalInRange::Auto
:
1185 MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
1187 return IsOrdinalInAutoRange(aOrdinal
);
1191 bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
1193 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1194 case NS_STYLE_COUNTER_SYSTEM_NUMERIC
:
1195 case NS_STYLE_COUNTER_SYSTEM_FIXED
:
1197 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
1198 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC
:
1199 return aOrdinal
>= 1;
1200 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE
:
1201 return aOrdinal
>= 0;
1202 case NS_STYLE_COUNTER_SYSTEM_EXTENDS
:
1203 return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal
);
1205 MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
1211 void CustomCounterStyle::GetPad(PadType
& aResult
) {
1212 if (!(mFlags
& FLAG_PAD_INITED
)) {
1213 mFlags
|= FLAG_PAD_INITED
;
1214 if (!Servo_CounterStyleRule_GetPad(mRule
, &mPad
.width
, &mPad
.symbol
)) {
1215 if (IsExtendsSystem()) {
1216 GetExtends()->GetPad(mPad
);
1219 mPad
.symbol
.Truncate();
1227 CounterStyle
* CustomCounterStyle::GetFallback() {
1229 mFallback
= CounterStyleManager::GetDecimalStyle();
1230 if (nsAtom
* fallback
= Servo_CounterStyleRule_GetFallback(mRule
)) {
1231 mFallback
= mManager
->ResolveCounterStyle(fallback
);
1232 } else if (IsExtendsSystem()) {
1233 mFallback
= GetExtends()->GetFallback();
1240 uint8_t CustomCounterStyle::GetSpeakAs() {
1241 if (!(mFlags
& FLAG_SPEAKAS_INITED
)) {
1248 bool CustomCounterStyle::UseNegativeSign() {
1249 if (mSystem
== NS_STYLE_COUNTER_SYSTEM_EXTENDS
) {
1250 return GetExtendsRoot()->UseNegativeSign();
1252 return SystemUsesNegativeSign(mSystem
);
1256 void CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal
,
1257 WritingMode aWritingMode
,
1258 nsAString
& aResult
, bool& aIsRTL
) {
1259 CounterStyle
* fallback
= GetFallback();
1260 // If it recursively falls back to this counter style again,
1261 // it will then fallback to decimal to break the loop.
1262 mFallback
= CounterStyleManager::GetDecimalStyle();
1263 fallback
->GetCounterText(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1264 mFallback
= fallback
;
1268 bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
1269 WritingMode aWritingMode
,
1273 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1274 return GetCyclicCounterText(aOrdinal
, aResult
, GetSymbols());
1275 case NS_STYLE_COUNTER_SYSTEM_FIXED
: {
1276 int32_t start
= Servo_CounterStyleRule_GetFixedFirstValue(mRule
);
1277 return GetFixedCounterText(aOrdinal
, aResult
, start
, GetSymbols());
1279 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC
:
1280 return GetSymbolicCounterText(aOrdinal
, aResult
, GetSymbols());
1281 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
1282 return GetAlphabeticCounterText(aOrdinal
, aResult
, GetSymbols());
1283 case NS_STYLE_COUNTER_SYSTEM_NUMERIC
:
1284 return GetNumericCounterText(aOrdinal
, aResult
, GetSymbols());
1285 case NS_STYLE_COUNTER_SYSTEM_ADDITIVE
:
1286 return GetAdditiveCounterText(aOrdinal
, aResult
, GetAdditiveSymbols());
1287 case NS_STYLE_COUNTER_SYSTEM_EXTENDS
:
1288 return GetExtendsRoot()->GetInitialCounterText(aOrdinal
, aWritingMode
,
1291 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1296 Span
<const nsString
> CustomCounterStyle::GetSymbols() {
1297 if (mSymbols
.IsEmpty()) {
1298 Servo_CounterStyleRule_GetSymbols(mRule
, &mSymbols
);
1300 return mSymbols
.AsSpan();
1303 Span
<const AdditiveSymbol
> CustomCounterStyle::GetAdditiveSymbols() {
1304 if (mAdditiveSymbols
.IsEmpty()) {
1305 Servo_CounterStyleRule_GetAdditiveSymbols(mRule
, &mAdditiveSymbols
);
1307 return mAdditiveSymbols
.AsSpan();
1310 // This method is used to provide the computed value for 'auto'.
1311 uint8_t CustomCounterStyle::GetSpeakAsAutoValue() {
1312 uint8_t system
= mSystem
;
1313 if (IsExtendsSystem()) {
1314 CounterStyle
* root
= GetExtendsRoot();
1315 if (!root
->IsCustomStyle()) {
1316 // It is safe to call GetSpeakAs on non-custom style.
1317 return root
->GetSpeakAs();
1319 system
= static_cast<CustomCounterStyle
*>(root
)->mSystem
;
1321 return GetDefaultSpeakAsForSystem(system
);
1324 // This method corresponds to the first stage of computation of the
1325 // value of speak-as. It will extract the value from the rule and
1326 // possibly recursively call itself on the extended style to figure
1327 // out the raw value. To keep things clear, this method is designed to
1328 // have no side effects (but functions it calls may still affect other
1329 // fields in the style.)
1330 void CustomCounterStyle::ComputeRawSpeakAs(uint8_t& aSpeakAs
,
1331 CounterStyle
*& aSpeakAsCounter
) {
1332 NS_ASSERTION(!(mFlags
& FLAG_SPEAKAS_INITED
),
1333 "ComputeRawSpeakAs is called with speak-as inited.");
1335 auto speakAs
= StyleCounterSpeakAs::None();
1336 Servo_CounterStyleRule_GetSpeakAs(mRule
, &speakAs
);
1337 switch (speakAs
.tag
) {
1338 case StyleCounterSpeakAs::Tag::Auto
:
1339 aSpeakAs
= GetSpeakAsAutoValue();
1341 case StyleCounterSpeakAs::Tag::Bullets
:
1342 aSpeakAs
= NS_STYLE_COUNTER_SPEAKAS_BULLETS
;
1344 case StyleCounterSpeakAs::Tag::Numbers
:
1345 aSpeakAs
= NS_STYLE_COUNTER_SPEAKAS_NUMBERS
;
1347 case StyleCounterSpeakAs::Tag::Words
:
1348 aSpeakAs
= NS_STYLE_COUNTER_SPEAKAS_WORDS
;
1350 case StyleCounterSpeakAs::Tag::Ident
:
1351 aSpeakAs
= NS_STYLE_COUNTER_SPEAKAS_OTHER
;
1352 aSpeakAsCounter
= mManager
->ResolveCounterStyle(speakAs
.AsIdent());
1354 case StyleCounterSpeakAs::Tag::None
: {
1355 if (!IsExtendsSystem()) {
1356 aSpeakAs
= GetSpeakAsAutoValue();
1358 CounterStyle
* extended
= GetExtends();
1359 if (!extended
->IsCustomStyle()) {
1360 // It is safe to call GetSpeakAs on non-custom style.
1361 aSpeakAs
= extended
->GetSpeakAs();
1363 CustomCounterStyle
* custom
=
1364 static_cast<CustomCounterStyle
*>(extended
);
1365 if (!(custom
->mFlags
& FLAG_SPEAKAS_INITED
)) {
1366 custom
->ComputeRawSpeakAs(aSpeakAs
, aSpeakAsCounter
);
1368 aSpeakAs
= custom
->mSpeakAs
;
1369 aSpeakAsCounter
= custom
->mSpeakAsCounter
;
1376 MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
1380 // This method corresponds to the second stage of getting speak-as
1381 // related values. It will recursively figure out the final value of
1382 // mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
1383 // caller is in a loop, and the root counter style in the chain
1384 // otherwise. It use the same loop detection algorithm as
1385 // CustomCounterStyle::ComputeExtends, see comments before that
1386 // method for more details.
1387 CounterStyle
* CustomCounterStyle::ComputeSpeakAs() {
1388 if (mFlags
& FLAG_SPEAKAS_INITED
) {
1389 if (mSpeakAs
== NS_STYLE_COUNTER_SPEAKAS_OTHER
) {
1390 return mSpeakAsCounter
;
1395 if (mFlags
& FLAG_SPEAKAS_VISITED
) {
1397 mFlags
|= FLAG_SPEAKAS_LOOP
;
1401 CounterStyle
* speakAsCounter
;
1402 ComputeRawSpeakAs(mSpeakAs
, speakAsCounter
);
1404 bool inLoop
= false;
1405 if (mSpeakAs
!= NS_STYLE_COUNTER_SPEAKAS_OTHER
) {
1406 mSpeakAsCounter
= nullptr;
1407 } else if (!speakAsCounter
->IsCustomStyle()) {
1408 mSpeakAsCounter
= speakAsCounter
;
1410 mFlags
|= FLAG_SPEAKAS_VISITED
;
1411 CounterStyle
* target
=
1412 static_cast<CustomCounterStyle
*>(speakAsCounter
)->ComputeSpeakAs();
1413 mFlags
&= ~FLAG_SPEAKAS_VISITED
;
1416 NS_ASSERTION(!(mFlags
& FLAG_SPEAKAS_LOOP
),
1417 "Invalid state for speak-as loop detecting");
1418 mSpeakAsCounter
= target
;
1420 mSpeakAs
= GetSpeakAsAutoValue();
1421 mSpeakAsCounter
= nullptr;
1422 if (mFlags
& FLAG_SPEAKAS_LOOP
) {
1423 mFlags
&= ~FLAG_SPEAKAS_LOOP
;
1430 mFlags
|= FLAG_SPEAKAS_INITED
;
1434 return mSpeakAsCounter
? mSpeakAsCounter
: this;
1437 // This method will recursively figure out mExtends in the whole chain.
1438 // It will return nullptr if the caller is in a loop, and return this
1439 // otherwise. To detect the loop, this method marks the style VISITED
1440 // before the recursive call. When a VISITED style is reached again, the
1441 // loop is detected, and flag LOOP will be marked on the first style in
1442 // loop. mExtends of all counter styles in loop will be set to decimal
1443 // according to the spec.
1444 CounterStyle
* CustomCounterStyle::ComputeExtends() {
1445 if (!IsExtendsSystem() || mExtends
) {
1448 if (mFlags
& FLAG_EXTENDS_VISITED
) {
1450 mFlags
|= FLAG_EXTENDS_LOOP
;
1454 nsAtom
* extended
= Servo_CounterStyleRule_GetExtended(mRule
);
1455 CounterStyle
* nextCounter
= mManager
->ResolveCounterStyle(extended
);
1456 CounterStyle
* target
= nextCounter
;
1457 if (nextCounter
->IsCustomStyle()) {
1458 mFlags
|= FLAG_EXTENDS_VISITED
;
1459 target
= static_cast<CustomCounterStyle
*>(nextCounter
)->ComputeExtends();
1460 mFlags
&= ~FLAG_EXTENDS_VISITED
;
1464 NS_ASSERTION(!(mFlags
& FLAG_EXTENDS_LOOP
),
1465 "Invalid state for extends loop detecting");
1466 mExtends
= nextCounter
;
1469 mExtends
= CounterStyleManager::GetDecimalStyle();
1470 if (mFlags
& FLAG_EXTENDS_LOOP
) {
1471 mFlags
&= ~FLAG_EXTENDS_LOOP
;
1479 CounterStyle
* CustomCounterStyle::GetExtends() {
1481 // Any extends loop will be eliminated in the method below.
1487 CounterStyle
* CustomCounterStyle::GetExtendsRoot() {
1488 if (!mExtendsRoot
) {
1489 CounterStyle
* extended
= GetExtends();
1490 mExtendsRoot
= extended
;
1491 if (extended
->IsCustomStyle()) {
1492 CustomCounterStyle
* custom
= static_cast<CustomCounterStyle
*>(extended
);
1493 if (custom
->IsExtendsSystem()) {
1494 // This will make mExtendsRoot in the whole extends chain be
1495 // set recursively, which could save work when part of a chain
1496 // is shared by multiple counter styles.
1497 mExtendsRoot
= custom
->GetExtendsRoot();
1501 return mExtendsRoot
;
1504 AnonymousCounterStyle::AnonymousCounterStyle(const nsAString
& aContent
)
1505 : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM
),
1506 mSingleString(true),
1507 mSystem(NS_STYLE_COUNTER_SYSTEM_CYCLIC
) {
1508 mSymbols
.SetCapacity(1);
1509 mSymbols
.AppendElement(aContent
);
1512 AnonymousCounterStyle::AnonymousCounterStyle(uint8_t aSystem
,
1513 nsTArray
<nsString
> aSymbols
)
1514 : CounterStyle(NS_STYLE_LIST_STYLE_CUSTOM
),
1515 mSingleString(false),
1517 mSymbols(std::move(aSymbols
)) {}
1520 void AnonymousCounterStyle::GetPrefix(nsAString
& aResult
) {
1525 void AnonymousCounterStyle::GetSuffix(nsAString
& aResult
) {
1526 if (IsSingleString()) {
1534 bool AnonymousCounterStyle::IsBullet() {
1536 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1537 // Only use ::-moz-list-bullet for cyclic system
1545 void AnonymousCounterStyle::GetNegative(NegativeType
& aResult
) {
1546 aResult
.before
.AssignLiteral(u
"-");
1547 aResult
.after
.Truncate();
1551 bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal
) {
1553 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1554 case NS_STYLE_COUNTER_SYSTEM_NUMERIC
:
1555 case NS_STYLE_COUNTER_SYSTEM_FIXED
:
1557 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
1558 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC
:
1559 return aOrdinal
>= 1;
1561 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1567 bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal
) {
1568 return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal
);
1572 void AnonymousCounterStyle::GetPad(PadType
& aResult
) {
1574 aResult
.symbol
.Truncate();
1578 CounterStyle
* AnonymousCounterStyle::GetFallback() {
1579 return CounterStyleManager::GetDecimalStyle();
1583 uint8_t AnonymousCounterStyle::GetSpeakAs() {
1584 return GetDefaultSpeakAsForSystem(mSystem
);
1588 bool AnonymousCounterStyle::UseNegativeSign() {
1589 return SystemUsesNegativeSign(mSystem
);
1593 bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal
,
1594 WritingMode aWritingMode
,
1598 case NS_STYLE_COUNTER_SYSTEM_CYCLIC
:
1599 return GetCyclicCounterText(aOrdinal
, aResult
, mSymbols
);
1600 case NS_STYLE_COUNTER_SYSTEM_FIXED
:
1601 return GetFixedCounterText(aOrdinal
, aResult
, 1, mSymbols
);
1602 case NS_STYLE_COUNTER_SYSTEM_SYMBOLIC
:
1603 return GetSymbolicCounterText(aOrdinal
, aResult
, mSymbols
);
1604 case NS_STYLE_COUNTER_SYSTEM_ALPHABETIC
:
1605 return GetAlphabeticCounterText(aOrdinal
, aResult
, mSymbols
);
1606 case NS_STYLE_COUNTER_SYSTEM_NUMERIC
:
1607 return GetNumericCounterText(aOrdinal
, aResult
, mSymbols
);
1609 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1614 bool CounterStyle::IsDependentStyle() const {
1616 // CustomCounterStyle
1617 case NS_STYLE_LIST_STYLE_CUSTOM
:
1618 // DependentBuiltinCounterStyle
1619 case NS_STYLE_LIST_STYLE_JAPANESE_INFORMAL
:
1620 case NS_STYLE_LIST_STYLE_JAPANESE_FORMAL
:
1621 case NS_STYLE_LIST_STYLE_KOREAN_HANGUL_FORMAL
:
1622 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_INFORMAL
:
1623 case NS_STYLE_LIST_STYLE_KOREAN_HANJA_FORMAL
:
1624 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_INFORMAL
:
1625 case NS_STYLE_LIST_STYLE_SIMP_CHINESE_FORMAL
:
1626 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_INFORMAL
:
1627 case NS_STYLE_LIST_STYLE_TRAD_CHINESE_FORMAL
:
1630 // BuiltinCounterStyle
1636 void CounterStyle::GetCounterText(CounterValue aOrdinal
,
1637 WritingMode aWritingMode
, nsAString
& aResult
,
1639 bool success
= IsOrdinalInRange(aOrdinal
);
1643 // generate initial representation
1644 bool useNegativeSign
= UseNegativeSign();
1645 nsAutoString initialText
;
1646 CounterValue ordinal
;
1647 if (!useNegativeSign
) {
1650 CheckedInt
<CounterValue
> absolute(Abs(aOrdinal
));
1651 ordinal
= absolute
.isValid() ? absolute
.value()
1652 : std::numeric_limits
<CounterValue
>::max();
1654 success
= GetInitialCounterText(ordinal
, aWritingMode
, initialText
, aIsRTL
);
1656 // add pad & negative, build the final result
1660 // We have to calculate the difference here since suffix part of negative
1661 // sign may be appended to initialText later.
1662 int32_t diff
= pad
.width
- unicode::CountGraphemeClusters(
1663 initialText
.Data(), initialText
.Length());
1665 if (useNegativeSign
&& aOrdinal
< 0) {
1666 NegativeType negative
;
1667 GetNegative(negative
);
1668 aResult
.Append(negative
.before
);
1669 // There is nothing between the suffix part of negative and initial
1670 // representation, so we append it directly here.
1671 initialText
.Append(negative
.after
);
1674 auto length
= pad
.symbol
.Length();
1675 if (diff
> LENGTH_LIMIT
|| length
> LENGTH_LIMIT
||
1676 diff
* length
> LENGTH_LIMIT
) {
1678 } else if (length
> 0) {
1679 for (int32_t i
= 0; i
< diff
; ++i
) {
1680 aResult
.Append(pad
.symbol
);
1685 aResult
.Append(initialText
);
1691 CallFallbackStyle(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1696 void CounterStyle::GetSpokenCounterText(CounterValue aOrdinal
,
1697 WritingMode aWritingMode
,
1698 nsAString
& aResult
, bool& aIsBullet
) {
1699 bool isRTL
; // we don't care about direction for spoken text
1701 switch (GetSpeakAs()) {
1702 case NS_STYLE_COUNTER_SPEAKAS_BULLETS
:
1703 aResult
.Assign(kDiscCharacter
);
1706 case NS_STYLE_COUNTER_SPEAKAS_NUMBERS
:
1707 DecimalToText(aOrdinal
, aResult
);
1709 case NS_STYLE_COUNTER_SPEAKAS_SPELL_OUT
:
1710 // we currently do not actually support 'spell-out',
1711 // so 'words' is used instead.
1712 case NS_STYLE_COUNTER_SPEAKAS_WORDS
:
1713 GetCounterText(aOrdinal
, WritingMode(), aResult
, isRTL
);
1715 case NS_STYLE_COUNTER_SPEAKAS_OTHER
:
1716 // This should be processed by CustomCounterStyle
1717 MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
1720 MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
1726 void CounterStyle::CallFallbackStyle(CounterValue aOrdinal
,
1727 WritingMode aWritingMode
,
1728 nsAString
& aResult
, bool& aIsRTL
) {
1729 GetFallback()->GetCounterText(aOrdinal
, aWritingMode
, aResult
, aIsRTL
);
1732 CounterStyleManager::CounterStyleManager(nsPresContext
* aPresContext
)
1733 : mPresContext(aPresContext
) {
1734 // Insert the static styles into cache table
1735 mStyles
.Put(nsGkAtoms::none
, GetNoneStyle());
1736 mStyles
.Put(nsGkAtoms::decimal
, GetDecimalStyle());
1737 mStyles
.Put(nsGkAtoms::disc
, GetDiscStyle());
1740 CounterStyleManager::~CounterStyleManager() {
1741 MOZ_ASSERT(!mPresContext
, "Disconnect should have been called");
1744 void CounterStyleManager::DestroyCounterStyle(CounterStyle
* aCounterStyle
) {
1745 if (aCounterStyle
->IsCustomStyle()) {
1746 MOZ_ASSERT(!aCounterStyle
->AsAnonymous(),
1747 "Anonymous counter styles "
1748 "are not managed by CounterStyleManager");
1749 static_cast<CustomCounterStyle
*>(aCounterStyle
)->Destroy();
1750 } else if (aCounterStyle
->IsDependentStyle()) {
1751 static_cast<DependentBuiltinCounterStyle
*>(aCounterStyle
)->Destroy();
1753 MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
1757 void CounterStyleManager::Disconnect() {
1758 CleanRetiredStyles();
1759 for (auto iter
= mStyles
.Iter(); !iter
.Done(); iter
.Next()) {
1760 CounterStyle
* style
= iter
.Data();
1761 if (style
->IsDependentStyle()) {
1762 DestroyCounterStyle(style
);
1766 mPresContext
= nullptr;
1769 CounterStyle
* CounterStyleManager::ResolveCounterStyle(nsAtom
* aName
) {
1770 MOZ_ASSERT(NS_IsMainThread());
1771 CounterStyle
* data
= GetCounterStyle(aName
);
1776 // Names are compared case-sensitively here. Predefined names should
1777 // have been lowercased by the parser.
1778 ServoStyleSet
* styleSet
= mPresContext
->StyleSet();
1779 auto* rule
= styleSet
->CounterStyleRuleForName(aName
);
1781 MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule
) == aName
);
1782 data
= new (mPresContext
) CustomCounterStyle(this, rule
);
1784 for (const BuiltinCounterStyle
& item
: gBuiltinStyleTable
) {
1785 if (item
.GetStyleName() == aName
) {
1786 int32_t style
= item
.GetStyle();
1787 data
= item
.IsDependentStyle()
1788 ? new (mPresContext
)
1789 DependentBuiltinCounterStyle(style
, this)
1790 : GetBuiltinStyle(style
);
1796 data
= GetDecimalStyle();
1798 mStyles
.Put(aName
, data
);
1803 CounterStyle
* CounterStyleManager::GetBuiltinStyle(int32_t aStyle
) {
1804 MOZ_ASSERT(0 <= aStyle
&& size_t(aStyle
) < sizeof(gBuiltinStyleTable
),
1805 "Require a valid builtin style constant");
1806 MOZ_ASSERT(!gBuiltinStyleTable
[aStyle
].IsDependentStyle(),
1807 "Cannot get dependent builtin style");
1808 // No method of BuiltinCounterStyle mutates the struct itself, so it
1809 // should be fine to cast const away.
1810 return const_cast<BuiltinCounterStyle
*>(&gBuiltinStyleTable
[aStyle
]);
1813 bool CounterStyleManager::NotifyRuleChanged() {
1814 bool changed
= false;
1815 for (auto iter
= mStyles
.Iter(); !iter
.Done(); iter
.Next()) {
1816 CounterStyle
* style
= iter
.Data();
1817 bool toBeUpdated
= false;
1818 bool toBeRemoved
= false;
1819 ServoStyleSet
* styleSet
= mPresContext
->StyleSet();
1820 auto* newRule
= styleSet
->CounterStyleRuleForName(iter
.Key());
1822 if (style
->IsCustomStyle()) {
1826 if (!style
->IsCustomStyle()) {
1829 auto custom
= static_cast<CustomCounterStyle
*>(style
);
1830 if (custom
->GetRule() != newRule
) {
1833 auto generation
= Servo_CounterStyleRule_GetGeneration(newRule
);
1834 if (custom
->GetRuleGeneration() != generation
) {
1836 custom
->ResetCachedData();
1841 changed
= changed
|| toBeUpdated
|| toBeRemoved
;
1843 if (style
->IsDependentStyle()) {
1844 // Add object to retired list so we can clean them up later.
1845 mRetiredStyles
.AppendElement(style
);
1852 for (auto iter
= mStyles
.Iter(); !iter
.Done(); iter
.Next()) {
1853 CounterStyle
* style
= iter
.Data();
1854 if (style
->IsCustomStyle()) {
1855 CustomCounterStyle
* custom
= static_cast<CustomCounterStyle
*>(style
);
1856 custom
->ResetDependentData();
1858 // There is no dependent data cached in DependentBuiltinCounterStyle
1859 // instances, so we don't need to reset their data.
1865 void CounterStyleManager::CleanRetiredStyles() {
1866 nsTArray
<CounterStyle
*> list(std::move(mRetiredStyles
));
1867 for (CounterStyle
* style
: list
) {
1868 DestroyCounterStyle(style
);
1872 } // namespace mozilla