tor-browser

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

units_router.cpp (5740B)


      1 // © 2020 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 
      4 #include "unicode/utypes.h"
      5 
      6 #if !UCONFIG_NO_FORMATTING
      7 
      8 #include "charstr.h"
      9 #include "cmemory.h"
     10 #include "cstring.h"
     11 #include "measunit_impl.h"
     12 #include "number_decimalquantity.h"
     13 #include "number_roundingutils.h"
     14 #include "resource.h"
     15 #include "unicode/measure.h"
     16 #include "units_data.h"
     17 #include "units_router.h"
     18 #include <cmath>
     19 
     20 U_NAMESPACE_BEGIN
     21 namespace units {
     22 
     23 using number::Precision;
     24 using number::impl::parseIncrementOption;
     25 
     26 Precision UnitsRouter::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
     27                                                UErrorCode &status) {
     28    if (U_FAILURE(status)) {
     29        // As a member of UsagePrefsHandler, which is a friend of Precision, we
     30        // get access to the default constructor.
     31        return {};
     32    }
     33    constexpr int32_t kSkelPrefixLen = 20;
     34    if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) {
     35        status = U_INVALID_FORMAT_ERROR;
     36        return {};
     37    }
     38    U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/');
     39    StringSegment segment(precisionSkeleton, false);
     40    segment.adjustOffset(kSkelPrefixLen);
     41    Precision result;
     42    parseIncrementOption(segment, result, status);
     43    return result;
     44 }
     45 
     46 UnitsRouter::UnitsRouter(StringPiece inputUnitIdentifier, const Locale &locale, StringPiece usage,
     47                         UErrorCode &status) {
     48    this->init(MeasureUnit::forIdentifier(inputUnitIdentifier, status), locale, usage, status);
     49 }
     50 
     51 UnitsRouter::UnitsRouter(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
     52                         UErrorCode &status) {
     53    this->init(std::move(inputUnit), locale, usage, status);
     54 }
     55 
     56 void UnitsRouter::init(const MeasureUnit &inputUnit, const Locale &locale, StringPiece usage,
     57                       UErrorCode &status) {
     58 
     59    if (U_FAILURE(status)) {
     60        return;
     61    }
     62 
     63    // TODO: do we want to pass in ConversionRates and UnitPreferences instead
     64    // of loading in each UnitsRouter instance? (Or make global?)
     65    ConversionRates conversionRates(status);
     66    UnitPreferences prefs(status);
     67 
     68    MeasureUnitImpl inputUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(inputUnit, status);
     69    MeasureUnitImpl baseUnitImpl =
     70        (extractCompoundBaseUnit(inputUnitImpl, conversionRates, status));
     71    CharString category = getUnitQuantity(baseUnitImpl, status);
     72    if (U_FAILURE(status)) {
     73        return;
     74    }
     75 
     76    const MaybeStackVector<UnitPreference> unitPrefs =
     77        prefs.getPreferencesFor(category.toStringPiece(), usage, locale, status);
     78    for (int32_t i = 0, n = unitPrefs.length(); i < n; ++i) {
     79        U_ASSERT(unitPrefs[i] != nullptr);
     80        const auto* const preference = unitPrefs[i];
     81 
     82        MeasureUnitImpl complexTargetUnitImpl =
     83            MeasureUnitImpl::forIdentifier(preference->unit.data(), status);
     84        if (U_FAILURE(status)) {
     85            return;
     86        }
     87 
     88        UnicodeString precision = preference->skeleton;
     89 
     90        // For now, we only have "precision-increment" in Units Preferences skeleton.
     91        // Therefore, we check if the skeleton starts with "precision-increment" and force the program to
     92        // fail otherwise.
     93        // NOTE:
     94        //  It is allowed to have an empty precision.
     95        if (!precision.isEmpty() && !precision.startsWith(u"precision-increment", 19)) {
     96            status = U_INTERNAL_PROGRAM_ERROR;
     97            return;
     98        }
     99 
    100        outputUnits_.emplaceBackAndCheckErrorCode(status,
    101                                                  complexTargetUnitImpl.copy(status).build(status));
    102        converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl,
    103                                                           preference->geq, std::move(precision),
    104                                                           conversionRates, status);
    105 
    106        if (U_FAILURE(status)) {
    107            return;
    108        }
    109    }
    110 }
    111 
    112 RouteResult UnitsRouter::route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const {
    113    // Find the matching preference
    114    const ConverterPreference *converterPreference = nullptr;
    115    for (int32_t i = 0, n = converterPreferences_.length(); i < n; i++) {
    116        converterPreference = converterPreferences_[i];
    117        if (converterPreference->converter.greaterThanOrEqual(std::abs(quantity) * (1 + DBL_EPSILON),
    118                                                              converterPreference->limit)) {
    119            break;
    120        }
    121    }
    122    U_ASSERT(converterPreference != nullptr);
    123 
    124    // Set up the rounder for this preference's precision
    125    if (rounder != nullptr && rounder->fPrecision.isBogus()) {
    126        if (converterPreference->precision.length() > 0) {
    127            rounder->fPrecision = parseSkeletonToPrecision(converterPreference->precision, status);
    128        } else {
    129            // We use the same rounding mode as COMPACT notation: known to be a
    130            // human-friendly rounding mode: integers, but add a decimal digit
    131            // as needed to ensure we have at least 2 significant digits.
    132            rounder->fPrecision = Precision::integer().withMinDigits(2);
    133        }
    134    }
    135 
    136    return RouteResult(converterPreference->converter.convert(quantity, rounder, status),
    137                       converterPreference->targetUnit.copy(status));
    138 }
    139 
    140 const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
    141    // TODO: consider pulling this from converterPreferences_ and dropping
    142    // outputUnits_?
    143    return &outputUnits_;
    144 }
    145 
    146 } // namespace units
    147 U_NAMESPACE_END
    148 
    149 #endif /* #if !UCONFIG_NO_FORMATTING */