Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/layout/style/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 60 kB image not shown  

Quelle  CounterStyleManager.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "CounterStyleManager.h"

#include <type_traits>

#include "mozilla/ArenaObjectID.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "mozilla/Types.h"
#include "mozilla/WritingModes.h"
#include "nsPresContext.h"
#include "nsPresContextInlines.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nsUnicodeProperties.h"
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"

namespace mozilla {

using AdditiveSymbol = StyleAdditiveSymbol;

struct NegativeType {
  nsString before, after;
};

struct PadType {
  int32_t width;
  nsString symbol;
};

// This limitation will be applied to some systems, and pad descriptor.
// Any initial representation generated by symbolic or additive which is
// longer than this limitation will be dropped. If any pad is longer
// than this, the whole counter text will be dropped as well.
// The spec requires user agents to support at least 60 Unicode code-
// points for counter text. However, this constant only limits the
// length in 16-bit units. So it has to be at least 120, since code-
// points outside the BMP will need 2 16-bit units.
#define LENGTH_LIMIT 150

static void SymbolToString(const StyleSymbol& aSymbol, nsAString& aResult) {
  if (aSymbol.IsIdent()) {
    return aSymbol.AsIdent().AsAtom()->ToString(aResult);
  }
  MOZ_ASSERT(aSymbol.IsString());
  return CopyUTF8toUTF16(aSymbol.AsString().AsString(), aResult);
}

static size_t SymbolLength(const StyleSymbol& aSymbol) {
  if (aSymbol.IsIdent()) {
    return aSymbol.AsIdent().AsAtom()->GetLength();
  }
  MOZ_ASSERT(aSymbol.IsString());
  return aSymbol.AsString().AsString().Length();
}

static bool GetCyclicCounterText(CounterValue aOrdinal, nsAString& aResult,
                                 Span<const StyleSymbol> aSymbols) {
  MOZ_ASSERT(aSymbols.Length() >= 1, "No symbol available for cyclic counter.");
  auto n = CounterValue(aSymbols.Length());
  CounterValue index = (aOrdinal - 1) % n;
  SymbolToString(aSymbols[index >= 0 ? index : index + n], aResult);
  return true;
}

static bool GetFixedCounterText(CounterValue aOrdinal, nsAString& aResult,
                                CounterValue aStart,
                                Span<const StyleSymbol> aSymbols) {
  CounterValue index = aOrdinal - aStart;
  if (index >= 0 && index < CounterValue(aSymbols.Length())) {
    SymbolToString(aSymbols[index], aResult);
    return true;
  }
  return false;
}

static bool GetSymbolicCounterText(CounterValue aOrdinal, nsAString& aResult,
                                   Span<const StyleSymbol> aSymbols) {
  MOZ_ASSERT(aSymbols.Length() >= 1,
             "No symbol available for symbolic counter.");
  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
  if (aOrdinal == 0) {
    return false;
  }

  aResult.Truncate();
  auto n = aSymbols.Length();
  const StyleSymbol& symbol = aSymbols[(aOrdinal - 1) % n];
  size_t len = (aOrdinal + n - 1) / n;
  auto symbolLength = SymbolLength(symbol);
  if (symbolLength > 0) {
    if (len > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
        len * symbolLength > LENGTH_LIMIT) {
      return false;
    }
    nsAutoString str;
    SymbolToString(symbol, str);
    for (size_t i = 0; i < len; ++i) {
      aResult.Append(str);
    }
  }
  return true;
}

static bool GetAlphabeticCounterText(CounterValue aOrdinal, nsAString& aResult,
                                     Span<const StyleSymbol> aSymbols) {
  MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for alphabetic counter.");
  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");
  if (aOrdinal == 0) {
    return false;
  }

  auto n = aSymbols.Length();
  // The precise length of this array should be
  // ceil(log((double) aOrdinal / n * (n - 1) + 1) / log(n)).
  // The max length is slightly smaller than which defined below.
  AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
  while (aOrdinal > 0) {
    --aOrdinal;
    indexes.AppendElement(aOrdinal % n);
    aOrdinal /= n;
  }

  aResult.Truncate();
  for (auto i = indexes.Length(); i > 0; --i) {
    const auto& symbol = aSymbols[indexes[i - 1]];
    if (symbol.IsIdent()) {
      aResult.Append(nsDependentAtomString(symbol.AsIdent().AsAtom()));
    } else {
      AppendUTF8toUTF16(symbol.AsString().AsString(), aResult);
    }
  }
  return true;
}

static bool GetNumericCounterText(CounterValue aOrdinal, nsAString& aResult,
                                  Span<const StyleSymbol> aSymbols) {
  MOZ_ASSERT(aSymbols.Length() >= 2, "Too few symbols for numeric counter.");
  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");

  if (aOrdinal == 0) {
    SymbolToString(aSymbols[0], aResult);
    return true;
  }

  auto n = aSymbols.Length();
  AutoTArray<int32_t, std::numeric_limits<CounterValue>::digits> indexes;
  while (aOrdinal > 0) {
    indexes.AppendElement(aOrdinal % n);
    aOrdinal /= n;
  }

  aResult.Truncate();
  for (auto i = indexes.Length(); i > 0; --i) {
    const auto& symbol = aSymbols[indexes[i - 1]];
    if (symbol.IsIdent()) {
      aResult.Append(nsDependentAtomString(symbol.AsIdent().AsAtom()));
    } else {
      AppendUTF8toUTF16(symbol.AsString().AsString(), aResult);
    }
  }
  return true;
}

static bool GetAdditiveCounterText(CounterValue aOrdinal, nsAString& aResult,
                                   Span<const AdditiveSymbol> aSymbols) {
  MOZ_ASSERT(aOrdinal >= 0, "Invalid ordinal.");

  if (aOrdinal == 0) {
    const AdditiveSymbol& last = aSymbols[aSymbols.Length() - 1];
    if (last.weight == 0) {
      aResult = last.symbol;
      return true;
    }
    return false;
  }

  aResult.Truncate();
  size_t length = 0;
  for (size_t i = 0, iEnd = aSymbols.Length(); i < iEnd; ++i) {
    const AdditiveSymbol& symbol = aSymbols[i];
    if (symbol.weight == 0) {
      break;
    }
    CounterValue times = aOrdinal / symbol.weight;
    if (times > 0) {
      auto symbolLength = symbol.symbol.Length();
      if (symbolLength > 0) {
        length += times * symbolLength;
        if (times > LENGTH_LIMIT || symbolLength > LENGTH_LIMIT ||
            length > LENGTH_LIMIT) {
          return false;
        }
        for (CounterValue j = 0; j < times; ++j) {
          aResult.Append(symbol.symbol);
        }
      }
      aOrdinal -= times * symbol.weight;
    }
  }
  return aOrdinal == 0;
}

static bool DecimalToText(CounterValue aOrdinal, nsAString& aResult) {
  aResult.AppendInt(aOrdinal);
  return true;
}

// We know cjk-ideographic need 31 characters to display 99,999,999,999,999,999
// georgian needs 6 at most
// armenian needs 12 at most
// hebrew may need more...

#define NUM_BUF_SIZE 34

enum CJKIdeographicLang { CHINESE, KOREAN, JAPANESE };
struct CJKIdeographicData {
  char16_t digit[10];
  char16_t unit[3];
  char16_t unit10K[2];
  uint8_t lang;
  bool informal;
};
static const CJKIdeographicData gDataJapaneseInformal = {
    {0x3007, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x5341, 0x767e, 0x5343},  // unit
    {0x4e07, 0x5104},          // unit10K
    JAPANESE,                  // lang
    true                       // informal
};
static const CJKIdeographicData gDataJapaneseFormal = {
    {0x96f6, 0x58f1, 0x5f10, 0x53c2, 0x56db, 0x4f0d, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x62fe, 0x767e, 0x9621},  // unit
    {0x842c, 0x5104},          // unit10K
    JAPANESE,                  // lang
    false                      // informal
};
static const CJKIdeographicData gDataKoreanHangulFormal = {
    {0xc601, 0xc77c, 0xc774, 0xc0bc, 0xc0ac, 0xc624, 0xc721, 0xce60, 0xd314,
     0xad6c},                  // digit
    {0xc2ed, 0xbc31, 0xcc9c},  // unit
    {0xb9cc, 0xc5b5},          // unit10K
    KOREAN,                    // lang
    false                      // informal
};
static const CJKIdeographicData gDataKoreanHanjaInformal = {
    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x5341, 0x767e, 0x5343},  // unit
    {0x842c, 0x5104},          // unit10K
    KOREAN,                    // lang
    true                       // informal
};
static const CJKIdeographicData gDataKoreanHanjaFormal = {
    {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x62fe, 0x767e, 0x4edf},  // unit
    {0x842c, 0x5104},          // unit10K
    KOREAN,                    // lang
    false                      // informal
};
static const CJKIdeographicData gDataSimpChineseInformal = {
    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x5341, 0x767e, 0x5343},  // unit
    {0x4e07, 0x4ebf},          // unit10K
    CHINESE,                   // lang
    true                       // informal
};
static const CJKIdeographicData gDataSimpChineseFormal = {
    {0x96f6, 0x58f9, 0x8d30, 0x53c1, 0x8086, 0x4f0d, 0x9646, 0x67d2, 0x634c,
     0x7396},                  // digit
    {0x62fe, 0x4f70, 0x4edf},  // unit
    {0x4e07, 0x4ebf},          // unit10K
    CHINESE,                   // lang
    false                      // informal
};
static const CJKIdeographicData gDataTradChineseInformal = {
    {0x96f6, 0x4e00, 0x4e8c, 0x4e09, 0x56db, 0x4e94, 0x516d, 0x4e03, 0x516b,
     0x4e5d},                  // digit
    {0x5341, 0x767e, 0x5343},  // unit
    {0x842c, 0x5104},          // unit10K
    CHINESE,                   // lang
    true                       // informal
};
static const CJKIdeographicData gDataTradChineseFormal = {
    {0x96f6, 0x58f9, 0x8cb3, 0x53c3, 0x8086, 0x4f0d, 0x9678, 0x67d2, 0x634c,
     0x7396},                  // digit
    {0x62fe, 0x4f70, 0x4edf},  // unit
    {0x842c, 0x5104},          // unit10K
    CHINESE,                   // lang
    false                      // informal
};

static bool CJKIdeographicToText(CounterValue aOrdinal, nsAString& aResult,
                                 const CJKIdeographicData& data) {
  NS_ASSERTION(aOrdinal >= 0, "Only accept non-negative ordinal");
  char16_t buf[NUM_BUF_SIZE];
  int32_t idx = NUM_BUF_SIZE;
  int32_t pos = 0;
  bool needZero = (aOrdinal == 0);
  int32_t unitidx = 0, unit10Kidx = 0;
  do {
    unitidx = pos % 4;
    if (unitidx == 0) {
      unit10Kidx = pos / 4;
    }
    auto cur = static_cast<std::make_unsigned_t<CounterValue>>(aOrdinal) % 10;
    if (cur == 0) {
      if (needZero) {
        needZero = false;
        buf[--idx] = data.digit[0];
      }
    } else {
      if (data.lang == CHINESE) {
        needZero = true;
      }
      if (unit10Kidx != 0) {
        if (data.lang == KOREAN && idx != NUM_BUF_SIZE) {
          buf[--idx] = ' ';
        }
        buf[--idx] = data.unit10K[unit10Kidx - 1];
      }
      if (unitidx != 0) {
        buf[--idx] = data.unit[unitidx - 1];
      }
      if (cur != 1) {
        buf[--idx] = data.digit[cur];
      } else {
        bool needOne = true;
        if (data.informal) {
          switch (data.lang) {
            case CHINESE:
              if (unitidx == 1 &&
                  (aOrdinal == 1 || (pos > 4 && aOrdinal % 1000 == 1))) {
                needOne = false;
              }
              break;
            case JAPANESE:
              if (unitidx > 0 &&
                  (unitidx != 3 || (pos == 3 && aOrdinal == 1))) {
                needOne = false;
              }
              break;
            case KOREAN:
              if (unitidx > 0 || (pos == 4 && (aOrdinal % 1000) == 1)) {
                needOne = false;
              }
              break;
          }
        }
        if (needOne) {
          buf[--idx] = data.digit[1];
        }
      }
      unit10Kidx = 0;
    }
    aOrdinal /= 10;
    pos++;
  } while (aOrdinal > 0);
  aResult.Assign(buf + idx, NUM_BUF_SIZE - idx);
  return true;
}

#define HEBREW_GERESH 0x05F3
static const char16_t gHebrewDigit[22] = {
    //   1       2       3       4       5       6       7       8       9
    0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8,
    //  10      20      30      40      50      60      70      80      90
    0x05D9, 0x05DB, 0x05DC, 0x05DE, 0x05E0, 0x05E1, 0x05E2, 0x05E4, 0x05E6,
    // 100     200     300     400
    0x05E7, 0x05E8, 0x05E9, 0x05EA};

static bool HebrewToText(CounterValue aOrdinal, nsAString& aResult) {
  if (aOrdinal < 1 || aOrdinal > 999999) {
    return false;
  }

  bool outputSep = false;
  nsAutoString allText, thousandsGroup;
  do {
    thousandsGroup.Truncate();
    int32_t n3 = aOrdinal % 1000;
    // Process digit for 100 - 900
    for (int32_t n1 = 400; n1 > 0;) {
      if (n3 >= n1) {
        n3 -= n1;
        thousandsGroup.Append(gHebrewDigit[(n1 / 100) - 1 + 18]);
      } else {
        n1 -= 100;
      }  // if
    }  // for

    // Process digit for 10 - 90
    int32_t n2;
    if (n3 >= 10) {
      // Special process for 15 and 16
      if ((15 == n3) || (16 == n3)) {
        // Special rule for religious reason...
        // 15 is represented by 9 and 6, not 10 and 5
        // 16 is represented by 9 and 7, not 10 and 6
        n2 = 9;
        thousandsGroup.Append(gHebrewDigit[n2 - 1]);
      } else {
        n2 = n3 - (n3 % 10);
        thousandsGroup.Append(gHebrewDigit[(n2 / 10) - 1 + 9]);
      }  // if
      n3 -= n2;
    }  // if

    // Process digit for 1 - 9
    if (n3 > 0) {
      thousandsGroup.Append(gHebrewDigit[n3 - 1]);
    }
    if (outputSep) {
      thousandsGroup.Append((char16_t)HEBREW_GERESH);
    }
    if (allText.IsEmpty()) {
      allText = thousandsGroup;
    } else {
      allText = thousandsGroup + allText;
    }
    aOrdinal /= 1000;
    outputSep = true;
  } while (aOrdinal >= 1);

  aResult = allText;
  return true;
}

// Convert ordinal to Ethiopic numeric representation.
// The detail is available at http://www.ethiopic.org/Numerals/
// The algorithm used here is based on the pseudo-code put up there by
// Daniel Yacob <yacob@geez.org>.
// Another reference is Unicode 3.0 standard section 11.1.
#define ETHIOPIC_ONE 0x1369
#define ETHIOPIC_TEN 0x1372
#define ETHIOPIC_HUNDRED 0x137B
#define ETHIOPIC_TEN_THOUSAND 0x137C

static bool EthiopicToText(CounterValue aOrdinal, nsAString& aResult) {
  if (aOrdinal < 1) {
    return false;
  }

  nsAutoString asciiNumberString;  // decimal string representation of ordinal
  DecimalToText(aOrdinal, asciiNumberString);
  uint8_t asciiStringLength = asciiNumberString.Length();

  // If number length is odd, add a leading "0"
  // the leading "0" preconditions the string to always have the
  // leading tens place populated, this avoids a check within the loop.
  // If we didn't add the leading "0", decrement asciiStringLength so
  // it will be equivalent to a zero-based index in both cases.
  if (asciiStringLength & 1) {
    asciiNumberString.InsertLiteral(u"0", 0);
  } else {
    asciiStringLength--;
  }

  aResult.Truncate();
  // Iterate from the highest digits to lowest
  // indexFromLeft       indexes digits (0 = most significant)
  // groupIndexFromRight indexes pairs of digits (0 = least significant)
  for (uint8_t indexFromLeft = 0, groupIndexFromRight = asciiStringLength >> 1;
       indexFromLeft <= asciiStringLength;
       indexFromLeft += 2, groupIndexFromRight--) {
    uint8_t tensValue = asciiNumberString.CharAt(indexFromLeft) & 0x0F;
    uint8_t unitsValue = asciiNumberString.CharAt(indexFromLeft + 1) & 0x0F;
    uint8_t groupValue = tensValue * 10 + unitsValue;

    bool oddGroup = (groupIndexFromRight & 1);

    // we want to clear ETHIOPIC_ONE when it is superfluous
    if (aOrdinal > 1 && groupValue == 1 &&  // one without a leading ten
        (oddGroup ||
         indexFromLeft == 0)) {  // preceding (100) or leading the sequence
      unitsValue = 0;
    }

    // put it all together...
    if (tensValue) {
      // map onto Ethiopic "tens":
      aResult.Append((char16_t)(tensValue + ETHIOPIC_TEN - 1));
    }
    if (unitsValue) {
      // map onto Ethiopic "units":
      aResult.Append((char16_t)(unitsValue + ETHIOPIC_ONE - 1));
    }
    // Add a separator for all even groups except the last,
    // and for odd groups with non-zero value.
    if (oddGroup) {
      if (groupValue) {
        aResult.Append((char16_t)ETHIOPIC_HUNDRED);
      }
    } else {
      if (groupIndexFromRight) {
        aResult.Append((char16_t)ETHIOPIC_TEN_THOUSAND);
      }
    }
  }
  return true;
}

static SpeakAs GetDefaultSpeakAsForSystem(StyleCounterSystem aSystem) {
  MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
             "Extends system does not have static default speak-as");
  switch (aSystem) {
    case StyleCounterSystem::Alphabetic:
      return SpeakAs::Spellout;
    case StyleCounterSystem::Cyclic:
      return SpeakAs::Bullets;
    default:
      return SpeakAs::Numbers;
  }
}

static bool SystemUsesNegativeSign(StyleCounterSystem aSystem) {
  MOZ_ASSERT(aSystem != StyleCounterSystem::Extends,
             "Cannot check this for extending style");
  switch (aSystem) {
    case StyleCounterSystem::Symbolic:
    case StyleCounterSystem::Alphabetic:
    case StyleCounterSystem::Numeric:
    case StyleCounterSystem::Additive:
      return true;
    default:
      return false;
  }
}

class BuiltinCounterStyle : public CounterStyle {
 public:
  constexpr BuiltinCounterStyle(ListStyle aStyle, nsStaticAtom* aName)
      : CounterStyle(aStyle), mName(aName) {}

  nsStaticAtom* GetStyleName() const { return mName; }

  void GetPrefix(nsAString& aResult) override;
  void GetSuffix(nsAString& aResult) override;
  void GetSpokenCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
                            nsAString& aResult, bool& aIsBullet) override;
  bool IsBullet() override;

  void GetNegative(NegativeType& aResult) override;
  bool IsOrdinalInRange(CounterValue aOrdinal) override;
  bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
  void GetPad(PadType& aResult) override;
  CounterStyle* GetFallback() override;
  SpeakAs GetSpeakAs() override;
  bool UseNegativeSign() override;

  bool GetInitialCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
                             nsAString& aResult, bool& aIsRTL) override;

 protected:
  constexpr BuiltinCounterStyle(const BuiltinCounterStyle& aOther)
      : CounterStyle(aOther.mStyle), mName(aOther.mName) {}

 private:
  nsStaticAtom* mName;
};

/* virtual */
void BuiltinCounterStyle::GetPrefix(nsAString& aResult) { aResult.Truncate(); }

/* virtual */
void BuiltinCounterStyle::GetSuffix(nsAString& aResult) {
  switch (mStyle) {
    case ListStyle::None:
      aResult.Truncate();
      break;

    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
    case ListStyle::EthiopicNumeric:
      aResult = ' ';
      break;

    case ListStyle::TradChineseInformal:
    case ListStyle::TradChineseFormal:
    case ListStyle::SimpChineseInformal:
    case ListStyle::SimpChineseFormal:
    case ListStyle::JapaneseInformal:
    case ListStyle::JapaneseFormal:
      aResult = 0x3001;
      break;

    case ListStyle::KoreanHangulFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHanjaFormal:
      aResult.AssignLiteral(u", ");
      break;

    default:
      aResult.AssignLiteral(u". ");
      break;
  }
}

static const char16_t kDiscCharacter = 0x2022;
static const char16_t kCircleCharacter = 0x25e6;
static const char16_t kSquareCharacter = 0x25aa;
static const char16_t kRightPointingCharacter = 0x25b8;
static const char16_t kLeftPointingCharacter = 0x25c2;
static const char16_t kDownPointingCharacter = 0x25be;
static const char16_t kUpPointingCharacter = 0x25b4;

/* virtual */
void BuiltinCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
                                               WritingMode aWritingMode,
                                               nsAString& aResult,
                                               bool& aIsBullet) {
  switch (mStyle) {
    case ListStyle::None:
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen: {
      // Same as the initial representation
      bool isRTL;
      GetInitialCounterText(aOrdinal, aWritingMode, aResult, isRTL);
      aIsBullet = true;
      break;
    }
    default:
      CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
                                         aIsBullet);
      break;
  }
}

/* virtual */
bool BuiltinCounterStyle::IsBullet() {
  switch (mStyle) {
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
      return true;
    default:
      return false;
  }
}

static const char16_t gJapaneseNegative[] = {0x30de, 0x30a4, 0x30ca, 0x30b9,
                                             0x0000};
static const char16_t gKoreanNegative[] = {0xb9c8, 0xc774, 0xb108,
                                           0xc2a4, 0x0020, 0x0000};
static const char16_t gSimpChineseNegative[] = {0x8d1f, 0x0000};
static const char16_t gTradChineseNegative[] = {0x8ca0, 0x0000};

/* virtual */
void BuiltinCounterStyle::GetNegative(NegativeType& aResult) {
  switch (mStyle) {
    case ListStyle::JapaneseFormal:
    case ListStyle::JapaneseInformal:
      aResult.before = gJapaneseNegative;
      break;

    case ListStyle::KoreanHangulFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHanjaFormal:
      aResult.before = gKoreanNegative;
      break;

    case ListStyle::SimpChineseFormal:
    case ListStyle::SimpChineseInformal:
      aResult.before = gSimpChineseNegative;
      break;

    case ListStyle::TradChineseFormal:
    case ListStyle::TradChineseInformal:
      aResult.before = gTradChineseNegative;
      break;

    default:
      aResult.before.AssignLiteral(u"-");
  }
  aResult.after.Truncate();
}

/* virtual */
bool BuiltinCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
  switch (mStyle) {
    default:
    // cyclic
    case ListStyle::None:
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
    // use DecimalToText
    case ListStyle::Decimal:
    // use CJKIdeographicToText
    case ListStyle::JapaneseFormal:
    case ListStyle::JapaneseInformal:
    case ListStyle::KoreanHanjaFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHangulFormal:
    case ListStyle::TradChineseFormal:
    case ListStyle::TradChineseInformal:
    case ListStyle::SimpChineseFormal:
    case ListStyle::SimpChineseInformal:
      return true;

    // use EthiopicToText
    case ListStyle::EthiopicNumeric:
      return aOrdinal >= 1;

    // use HebrewToText
    case ListStyle::Hebrew:
      return aOrdinal >= 1 && aOrdinal <= 999999;
  }
}

/* virtual */
bool BuiltinCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
  switch (mStyle) {
    // cyclic:
    case ListStyle::None:
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
    // numeric:
    case ListStyle::Decimal:
      return true;

    // additive:
    case ListStyle::Hebrew:
      return aOrdinal >= 0;

    // complex predefined:
    case ListStyle::JapaneseFormal:
    case ListStyle::JapaneseInformal:
    case ListStyle::KoreanHanjaFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHangulFormal:
    case ListStyle::TradChineseFormal:
    case ListStyle::TradChineseInformal:
    case ListStyle::SimpChineseFormal:
    case ListStyle::SimpChineseInformal:
    case ListStyle::EthiopicNumeric:
      return IsOrdinalInRange(aOrdinal);

    default:
      MOZ_ASSERT_UNREACHABLE("Unknown counter style");
      return false;
  }
}

/* virtual */
void BuiltinCounterStyle::GetPad(PadType& aResult) {
  aResult.width = 0;
  aResult.symbol.Truncate();
}

/* virtual */
CounterStyle* BuiltinCounterStyle::GetFallback() {
  // Fallback of dependent builtin counter styles are handled in class
  // DependentBuiltinCounterStyle.
  return CounterStyleManager::GetDecimalStyle();
}

/* virtual */
SpeakAs BuiltinCounterStyle::GetSpeakAs() {
  switch (mStyle) {
    case ListStyle::None:
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
      return SpeakAs::Bullets;
    default:
      return SpeakAs::Numbers;
  }
}

/* virtual */
bool BuiltinCounterStyle::UseNegativeSign() {
  switch (mStyle) {
    case ListStyle::None:
    case ListStyle::Disc:
    case ListStyle::Circle:
    case ListStyle::Square:
    case ListStyle::DisclosureClosed:
    case ListStyle::DisclosureOpen:
      return false;
    default:
      return true;
  }
}

/* virtual */
bool BuiltinCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
                                                WritingMode aWritingMode,
                                                nsAString& aResult,
                                                bool& aIsRTL) {
  aIsRTL = false;
  switch (mStyle) {
    // used by counters & extends counter-style code only
    // XXX We really need to do this the same way we do list bullets.
    case ListStyle::None:
      aResult.Truncate();
      return true;
    case ListStyle::Disc:
      aResult.Assign(kDiscCharacter);
      return true;
    case ListStyle::Circle:
      aResult.Assign(kCircleCharacter);
      return true;
    case ListStyle::Square:
      aResult.Assign(kSquareCharacter);
      return true;
    case ListStyle::DisclosureClosed:
      if (aWritingMode.IsVertical()) {
        if (aWritingMode.IsBidiLTR()) {
          aResult.Assign(kDownPointingCharacter);
        } else {
          aResult.Assign(kUpPointingCharacter);
        }
      } else if (aWritingMode.IsBidiLTR()) {
        aResult.Assign(kRightPointingCharacter);
      } else {
        aResult.Assign(kLeftPointingCharacter);
      }
      return true;
    case ListStyle::DisclosureOpen:
      if (!aWritingMode.IsVertical()) {
        aResult.Assign(kDownPointingCharacter);
      } else if (aWritingMode.IsVerticalLR()) {
        aResult.Assign(kRightPointingCharacter);
      } else {
        aResult.Assign(kLeftPointingCharacter);
      }
      return true;

    case ListStyle::Decimal:
      return DecimalToText(aOrdinal, aResult);

    case ListStyle::TradChineseInformal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseInformal);
    case ListStyle::TradChineseFormal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataTradChineseFormal);
    case ListStyle::SimpChineseInformal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseInformal);
    case ListStyle::SimpChineseFormal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataSimpChineseFormal);
    case ListStyle::JapaneseInformal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseInformal);
    case ListStyle::JapaneseFormal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataJapaneseFormal);
    case ListStyle::KoreanHangulFormal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHangulFormal);
    case ListStyle::KoreanHanjaInformal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaInformal);
    case ListStyle::KoreanHanjaFormal:
      return CJKIdeographicToText(aOrdinal, aResult, gDataKoreanHanjaFormal);

    case ListStyle::Hebrew:
      aIsRTL = true;
      return HebrewToText(aOrdinal, aResult);

    case ListStyle::EthiopicNumeric:
      return EthiopicToText(aOrdinal, aResult);

    default:
      MOZ_ASSERT_UNREACHABLE("Unknown builtin counter style");
      return false;
  }
}

static constexpr BuiltinCounterStyle gBuiltinStyleTable[] = {
#define BUILTIN_COUNTER_STYLE(value_, atom_) \
  {ListStyle::value_, nsGkAtoms::atom_},
#include "BuiltinCounterStyleList.h"
#undef BUILTIN_COUNTER_STYLE
};

#define BUILTIN_COUNTER_STYLE(value_, atom_)                                   \
  static_assert(                                                               \
      gBuiltinStyleTable[static_cast<size_t>(ListStyle::value_)].GetStyle() == \
          ListStyle::value_,                                                   \
      "Builtin counter style " #atom_ " has unmatched index and value.");
#include "BuiltinCounterStyleList.h"
#undef BUILTIN_COUNTER_STYLE

class DependentBuiltinCounterStyle final : public BuiltinCounterStyle {
 public:
  DependentBuiltinCounterStyle(ListStyle aStyle, CounterStyleManager* aManager)
      : BuiltinCounterStyle(gBuiltinStyleTable[static_cast<size_t>(aStyle)]),
        mManager(aManager) {
    NS_ASSERTION(IsDependentStyle(), "Not a dependent builtin style");
    MOZ_ASSERT(!IsCustomStyle(), "Not a builtin style");
  }

  virtual CounterStyle* GetFallback() override;

  voidoperator new(size_t sz, nsPresContext* aPresContext) {
    return aPresContext->PresShell()->AllocateByObjectID(
        eArenaObjectID_DependentBuiltinCounterStyle, sz);
  }

  void Destroy() {
    PresShell* presShell = mManager->PresContext()->PresShell();
    this->~DependentBuiltinCounterStyle();
    presShell->FreeByObjectID(eArenaObjectID_DependentBuiltinCounterStyle,
                              this);
  }

 private:
  ~DependentBuiltinCounterStyle() = default;

  CounterStyleManager* mManager;
};

/* virtual */
CounterStyle* DependentBuiltinCounterStyle::GetFallback() {
  switch (GetStyle()) {
    case ListStyle::JapaneseInformal:
    case ListStyle::JapaneseFormal:
    case ListStyle::KoreanHangulFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHanjaFormal:
    case ListStyle::SimpChineseInformal:
    case ListStyle::SimpChineseFormal:
    case ListStyle::TradChineseInformal:
    case ListStyle::TradChineseFormal:
      // These styles all have a larger range than cjk-decimal, so the
      // only case fallback is accessed is that they are extended.
      // Since extending styles will cache the data themselves, we need
      // not cache it here.
      return mManager->ResolveCounterStyle(nsGkAtoms::cjk_decimal);
    default:
      MOZ_ASSERT_UNREACHABLE("Not a valid dependent builtin style");
      return BuiltinCounterStyle::GetFallback();
  }
}

class CustomCounterStyle final : public CounterStyle {
 public:
  CustomCounterStyle(CounterStyleManager* aManager,
                     const StyleLockedCounterStyleRule* aRule)
      : CounterStyle(ListStyle::Custom),
        mManager(aManager),
        mRule(aRule),
        mRuleGeneration(Servo_CounterStyleRule_GetGeneration(aRule)),
        mSystem(Servo_CounterStyleRule_GetSystem(aRule)),
        mFlags(0),
        mFallback(nullptr),
        mSpeakAsCounter(nullptr),
        mExtends(nullptr),
        mExtendsRoot(nullptr) {}

  // This method will clear all cached data in the style and update the
  // generation number of the rule. It should be called when the rule of
  // this style is changed.
  void ResetCachedData();

  // This method will reset all cached data which may depend on other
  // counter style. It will reset all pointers to other counter styles.
  // For counter style extends other, in addition, all fields will be
  // reset to uninitialized state. This method should be called when any
  // other counter style is added, removed, or changed.
  void ResetDependentData();

  const StyleLockedCounterStyleRule* GetRule() const { return mRule; }
  uint32_t GetRuleGeneration() const { return mRuleGeneration; }

  void GetPrefix(nsAString& aResult) override;
  void GetSuffix(nsAString& aResult) override;
  void GetSpokenCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
                            nsAString& aResult, bool& aIsBullet) override;
  bool IsBullet() override;

  void GetNegative(NegativeType& aResult) override;
  bool IsOrdinalInRange(CounterValue aOrdinal) override;
  bool IsOrdinalInAutoRange(CounterValue aOrdinal) override;
  void GetPad(PadType& aResult) override;
  CounterStyle* GetFallback() override;
  SpeakAs GetSpeakAs() override;
  bool UseNegativeSign() override;

  void CallFallbackStyle(CounterValue aOrdinal, WritingMode aWritingMode,
                         nsAString& aResult, bool& aIsRTL) override;
  bool GetInitialCounterText(CounterValue aOrdinal, WritingMode aWritingMode,
                             nsAString& aResult, bool& aIsRTL) override;

  bool IsExtendsSystem() { return mSystem == StyleCounterSystem::Extends; }

  voidoperator new(size_t sz, nsPresContext* aPresContext) {
    return aPresContext->PresShell()->AllocateByObjectID(
        eArenaObjectID_CustomCounterStyle, sz);
  }

  void Destroy() {
    PresShell* presShell = mManager->PresContext()->PresShell();
    this->~CustomCounterStyle();
    presShell->FreeByObjectID(eArenaObjectID_CustomCounterStyle, this);
  }

 private:
  ~CustomCounterStyle() = default;

  Span<const StyleSymbol> GetSymbols();
  Span<const AdditiveSymbol> GetAdditiveSymbols();

  // The speak-as values of counter styles may form a loop, and the
  // loops may have complex interaction with the loop formed by
  // extending. To solve this problem, the computation of speak-as is
  // divided into two phases:
  // 1. figure out the raw value, by ComputeRawSpeakAs, and
  // 2. eliminate loop, by ComputeSpeakAs.
  // See comments before the definitions of these methods for details.
  SpeakAs GetSpeakAsAutoValue();
  void ComputeRawSpeakAs(SpeakAs& aSpeakAs, CounterStyle*& aSpeakAsCounter);
  CounterStyle* ComputeSpeakAs();

  CounterStyle* ComputeExtends();
  CounterStyle* GetExtends();
  CounterStyle* GetExtendsRoot();

  // CounterStyleManager should always overlive any CounterStyle as it
  // is owned by nsPresContext, and will be released after all nodes and
  // frames are released.
  CounterStyleManager* mManager;

  RefPtr<const StyleLockedCounterStyleRule> mRule;
  uint32_t mRuleGeneration;

  StyleCounterSystem mSystem;
  // GetSpeakAs will ensure that private member mSpeakAs is initialized before
  // used
  MOZ_INIT_OUTSIDE_CTOR SpeakAs mSpeakAs;

  enum {
    // loop detection
    FLAG_EXTENDS_VISITED = 1 << 0,
    FLAG_EXTENDS_LOOP = 1 << 1,
    FLAG_SPEAKAS_VISITED = 1 << 2,
    FLAG_SPEAKAS_LOOP = 1 << 3,
    // field status
    FLAG_NEGATIVE_INITED = 1 << 4,
    FLAG_PREFIX_INITED = 1 << 5,
    FLAG_SUFFIX_INITED = 1 << 6,
    FLAG_PAD_INITED = 1 << 7,
    FLAG_SPEAKAS_INITED = 1 << 8,
  };
  uint16_t mFlags;

  // Fields below will be initialized when necessary.
  StyleOwnedSlice<AdditiveSymbol> mAdditiveSymbols;
  NegativeType mNegative;
  nsString mPrefix, mSuffix;
  PadType mPad;

  // CounterStyleManager will guarantee that none of the pointers below
  // refers to a freed CounterStyle. There are two possible cases where
  // the manager will release its reference to a CounterStyle: 1. the
  // manager itself is released, 2. a rule is invalidated. In the first
  // case, all counter style are removed from the manager, and should
  // also have been dereferenced from other objects. All styles will be
  // released all together. In the second case, CounterStyleManager::
  // NotifyRuleChanged will guarantee that all pointers will be reset
  // before any CounterStyle is released.

  CounterStyle* mFallback;
  // This field refers to the last counter in a speak-as chain.
  // That counter must not speak as another counter.
  CounterStyle* mSpeakAsCounter;

  CounterStyle* mExtends;
  // This field refers to the last counter in the extends chain. The
  // counter must be either a builtin style or a style whose system is
  // not 'extends'.
  CounterStyle* mExtendsRoot;
};

void CustomCounterStyle::ResetCachedData() {
  mAdditiveSymbols.Clear();
  mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
              FLAG_PAD_INITED | FLAG_SPEAKAS_INITED);
  mFallback = nullptr;
  mSpeakAsCounter = nullptr;
  mExtends = nullptr;
  mExtendsRoot = nullptr;
  mRuleGeneration = Servo_CounterStyleRule_GetGeneration(mRule);
}

void CustomCounterStyle::ResetDependentData() {
  mFlags &= ~FLAG_SPEAKAS_INITED;
  mSpeakAsCounter = nullptr;
  mFallback = nullptr;
  mExtends = nullptr;
  mExtendsRoot = nullptr;
  if (IsExtendsSystem()) {
    mFlags &= ~(FLAG_NEGATIVE_INITED | FLAG_PREFIX_INITED | FLAG_SUFFIX_INITED |
                FLAG_PAD_INITED);
  }
}

/* virtual */
void CustomCounterStyle::GetPrefix(nsAString& aResult) {
  if (!(mFlags & FLAG_PREFIX_INITED)) {
    mFlags |= FLAG_PREFIX_INITED;

    if (!Servo_CounterStyleRule_GetPrefix(mRule, &mPrefix)) {
      if (IsExtendsSystem()) {
        GetExtends()->GetPrefix(mPrefix);
      } else {
        mPrefix.Truncate();
      }
    }
  }
  aResult = mPrefix;
}

/* virtual */
void CustomCounterStyle::GetSuffix(nsAString& aResult) {
  if (!(mFlags & FLAG_SUFFIX_INITED)) {
    mFlags |= FLAG_SUFFIX_INITED;

    if (!Servo_CounterStyleRule_GetSuffix(mRule, &mSuffix)) {
      if (IsExtendsSystem()) {
        GetExtends()->GetSuffix(mSuffix);
      } else {
        mSuffix.AssignLiteral(u". ");
      }
    }
  }
  aResult = mSuffix;
}

/* virtual */
void CustomCounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
                                              WritingMode aWritingMode,
                                              nsAString& aResult,
                                              bool& aIsBullet) {
  if (GetSpeakAs() != SpeakAs::Other) {
    CounterStyle::GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
                                       aIsBullet);
  } else {
    MOZ_ASSERT(mSpeakAsCounter,
               "mSpeakAsCounter should have been initialized.");
    mSpeakAsCounter->GetSpokenCounterText(aOrdinal, aWritingMode, aResult,
                                          aIsBullet);
  }
}

/* virtual */
bool CustomCounterStyle::IsBullet() {
  switch (mSystem) {
    case StyleCounterSystem::Cyclic:
      // Only use ::-moz-list-bullet for cyclic system
      return true;
    case StyleCounterSystem::Extends:
      return GetExtendsRoot()->IsBullet();
    default:
      return false;
  }
}

/* virtual */
void CustomCounterStyle::GetNegative(NegativeType& aResult) {
  if (!(mFlags & FLAG_NEGATIVE_INITED)) {
    mFlags |= FLAG_NEGATIVE_INITED;
    if (!Servo_CounterStyleRule_GetNegative(mRule, &mNegative.before,
                                            &mNegative.after)) {
      if (IsExtendsSystem()) {
        GetExtends()->GetNegative(mNegative);
      } else {
        mNegative.before.AssignLiteral(u"-");
        mNegative.after.Truncate();
      }
    }
  }
  aResult = mNegative;
}

/* virtual */
bool CustomCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
  auto inRange = Servo_CounterStyleRule_IsInRange(mRule, aOrdinal);
  switch (inRange) {
    case StyleIsOrdinalInRange::InRange:
      return true;
    case StyleIsOrdinalInRange::NotInRange:
      return false;
    case StyleIsOrdinalInRange::NoOrdinalSpecified:
      if (IsExtendsSystem()) {
        return GetExtends()->IsOrdinalInRange(aOrdinal);
      }
      break;
    case StyleIsOrdinalInRange::Auto:
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unkown result from IsInRange?");
  }
  return IsOrdinalInAutoRange(aOrdinal);
}

/* virtual */
bool CustomCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
  switch (mSystem) {
    case StyleCounterSystem::Cyclic:
    case StyleCounterSystem::Numeric:
    case StyleCounterSystem::Fixed:
      return true;
    case StyleCounterSystem::Alphabetic:
    case StyleCounterSystem::Symbolic:
      return aOrdinal >= 1;
    case StyleCounterSystem::Additive:
      return aOrdinal >= 0;
    case StyleCounterSystem::Extends:
      return GetExtendsRoot()->IsOrdinalInAutoRange(aOrdinal);
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid system for computing auto value.");
      return false;
  }
}

/* virtual */
void CustomCounterStyle::GetPad(PadType& aResult) {
  if (!(mFlags & FLAG_PAD_INITED)) {
    mFlags |= FLAG_PAD_INITED;
    if (!Servo_CounterStyleRule_GetPad(mRule, &mPad.width, &mPad.symbol)) {
      if (IsExtendsSystem()) {
        GetExtends()->GetPad(mPad);
      } else {
        mPad.width = 0;
        mPad.symbol.Truncate();
      }
    }
  }
  aResult = mPad;
}

/* virtual */
CounterStyle* CustomCounterStyle::GetFallback() {
  if (!mFallback) {
    mFallback = CounterStyleManager::GetDecimalStyle();
    if (nsAtom* fallback = Servo_CounterStyleRule_GetFallback(mRule)) {
      mFallback = mManager->ResolveCounterStyle(fallback);
    } else if (IsExtendsSystem()) {
      mFallback = GetExtends()->GetFallback();
    }
  }
  return mFallback;
}

/* virtual */
SpeakAs CustomCounterStyle::GetSpeakAs() {
  if (!(mFlags & FLAG_SPEAKAS_INITED)) {
    ComputeSpeakAs();
  }
  return mSpeakAs;
}

/* virtual */
bool CustomCounterStyle::UseNegativeSign() {
  if (mSystem == StyleCounterSystem::Extends) {
    return GetExtendsRoot()->UseNegativeSign();
  }
  return SystemUsesNegativeSign(mSystem);
}

/* virtual */
void CustomCounterStyle::CallFallbackStyle(CounterValue aOrdinal,
                                           WritingMode aWritingMode,
                                           nsAString& aResult, bool& aIsRTL) {
  CounterStyle* fallback = GetFallback();
  // If it recursively falls back to this counter style again,
  // it will then fallback to decimal to break the loop.
  mFallback = CounterStyleManager::GetDecimalStyle();
  fallback->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
  mFallback = fallback;
}

/* virtual */
bool CustomCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
                                               WritingMode aWritingMode,
                                               nsAString& aResult,
                                               bool& aIsRTL) {
  switch (mSystem) {
    case StyleCounterSystem::Cyclic:
      return GetCyclicCounterText(aOrdinal, aResult, GetSymbols());
    case StyleCounterSystem::Fixed: {
      int32_t start = Servo_CounterStyleRule_GetFixedFirstValue(mRule);
      return GetFixedCounterText(aOrdinal, aResult, start, GetSymbols());
    }
    case StyleCounterSystem::Symbolic:
      return GetSymbolicCounterText(aOrdinal, aResult, GetSymbols());
    case StyleCounterSystem::Alphabetic:
      return GetAlphabeticCounterText(aOrdinal, aResult, GetSymbols());
    case StyleCounterSystem::Numeric:
      return GetNumericCounterText(aOrdinal, aResult, GetSymbols());
    case StyleCounterSystem::Additive:
      return GetAdditiveCounterText(aOrdinal, aResult, GetAdditiveSymbols());
    case StyleCounterSystem::Extends:
      return GetExtendsRoot()->GetInitialCounterText(aOrdinal, aWritingMode,
                                                     aResult, aIsRTL);
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid system.");
      return false;
  }
}

Span<const StyleSymbol> CustomCounterStyle::GetSymbols() {
  size_t count = 0;
  const StyleSymbol* ptr = Servo_CounterStyleRule_GetSymbols(mRule, &count);
  return Span(ptr, count);
}

Span<const AdditiveSymbol> CustomCounterStyle::GetAdditiveSymbols() {
  if (mAdditiveSymbols.IsEmpty()) {
    Servo_CounterStyleRule_GetAdditiveSymbols(mRule, &mAdditiveSymbols);
  }
  return mAdditiveSymbols.AsSpan();
}

// This method is used to provide the computed value for 'auto'.
SpeakAs CustomCounterStyle::GetSpeakAsAutoValue() {
  auto system = mSystem;
  if (IsExtendsSystem()) {
    CounterStyle* root = GetExtendsRoot();
    if (!root->IsCustomStyle()) {
      // It is safe to call GetSpeakAs on non-custom style.
      return root->GetSpeakAs();
    }
    system = static_cast<CustomCounterStyle*>(root)->mSystem;
  }
  return GetDefaultSpeakAsForSystem(system);
}

// This method corresponds to the first stage of computation of the
// value of speak-as. It will extract the value from the rule and
// possibly recursively call itself on the extended style to figure
// out the raw value. To keep things clear, this method is designed to
// have no side effects (but functions it calls may still affect other
// fields in the style.)
void CustomCounterStyle::ComputeRawSpeakAs(SpeakAs& aSpeakAs,
                                           CounterStyle*& aSpeakAsCounter) {
  NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_INITED),
               "ComputeRawSpeakAs is called with speak-as inited.");

  auto speakAs = StyleCounterSpeakAs::None();
  Servo_CounterStyleRule_GetSpeakAs(mRule, &speakAs);
  switch (speakAs.tag) {
    case StyleCounterSpeakAs::Tag::Auto:
      aSpeakAs = GetSpeakAsAutoValue();
      break;
    case StyleCounterSpeakAs::Tag::Bullets:
      aSpeakAs = SpeakAs::Bullets;
      break;
    case StyleCounterSpeakAs::Tag::Numbers:
      aSpeakAs = SpeakAs::Numbers;
      break;
    case StyleCounterSpeakAs::Tag::Words:
      aSpeakAs = SpeakAs::Words;
      break;
    case StyleCounterSpeakAs::Tag::Ident:
      aSpeakAs = SpeakAs::Other;
      aSpeakAsCounter = mManager->ResolveCounterStyle(speakAs.AsIdent());
      break;
    case StyleCounterSpeakAs::Tag::None: {
      if (!IsExtendsSystem()) {
        aSpeakAs = GetSpeakAsAutoValue();
      } else {
        CounterStyle* extended = GetExtends();
        if (!extended->IsCustomStyle()) {
          // It is safe to call GetSpeakAs on non-custom style.
          aSpeakAs = extended->GetSpeakAs();
        } else {
          CustomCounterStyle* custom =
              static_cast<CustomCounterStyle*>(extended);
          if (!(custom->mFlags & FLAG_SPEAKAS_INITED)) {
            custom->ComputeRawSpeakAs(aSpeakAs, aSpeakAsCounter);
          } else {
            aSpeakAs = custom->mSpeakAs;
            aSpeakAsCounter = custom->mSpeakAsCounter;
          }
        }
      }
      break;
    }
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
  }
}

// This method corresponds to the second stage of getting speak-as
// related values. It will recursively figure out the final value of
// mSpeakAs and mSpeakAsCounter. This method returns nullptr if the
// caller is in a loop, and the root counter style in the chain
// otherwise. It use the same loop detection algorithm as
// CustomCounterStyle::ComputeExtends, see comments before that
// method for more details.
CounterStyle* CustomCounterStyle::ComputeSpeakAs() {
  if (mFlags & FLAG_SPEAKAS_INITED) {
    if (mSpeakAs == SpeakAs::Other) {
      return mSpeakAsCounter;
    }
    return this;
  }

  if (mFlags & FLAG_SPEAKAS_VISITED) {
    // loop detected
    mFlags |= FLAG_SPEAKAS_LOOP;
    return nullptr;
  }

  CounterStyle* speakAsCounter;
  ComputeRawSpeakAs(mSpeakAs, speakAsCounter);

  bool inLoop = false;
  if (mSpeakAs != SpeakAs::Other) {
    mSpeakAsCounter = nullptr;
  } else if (!speakAsCounter->IsCustomStyle()) {
    mSpeakAsCounter = speakAsCounter;
  } else {
    mFlags |= FLAG_SPEAKAS_VISITED;
    CounterStyle* target =
        static_cast<CustomCounterStyle*>(speakAsCounter)->ComputeSpeakAs();
    mFlags &= ~FLAG_SPEAKAS_VISITED;

    if (target) {
      NS_ASSERTION(!(mFlags & FLAG_SPEAKAS_LOOP),
                   "Invalid state for speak-as loop detecting");
      mSpeakAsCounter = target;
    } else {
      mSpeakAs = GetSpeakAsAutoValue();
      mSpeakAsCounter = nullptr;
      if (mFlags & FLAG_SPEAKAS_LOOP) {
        mFlags &= ~FLAG_SPEAKAS_LOOP;
      } else {
        inLoop = true;
      }
    }
  }

  mFlags |= FLAG_SPEAKAS_INITED;
  if (inLoop) {
    return nullptr;
  }
  return mSpeakAsCounter ? mSpeakAsCounter : this;
}

// This method will recursively figure out mExtends in the whole chain.
// It will return nullptr if the caller is in a loop, and return this
// otherwise. To detect the loop, this method marks the style VISITED
// before the recursive call. When a VISITED style is reached again, the
// loop is detected, and flag LOOP will be marked on the first style in
// loop. mExtends of all counter styles in loop will be set to decimal
// according to the spec.
CounterStyle* CustomCounterStyle::ComputeExtends() {
  if (!IsExtendsSystem() || mExtends) {
    return this;
  }
  if (mFlags & FLAG_EXTENDS_VISITED) {
    // loop detected
    mFlags |= FLAG_EXTENDS_LOOP;
    return nullptr;
  }

  nsAtom* extended = Servo_CounterStyleRule_GetExtended(mRule);
  CounterStyle* nextCounter = mManager->ResolveCounterStyle(extended);
  CounterStyle* target = nextCounter;
  if (nextCounter->IsCustomStyle()) {
    mFlags |= FLAG_EXTENDS_VISITED;
    target = static_cast<CustomCounterStyle*>(nextCounter)->ComputeExtends();
    mFlags &= ~FLAG_EXTENDS_VISITED;
  }

  if (target) {
    NS_ASSERTION(!(mFlags & FLAG_EXTENDS_LOOP),
                 "Invalid state for extends loop detecting");
    mExtends = nextCounter;
    return this;
  } else {
    mExtends = CounterStyleManager::GetDecimalStyle();
    if (mFlags & FLAG_EXTENDS_LOOP) {
      mFlags &= ~FLAG_EXTENDS_LOOP;
      return this;
    } else {
      return nullptr;
    }
  }
}

CounterStyle* CustomCounterStyle::GetExtends() {
  if (!mExtends) {
    // Any extends loop will be eliminated in the method below.
    ComputeExtends();
  }
  return mExtends;
}

CounterStyle* CustomCounterStyle::GetExtendsRoot() {
  if (!mExtendsRoot) {
    CounterStyle* extended = GetExtends();
    mExtendsRoot = extended;
    if (extended->IsCustomStyle()) {
      CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(extended);
      if (custom->IsExtendsSystem()) {
        // This will make mExtendsRoot in the whole extends chain be
        // set recursively, which could save work when part of a chain
        // is shared by multiple counter styles.
        mExtendsRoot = custom->GetExtendsRoot();
      }
    }
  }
  return mExtendsRoot;
}

AnonymousCounterStyle::AnonymousCounterStyle(StyleSymbolsType aType,
                                             Span<const StyleSymbol> aSymbols)
    : CounterStyle(ListStyle::Custom),
      mSymbolsType(aType),
      mSymbols(aSymbols) {}

/* virtual */
void AnonymousCounterStyle::GetPrefix(nsAString& aResult) {
  aResult.Truncate();
}

/* virtual */
void AnonymousCounterStyle::GetSuffix(nsAString& aResult) { aResult = ' '; }

/* virtual */
bool AnonymousCounterStyle::IsBullet() {
  // Only use ::-moz-list-bullet for cyclic system
  return mSymbolsType == StyleSymbolsType::Cyclic;
}

/* virtual */
void AnonymousCounterStyle::GetNegative(NegativeType& aResult) {
  aResult.before.AssignLiteral(u"-");
  aResult.after.Truncate();
}

/* virtual */
bool AnonymousCounterStyle::IsOrdinalInRange(CounterValue aOrdinal) {
  switch (mSymbolsType) {
    case StyleSymbolsType::Cyclic:
    case StyleSymbolsType::Numeric:
    case StyleSymbolsType::Fixed:
      return true;
    case StyleSymbolsType::Alphabetic:
    case StyleSymbolsType::Symbolic:
      return aOrdinal >= 1;
    default:
      MOZ_ASSERT_UNREACHABLE("Invalid system.");
      return false;
  }
}

/* virtual */
bool AnonymousCounterStyle::IsOrdinalInAutoRange(CounterValue aOrdinal) {
  return AnonymousCounterStyle::IsOrdinalInRange(aOrdinal);
}

/* virtual */
void AnonymousCounterStyle::GetPad(PadType& aResult) {
  aResult.width = 0;
  aResult.symbol.Truncate();
}

/* virtual */
CounterStyle* AnonymousCounterStyle::GetFallback() {
  return CounterStyleManager::GetDecimalStyle();
}

StyleCounterSystem AnonymousCounterStyle::GetSystem() const {
  switch (mSymbolsType) {
    case StyleSymbolsType::Cyclic:
      return StyleCounterSystem::Cyclic;
    case StyleSymbolsType::Numeric:
      return StyleCounterSystem::Numeric;
    case StyleSymbolsType::Fixed:
      return StyleCounterSystem::Fixed;
    case StyleSymbolsType::Alphabetic:
      return StyleCounterSystem::Alphabetic;
    case StyleSymbolsType::Symbolic:
      return StyleCounterSystem::Symbolic;
  }
  MOZ_ASSERT_UNREACHABLE("Unknown symbols() type");
  return StyleCounterSystem::Cyclic;
}

/* virtual */
SpeakAs AnonymousCounterStyle::GetSpeakAs() {
  return GetDefaultSpeakAsForSystem(GetSystem());
}

/* virtual */
bool AnonymousCounterStyle::UseNegativeSign() {
  return SystemUsesNegativeSign(GetSystem());
}

/* virtual */
bool AnonymousCounterStyle::GetInitialCounterText(CounterValue aOrdinal,
                                                  WritingMode aWritingMode,
                                                  nsAString& aResult,
                                                  bool& aIsRTL) {
  switch (mSymbolsType) {
    case StyleSymbolsType::Cyclic:
      return GetCyclicCounterText(aOrdinal, aResult, mSymbols);
    case StyleSymbolsType::Numeric:
      return GetNumericCounterText(aOrdinal, aResult, mSymbols);
    case StyleSymbolsType::Fixed:
      return GetFixedCounterText(aOrdinal, aResult, 1, mSymbols);
    case StyleSymbolsType::Alphabetic:
      return GetAlphabeticCounterText(aOrdinal, aResult, mSymbols);
    case StyleSymbolsType::Symbolic:
      return GetSymbolicCounterText(aOrdinal, aResult, mSymbols);
  }
  MOZ_ASSERT_UNREACHABLE("Invalid system.");
  return false;
}

bool CounterStyle::IsDependentStyle() const {
  switch (mStyle) {
    // CustomCounterStyle
    case ListStyle::Custom:
    // DependentBuiltinCounterStyle
    case ListStyle::JapaneseInformal:
    case ListStyle::JapaneseFormal:
    case ListStyle::KoreanHangulFormal:
    case ListStyle::KoreanHanjaInformal:
    case ListStyle::KoreanHanjaFormal:
    case ListStyle::SimpChineseInformal:
    case ListStyle::SimpChineseFormal:
    case ListStyle::TradChineseInformal:
    case ListStyle::TradChineseFormal:
      return true;

    // BuiltinCounterStyle
    default:
      return false;
  }
}

void CounterStyle::GetCounterText(CounterValue aOrdinal,
                                  WritingMode aWritingMode, nsAString& aResult,
                                  bool& aIsRTL) {
  bool success = IsOrdinalInRange(aOrdinal);
  aIsRTL = false;

  if (success) {
    // generate initial representation
    bool useNegativeSign = UseNegativeSign();
    nsAutoString initialText;
    CounterValue ordinal;
    if (!useNegativeSign) {
      ordinal = aOrdinal;
    } else {
      CheckedInt<CounterValue> absolute(Abs(aOrdinal));
      ordinal = absolute.isValid() ? absolute.value()
                                   : std::numeric_limits<CounterValue>::max();
    }
    success = GetInitialCounterText(ordinal, aWritingMode, initialText, aIsRTL);

    // add pad & negative, build the final result
    if (success) {
      aResult.Truncate();
      if (useNegativeSign && aOrdinal < 0) {
        NegativeType negative;
        GetNegative(negative);
        aResult.Append(negative.before);
        // There is nothing between the suffix part of negative and initial
        // representation, so we append it directly here.
        initialText.Append(negative.after);
      }
      PadType pad;
      GetPad(pad);
      int32_t diff =
          pad.width -
          narrow_cast<int32_t>(unicode::CountGraphemeClusters(initialText) +
                               unicode::CountGraphemeClusters(aResult));
      if (diff > 0) {
        auto length = pad.symbol.Length();
        if (diff > LENGTH_LIMIT || length > LENGTH_LIMIT ||
            diff * length > LENGTH_LIMIT) {
          success = false;
        } else if (length > 0) {
          for (int32_t i = 0; i < diff; ++i) {
            aResult.Append(pad.symbol);
          }
        }
      }
      if (success) {
        aResult.Append(initialText);
      }
    }
  }

  if (!success) {
    CallFallbackStyle(aOrdinal, aWritingMode, aResult, aIsRTL);
  }
}

/* virtual */
void CounterStyle::GetSpokenCounterText(CounterValue aOrdinal,
                                        WritingMode aWritingMode,
                                        nsAString& aResult, bool& aIsBullet) {
  bool isRTL;  // we don't care about direction for spoken text
  aIsBullet = false;
  switch (GetSpeakAs()) {
    case SpeakAs::Bullets:
      aResult.Assign(kDiscCharacter);
      aIsBullet = true;
      break;
    case SpeakAs::Numbers:
      DecimalToText(aOrdinal, aResult);
      break;
    case SpeakAs::Spellout:
      // we currently do not actually support 'spell-out',
      // so 'words' is used instead.
    case SpeakAs::Words:
      GetCounterText(aOrdinal, WritingMode(), aResult, isRTL);
      break;
    case SpeakAs::Other:
      // This should be processed by CustomCounterStyle
      MOZ_ASSERT_UNREACHABLE("Invalid speak-as value");
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unknown speak-as value");
      break;
  }
}

/* virtual */
void CounterStyle::CallFallbackStyle(CounterValue aOrdinal,
                                     WritingMode aWritingMode,
                                     nsAString& aResult, bool& aIsRTL) {
  GetFallback()->GetCounterText(aOrdinal, aWritingMode, aResult, aIsRTL);
}

CounterStyleManager::CounterStyleManager(nsPresContext* aPresContext)
    : mPresContext(aPresContext) {
  // Insert the static styles into cache table
  mStyles.InsertOrUpdate(nsGkAtoms::none, GetNoneStyle());
  mStyles.InsertOrUpdate(nsGkAtoms::decimal, GetDecimalStyle());
  mStyles.InsertOrUpdate(nsGkAtoms::disc, GetDiscStyle());
}

CounterStyleManager::~CounterStyleManager() {
  MOZ_ASSERT(!mPresContext, "Disconnect should have been called");
}

void CounterStyleManager::DestroyCounterStyle(CounterStyle* aCounterStyle) {
  if (aCounterStyle->IsCustomStyle()) {
    MOZ_ASSERT(!aCounterStyle->AsAnonymous(),
               "Anonymous counter styles "
               "are not managed by CounterStyleManager");
    static_cast<CustomCounterStyle*>(aCounterStyle)->Destroy();
  } else if (aCounterStyle->IsDependentStyle()) {
    static_cast<DependentBuiltinCounterStyle*>(aCounterStyle)->Destroy();
  } else {
    MOZ_ASSERT_UNREACHABLE("Builtin counter styles should not be destroyed");
  }
}

void CounterStyleManager::Disconnect() {
  CleanRetiredStyles();
  for (CounterStyle* style : mStyles.Values()) {
    if (style->IsDependentStyle()) {
      DestroyCounterStyle(style);
    }
  }
  mStyles.Clear();
  mPresContext = nullptr;
}

CounterStyle* CounterStyleManager::ResolveCounterStyle(nsAtom* aName) {
  MOZ_ASSERT(NS_IsMainThread());
  CounterStyle* data = GetCounterStyle(aName);
  if (data) {
    return data;
  }

  // Names are compared case-sensitively here. Predefined names should
  // have been lowercased by the parser.
  ServoStyleSet* styleSet = mPresContext->StyleSet();
  auto* rule = styleSet->CounterStyleRuleForName(aName);
  if (rule) {
    MOZ_ASSERT(Servo_CounterStyleRule_GetName(rule) == aName);
    data = new (mPresContext) CustomCounterStyle(this, rule);
  } else {
    for (const BuiltinCounterStyle& item : gBuiltinStyleTable) {
      if (item.GetStyleName() == aName) {
        const auto style = item.GetStyle();
        data = item.IsDependentStyle()
                   ? new (mPresContext)
                         DependentBuiltinCounterStyle(style, this)
                   : GetBuiltinStyle(style);
        break;
      }
    }
  }
  if (!data) {
    data = GetDecimalStyle();
  }
  mStyles.InsertOrUpdate(aName, data);
  return data;
}

/* static */
CounterStyle* CounterStyleManager::GetBuiltinStyle(ListStyle aStyle) {
  MOZ_ASSERT(size_t(aStyle) < std::size(gBuiltinStyleTable),
             "Require a valid builtin style constant");
  MOZ_ASSERT(!gBuiltinStyleTable[size_t(aStyle)].IsDependentStyle(),
             "Cannot get dependent builtin style");
  // No method of BuiltinCounterStyle mutates the struct itself, so it
  // should be fine to cast const away.
  return const_cast<BuiltinCounterStyle*>(&gBuiltinStyleTable[size_t(aStyle)]);
}

bool CounterStyleManager::NotifyRuleChanged() {
  bool changed = false;
  for (auto iter = mStyles.Iter(); !iter.Done(); iter.Next()) {
    CounterStyle* style = iter.Data();
    bool toBeUpdated = false;
    bool toBeRemoved = false;
    ServoStyleSet* styleSet = mPresContext->StyleSet();
    auto* newRule = styleSet->CounterStyleRuleForName(iter.Key());
    if (!newRule) {
      if (style->IsCustomStyle()) {
        toBeRemoved = true;
      }
    } else {
      if (!style->IsCustomStyle()) {
        toBeRemoved = true;
      } else {
        auto custom = static_cast<CustomCounterStyle*>(style);
        if (custom->GetRule() != newRule) {
          toBeRemoved = true;
        } else {
          auto generation = Servo_CounterStyleRule_GetGeneration(newRule);
          if (custom->GetRuleGeneration() != generation) {
            toBeUpdated = true;
            custom->ResetCachedData();
          }
        }
      }
    }
    changed = changed || toBeUpdated || toBeRemoved;
    if (toBeRemoved) {
      if (style->IsDependentStyle()) {
        // Add object to retired list so we can clean them up later.
        mRetiredStyles.AppendElement(style);
      }
      iter.Remove();
    }
  }

  if (changed) {
    for (CounterStyle* style : mStyles.Values()) {
      if (style->IsCustomStyle()) {
        CustomCounterStyle* custom = static_cast<CustomCounterStyle*>(style);
        custom->ResetDependentData();
      }
      // There is no dependent data cached in DependentBuiltinCounterStyle
      // instances, so we don't need to reset their data.
    }
  }
  return changed;
}

void CounterStyleManager::CleanRetiredStyles() {
  nsTArray<CounterStyle*> list(std::move(mRetiredStyles));
  for (CounterStyle* style : list) {
    DestroyCounterStyle(style);
  }
}

}  // namespace mozilla

Messung V0.5
C=89 H=95 G=91

¤ Dauer der Verarbeitung: 0.27 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.