CounterStyle.define constructor

CounterStyle.define({
  1. required String name,
  2. System system = System.symbolic,
  3. String negative = '-',
  4. String prefix = '',
  5. String suffix = '\u002E\u0020',
  6. IntRange? range,
  7. int padLength = 0,
  8. String padCharacter = '',
  9. String fallback = 'decimal',
  10. List<String> symbols = const [],
  11. Map<int, String> additiveSymbols = const {},
})

A simple way to define CounterStyle. Based off of systems defined at https://www.w3.org/TR/css-counter-styles-3/#counter-style-system

Implementation

factory CounterStyle.define({
  /// The name of the system. Not used internally, but could be used in a
  /// list of CounterStyle's to lookup a given style.
  required String name,

  /// The system type. See [System] for more details.
  System system = System.symbolic,

  /// The character to prepend to negative values.
  String negative = '-',
  // TODO add negativeSuffix

  /// A prefix to add when generating marker content
  String prefix = '',

  /// A suffix to add when generating marker content (Defaults to
  /// a full stop followed by a space: ". ").
  String suffix = '\u002E\u0020',

  /// The range of values this CounterStyle can accept. If a counter value is
  /// given outside of this range, then the CounterStyle will fall back on
  /// the CounterStyle defined by [fallback].
  ///
  /// If null, defaults to the given [System]'s range
  IntRange? range,

  /// The length each output must have at minimum, including negative symbols, but not
  /// including any suffix or prefix symbols.
  /// padLength must be greater than or equal to 0.
  int padLength = 0,

  /// The character with which to pad the output to reach the given padLength.
  /// If more than one character is given in [padCharacter], then the output
  /// will be longer than [padLength] (but never shorter).
  String padCharacter = '',

  /// The CounterStyle to fall back on if the given algorithm can't compute
  /// an output or is given out-of-range input.
  String fallback = 'decimal',

  /// The list of symbols used by this algorithm
  List<String> symbols = const [],

  /// A map of weights to symbols used by the additive algorithm
  Map<int, String> additiveSymbols = const {},

  //TODO speak-as descriptor (https://www.w3.org/TR/css-counter-styles-3/#counter-style-speak-as)
}) {
  assert(padLength >= 0);
  assert(symbols.isNotEmpty || additiveSymbols.isNotEmpty);

  range ??= system.range;

  algorithm(int count) {
    if (!range!.withinRange(count)) {
      return CounterStyleRegistry.lookup(fallback)._algorithm(count);
    }

    switch (system) {
      case System.cyclic:
        assert(symbols.isNotEmpty);
        return symbols[(count - 1) % symbols.length];
      case System.fixed:
        assert(symbols.isNotEmpty);

        //TODO this could potentially be defined by the user (see https://www.w3.org/TR/css-counter-styles-3/#fixed-system)
        int firstSymbolValue = 1;

        if (count >= firstSymbolValue &&
            count < firstSymbolValue + symbols.length) {
          return symbols[count - firstSymbolValue];
        } else {
          return CounterStyleRegistry.lookup(fallback)._algorithm(count);
        }
      case System.numeric:
        assert(symbols.length >= 2);
        int n = symbols.length;
        String result = '';

        int value = count;
        if (value == 0) {
          result = symbols[0];
        }

        while (value != 0) {
          result = '${symbols[value % n]}$result';
          value = value ~/ n;
        }

        return result;
      case System.alphabetic:
        assert(symbols.length >= 2);
        int n = symbols.length;
        String result = '';

        int value = count;
        while (value != 0) {
          value--;
          result = '${symbols[value % n]}$result';
          value = value ~/ n;
        }
        return result;
      case System.symbolic:
        int n = symbols.length;
        final representation = StringBuffer();
        for (int i = 0; i < ((count ~/ n) + 1); i++) {
          representation.write(symbols[(count - 1) % n]);
        }
        return representation.toString();
      case System.additive:
        assert(additiveSymbols.isNotEmpty);
        int value = count;
        final tuples = additiveSymbols.entries;

        if (value == 0) {
          if (additiveSymbols.containsKey(0)) {
            return additiveSymbols[0]!;
          }

          return CounterStyleRegistry.lookup(fallback)._algorithm(count);
        }

        final buffer = StringBuffer();
        for (var tuple in tuples) {
          final weight = tuple.key;
          final symbol = tuple.value;

          if (weight == 0 || weight > value) continue;

          final reps = value ~/ weight;
          for (int i = 0; i < reps; i++) {
            buffer.write(symbol);
          }
          value -= weight * reps;
          if (value == 0) {
            return buffer.toString();
          }
        }

        return CounterStyleRegistry.lookup(fallback)._algorithm(count);
    }
  }

  return CounterStyle._(
    name: name,
    algorithm: algorithm,
    negative: negative,
    prefix: prefix,
    suffix: suffix,
    range: range,
    padLength: padLength,
    padCharacter: padCharacter,
    usesNegative: system.usesNegative,
    fallbackStyle: fallback,
  );
}