tor-browser

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

nsMathMLOperators.cpp (15570B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsMathMLOperators.h"
      8 
      9 #include "mozilla/StaticPrefs_mathml.h"
     10 #include "mozilla/intl/UnicodeProperties.h"
     11 #include "nsCOMPtr.h"
     12 #include "nsCRT.h"
     13 #include "nsHashKeys.h"
     14 #include "nsIPersistentProperties2.h"
     15 #include "nsISimpleEnumerator.h"
     16 #include "nsNetUtil.h"
     17 #include "nsTArray.h"
     18 #include "nsTHashMap.h"
     19 
     20 using namespace mozilla;
     21 
     22 // operator dictionary entry
     23 struct OperatorData {
     24  OperatorData(void) : mFlags(0), mLeadingSpace(0.0f), mTrailingSpace(0.0f) {}
     25 
     26  // member data
     27  nsString mStr;
     28  nsOperatorFlags mFlags;
     29  float mLeadingSpace;   // unit is em
     30  float mTrailingSpace;  // unit is em
     31 };
     32 
     33 static int32_t gTableRefCount = 0;
     34 static uint32_t gOperatorCount = 0;
     35 static OperatorData* gOperatorArray = nullptr;
     36 static nsTHashMap<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
     37 static bool gGlobalsInitialized = false;
     38 
     39 static const char16_t kDashCh = char16_t('#');
     40 static const char16_t kColonCh = char16_t(':');
     41 
     42 static uint32_t ToUnicodeCodePoint(const nsString& aOperator) {
     43  if (aOperator.Length() == 1) {
     44    return aOperator[0];
     45  }
     46  if (aOperator.Length() == 2 &&
     47      NS_IS_SURROGATE_PAIR(aOperator[0], aOperator[1])) {
     48    return SURROGATE_TO_UCS4(aOperator[0], aOperator[1]);
     49  }
     50  return 0;
     51 }
     52 
     53 static void SetBooleanProperty(OperatorData* aOperatorData, nsString aName) {
     54  if (aName.IsEmpty()) {
     55    return;
     56  }
     57 
     58  if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length())) {
     59    aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY;
     60  } else if (aName.EqualsLiteral("fence")) {
     61    aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE;
     62  } else if (!StaticPrefs::mathml_operator_dictionary_accent_disabled() &&
     63             aName.EqualsLiteral("accent")) {
     64    aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT;
     65  } else if (aName.EqualsLiteral("largeop")) {
     66    aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP;
     67  } else if (aName.EqualsLiteral("separator")) {
     68    aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR;
     69  } else if (aName.EqualsLiteral("movablelimits")) {
     70    aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS;
     71  } else if (aName.EqualsLiteral("symmetric")) {
     72    aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
     73  }
     74 }
     75 
     76 static void SetProperty(OperatorData* aOperatorData, nsString aName,
     77                        nsString aValue) {
     78  if (aName.IsEmpty() || aValue.IsEmpty()) {
     79    return;
     80  }
     81 
     82  if (aName.EqualsLiteral("direction")) {
     83    if (aValue.EqualsLiteral("vertical")) {
     84      aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL;
     85    } else if (aValue.EqualsLiteral("horizontal")) {
     86      aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
     87    } else {
     88      return;  // invalid value
     89    }
     90  } else {
     91    bool isLeadingSpace;
     92    if (aName.EqualsLiteral("lspace")) {
     93      isLeadingSpace = true;
     94    } else if (aName.EqualsLiteral("rspace")) {
     95      isLeadingSpace = false;
     96    } else {
     97      return;  // input is not applicable
     98    }
     99 
    100    // aValue is assumed to be a digit from 0 to 7
    101    nsresult error = NS_OK;
    102    float space = aValue.ToFloat(&error) / 18.0;
    103    if (NS_FAILED(error)) {
    104      return;
    105    }
    106 
    107    if (isLeadingSpace) {
    108      aOperatorData->mLeadingSpace = space;
    109    } else {
    110      aOperatorData->mTrailingSpace = space;
    111    }
    112  }
    113 }
    114 
    115 static bool SetOperator(OperatorData* aOperatorData, nsOperatorFlags aForm,
    116                        const nsCString& aOperator, nsString& aAttributes)
    117 
    118 {
    119  static const char16_t kNullCh = char16_t('\0');
    120 
    121  // aOperator is in the expanded format \uNNNN\uNNNN ...
    122  // First compress these Unicode points to the internal nsString format
    123  int32_t i = 0;
    124  nsAutoString name, value;
    125  int32_t len = aOperator.Length();
    126  char16_t c = aOperator[i++];
    127  uint32_t state = 0;
    128  char16_t uchar = 0;
    129  while (i <= len) {
    130    if (0 == state) {
    131      if (c != '\\') {
    132        return false;
    133      }
    134      if (i < len) {
    135        c = aOperator[i];
    136      }
    137      i++;
    138      if (('u' != c) && ('U' != c)) {
    139        return false;
    140      }
    141      if (i < len) {
    142        c = aOperator[i];
    143      }
    144      i++;
    145      state++;
    146    } else {
    147      if (('0' <= c) && (c <= '9')) {
    148        uchar = (uchar << 4) | (c - '0');
    149      } else if (('a' <= c) && (c <= 'f')) {
    150        uchar = (uchar << 4) | (c - 'a' + 0x0a);
    151      } else if (('A' <= c) && (c <= 'F')) {
    152        uchar = (uchar << 4) | (c - 'A' + 0x0a);
    153      } else {
    154        return false;
    155      }
    156      if (i < len) {
    157        c = aOperator[i];
    158      }
    159      i++;
    160      state++;
    161      if (5 == state) {
    162        value.Append(uchar);
    163        uchar = 0;
    164        state = 0;
    165      }
    166    }
    167  }
    168  if (0 != state) {
    169    return false;
    170  }
    171 
    172  // Quick return when the caller doesn't care about the attributes and just
    173  // wants to know if this is a valid operator (this is the case at the first
    174  // pass of the parsing of the dictionary in InitOperators())
    175  if (!aForm) {
    176    return true;
    177  }
    178 
    179  // Add operator to hash table
    180  aOperatorData->mFlags |= aForm;
    181  aOperatorData->mStr.Assign(value);
    182  value.AppendInt(aForm, 10);
    183  gOperatorTable->InsertOrUpdate(value, aOperatorData);
    184 
    185 #ifdef DEBUG
    186  NS_LossyConvertUTF16toASCII str(aAttributes);
    187 #endif
    188  // Loop over the space-delimited list of attributes to get the name:value
    189  // pairs
    190  aAttributes.Append(kNullCh);  // put an extra null at the end
    191  char16_t* start = aAttributes.BeginWriting();
    192  char16_t* end = start;
    193  while ((kNullCh != *start) && (kDashCh != *start)) {
    194    name.SetLength(0);
    195    value.SetLength(0);
    196    // skip leading space, the dash amounts to the end of the line
    197    while ((kNullCh != *start) && (kDashCh != *start) &&
    198           nsCRT::IsAsciiSpace(*start)) {
    199      ++start;
    200    }
    201    end = start;
    202    // look for ':'
    203    while ((kNullCh != *end) && (kDashCh != *end) &&
    204           !nsCRT::IsAsciiSpace(*end) && (kColonCh != *end)) {
    205      ++end;
    206    }
    207    // If ':' is not found, then it's a boolean property
    208    bool IsBooleanProperty = (kColonCh != *end);
    209    *end = kNullCh;  // end segment here
    210    // this segment is the name
    211    if (start < end) {
    212      name.Assign(start);
    213    }
    214    if (IsBooleanProperty) {
    215      SetBooleanProperty(aOperatorData, name);
    216    } else {
    217      start = ++end;
    218      // look for space or end of line
    219      while ((kNullCh != *end) && (kDashCh != *end) &&
    220             !nsCRT::IsAsciiSpace(*end)) {
    221        ++end;
    222      }
    223      *end = kNullCh;  // end segment here
    224      if (start < end) {
    225        // this segment is the value
    226        value.Assign(start);
    227      }
    228      SetProperty(aOperatorData, name, value);
    229    }
    230    start = ++end;
    231  }
    232  return true;
    233 }
    234 
    235 static nsresult InitOperators(void) {
    236  // Load the property file containing the Operator Dictionary
    237  nsresult rv;
    238  nsCOMPtr<nsIPersistentProperties> mathfontProp;
    239  rv = NS_LoadPersistentPropertiesFromURISpec(
    240      getter_AddRefs(mathfontProp),
    241      "resource://gre/res/fonts/mathfont.properties"_ns);
    242 
    243  if (NS_FAILED(rv)) {
    244    return rv;
    245  }
    246 
    247  // Parse the Operator Dictionary in two passes.
    248  // The first pass is to count the number of operators; the second pass is to
    249  // allocate the necessary space for them and to add them in the hash table.
    250  for (int32_t pass = 1; pass <= 2; pass++) {
    251    OperatorData dummyData;
    252    OperatorData* operatorData = &dummyData;
    253    nsCOMPtr<nsISimpleEnumerator> iterator;
    254    if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) {
    255      bool more;
    256      uint32_t index = 0;
    257      nsAutoCString name;
    258      nsAutoString attributes;
    259      while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) {
    260        nsCOMPtr<nsISupports> supports;
    261        nsCOMPtr<nsIPropertyElement> element;
    262        if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) {
    263          element = do_QueryInterface(supports);
    264          if (NS_SUCCEEDED(element->GetKey(name)) &&
    265              NS_SUCCEEDED(element->GetValue(attributes))) {
    266            // expected key: operator.\uNNNN.{infix,postfix,prefix}
    267            if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) {
    268              name.Cut(0, 9);  // 9 is the length of "operator.";
    269              int32_t len = name.Length();
    270              nsOperatorFlags form = 0;
    271              if (kNotFound != name.RFind(".infix")) {
    272                form = NS_MATHML_OPERATOR_FORM_INFIX;
    273                len -= 6;  // 6 is the length of ".infix";
    274              } else if (kNotFound != name.RFind(".postfix")) {
    275                form = NS_MATHML_OPERATOR_FORM_POSTFIX;
    276                len -= 8;  // 8 is the length of ".postfix";
    277              } else if (kNotFound != name.RFind(".prefix")) {
    278                form = NS_MATHML_OPERATOR_FORM_PREFIX;
    279                len -= 7;  // 7 is the length of ".prefix";
    280              } else {
    281                continue;  // input is not applicable
    282              }
    283              name.SetLength(len);
    284              if (2 == pass) {  // allocate space and start the storage
    285                if (!gOperatorArray) {
    286                  if (0 == gOperatorCount) {
    287                    return NS_ERROR_UNEXPECTED;
    288                  }
    289                  gOperatorArray = new OperatorData[gOperatorCount];
    290                }
    291                operatorData = &gOperatorArray[index];
    292              } else {
    293                form = 0;  // to quickly return from SetOperator() at pass 1
    294              }
    295              // See if the operator should be retained
    296              if (SetOperator(operatorData, form, name, attributes)) {
    297                index++;
    298                if (1 == pass) {
    299                  gOperatorCount = index;
    300                }
    301              }
    302            }
    303          }
    304        }
    305      }
    306    }
    307  }
    308  return NS_OK;
    309 }
    310 
    311 static nsresult InitOperatorGlobals() {
    312  gGlobalsInitialized = true;
    313  nsresult rv = NS_ERROR_OUT_OF_MEMORY;
    314  gOperatorTable = new nsTHashMap<nsStringHashKey, OperatorData*>();
    315  if (gOperatorTable) {
    316    rv = InitOperators();
    317  }
    318  if (NS_FAILED(rv)) {
    319    nsMathMLOperators::CleanUp();
    320  }
    321  return rv;
    322 }
    323 
    324 void nsMathMLOperators::CleanUp() {
    325  if (gOperatorArray) {
    326    delete[] gOperatorArray;
    327    gOperatorArray = nullptr;
    328  }
    329  if (gOperatorTable) {
    330    delete gOperatorTable;
    331    gOperatorTable = nullptr;
    332  }
    333 }
    334 
    335 void nsMathMLOperators::AddRefTable(void) { gTableRefCount++; }
    336 
    337 void nsMathMLOperators::ReleaseTable(void) {
    338  if (0 == --gTableRefCount) {
    339    CleanUp();
    340  }
    341 }
    342 
    343 static OperatorData* GetOperatorData(const nsString& aOperator,
    344                                     const uint8_t aForm) {
    345  nsAutoString key(aOperator);
    346  key.AppendInt(aForm);
    347  return gOperatorTable->Get(key);
    348 }
    349 
    350 bool nsMathMLOperators::LookupOperator(const nsString& aOperator,
    351                                       const uint8_t aForm,
    352                                       nsOperatorFlags* aFlags,
    353                                       float* aLeadingSpace,
    354                                       float* aTrailingSpace) {
    355  NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
    356  NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
    357 
    358  // Operator strings must be of length 1 or 2 in UTF-16.
    359  // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
    360  if (aOperator.IsEmpty() || aOperator.Length() > 2) {
    361    return false;
    362  }
    363 
    364  if (aOperator.Length() == 2) {
    365    // Try and handle Arabic operators.
    366    // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
    367    if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
    368      if (aForm == NS_MATHML_OPERATOR_FORM_POSTFIX &&
    369          (codePoint == 0x1EEF0 || codePoint == 0x1EEF1)) {
    370        // Use category I.
    371        // https://w3c.github.io/mathml-core/#operator-dictionary-categories-values
    372        *aFlags = NS_MATHML_OPERATOR_FORM_POSTFIX |
    373                  NS_MATHML_OPERATOR_STRETCHY |
    374                  NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
    375        *aLeadingSpace = 0;
    376        *aTrailingSpace = 0;
    377        return true;
    378      }
    379      return false;
    380    }
    381 
    382    // Ignore the combining "negation" suffix for 2-character strings.
    383    // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator
    384    if (aOperator[1] == 0x0338 || aOperator[1] == 0x20D2) {
    385      nsAutoString newOperator;
    386      newOperator.Append(aOperator[0]);
    387      return LookupOperator(newOperator, aForm, aFlags, aLeadingSpace,
    388                            aTrailingSpace);
    389    }
    390  }
    391 
    392  if (!gGlobalsInitialized) {
    393    InitOperatorGlobals();
    394  }
    395  if (gOperatorTable) {
    396    if (OperatorData* data = GetOperatorData(aOperator, aForm)) {
    397      NS_ASSERTION(data->mStr.Equals(aOperator), "bad setup");
    398      *aFlags = data->mFlags;
    399      *aLeadingSpace = data->mLeadingSpace;
    400      *aTrailingSpace = data->mTrailingSpace;
    401      return true;
    402    }
    403  }
    404 
    405  return false;
    406 }
    407 
    408 bool nsMathMLOperators::LookupOperatorWithFallback(const nsString& aOperator,
    409                                                   const uint8_t aForm,
    410                                                   nsOperatorFlags* aFlags,
    411                                                   float* aLeadingSpace,
    412                                                   float* aTrailingSpace) {
    413  if (LookupOperator(aOperator, aForm, aFlags, aLeadingSpace, aTrailingSpace)) {
    414    return true;
    415  }
    416  for (const auto& form :
    417       {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
    418        NS_MATHML_OPERATOR_FORM_PREFIX}) {
    419    if (form == aForm) {
    420      // This form was tried above, skip it.
    421      continue;
    422    }
    423    if (LookupOperator(aOperator, form, aFlags, aLeadingSpace,
    424                       aTrailingSpace)) {
    425      return true;
    426    }
    427  }
    428  return false;
    429 }
    430 
    431 /* static */
    432 bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) {
    433  if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
    434    return intl::UnicodeProperties::IsMirrored(codePoint);
    435  }
    436  return false;
    437 }
    438 
    439 /* static */
    440 nsString nsMathMLOperators::GetMirroredOperator(const nsString& aOperator) {
    441  nsString result;
    442  if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
    443    result.Assign(intl::UnicodeProperties::CharMirror(codePoint));
    444  }
    445  return result;
    446 }
    447 
    448 /* static */
    449 bool nsMathMLOperators::IsIntegralOperator(const nsString& aOperator) {
    450  if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
    451    return (0x222B <= codePoint && codePoint <= 0x2233) ||
    452           (0x2A0B <= codePoint && codePoint <= 0x2A1C);
    453  }
    454  return false;
    455 }
    456 
    457 /* static */
    458 nsStretchDirection nsMathMLOperators::GetStretchyDirection(
    459    const nsString& aOperator) {
    460  // Search any entry for that operator and return the corresponding direction.
    461  // It is assumed that all the forms have same direction.
    462  for (const auto& form :
    463       {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX,
    464        NS_MATHML_OPERATOR_FORM_PREFIX}) {
    465    nsOperatorFlags flags;
    466    float dummy;
    467    if (nsMathMLOperators::LookupOperator(aOperator, form, &flags, &dummy,
    468                                          &dummy)) {
    469      if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
    470        return NS_STRETCH_DIRECTION_VERTICAL;
    471      }
    472      if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
    473        return NS_STRETCH_DIRECTION_HORIZONTAL;
    474      }
    475    }
    476  }
    477  return NS_STRETCH_DIRECTION_UNSUPPORTED;
    478 }