no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / layout / style / CounterStyleManager.cpp
blob356948de2794fa92bd2df917a1e04888a614146c
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 <type_traits>
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"
20 #include "nsString.h"
21 #include "nsTArray.h"
22 #include "nsTHashtable.h"
23 #include "nsUnicodeProperties.h"
24 #include "mozilla/ServoBindings.h"
25 #include "mozilla/ServoStyleSet.h"
27 namespace mozilla {
29 using AdditiveSymbol = StyleAdditiveSymbol;
31 struct NegativeType {
32 nsString before, after;
35 struct PadType {
36 int32_t width;
37 nsString symbol;
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];
56 return true;
59 static bool GetFixedCounterText(CounterValue aOrdinal, nsAString& aResult,
60 CounterValue aStart,
61 Span<const nsString> aSymbols) {
62 CounterValue index = aOrdinal - aStart;
63 if (index >= 0 && index < CounterValue(aSymbols.Length())) {
64 aResult = aSymbols[index];
65 return true;
66 } else {
67 return false;
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.");
76 if (aOrdinal == 0) {
77 return false;
80 aResult.Truncate();
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) {
88 return false;
90 for (size_t i = 0; i < len; ++i) {
91 aResult.Append(symbol);
94 return true;
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.");
101 if (aOrdinal == 0) {
102 return false;
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) {
111 --aOrdinal;
112 indexes.AppendElement(aOrdinal % n);
113 aOrdinal /= n;
116 aResult.Truncate();
117 for (auto i = indexes.Length(); i > 0; --i) {
118 aResult.Append(aSymbols[indexes[i - 1]]);
120 return true;
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.");
128 if (aOrdinal == 0) {
129 aResult = aSymbols[0];
130 return true;
133 auto n = aSymbols.Length();
134 AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
135 while (aOrdinal > 0) {
136 indexes.AppendElement(aOrdinal % n);
137 aOrdinal /= n;
140 aResult.Truncate();
141 for (auto i = indexes.Length(); i > 0; --i) {
142 aResult.Append(aSymbols[indexes[i - 1]]);
144 return true;
147 static bool GetAdditiveCounterText(CounterValue aOrdinal, nsAString& aResult,
148 Span<const AdditiveSymbol> aSymbols) {
149 MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
151 if (aOrdinal == 0) {
152 const AdditiveSymbol& last = aSymbols[aSymbols.Length() - 1];
153 if (last.weight == 0) {
154 aResult = last.symbol;
155 return true;
157 return false;
160 aResult.Truncate();
161 size_t length = 0;
162 for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
163 const AdditiveSymbol& symbol = aSymbols[i];
164 if (symbol.weight == 0) {
165 break;
167 CounterValue times = aOrdinal / symbol.weight;
168 if (times > 0) {
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) {
174 return false;
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);
188 return true;
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 {
200 char16_t digit[10];
201 char16_t unit[3];
202 char16_t unit10K[2];
203 uint8_t lang;
204 bool informal;
206 static const CJKIdeographicData gDataJapaneseInformal = {
207 {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
208 0x4e5d}, // digit
209 {0x5341, 0x767e, 0x5343}, // unit
210 {0x4e07, 0x5104}, // unit10K
211 JAPANESE, // lang
212 true // informal
214 static const CJKIdeographicData gDataJapaneseFormal = {
215 {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
216 0x4e5d}, // digit
217 {0x62fe, 0x767e, 0x9621}, // unit
218 {0x842c, 0x5104}, // unit10K
219 JAPANESE, // lang
220 false // informal
222 static const CJKIdeographicData gDataKoreanHangulFormal = {
223 {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
224 0xad6c}, // digit
225 {0xc2ed, 0xbc31, 0xcc9c}, // unit
226 {0xb9cc, 0xc5b5}, // unit10K
227 KOREAN, // lang
228 false // informal
230 static const CJKIdeographicData gDataKoreanHanjaInformal = {
231 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
232 0x4e5d}, // digit
233 {0x5341, 0x767e, 0x5343}, // unit
234 {0x842c, 0x5104}, // unit10K
235 KOREAN, // lang
236 true // informal
238 static const CJKIdeographicData gDataKoreanHanjaFormal = {
239 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
240 0x4e5d}, // digit
241 {0x62fe, 0x767e, 0x4edf}, // unit
242 {0x842c, 0x5104}, // unit10K
243 KOREAN, // lang
244 false // informal
246 static const CJKIdeographicData gDataSimpChineseInformal = {
247 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
248 0x4e5d}, // digit
249 {0x5341, 0x767e, 0x5343}, // unit
250 {0x4e07, 0x4ebf}, // unit10K
251 CHINESE, // lang
252 true // informal
254 static const CJKIdeographicData gDataSimpChineseFormal = {
255 {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
256 0x7396}, // digit
257 {0x62fe, 0x4f70, 0x4edf}, // unit
258 {0x4e07, 0x4ebf}, // unit10K
259 CHINESE, // lang
260 false // informal
262 static const CJKIdeographicData gDataTradChineseInformal = {
263 {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
264 0x4e5d}, // digit
265 {0x5341, 0x767e, 0x5343}, // unit
266 {0x842c, 0x5104}, // unit10K
267 CHINESE, // lang
268 true // informal
270 static const CJKIdeographicData gDataTradChineseFormal = {
271 {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
272 0x7396}, // digit
273 {0x62fe, 0x4f70, 0x4edf}, // unit
274 {0x842c, 0x5104}, // unit10K
275 CHINESE, // lang
276 false // informal
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;
284 int32_t pos = 0;
285 bool needZero = (aOrdinal == 0);
286 int32_t unitidx = 0, unit10Kidx = 0;
287 do {
288 unitidx = pos % 4;
289 if (unitidx == 0) {
290 unit10Kidx = pos / 4;
292 auto cur = static_cast<std::make_unsigned_t<CounterValue>>(aOrdinal) % 10;
293 if (cur == 0) {
294 if (needZero) {
295 needZero = false;
296 buf[--idx] = data.digit[0];
298 } else {
299 if (data.lang == CHINESE) {
300 needZero = true;
302 if (unit10Kidx != 0) {
303 if (data.lang == KOREAN && idx != NUM_BUF_SIZE) {
304 buf[--idx] = ' ';
306 buf[--idx] = data.unit10K[unit10Kidx - 1];
308 if (unitidx != 0) {
309 buf[--idx] = data.unit[unitidx - 1];
311 if (cur != 1) {
312 buf[--idx] = data.digit[cur];
313 } else {
314 bool needOne = true;
315 if (data.informal) {
316 switch (data.lang) {
317 case CHINESE:
318 if (unitidx == 1 &&
319 (aOrdinal == 1 || (pos > 4 && aOrdinal % 1000 == 1))) {
320 needOne = false;
322 break;
323 case JAPANESE:
324 if (unitidx > 0 &&
325 (unitidx != 3 || (pos == 3 && aOrdinal == 1))) {
326 needOne = false;
328 break;
329 case KOREAN:
330 if (unitidx > 0 || (pos == 4 && (aOrdinal % 1000) == 1)) {
331 needOne = false;
333 break;
336 if (needOne) {
337 buf[--idx] = data.digit[1];
340 unit10Kidx = 0;
342 aOrdinal /= 10;
343 pos++;
344 } while (aOrdinal > 0);
345 aResult.Assign(buf + idx, NUM_BUF_SIZE - idx);
346 return true;
349 #define HEBREW_GERESH 0x05F3
350 static const char16_t gHebrewDigit[22] = {
351 // 1 2 3 4 5 6 7 8 9
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,
355 // 100 200 300 400
356 0x05E7, 0x05E8, 0x05E9, 0x05EA};
358 static bool HebrewToText(CounterValue aOrdinal, nsAString& aResult) {
359 if (aOrdinal < 1 || aOrdinal > 999999) {
360 return false;
363 bool outputSep = false;
364 nsAutoString allText, thousandsGroup;
365 do {
366 thousandsGroup.Truncate();
367 int32_t n3 = aOrdinal % 1000;
368 // Process digit for 100 - 900
369 for (int32_t n1 = 400; n1 > 0;) {
370 if (n3 >= n1) {
371 n3 -= n1;
372 thousandsGroup.Append(gHebrewDigit[(n1 / 100) - 1 + 18]);
373 } else {
374 n1 -= 100;
375 } // if
376 } // for
378 // Process digit for 10 - 90
379 int32_t n2;
380 if (n3 >= 10) {
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
386 n2 = 9;
387 thousandsGroup.Append(gHebrewDigit[n2 - 1]);
388 } else {
389 n2 = n3 - (n3 % 10);
390 thousandsGroup.Append(gHebrewDigit[(n2 / 10) - 1 + 9]);
391 } // if
392 n3 -= n2;
393 } // if
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;
400 else
401 allText = thousandsGroup + allText;
402 aOrdinal /= 1000;
403 outputSep = true;
404 } while (aOrdinal >= 1);
406 aResult = allText;
407 return true;
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) {
421 if (aOrdinal < 1) {
422 return false;
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);
436 } else {
437 asciiStringLength--;
440 aResult.Truncate();
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
455 (oddGroup ||
456 indexFromLeft == 0)) { // preceding (100) or leading the sequence
457 unitsValue = 0;
460 // put it all together...
461 if (tensValue) {
462 // map onto Ethiopic "tens":
463 aResult.Append((char16_t)(tensValue + ETHIOPIC_TEN - 1));
465 if (unitsValue) {
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.
471 if (oddGroup) {
472 if (groupValue) {
473 aResult.Append((char16_t)ETHIOPIC_HUNDRED);
475 } else {
476 if (groupIndexFromRight) {
477 aResult.Append((char16_t)ETHIOPIC_TEN_THOUSAND);
481 return true;
484 static SpeakAs GetDefaultSpeakAsForSystem(StyleCounterSystem aSystem) {
485 MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
486 "Extends system does not have static default speak-as");
487 switch (aSystem) {
488 case StyleCounterSystem::Alphabetic:
489 return SpeakAs::Spellout;
490 case StyleCounterSystem::Cyclic:
491 return SpeakAs::Bullets;
492 default:
493 return SpeakAs::Numbers;
497 static bool SystemUsesNegativeSign(StyleCounterSystem aSystem) {
498 MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
499 "Cannot check this for extending style");
500 switch (aSystem) {
501 case StyleCounterSystem::Symbolic:
502 case StyleCounterSystem::Alphabetic:
503 case StyleCounterSystem::Numeric:
504 case StyleCounterSystem::Additive:
505 return true;
506 default:
507 return false;
511 class BuiltinCounterStyle : public CounterStyle {
512 public:
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,
522 nsAString& aResult,
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;
538 protected:
539 constexpr BuiltinCounterStyle(const BuiltinCounterStyle& aOther)
540 : CounterStyle(aOther.mStyle), mName(aOther.mName) {}
542 private:
543 nsStaticAtom* mName;
546 /* virtual */
547 void BuiltinCounterStyle::GetPrefix(nsAString& aResult) { aResult.Truncate(); }
549 /* virtual */
550 void BuiltinCounterStyle::GetSuffix(nsAString& aResult) {
551 switch (mStyle) {
552 case ListStyle::None:
553 aResult.Truncate();
554 break;
556 case ListStyle::Disc:
557 case ListStyle::Circle:
558 case ListStyle::Square:
559 case ListStyle::DisclosureClosed:
560 case ListStyle::DisclosureOpen:
561 case ListStyle::EthiopicNumeric:
562 aResult = ' ';
563 break;
565 case ListStyle::TradChineseInformal:
566 case ListStyle::TradChineseFormal:
567 case ListStyle::SimpChineseInformal:
568 case ListStyle::SimpChineseFormal:
569 case ListStyle::JapaneseInformal:
570 case ListStyle::JapaneseFormal:
571 aResult = 0x3001;
572 break;
574 case ListStyle::KoreanHangulFormal:
575 case ListStyle::KoreanHanjaInformal:
576 case ListStyle::KoreanHanjaFormal:
577 aResult.AssignLiteral(u", ");
578 break;
580 default:
581 aResult.AssignLiteral(u". ");
582 break;
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;
593 /* virtual */
594 void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
595 WritingMode aWritingMode,
596 nsAString& aResult,
597 bool& aIsBullet) {
598 switch (mStyle) {
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
606 bool isRTL;
607 GetInitialCounterText(aOrdinal, aWritingMode, aResult, isRTL);
608 aIsBullet = true;
609 break;
611 default:
612 CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
613 aIsBullet);
614 break;
618 /* virtual */
619 bool BuiltinCounterStyle::IsBullet() {
620 switch (mStyle) {
621 case ListStyle::Disc:
622 case ListStyle::Circle:
623 case ListStyle::Square:
624 case ListStyle::DisclosureClosed:
625 case ListStyle::DisclosureOpen:
626 return true;
627 default:
628 return false;
632 static const char16_t gJapaneseNegative[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
633 0x0000};
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};
639 /* virtual */
640 void BuiltinCounterStyle::GetNegative(NegativeType& aResult) {
641 switch (mStyle) {
642 case ListStyle::JapaneseFormal:
643 case ListStyle::JapaneseInformal:
644 aResult.before = gJapaneseNegative;
645 break;
647 case ListStyle::KoreanHangulFormal:
648 case ListStyle::KoreanHanjaInformal:
649 case ListStyle::KoreanHanjaFormal:
650 aResult.before = gKoreanNegative;
651 break;
653 case ListStyle::SimpChineseFormal:
654 case ListStyle::SimpChineseInformal:
655 aResult.before = gSimpChineseNegative;
656 break;
658 case ListStyle::TradChineseFormal:
659 case ListStyle::TradChineseInformal:
660 aResult.before = gTradChineseNegative;
661 break;
663 default:
664 aResult.before.AssignLiteral(u"-");
666 aResult.after.Truncate();
669 /* virtual */
670 bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
671 switch (mStyle) {
672 default:
673 // cyclic
674 case ListStyle::None:
675 case ListStyle::Disc:
676 case ListStyle::Circle:
677 case ListStyle::Square:
678 case ListStyle::DisclosureClosed:
679 case ListStyle::DisclosureOpen:
680 // use DecimalToText
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:
692 return true;
694 // use EthiopicToText
695 case ListStyle::EthiopicNumeric:
696 return aOrdinal >= 1;
698 // use HebrewToText
699 case ListStyle::Hebrew:
700 return aOrdinal >= 1 && aOrdinal <= 999999;
704 /* virtual */
705 bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
706 switch (mStyle) {
707 // cyclic:
708 case ListStyle::None:
709 case ListStyle::Disc:
710 case ListStyle::Circle:
711 case ListStyle::Square:
712 case ListStyle::DisclosureClosed:
713 case ListStyle::DisclosureOpen:
714 // numeric:
715 case ListStyle::Decimal:
716 return true;
718 // additive:
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);
735 default:
736 MOZ_ASSERT_UNREACHABLE("Unknown counter style");
737 return false;
741 /* virtual */
742 void BuiltinCounterStyle::GetPad(PadType& aResult) {
743 aResult.width = 0;
744 aResult.symbol.Truncate();
747 /* virtual */
748 CounterStyle* BuiltinCounterStyle::GetFallback() {
749 // Fallback of dependent builtin counter styles are handled in class
750 // DependentBuiltinCounterStyle.
751 return CounterStyleManager::GetDecimalStyle();
754 /* virtual */
755 SpeakAs BuiltinCounterStyle::GetSpeakAs() {
756 switch (mStyle) {
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;
764 default:
765 return SpeakAs::Numbers;
769 /* virtual */
770 bool BuiltinCounterStyle::UseNegativeSign() {
771 switch (mStyle) {
772 case ListStyle::None:
773 case ListStyle::Disc:
774 case ListStyle::Circle:
775 case ListStyle::Square:
776 case ListStyle::DisclosureClosed:
777 case ListStyle::DisclosureOpen:
778 return false;
779 default:
780 return true;
784 /* virtual */
785 bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
786 WritingMode aWritingMode,
787 nsAString& aResult,
788 bool& aIsRTL) {
789 aIsRTL = false;
790 switch (mStyle) {
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:
794 aResult.Truncate();
795 return true;
796 case ListStyle::Disc:
797 aResult.Assign(kDiscCharacter);
798 return true;
799 case ListStyle::Circle:
800 aResult.Assign(kCircleCharacter);
801 return true;
802 case ListStyle::Square:
803 aResult.Assign(kSquareCharacter);
804 return true;
805 case ListStyle::DisclosureClosed:
806 if (aWritingMode.IsVertical()) {
807 aResult.Assign(kDownPointingCharacter);
808 } else if (aWritingMode.IsBidiLTR()) {
809 aResult.Assign(kRightPointingCharacter);
810 } else {
811 aResult.Assign(kLeftPointingCharacter);
813 return true;
814 case ListStyle::DisclosureOpen:
815 if (!aWritingMode.IsVertical()) {
816 aResult.Assign(kDownPointingCharacter);
817 } else if (aWritingMode.IsVerticalLR()) {
818 aResult.Assign(kRightPointingCharacter);
819 } else {
820 aResult.Assign(kLeftPointingCharacter);
822 return true;
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:
847 aIsRTL = true;
848 return HebrewToText(aOrdinal, aResult);
850 case ListStyle::EthiopicNumeric:
851 return EthiopicToText(aOrdinal, aResult);
853 default:
854 MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
855 return false;
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_) \
867 static_assert( \
868 gBuiltinStyleTable[static_cast<size_t>(ListStyle::value_)].GetStyle() == \
869 ListStyle::value_, \
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 {
875 public:
876 DependentBuiltinCounterStyle(ListStyle aStyle, CounterStyleManager* aManager)
877 : BuiltinCounterStyle(gBuiltinStyleTable[static_cast<size_t>(aStyle)]),
878 mManager(aManager) {
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);
890 void Destroy() {
891 PresShell* presShell = mManager->PresContext()->PresShell();
892 this->~DependentBuiltinCounterStyle();
893 presShell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle,
894 this);
897 private:
898 ~DependentBuiltinCounterStyle() = default;
900 CounterStyleManager* mManager;
903 /* virtual */
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);
920 default:
921 MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
922 return BuiltinCounterStyle::GetFallback();
926 class CustomCounterStyle final : public CounterStyle {
927 public:
928 CustomCounterStyle(CounterStyleManager* aManager,
929 const StyleLockedCounterStyleRule* aRule)
930 : CounterStyle(ListStyle::Custom),
931 mManager(aManager),
932 mRule(aRule),
933 mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule)),
934 mSystem(Servo_CounterStyleRule_GetSystem(aRule)),
935 mFlags(0),
936 mFallback(nullptr),
937 mSpeakAsCounter(nullptr),
938 mExtends(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,
960 nsAString& aResult,
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);
986 void Destroy() {
987 PresShell* presShell = mManager->PresContext()->PresShell();
988 this->~CustomCounterStyle();
989 presShell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
992 private:
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
1023 // used
1024 MOZ_INIT_OUTSIDE_CTOR SpeakAs mSpeakAs;
1026 enum {
1027 // loop detection
1028 FLAG_EXTENDS_VISITED = 1 << 0,
1029 FLAG_EXTENDS_LOOP = 1 << 1,
1030 FLAG_SPEAKAS_VISITED = 1 << 2,
1031 FLAG_SPEAKAS_LOOP = 1 << 3,
1032 // field status
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,
1039 uint16_t mFlags;
1041 // Fields below will be initialized when necessary.
1042 StyleOwnedSlice<nsString> mSymbols;
1043 StyleOwnedSlice<AdditiveSymbol> mAdditiveSymbols;
1044 NegativeType mNegative;
1045 nsString mPrefix, mSuffix;
1046 PadType mPad;
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
1066 // not 'extends'.
1067 CounterStyle* mExtendsRoot;
1070 void CustomCounterStyle::ResetCachedData() {
1071 mSymbols.Clear();
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;
1077 mExtends = 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;
1086 mExtends = nullptr;
1087 mExtendsRoot = nullptr;
1088 if (IsExtendsSystem()) {
1089 mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
1090 FLAG_PAD_INITED);
1094 /* virtual */
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);
1102 } else {
1103 mPrefix.Truncate();
1107 aResult = mPrefix;
1110 /* virtual */
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);
1118 } else {
1119 mSuffix.AssignLiteral(u". ");
1123 aResult = mSuffix;
1126 /* virtual */
1127 void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
1128 WritingMode aWritingMode,
1129 nsAString& aResult,
1130 bool& aIsBullet) {
1131 if (GetSpeakAs() != SpeakAs::Other) {
1132 CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
1133 aIsBullet);
1134 } else {
1135 MOZ_ASSERT(mSpeakAsCounter,
1136 "mSpeakAsCounter should have been initialized.");
1137 mSpeakAsCounter->GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
1138 aIsBullet);
1142 /* virtual */
1143 bool CustomCounterStyle::IsBullet() {
1144 switch (mSystem) {
1145 case StyleCounterSystem::Cyclic:
1146 // Only use ::-moz-list-bullet for cyclic system
1147 return true;
1148 case StyleCounterSystem::Extends:
1149 return GetExtendsRoot()->IsBullet();
1150 default:
1151 return false;
1155 /* virtual */
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);
1163 } else {
1164 mNegative.before.AssignLiteral(u"-");
1165 mNegative.after.Truncate();
1169 aResult = mNegative;
1172 /* virtual */
1173 bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
1174 auto inRange = Servo_CounterStyleRule_IsInRange(mRule, aOrdinal);
1175 switch (inRange) {
1176 case StyleIsOrdinalInRange::InRange:
1177 return true;
1178 case StyleIsOrdinalInRange::NotInRange:
1179 return false;
1180 case StyleIsOrdinalInRange::NoOrdinalSpecified:
1181 if (IsExtendsSystem()) {
1182 return GetExtends()->IsOrdinalInRange(aOrdinal);
1184 break;
1185 case StyleIsOrdinalInRange::Auto:
1186 break;
1187 default:
1188 MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
1190 return IsOrdinalInAutoRange(aOrdinal);
1193 /* virtual */
1194 bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
1195 switch (mSystem) {
1196 case StyleCounterSystem::Cyclic:
1197 case StyleCounterSystem::Numeric:
1198 case StyleCounterSystem::Fixed:
1199 return true;
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);
1207 default:
1208 MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
1209 return false;
1213 /* virtual */
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);
1220 } else {
1221 mPad.width = 0;
1222 mPad.symbol.Truncate();
1226 aResult = mPad;
1229 /* virtual */
1230 CounterStyle* CustomCounterStyle::GetFallback() {
1231 if (!mFallback) {
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();
1239 return mFallback;
1242 /* virtual */
1243 SpeakAs CustomCounterStyle::GetSpeakAs() {
1244 if (!(mFlags & FLAG_SPEAKAS_INITED)) {
1245 ComputeSpeakAs();
1247 return mSpeakAs;
1250 /* virtual */
1251 bool CustomCounterStyle::UseNegativeSign() {
1252 if (mSystem == StyleCounterSystem::Extends) {
1253 return GetExtendsRoot()->UseNegativeSign();
1255 return SystemUsesNegativeSign(mSystem);
1258 /* virtual */
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;
1270 /* virtual */
1271 bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
1272 WritingMode aWritingMode,
1273 nsAString& aResult,
1274 bool& aIsRTL) {
1275 switch (mSystem) {
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,
1292 aResult, aIsRTL);
1293 default:
1294 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1295 return false;
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();
1343 break;
1344 case StyleCounterSpeakAs::Tag::Bullets:
1345 aSpeakAs = SpeakAs::Bullets;
1346 break;
1347 case StyleCounterSpeakAs::Tag::Numbers:
1348 aSpeakAs = SpeakAs::Numbers;
1349 break;
1350 case StyleCounterSpeakAs::Tag::Words:
1351 aSpeakAs = SpeakAs::Words;
1352 break;
1353 case StyleCounterSpeakAs::Tag::Ident:
1354 aSpeakAs = SpeakAs::Other;
1355 aSpeakAsCounter = mManager->ResolveCounterStyle(speakAs.AsIdent());
1356 break;
1357 case StyleCounterSpeakAs::Tag::None: {
1358 if (!IsExtendsSystem()) {
1359 aSpeakAs = GetSpeakAsAutoValue();
1360 } else {
1361 CounterStyle* extended = GetExtends();
1362 if (!extended->IsCustomStyle()) {
1363 // It is safe to call GetSpeakAs on non-custom style.
1364 aSpeakAs = extended->GetSpeakAs();
1365 } else {
1366 CustomCounterStyle* custom =
1367 static_cast<CustomCounterStyle*>(extended);
1368 if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
1369 custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
1370 } else {
1371 aSpeakAs = custom->mSpeakAs;
1372 aSpeakAsCounter = custom->mSpeakAsCounter;
1376 break;
1378 default:
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;
1395 return this;
1398 if (mFlags & FLAG_SPEAKAS_VISITED) {
1399 // loop detected
1400 mFlags |= FLAG_SPEAKAS_LOOP;
1401 return nullptr;
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;
1412 } else {
1413 mFlags |= FLAG_SPEAKAS_VISITED;
1414 CounterStyle* target =
1415 static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
1416 mFlags &= ~FLAG_SPEAKAS_VISITED;
1418 if (target) {
1419 NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
1420 "Invalid state for speak-as loop detecting");
1421 mSpeakAsCounter = target;
1422 } else {
1423 mSpeakAs = GetSpeakAsAutoValue();
1424 mSpeakAsCounter = nullptr;
1425 if (mFlags & FLAG_SPEAKAS_LOOP) {
1426 mFlags &= ~FLAG_SPEAKAS_LOOP;
1427 } else {
1428 inLoop = true;
1433 mFlags |= FLAG_SPEAKAS_INITED;
1434 if (inLoop) {
1435 return nullptr;
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) {
1449 return this;
1451 if (mFlags & FLAG_EXTENDS_VISITED) {
1452 // loop detected
1453 mFlags |= FLAG_EXTENDS_LOOP;
1454 return nullptr;
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;
1466 if (target) {
1467 NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
1468 "Invalid state for extends loop detecting");
1469 mExtends = nextCounter;
1470 return this;
1471 } else {
1472 mExtends = CounterStyleManager::GetDecimalStyle();
1473 if (mFlags & FLAG_EXTENDS_LOOP) {
1474 mFlags &= ~FLAG_EXTENDS_LOOP;
1475 return this;
1476 } else {
1477 return nullptr;
1482 CounterStyle* CustomCounterStyle::GetExtends() {
1483 if (!mExtends) {
1484 // Any extends loop will be eliminated in the method below.
1485 ComputeExtends();
1487 return mExtends;
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)) {}
1522 /* virtual */
1523 void AnonymousCounterStyle::GetPrefix(nsAString& aResult) {
1524 aResult.Truncate();
1527 /* virtual */
1528 void AnonymousCounterStyle::GetSuffix(nsAString& aResult) {
1529 if (IsSingleString()) {
1530 aResult.Truncate();
1531 } else {
1532 aResult = ' ';
1536 /* virtual */
1537 bool AnonymousCounterStyle::IsBullet() {
1538 // Only use ::-moz-list-bullet for cyclic system
1539 return mSymbolsType == StyleSymbolsType::Cyclic;
1542 /* virtual */
1543 void AnonymousCounterStyle::GetNegative(NegativeType& aResult) {
1544 aResult.before.AssignLiteral(u"-");
1545 aResult.after.Truncate();
1548 /* virtual */
1549 bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
1550 switch (mSymbolsType) {
1551 case StyleSymbolsType::Cyclic:
1552 case StyleSymbolsType::Numeric:
1553 case StyleSymbolsType::Fixed:
1554 return true;
1555 case StyleSymbolsType::Alphabetic:
1556 case StyleSymbolsType::Symbolic:
1557 return aOrdinal >= 1;
1558 default:
1559 MOZ_ASSERT_UNREACHABLE("Invalid system.");
1560 return false;
1564 /* virtual */
1565 bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
1566 return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal);
1569 /* virtual */
1570 void AnonymousCounterStyle::GetPad(PadType& aResult) {
1571 aResult.width = 0;
1572 aResult.symbol.Truncate();
1575 /* virtual */
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;
1597 /* virtual */
1598 SpeakAs AnonymousCounterStyle::GetSpeakAs() {
1599 return GetDefaultSpeakAsForSystem(GetSystem());
1602 /* virtual */
1603 bool AnonymousCounterStyle::UseNegativeSign() {
1604 return SystemUsesNegativeSign(GetSystem());
1607 /* virtual */
1608 bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
1609 WritingMode aWritingMode,
1610 nsAString& aResult,
1611 bool& aIsRTL) {
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.");
1625 return false;
1628 bool CounterStyle::IsDependentStyle() const {
1629 switch (mStyle) {
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:
1642 return true;
1644 // BuiltinCounterStyle
1645 default:
1646 return false;
1650 void CounterStyle::GetCounterText(CounterValue aOrdinal,
1651 WritingMode aWritingMode, nsAString& aResult,
1652 bool& aIsRTL) {
1653 bool success = IsOrdinalInRange(aOrdinal);
1654 aIsRTL = false;
1656 if (success) {
1657 // generate initial representation
1658 bool useNegativeSign = UseNegativeSign();
1659 nsAutoString initialText;
1660 CounterValue ordinal;
1661 if (!useNegativeSign) {
1662 ordinal = aOrdinal;
1663 } else {
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
1671 if (success) {
1672 aResult.Truncate();
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);
1681 PadType pad;
1682 GetPad(pad);
1683 int32_t diff =
1684 pad.width -
1685 narrow_cast<int32_t>(unicode::CountGraphemeClusters(initialText) +
1686 unicode::CountGraphemeClusters(aResult));
1687 if (diff > 0) {
1688 auto length = pad.symbol.Length();
1689 if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
1690 diff * length > LENGTH_LIMIT) {
1691 success = false;
1692 } else if (length > 0) {
1693 for (int32_t i = 0; i < diff; ++i) {
1694 aResult.Append(pad.symbol);
1698 if (success) {
1699 aResult.Append(initialText);
1704 if (!success) {
1705 CallFallbackStyle(aOrdinal, aWritingMode, aResult, aIsRTL);
1709 /* virtual */
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
1714 aIsBullet = false;
1715 switch (GetSpeakAs()) {
1716 case SpeakAs::Bullets:
1717 aResult.Assign(kDiscCharacter);
1718 aIsBullet = true;
1719 break;
1720 case SpeakAs::Numbers:
1721 DecimalToText(aOrdinal, aResult);
1722 break;
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);
1728 break;
1729 case SpeakAs::Other:
1730 // This should be processed by CustomCounterStyle
1731 MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
1732 break;
1733 default:
1734 MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
1735 break;
1739 /* virtual */
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();
1766 } else {
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);
1778 mStyles.Clear();
1779 mPresContext = nullptr;
1782 CounterStyle* CounterStyleManager::ResolveCounterStyle(nsAtom* aName) {
1783 MOZ_ASSERT(NS_IsMainThread());
1784 CounterStyle* data = GetCounterStyle(aName);
1785 if (data) {
1786 return data;
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);
1793 if (rule) {
1794 MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule) == aName);
1795 data = new (mPresContext) CustomCounterStyle(this, rule);
1796 } else {
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);
1804 break;
1808 if (!data) {
1809 data = GetDecimalStyle();
1811 mStyles.InsertOrUpdate(aName, data);
1812 return data;
1815 /* static */
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());
1834 if (!newRule) {
1835 if (style->IsCustomStyle()) {
1836 toBeRemoved = true;
1838 } else {
1839 if (!style->IsCustomStyle()) {
1840 toBeRemoved = true;
1841 } else {
1842 auto custom = static_cast<CustomCounterStyle*>(style);
1843 if (custom->GetRule() != newRule) {
1844 toBeRemoved = true;
1845 } else {
1846 auto generation = Servo_CounterStyleRule_GetGeneration(newRule);
1847 if (custom->GetRuleGeneration() != generation) {
1848 toBeUpdated = true;
1849 custom->ResetCachedData();
1854 changed = changed || toBeUpdated || toBeRemoved;
1855 if (toBeRemoved) {
1856 if (style->IsDependentStyle()) {
1857 // Add object to retired list so we can clean them up later.
1858 mRetiredStyles.AppendElement(style);
1860 iter.Remove();
1864 if (changed) {
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.
1874 return changed;
1877 void CounterStyleManager::CleanRetiredStyles() {
1878 nsTArray<CounterStyle*> list(std::move(mRetiredStyles));
1879 for (CounterStyle* style : list) {
1880 DestroyCounterStyle(style);
1884 } // namespace mozilla