diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /intl/components/src/NumberFormatterSkeleton.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'intl/components/src/NumberFormatterSkeleton.cpp')
-rw-r--r-- | intl/components/src/NumberFormatterSkeleton.cpp | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/intl/components/src/NumberFormatterSkeleton.cpp b/intl/components/src/NumberFormatterSkeleton.cpp new file mode 100644 index 0000000000..5f62d77c2b --- /dev/null +++ b/intl/components/src/NumberFormatterSkeleton.cpp @@ -0,0 +1,473 @@ +/* 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 "NumberFormatterSkeleton.h" +#include "NumberFormat.h" + +#include "MeasureUnitGenerated.h" + +#include "mozilla/RangedPtr.h" + +#include <algorithm> +#include <limits> + +#include "unicode/unumberrangeformatter.h" + +namespace mozilla::intl { + +NumberFormatterSkeleton::NumberFormatterSkeleton( + const NumberFormatOptions& options) { + if (options.mCurrency.isSome()) { + if (!currency(options.mCurrency->first) || + !currencyDisplay(options.mCurrency->second)) { + return; + } + } else if (options.mUnit.isSome()) { + if (!unit(options.mUnit->first) || !unitDisplay(options.mUnit->second)) { + return; + } + } else if (options.mPercent) { + if (!percent()) { + return; + } + } + + if (options.mRoundingIncrement != 1) { + auto fd = options.mFractionDigits.valueOr(std::pair{0, 0}); + if (!roundingIncrement(options.mRoundingIncrement, fd.first, fd.second, + options.mStripTrailingZero)) { + return; + } + } else if (options.mRoundingPriority == + NumberFormatOptions::RoundingPriority::Auto) { + if (options.mFractionDigits.isSome()) { + if (!fractionDigits(options.mFractionDigits->first, + options.mFractionDigits->second, + options.mStripTrailingZero)) { + return; + } + } + + if (options.mSignificantDigits.isSome()) { + if (!significantDigits(options.mSignificantDigits->first, + options.mSignificantDigits->second, + options.mStripTrailingZero)) { + return; + } + } + } else { + MOZ_ASSERT(options.mFractionDigits); + MOZ_ASSERT(options.mSignificantDigits); + + bool relaxed = options.mRoundingPriority == + NumberFormatOptions::RoundingPriority::MorePrecision; + if (!fractionWithSignificantDigits(options.mFractionDigits->first, + options.mFractionDigits->second, + options.mSignificantDigits->first, + options.mSignificantDigits->second, + relaxed, options.mStripTrailingZero)) { + return; + } + } + + if (options.mMinIntegerDigits.isSome()) { + if (!minIntegerDigits(*options.mMinIntegerDigits)) { + return; + } + } + + if (!grouping(options.mGrouping)) { + return; + } + + if (!notation(options.mNotation)) { + return; + } + + if (!signDisplay(options.mSignDisplay)) { + return; + } + + if (!roundingMode(options.mRoundingMode)) { + return; + } + + mValidSkeleton = true; +} + +bool NumberFormatterSkeleton::currency(std::string_view currency) { + MOZ_ASSERT(currency.size() == 3, + "IsWellFormedCurrencyCode permits only length-3 strings"); + + char16_t currencyChars[] = {static_cast<char16_t>(currency[0]), + static_cast<char16_t>(currency[1]), + static_cast<char16_t>(currency[2]), '\0'}; + return append(u"currency/") && append(currencyChars) && append(' '); +} + +bool NumberFormatterSkeleton::currencyDisplay( + NumberFormatOptions::CurrencyDisplay display) { + switch (display) { + case NumberFormatOptions::CurrencyDisplay::Code: + return appendToken(u"unit-width-iso-code"); + case NumberFormatOptions::CurrencyDisplay::Name: + return appendToken(u"unit-width-full-name"); + case NumberFormatOptions::CurrencyDisplay::Symbol: + // Default, no additional tokens needed. + return true; + case NumberFormatOptions::CurrencyDisplay::NarrowSymbol: + return appendToken(u"unit-width-narrow"); + } + MOZ_ASSERT_UNREACHABLE("unexpected currency display type"); + return false; +} + +static const SimpleMeasureUnit& FindSimpleMeasureUnit(std::string_view name) { + const auto* measureUnit = std::lower_bound( + std::begin(simpleMeasureUnits), std::end(simpleMeasureUnits), name, + [](const auto& measureUnit, std::string_view name) { + return name.compare(measureUnit.name) > 0; + }); + MOZ_ASSERT(measureUnit != std::end(simpleMeasureUnits), + "unexpected unit identifier: unit not found"); + MOZ_ASSERT(measureUnit->name == name, + "unexpected unit identifier: wrong unit found"); + return *measureUnit; +} + +static constexpr size_t MaxUnitLength() { + size_t length = 0; + for (const auto& unit : simpleMeasureUnits) { + length = std::max(length, std::char_traits<char>::length(unit.name)); + } + return length * 2 + std::char_traits<char>::length("-per-"); +} + +bool NumberFormatterSkeleton::unit(std::string_view unit) { + MOZ_RELEASE_ASSERT(unit.length() <= MaxUnitLength()); + + auto appendUnit = [this](const SimpleMeasureUnit& unit) { + return append(unit.type, strlen(unit.type)) && append('-') && + append(unit.name, strlen(unit.name)); + }; + + // |unit| can be a compound unit identifier, separated by "-per-". + static constexpr char separator[] = "-per-"; + size_t separator_len = strlen(separator); + size_t offset = unit.find(separator); + if (offset != std::string_view::npos) { + const auto& numerator = FindSimpleMeasureUnit(unit.substr(0, offset)); + const auto& denominator = FindSimpleMeasureUnit( + std::string_view(unit.data() + offset + separator_len, + unit.length() - offset - separator_len)); + return append(u"measure-unit/") && appendUnit(numerator) && append(' ') && + append(u"per-measure-unit/") && appendUnit(denominator) && + append(' '); + } + + const auto& simple = FindSimpleMeasureUnit(unit); + return append(u"measure-unit/") && appendUnit(simple) && append(' '); +} + +bool NumberFormatterSkeleton::unitDisplay( + NumberFormatOptions::UnitDisplay display) { + switch (display) { + case NumberFormatOptions::UnitDisplay::Short: + return appendToken(u"unit-width-short"); + case NumberFormatOptions::UnitDisplay::Narrow: + return appendToken(u"unit-width-narrow"); + case NumberFormatOptions::UnitDisplay::Long: + return appendToken(u"unit-width-full-name"); + } + MOZ_ASSERT_UNREACHABLE("unexpected unit display type"); + return false; +} + +bool NumberFormatterSkeleton::percent() { + return appendToken(u"percent scale/100"); +} + +bool NumberFormatterSkeleton::fractionDigits(uint32_t min, uint32_t max, + bool stripTrailingZero) { + // Note: |min| can be zero here. + MOZ_ASSERT(min <= max); + if (!append('.') || !appendN('0', min) || !appendN('#', max - min)) { + return false; + } + if (stripTrailingZero) { + if (!append(u"/w")) { + return false; + } + } + return append(' '); +} + +bool NumberFormatterSkeleton::fractionWithSignificantDigits( + uint32_t mnfd, uint32_t mxfd, uint32_t mnsd, uint32_t mxsd, bool relaxed, + bool stripTrailingZero) { + // Note: |mnfd| can be zero here. + MOZ_ASSERT(mnfd <= mxfd); + MOZ_ASSERT(mnsd > 0); + MOZ_ASSERT(mnsd <= mxsd); + + if (!append('.') || !appendN('0', mnfd) || !appendN('#', mxfd - mnfd)) { + return false; + } + if (!append('/') || !appendN('@', mnsd) || !appendN('#', mxsd - mnsd)) { + return false; + } + if (!append(relaxed ? 'r' : 's')) { + return false; + } + if (stripTrailingZero) { + if (!append(u"/w")) { + return false; + } + } + return append(' '); +} + +bool NumberFormatterSkeleton::minIntegerDigits(uint32_t min) { + MOZ_ASSERT(min > 0); + return append(u"integer-width/+") && appendN('0', min) && append(' '); +} + +bool NumberFormatterSkeleton::significantDigits(uint32_t min, uint32_t max, + bool stripTrailingZero) { + MOZ_ASSERT(min > 0); + MOZ_ASSERT(min <= max); + if (!appendN('@', min) || !appendN('#', max - min)) { + return false; + } + if (stripTrailingZero) { + if (!append(u"/w")) { + return false; + } + } + return append(' '); +} + +bool NumberFormatterSkeleton::grouping(NumberFormatOptions::Grouping grouping) { + switch (grouping) { + case NumberFormatOptions::Grouping::Auto: + // Default, no additional tokens needed. + return true; + case NumberFormatOptions::Grouping::Always: + return appendToken(u"group-on-aligned"); + case NumberFormatOptions::Grouping::Min2: + return appendToken(u"group-min2"); + case NumberFormatOptions::Grouping::Never: + return appendToken(u"group-off"); + } + MOZ_ASSERT_UNREACHABLE("unexpected grouping mode"); + return false; +} + +bool NumberFormatterSkeleton::notation(NumberFormatOptions::Notation style) { + switch (style) { + case NumberFormatOptions::Notation::Standard: + // Default, no additional tokens needed. + return true; + case NumberFormatOptions::Notation::Scientific: + return appendToken(u"scientific"); + case NumberFormatOptions::Notation::Engineering: + return appendToken(u"engineering"); + case NumberFormatOptions::Notation::CompactShort: + return appendToken(u"compact-short"); + case NumberFormatOptions::Notation::CompactLong: + return appendToken(u"compact-long"); + } + MOZ_ASSERT_UNREACHABLE("unexpected notation style"); + return false; +} + +bool NumberFormatterSkeleton::signDisplay( + NumberFormatOptions::SignDisplay display) { + switch (display) { + case NumberFormatOptions::SignDisplay::Auto: + // Default, no additional tokens needed. + return true; + case NumberFormatOptions::SignDisplay::Always: + return appendToken(u"sign-always"); + case NumberFormatOptions::SignDisplay::Never: + return appendToken(u"sign-never"); + case NumberFormatOptions::SignDisplay::ExceptZero: + return appendToken(u"sign-except-zero"); + case NumberFormatOptions::SignDisplay::Negative: + return appendToken(u"sign-negative"); + case NumberFormatOptions::SignDisplay::Accounting: + return appendToken(u"sign-accounting"); + case NumberFormatOptions::SignDisplay::AccountingAlways: + return appendToken(u"sign-accounting-always"); + case NumberFormatOptions::SignDisplay::AccountingExceptZero: + return appendToken(u"sign-accounting-except-zero"); + case NumberFormatOptions::SignDisplay::AccountingNegative: + return appendToken(u"sign-accounting-negative"); + } + MOZ_ASSERT_UNREACHABLE("unexpected sign display type"); + return false; +} + +bool NumberFormatterSkeleton::roundingIncrement(uint32_t increment, + uint32_t mnfd, uint32_t mxfd, + bool stripTrailingZero) { + // Note: |mnfd| can be zero here. + MOZ_ASSERT(mnfd <= mxfd); + MOZ_ASSERT(increment > 1); + + // Limit |mxfd| to 100. (20 is the current limit for ECMA-402, but there are + // plans to change it to 100.) + constexpr size_t maxFracDigits = 100; + MOZ_RELEASE_ASSERT(mxfd <= maxFracDigits); + + static constexpr char digits[] = "0123456789"; + + // We need enough space to print any uint32_t, which is possibly shifted by + // |mxfd| decimal places. And additionally we need to reserve space for "0.". + static_assert(std::numeric_limits<uint32_t>::digits10 + 1 < maxFracDigits); + constexpr size_t maxLength = maxFracDigits + 2; + + char chars[maxLength]; + RangedPtr<char> ptr(chars + maxLength, chars, maxLength); + const RangedPtr<char> end = ptr; + + // Convert to a signed integer, so we don't have to worry about underflows. + int32_t maxFrac = int32_t(mxfd); + + // Write |increment| from back to front. + while (increment != 0) { + *--ptr = digits[increment % 10]; + increment /= 10; + maxFrac -= 1; + + if (maxFrac == 0) { + *--ptr = '.'; + } + } + + // Write any remaining zeros from |mxfd| and prepend '0' if we last wrote the + // decimal point. + while (maxFrac >= 0) { + MOZ_ASSERT_IF(maxFrac == 0, *ptr == '.'); + + *--ptr = '0'; + maxFrac -= 1; + + if (maxFrac == 0) { + *--ptr = '.'; + } + } + + MOZ_ASSERT(ptr < end, "At least one character is written."); + MOZ_ASSERT(*ptr != '.', "First character is a digit."); + + if (!append(u"precision-increment/") || !append(ptr.get(), end - ptr)) { + return false; + } + if (stripTrailingZero) { + if (!append(u"/w")) { + return false; + } + } + return append(' '); +} + +bool NumberFormatterSkeleton::roundingMode( + NumberFormatOptions::RoundingMode rounding) { + switch (rounding) { + case NumberFormatOptions::RoundingMode::Ceil: + return appendToken(u"rounding-mode-ceiling"); + case NumberFormatOptions::RoundingMode::Floor: + return appendToken(u"rounding-mode-floor"); + case NumberFormatOptions::RoundingMode::Expand: + return appendToken(u"rounding-mode-up"); + case NumberFormatOptions::RoundingMode::Trunc: + return appendToken(u"rounding-mode-down"); + case NumberFormatOptions::RoundingMode::HalfCeil: + return appendToken(u"rounding-mode-half-ceiling"); + case NumberFormatOptions::RoundingMode::HalfFloor: + return appendToken(u"rounding-mode-half-floor"); + case NumberFormatOptions::RoundingMode::HalfExpand: + return appendToken(u"rounding-mode-half-up"); + case NumberFormatOptions::RoundingMode::HalfTrunc: + return appendToken(u"rounding-mode-half-down"); + case NumberFormatOptions::RoundingMode::HalfEven: + return appendToken(u"rounding-mode-half-even"); + case NumberFormatOptions::RoundingMode::HalfOdd: + return appendToken(u"rounding-mode-half-odd"); + } + MOZ_ASSERT_UNREACHABLE("unexpected rounding mode"); + return false; +} + +UNumberFormatter* NumberFormatterSkeleton::toFormatter( + std::string_view locale) { + if (!mValidSkeleton) { + return nullptr; + } + + UErrorCode status = U_ZERO_ERROR; + UNumberFormatter* nf = unumf_openForSkeletonAndLocale( + mVector.begin(), mVector.length(), AssertNullTerminatedString(locale), + &status); + if (U_FAILURE(status)) { + return nullptr; + } + return nf; +} + +static UNumberRangeCollapse ToUNumberRangeCollapse( + NumberRangeFormatOptions::RangeCollapse collapse) { + using RangeCollapse = NumberRangeFormatOptions::RangeCollapse; + switch (collapse) { + case RangeCollapse::Auto: + return UNUM_RANGE_COLLAPSE_AUTO; + case RangeCollapse::None: + return UNUM_RANGE_COLLAPSE_NONE; + case RangeCollapse::Unit: + return UNUM_RANGE_COLLAPSE_UNIT; + case RangeCollapse::All: + return UNUM_RANGE_COLLAPSE_ALL; + } + MOZ_ASSERT_UNREACHABLE("unexpected range collapse"); + return UNUM_RANGE_COLLAPSE_NONE; +} + +static UNumberRangeIdentityFallback ToUNumberRangeIdentityFallback( + NumberRangeFormatOptions::RangeIdentityFallback identity) { + using RangeIdentityFallback = NumberRangeFormatOptions::RangeIdentityFallback; + switch (identity) { + case RangeIdentityFallback::SingleValue: + return UNUM_IDENTITY_FALLBACK_SINGLE_VALUE; + case RangeIdentityFallback::ApproximatelyOrSingleValue: + return UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE; + case RangeIdentityFallback::Approximately: + return UNUM_IDENTITY_FALLBACK_APPROXIMATELY; + case RangeIdentityFallback::Range: + return UNUM_IDENTITY_FALLBACK_RANGE; + } + MOZ_ASSERT_UNREACHABLE("unexpected range identity fallback"); + return UNUM_IDENTITY_FALLBACK_RANGE; +} + +UNumberRangeFormatter* NumberFormatterSkeleton::toRangeFormatter( + std::string_view locale, NumberRangeFormatOptions::RangeCollapse collapse, + NumberRangeFormatOptions::RangeIdentityFallback identity) { + if (!mValidSkeleton) { + return nullptr; + } + + UParseError* perror = nullptr; + UErrorCode status = U_ZERO_ERROR; + UNumberRangeFormatter* nrf = + unumrf_openForSkeletonWithCollapseAndIdentityFallback( + mVector.begin(), mVector.length(), ToUNumberRangeCollapse(collapse), + ToUNumberRangeIdentityFallback(identity), + AssertNullTerminatedString(locale), perror, &status); + if (U_FAILURE(status)) { + return nullptr; + } + return nrf; +} + +} // namespace mozilla::intl |