NumberRangeFormat.h (7439B)
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 #ifndef intl_components_NumberRangeFormat_h_ 5 #define intl_components_NumberRangeFormat_h_ 6 7 #include "mozilla/FloatingPoint.h" 8 #include "mozilla/intl/ICUError.h" 9 #include "mozilla/intl/NumberFormat.h" 10 #include "mozilla/Result.h" 11 #include "mozilla/UniquePtr.h" 12 13 #include <stdint.h> 14 #include <string_view> 15 16 #include "unicode/utypes.h" 17 18 struct UFormattedNumberRange; 19 struct UNumberRangeFormatter; 20 struct UPluralRules; 21 22 namespace mozilla::intl { 23 24 /** 25 * NumberRangeFormatOptions supports the same set of options as 26 * NumberFormatOptions and additionally allows to control how to display ranges. 27 */ 28 struct MOZ_STACK_CLASS NumberRangeFormatOptions : public NumberFormatOptions { 29 /** 30 * Controls if and how to collapse identical parts in a range. 31 */ 32 enum class RangeCollapse { 33 /** 34 * Apply locale-specific heuristics. 35 */ 36 Auto, 37 38 /** 39 * Never collapse identical parts. 40 */ 41 None, 42 43 /** 44 * Collapse identical unit parts. 45 */ 46 Unit, 47 48 /** 49 * Collapse all identical parts. 50 */ 51 All, 52 } mRangeCollapse = RangeCollapse::Auto; 53 54 /** 55 * Controls how to display identical numbers. 56 */ 57 enum class RangeIdentityFallback { 58 /** 59 * Display the range as a single value. 60 */ 61 SingleValue, 62 63 /** 64 * Display the range as a single value if both numbers were equal before 65 * rounding. Otherwise display with a locale-sensitive approximation 66 * pattern. 67 */ 68 ApproximatelyOrSingleValue, 69 70 /** 71 * Display with a locale-sensitive approximation pattern. 72 */ 73 Approximately, 74 75 /** 76 * Display as a range expression. 77 */ 78 Range, 79 } mRangeIdentityFallback = RangeIdentityFallback::SingleValue; 80 }; 81 82 /** 83 * A NumberRangeFormat implementation that roughly mirrors the API provided by 84 * the ECMA-402 Intl.NumberFormat object for formatting number ranges. 85 * 86 * https://tc39.es/ecma402/#numberformat-objects 87 */ 88 class NumberRangeFormat final { 89 public: 90 /** 91 * Initialize a new NumberRangeFormat for the provided locale and using the 92 * provided options. 93 * 94 * https://tc39.es/ecma402/#sec-initializenumberformat 95 */ 96 static Result<UniquePtr<NumberRangeFormat>, ICUError> TryCreate( 97 std::string_view aLocale, const NumberRangeFormatOptions& aOptions); 98 99 NumberRangeFormat() = default; 100 NumberRangeFormat(const NumberRangeFormat&) = delete; 101 NumberRangeFormat& operator=(const NumberRangeFormat&) = delete; 102 103 ~NumberRangeFormat(); 104 105 /** 106 * Formats a double range to a utf-16 string. The string view is valid until 107 * another number range is formatted. Accessing the string view after this 108 * event is undefined behavior. 109 * 110 * https://tc39.es/ecma402/#sec-formatnumericrange 111 */ 112 Result<std::u16string_view, ICUError> format(double start, double end) const { 113 if (!formatInternal(start, end)) { 114 return Err(ICUError::InternalError); 115 } 116 117 return formatResult(); 118 } 119 120 /** 121 * Formats a double range to a utf-16 string, and fills the provided parts 122 * vector. The string view is valid until another number is formatted. 123 * Accessing the string view after this event is undefined behavior. 124 * 125 * https://tc39.es/ecma402/#sec-partitionnumberrangepattern 126 */ 127 Result<std::u16string_view, ICUError> formatToParts( 128 double start, double end, NumberPartVector& parts) const { 129 if (!formatInternal(start, end)) { 130 return Err(ICUError::InternalError); 131 } 132 133 bool isNegativeStart = !std::isnan(start) && IsNegative(start); 134 bool isNegativeEnd = !std::isnan(end) && IsNegative(end); 135 136 return formatResultToParts(Some(start), isNegativeStart, Some(end), 137 isNegativeEnd, parts); 138 } 139 140 /** 141 * Formats a decimal number range to a utf-16 string. The string view is valid 142 * until another number range is formatted. Accessing the string view after 143 * this event is undefined behavior. 144 * 145 * https://tc39.es/ecma402/#sec-formatnumericrange 146 */ 147 Result<std::u16string_view, ICUError> format(std::string_view start, 148 std::string_view end) const { 149 if (!formatInternal(start, end)) { 150 return Err(ICUError::InternalError); 151 } 152 153 return formatResult(); 154 } 155 156 /** 157 * Formats a string encoded decimal number range to a utf-16 string, and fills 158 * the provided parts vector. The string view is valid until another number is 159 * formatted. Accessing the string view after this event is undefined 160 * behavior. 161 * 162 * https://tc39.es/ecma402/#sec-partitionnumberrangepattern 163 */ 164 Result<std::u16string_view, ICUError> formatToParts( 165 std::string_view start, std::string_view end, 166 NumberPartVector& parts) const { 167 if (!formatInternal(start, end)) { 168 return Err(ICUError::InternalError); 169 } 170 171 Maybe<double> numStart = Nothing(); 172 if (start == "Infinity" || start == "+Infinity") { 173 numStart.emplace(PositiveInfinity<double>()); 174 } else if (start == "-Infinity") { 175 numStart.emplace(NegativeInfinity<double>()); 176 } else { 177 // Not currently expected, so we assert here. 178 MOZ_ASSERT(start != "NaN"); 179 } 180 181 Maybe<double> numEnd = Nothing(); 182 if (end == "Infinity" || end == "+Infinity") { 183 numEnd.emplace(PositiveInfinity<double>()); 184 } else if (end == "-Infinity") { 185 numEnd.emplace(NegativeInfinity<double>()); 186 } else { 187 // Not currently expected, so we assert here. 188 MOZ_ASSERT(end != "NaN"); 189 } 190 191 bool isNegativeStart = !start.empty() && start[0] == '-'; 192 bool isNegativeEnd = !end.empty() && end[0] == '-'; 193 194 return formatResultToParts(numStart, isNegativeStart, numEnd, isNegativeEnd, 195 parts); 196 } 197 198 /** 199 * Formats the number range and selects the keyword by using a provided 200 * UPluralRules object. 201 * 202 * https://tc39.es/ecma402/#sec-intl.pluralrules.prototype.selectrange 203 * 204 * TODO(1713917) This is necessary because both PluralRules and 205 * NumberRangeFormat have a shared dependency on the raw UFormattedNumberRange 206 * type. Once we transition to using ICU4X, the FFI calls should no 207 * longer require such shared dependencies. At that time, this 208 * functionality should be removed from NumberRangeFormat and invoked 209 * solely from PluralRules. 210 */ 211 Result<int32_t, ICUError> selectForRange( 212 double start, double end, char16_t* keyword, int32_t keywordSize, 213 const UPluralRules* pluralRules) const; 214 215 private: 216 UNumberRangeFormatter* mNumberRangeFormatter = nullptr; 217 UFormattedNumberRange* mFormattedNumberRange = nullptr; 218 bool mFormatForUnit = false; 219 220 Result<Ok, ICUError> initialize(std::string_view aLocale, 221 const NumberRangeFormatOptions& aOptions); 222 223 [[nodiscard]] bool formatInternal(double start, double end) const; 224 225 [[nodiscard]] bool formatInternal(std::string_view start, 226 std::string_view end) const; 227 228 Result<std::u16string_view, ICUError> formatResult() const; 229 230 Result<std::u16string_view, ICUError> formatResultToParts( 231 Maybe<double> start, bool startIsNegative, Maybe<double> end, 232 bool endIsNegative, NumberPartVector& parts) const; 233 }; 234 235 } // namespace mozilla::intl 236 237 #endif