tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NumberFormatterSkeleton.cpp (15827B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 #include "NumberFormatterSkeleton.h"
      5 #include "NumberFormat.h"
      6 
      7 #include "MeasureUnitGenerated.h"
      8 
      9 #include "mozilla/RangedPtr.h"
     10 
     11 #include <algorithm>
     12 #include <limits>
     13 
     14 #include "unicode/unumberrangeformatter.h"
     15 
     16 namespace mozilla::intl {
     17 
     18 NumberFormatterSkeleton::NumberFormatterSkeleton(
     19    const NumberFormatOptions& options) {
     20  if (options.mCurrency.isSome()) {
     21    if (!currency(options.mCurrency->first) ||
     22        !currencyDisplay(options.mCurrency->second)) {
     23      return;
     24    }
     25  } else if (options.mUnit.isSome()) {
     26    if (!unit(options.mUnit->first) || !unitDisplay(options.mUnit->second)) {
     27      return;
     28    }
     29  } else if (options.mPercent) {
     30    if (!percent()) {
     31      return;
     32    }
     33  }
     34 
     35  if (options.mRoundingIncrement != 1) {
     36    auto fd = options.mFractionDigits.valueOr(std::pair{0, 0});
     37    if (!roundingIncrement(options.mRoundingIncrement, fd.first, fd.second,
     38                           options.mStripTrailingZero)) {
     39      return;
     40    }
     41  } else if (options.mRoundingPriority ==
     42             NumberFormatOptions::RoundingPriority::Auto) {
     43    if (options.mFractionDigits.isSome()) {
     44      if (!fractionDigits(options.mFractionDigits->first,
     45                          options.mFractionDigits->second,
     46                          options.mStripTrailingZero)) {
     47        return;
     48      }
     49    }
     50 
     51    if (options.mSignificantDigits.isSome()) {
     52      if (!significantDigits(options.mSignificantDigits->first,
     53                             options.mSignificantDigits->second,
     54                             options.mStripTrailingZero)) {
     55        return;
     56      }
     57    }
     58  } else {
     59    MOZ_ASSERT(options.mFractionDigits);
     60    MOZ_ASSERT(options.mSignificantDigits);
     61 
     62    bool relaxed = options.mRoundingPriority ==
     63                   NumberFormatOptions::RoundingPriority::MorePrecision;
     64    if (!fractionWithSignificantDigits(options.mFractionDigits->first,
     65                                       options.mFractionDigits->second,
     66                                       options.mSignificantDigits->first,
     67                                       options.mSignificantDigits->second,
     68                                       relaxed, options.mStripTrailingZero)) {
     69      return;
     70    }
     71  }
     72 
     73  if (options.mMinIntegerDigits.isSome()) {
     74    if (!minIntegerDigits(*options.mMinIntegerDigits)) {
     75      return;
     76    }
     77  }
     78 
     79  if (!grouping(options.mGrouping)) {
     80    return;
     81  }
     82 
     83  if (!notation(options.mNotation)) {
     84    return;
     85  }
     86 
     87  if (!signDisplay(options.mSignDisplay)) {
     88    return;
     89  }
     90 
     91  if (!roundingMode(options.mRoundingMode)) {
     92    return;
     93  }
     94 
     95  mValidSkeleton = true;
     96 }
     97 
     98 bool NumberFormatterSkeleton::currency(std::string_view currency) {
     99  MOZ_ASSERT(currency.size() == 3,
    100             "IsWellFormedCurrencyCode permits only length-3 strings");
    101 
    102  char16_t currencyChars[] = {static_cast<char16_t>(currency[0]),
    103                              static_cast<char16_t>(currency[1]),
    104                              static_cast<char16_t>(currency[2]), '\0'};
    105  return append(u"currency/") && append(currencyChars) && append(' ');
    106 }
    107 
    108 bool NumberFormatterSkeleton::currencyDisplay(
    109    NumberFormatOptions::CurrencyDisplay display) {
    110  switch (display) {
    111    case NumberFormatOptions::CurrencyDisplay::Code:
    112      return appendToken(u"unit-width-iso-code");
    113    case NumberFormatOptions::CurrencyDisplay::Name:
    114      return appendToken(u"unit-width-full-name");
    115    case NumberFormatOptions::CurrencyDisplay::Symbol:
    116      // Default, no additional tokens needed.
    117      return true;
    118    case NumberFormatOptions::CurrencyDisplay::NarrowSymbol:
    119      return appendToken(u"unit-width-narrow");
    120  }
    121  MOZ_ASSERT_UNREACHABLE("unexpected currency display type");
    122  return false;
    123 }
    124 
    125 static const SimpleMeasureUnit& FindSimpleMeasureUnit(std::string_view name) {
    126  const auto* measureUnit = std::lower_bound(
    127      std::begin(simpleMeasureUnits), std::end(simpleMeasureUnits), name,
    128      [](const auto& measureUnit, std::string_view name) {
    129        return name.compare(measureUnit.name) > 0;
    130      });
    131  MOZ_ASSERT(measureUnit != std::end(simpleMeasureUnits),
    132             "unexpected unit identifier: unit not found");
    133  MOZ_ASSERT(measureUnit->name == name,
    134             "unexpected unit identifier: wrong unit found");
    135  return *measureUnit;
    136 }
    137 
    138 static constexpr size_t MaxUnitLength() {
    139  size_t length = 0;
    140  for (const auto& unit : simpleMeasureUnits) {
    141    length = std::max(length, std::char_traits<char>::length(unit.name));
    142  }
    143  return length * 2 + std::char_traits<char>::length("-per-");
    144 }
    145 
    146 bool NumberFormatterSkeleton::unit(std::string_view unit) {
    147  MOZ_RELEASE_ASSERT(unit.length() <= MaxUnitLength());
    148 
    149  auto appendUnit = [this](const SimpleMeasureUnit& unit) {
    150    return append(unit.type, strlen(unit.type)) && append('-') &&
    151           append(unit.name, strlen(unit.name));
    152  };
    153 
    154  // |unit| can be a compound unit identifier, separated by "-per-".
    155  static constexpr char separator[] = "-per-";
    156  size_t separator_len = strlen(separator);
    157  size_t offset = unit.find(separator);
    158  if (offset != std::string_view::npos) {
    159    const auto& numerator = FindSimpleMeasureUnit(unit.substr(0, offset));
    160    const auto& denominator = FindSimpleMeasureUnit(
    161        std::string_view(unit.data() + offset + separator_len,
    162                         unit.length() - offset - separator_len));
    163    return append(u"measure-unit/") && appendUnit(numerator) && append(' ') &&
    164           append(u"per-measure-unit/") && appendUnit(denominator) &&
    165           append(' ');
    166  }
    167 
    168  const auto& simple = FindSimpleMeasureUnit(unit);
    169  return append(u"measure-unit/") && appendUnit(simple) && append(' ');
    170 }
    171 
    172 bool NumberFormatterSkeleton::unitDisplay(
    173    NumberFormatOptions::UnitDisplay display) {
    174  switch (display) {
    175    case NumberFormatOptions::UnitDisplay::Short:
    176      return appendToken(u"unit-width-short");
    177    case NumberFormatOptions::UnitDisplay::Narrow:
    178      return appendToken(u"unit-width-narrow");
    179    case NumberFormatOptions::UnitDisplay::Long:
    180      return appendToken(u"unit-width-full-name");
    181  }
    182  MOZ_ASSERT_UNREACHABLE("unexpected unit display type");
    183  return false;
    184 }
    185 
    186 bool NumberFormatterSkeleton::percent() {
    187  return appendToken(u"percent scale/100");
    188 }
    189 
    190 bool NumberFormatterSkeleton::fractionDigits(uint32_t min, uint32_t max,
    191                                             bool stripTrailingZero) {
    192  // Note: |min| can be zero here.
    193  MOZ_ASSERT(min <= max);
    194  if (!append('.') || !appendN('0', min) || !appendN('#', max - min)) {
    195    return false;
    196  }
    197  if (stripTrailingZero) {
    198    if (!append(u"/w")) {
    199      return false;
    200    }
    201  }
    202  return append(' ');
    203 }
    204 
    205 bool NumberFormatterSkeleton::fractionWithSignificantDigits(
    206    uint32_t mnfd, uint32_t mxfd, uint32_t mnsd, uint32_t mxsd, bool relaxed,
    207    bool stripTrailingZero) {
    208  // Note: |mnfd| can be zero here.
    209  MOZ_ASSERT(mnfd <= mxfd);
    210  MOZ_ASSERT(mnsd > 0);
    211  MOZ_ASSERT(mnsd <= mxsd);
    212 
    213  if (!append('.') || !appendN('0', mnfd) || !appendN('#', mxfd - mnfd)) {
    214    return false;
    215  }
    216  if (!append('/') || !appendN('@', mnsd) || !appendN('#', mxsd - mnsd)) {
    217    return false;
    218  }
    219  if (!append(relaxed ? 'r' : 's')) {
    220    return false;
    221  }
    222  if (stripTrailingZero) {
    223    if (!append(u"/w")) {
    224      return false;
    225    }
    226  }
    227  return append(' ');
    228 }
    229 
    230 bool NumberFormatterSkeleton::minIntegerDigits(uint32_t min) {
    231  MOZ_ASSERT(min > 0);
    232  return append(u"integer-width/+") && appendN('0', min) && append(' ');
    233 }
    234 
    235 bool NumberFormatterSkeleton::significantDigits(uint32_t min, uint32_t max,
    236                                                bool stripTrailingZero) {
    237  MOZ_ASSERT(min > 0);
    238  MOZ_ASSERT(min <= max);
    239  if (!appendN('@', min) || !appendN('#', max - min)) {
    240    return false;
    241  }
    242  if (stripTrailingZero) {
    243    if (!append(u"/w")) {
    244      return false;
    245    }
    246  }
    247  return append(' ');
    248 }
    249 
    250 bool NumberFormatterSkeleton::grouping(NumberFormatOptions::Grouping grouping) {
    251  switch (grouping) {
    252    case NumberFormatOptions::Grouping::Auto:
    253      // Default, no additional tokens needed.
    254      return true;
    255    case NumberFormatOptions::Grouping::Always:
    256      return appendToken(u"group-on-aligned");
    257    case NumberFormatOptions::Grouping::Min2:
    258      return appendToken(u"group-min2");
    259    case NumberFormatOptions::Grouping::Never:
    260      return appendToken(u"group-off");
    261  }
    262  MOZ_ASSERT_UNREACHABLE("unexpected grouping mode");
    263  return false;
    264 }
    265 
    266 bool NumberFormatterSkeleton::notation(NumberFormatOptions::Notation style) {
    267  switch (style) {
    268    case NumberFormatOptions::Notation::Standard:
    269      // Default, no additional tokens needed.
    270      return true;
    271    case NumberFormatOptions::Notation::Scientific:
    272      return appendToken(u"scientific");
    273    case NumberFormatOptions::Notation::Engineering:
    274      return appendToken(u"engineering");
    275    case NumberFormatOptions::Notation::CompactShort:
    276      return appendToken(u"compact-short");
    277    case NumberFormatOptions::Notation::CompactLong:
    278      return appendToken(u"compact-long");
    279  }
    280  MOZ_ASSERT_UNREACHABLE("unexpected notation style");
    281  return false;
    282 }
    283 
    284 bool NumberFormatterSkeleton::signDisplay(
    285    NumberFormatOptions::SignDisplay display) {
    286  switch (display) {
    287    case NumberFormatOptions::SignDisplay::Auto:
    288      // Default, no additional tokens needed.
    289      return true;
    290    case NumberFormatOptions::SignDisplay::Always:
    291      return appendToken(u"sign-always");
    292    case NumberFormatOptions::SignDisplay::Never:
    293      return appendToken(u"sign-never");
    294    case NumberFormatOptions::SignDisplay::ExceptZero:
    295      return appendToken(u"sign-except-zero");
    296    case NumberFormatOptions::SignDisplay::Negative:
    297      return appendToken(u"sign-negative");
    298    case NumberFormatOptions::SignDisplay::Accounting:
    299      return appendToken(u"sign-accounting");
    300    case NumberFormatOptions::SignDisplay::AccountingAlways:
    301      return appendToken(u"sign-accounting-always");
    302    case NumberFormatOptions::SignDisplay::AccountingExceptZero:
    303      return appendToken(u"sign-accounting-except-zero");
    304    case NumberFormatOptions::SignDisplay::AccountingNegative:
    305      return appendToken(u"sign-accounting-negative");
    306  }
    307  MOZ_ASSERT_UNREACHABLE("unexpected sign display type");
    308  return false;
    309 }
    310 
    311 bool NumberFormatterSkeleton::roundingIncrement(uint32_t increment,
    312                                                uint32_t mnfd, uint32_t mxfd,
    313                                                bool stripTrailingZero) {
    314  // Note: |mnfd| can be zero here.
    315  MOZ_ASSERT(mnfd <= mxfd);
    316  MOZ_ASSERT(increment > 1);
    317 
    318  // Limit |mxfd| to 100.
    319  constexpr size_t maxFracDigits = 100;
    320  MOZ_RELEASE_ASSERT(mxfd <= maxFracDigits);
    321 
    322  static constexpr char digits[] = "0123456789";
    323 
    324  // We need enough space to print any uint32_t, which is possibly shifted by
    325  // |mxfd| decimal places. And additionally we need to reserve space for "0.".
    326  static_assert(std::numeric_limits<uint32_t>::digits10 + 1 < maxFracDigits);
    327  constexpr size_t maxLength = maxFracDigits + 2;
    328 
    329  char chars[maxLength];
    330  RangedPtr<char> ptr(chars + maxLength, chars, maxLength);
    331  const RangedPtr<char> end = ptr;
    332 
    333  // Convert to a signed integer, so we don't have to worry about underflows.
    334  int32_t maxFrac = int32_t(mxfd);
    335 
    336  // Write |increment| from back to front.
    337  while (increment != 0) {
    338    *--ptr = digits[increment % 10];
    339    increment /= 10;
    340    maxFrac -= 1;
    341 
    342    if (maxFrac == 0) {
    343      *--ptr = '.';
    344    }
    345  }
    346 
    347  // Write any remaining zeros from |mxfd| and prepend '0' if we last wrote the
    348  // decimal point.
    349  while (maxFrac >= 0) {
    350    MOZ_ASSERT_IF(maxFrac == 0, *ptr == '.');
    351 
    352    *--ptr = '0';
    353    maxFrac -= 1;
    354 
    355    if (maxFrac == 0) {
    356      *--ptr = '.';
    357    }
    358  }
    359 
    360  MOZ_ASSERT(ptr < end, "At least one character is written.");
    361  MOZ_ASSERT(*ptr != '.', "First character is a digit.");
    362 
    363  if (!append(u"precision-increment/") || !append(ptr.get(), end - ptr)) {
    364    return false;
    365  }
    366  if (stripTrailingZero) {
    367    if (!append(u"/w")) {
    368      return false;
    369    }
    370  }
    371  return append(' ');
    372 }
    373 
    374 bool NumberFormatterSkeleton::roundingMode(
    375    NumberFormatOptions::RoundingMode rounding) {
    376  switch (rounding) {
    377    case NumberFormatOptions::RoundingMode::Ceil:
    378      return appendToken(u"rounding-mode-ceiling");
    379    case NumberFormatOptions::RoundingMode::Floor:
    380      return appendToken(u"rounding-mode-floor");
    381    case NumberFormatOptions::RoundingMode::Expand:
    382      return appendToken(u"rounding-mode-up");
    383    case NumberFormatOptions::RoundingMode::Trunc:
    384      return appendToken(u"rounding-mode-down");
    385    case NumberFormatOptions::RoundingMode::HalfCeil:
    386      return appendToken(u"rounding-mode-half-ceiling");
    387    case NumberFormatOptions::RoundingMode::HalfFloor:
    388      return appendToken(u"rounding-mode-half-floor");
    389    case NumberFormatOptions::RoundingMode::HalfExpand:
    390      return appendToken(u"rounding-mode-half-up");
    391    case NumberFormatOptions::RoundingMode::HalfTrunc:
    392      return appendToken(u"rounding-mode-half-down");
    393    case NumberFormatOptions::RoundingMode::HalfEven:
    394      return appendToken(u"rounding-mode-half-even");
    395    case NumberFormatOptions::RoundingMode::HalfOdd:
    396      return appendToken(u"rounding-mode-half-odd");
    397  }
    398  MOZ_ASSERT_UNREACHABLE("unexpected rounding mode");
    399  return false;
    400 }
    401 
    402 UNumberFormatter* NumberFormatterSkeleton::toFormatter(
    403    std::string_view locale) {
    404  if (!mValidSkeleton) {
    405    return nullptr;
    406  }
    407 
    408  UErrorCode status = U_ZERO_ERROR;
    409  UNumberFormatter* nf = unumf_openForSkeletonAndLocale(
    410      mVector.begin(), mVector.length(), AssertNullTerminatedString(locale),
    411      &status);
    412  if (U_FAILURE(status)) {
    413    return nullptr;
    414  }
    415  return nf;
    416 }
    417 
    418 static UNumberRangeCollapse ToUNumberRangeCollapse(
    419    NumberRangeFormatOptions::RangeCollapse collapse) {
    420  using RangeCollapse = NumberRangeFormatOptions::RangeCollapse;
    421  switch (collapse) {
    422    case RangeCollapse::Auto:
    423      return UNUM_RANGE_COLLAPSE_AUTO;
    424    case RangeCollapse::None:
    425      return UNUM_RANGE_COLLAPSE_NONE;
    426    case RangeCollapse::Unit:
    427      return UNUM_RANGE_COLLAPSE_UNIT;
    428    case RangeCollapse::All:
    429      return UNUM_RANGE_COLLAPSE_ALL;
    430  }
    431  MOZ_ASSERT_UNREACHABLE("unexpected range collapse");
    432  return UNUM_RANGE_COLLAPSE_NONE;
    433 }
    434 
    435 static UNumberRangeIdentityFallback ToUNumberRangeIdentityFallback(
    436    NumberRangeFormatOptions::RangeIdentityFallback identity) {
    437  using RangeIdentityFallback = NumberRangeFormatOptions::RangeIdentityFallback;
    438  switch (identity) {
    439    case RangeIdentityFallback::SingleValue:
    440      return UNUM_IDENTITY_FALLBACK_SINGLE_VALUE;
    441    case RangeIdentityFallback::ApproximatelyOrSingleValue:
    442      return UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE;
    443    case RangeIdentityFallback::Approximately:
    444      return UNUM_IDENTITY_FALLBACK_APPROXIMATELY;
    445    case RangeIdentityFallback::Range:
    446      return UNUM_IDENTITY_FALLBACK_RANGE;
    447  }
    448  MOZ_ASSERT_UNREACHABLE("unexpected range identity fallback");
    449  return UNUM_IDENTITY_FALLBACK_RANGE;
    450 }
    451 
    452 UNumberRangeFormatter* NumberFormatterSkeleton::toRangeFormatter(
    453    std::string_view locale, NumberRangeFormatOptions::RangeCollapse collapse,
    454    NumberRangeFormatOptions::RangeIdentityFallback identity) {
    455  if (!mValidSkeleton) {
    456    return nullptr;
    457  }
    458 
    459  UParseError* perror = nullptr;
    460  UErrorCode status = U_ZERO_ERROR;
    461  UNumberRangeFormatter* nrf =
    462      unumrf_openForSkeletonWithCollapseAndIdentityFallback(
    463          mVector.begin(), mVector.length(), ToUNumberRangeCollapse(collapse),
    464          ToUNumberRangeIdentityFallback(identity),
    465          AssertNullTerminatedString(locale), perror, &status);
    466  if (U_FAILURE(status)) {
    467    return nullptr;
    468  }
    469  return nrf;
    470 }
    471 
    472 }  // namespace mozilla::intl