diff options
Diffstat (limited to 'layout/mathml/nsMathMLOperators.cpp')
-rw-r--r-- | layout/mathml/nsMathMLOperators.cpp | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLOperators.cpp b/layout/mathml/nsMathMLOperators.cpp new file mode 100644 index 0000000000..4a78587eb8 --- /dev/null +++ b/layout/mathml/nsMathMLOperators.cpp @@ -0,0 +1,431 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "nsMathMLOperators.h" +#include "nsCOMPtr.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsNetUtil.h" +#include "nsTArray.h" + +#include "mozilla/intl/UnicodeProperties.h" +#include "nsIPersistentProperties2.h" +#include "nsISimpleEnumerator.h" +#include "nsCRT.h" + +// operator dictionary entry +struct OperatorData { + OperatorData(void) : mFlags(0), mLeadingSpace(0.0f), mTrailingSpace(0.0f) {} + + // member data + nsString mStr; + nsOperatorFlags mFlags; + float mLeadingSpace; // unit is em + float mTrailingSpace; // unit is em +}; + +static int32_t gTableRefCount = 0; +static uint32_t gOperatorCount = 0; +static OperatorData* gOperatorArray = nullptr; +static nsTHashMap<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr; +static bool gGlobalsInitialized = false; + +static const char16_t kDashCh = char16_t('#'); +static const char16_t kColonCh = char16_t(':'); + +static uint32_t ToUnicodeCodePoint(const nsString& aOperator) { + if (aOperator.Length() == 1) { + return aOperator[0]; + } + if (aOperator.Length() == 2 && + NS_IS_SURROGATE_PAIR(aOperator[0], aOperator[1])) { + return SURROGATE_TO_UCS4(aOperator[0], aOperator[1]); + } + return 0; +} + +static void SetBooleanProperty(OperatorData* aOperatorData, nsString aName) { + if (aName.IsEmpty()) return; + + if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length())) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY; + else if (aName.EqualsLiteral("fence")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE; + else if (aName.EqualsLiteral("accent")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT; + else if (aName.EqualsLiteral("largeop")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP; + else if (aName.EqualsLiteral("separator")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR; + else if (aName.EqualsLiteral("movablelimits")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS; + else if (aName.EqualsLiteral("symmetric")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC; +} + +static void SetProperty(OperatorData* aOperatorData, nsString aName, + nsString aValue) { + if (aName.IsEmpty() || aValue.IsEmpty()) return; + + if (aName.EqualsLiteral("direction")) { + if (aValue.EqualsLiteral("vertical")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL; + else if (aValue.EqualsLiteral("horizontal")) + aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL; + else + return; // invalid value + } else { + bool isLeadingSpace; + if (aName.EqualsLiteral("lspace")) + isLeadingSpace = true; + else if (aName.EqualsLiteral("rspace")) + isLeadingSpace = false; + else + return; // input is not applicable + + // aValue is assumed to be a digit from 0 to 7 + nsresult error = NS_OK; + float space = aValue.ToFloat(&error) / 18.0; + if (NS_FAILED(error)) return; + + if (isLeadingSpace) + aOperatorData->mLeadingSpace = space; + else + aOperatorData->mTrailingSpace = space; + } +} + +static bool SetOperator(OperatorData* aOperatorData, nsOperatorFlags aForm, + const nsCString& aOperator, nsString& aAttributes) + +{ + static const char16_t kNullCh = char16_t('\0'); + + // aOperator is in the expanded format \uNNNN\uNNNN ... + // First compress these Unicode points to the internal nsString format + int32_t i = 0; + nsAutoString name, value; + int32_t len = aOperator.Length(); + char16_t c = aOperator[i++]; + uint32_t state = 0; + char16_t uchar = 0; + while (i <= len) { + if (0 == state) { + if (c != '\\') return false; + if (i < len) c = aOperator[i]; + i++; + if (('u' != c) && ('U' != c)) return false; + if (i < len) c = aOperator[i]; + i++; + state++; + } else { + if (('0' <= c) && (c <= '9')) + uchar = (uchar << 4) | (c - '0'); + else if (('a' <= c) && (c <= 'f')) + uchar = (uchar << 4) | (c - 'a' + 0x0a); + else if (('A' <= c) && (c <= 'F')) + uchar = (uchar << 4) | (c - 'A' + 0x0a); + else + return false; + if (i < len) c = aOperator[i]; + i++; + state++; + if (5 == state) { + value.Append(uchar); + uchar = 0; + state = 0; + } + } + } + if (0 != state) return false; + + // Quick return when the caller doesn't care about the attributes and just + // wants to know if this is a valid operator (this is the case at the first + // pass of the parsing of the dictionary in InitOperators()) + if (!aForm) return true; + + // Add operator to hash table + aOperatorData->mFlags |= aForm; + aOperatorData->mStr.Assign(value); + value.AppendInt(aForm, 10); + gOperatorTable->InsertOrUpdate(value, aOperatorData); + +#ifdef DEBUG + NS_LossyConvertUTF16toASCII str(aAttributes); +#endif + // Loop over the space-delimited list of attributes to get the name:value + // pairs + aAttributes.Append(kNullCh); // put an extra null at the end + char16_t* start = aAttributes.BeginWriting(); + char16_t* end = start; + while ((kNullCh != *start) && (kDashCh != *start)) { + name.SetLength(0); + value.SetLength(0); + // skip leading space, the dash amounts to the end of the line + while ((kNullCh != *start) && (kDashCh != *start) && + nsCRT::IsAsciiSpace(*start)) { + ++start; + } + end = start; + // look for ':' + while ((kNullCh != *end) && (kDashCh != *end) && + !nsCRT::IsAsciiSpace(*end) && (kColonCh != *end)) { + ++end; + } + // If ':' is not found, then it's a boolean property + bool IsBooleanProperty = (kColonCh != *end); + *end = kNullCh; // end segment here + // this segment is the name + if (start < end) { + name.Assign(start); + } + if (IsBooleanProperty) { + SetBooleanProperty(aOperatorData, name); + } else { + start = ++end; + // look for space or end of line + while ((kNullCh != *end) && (kDashCh != *end) && + !nsCRT::IsAsciiSpace(*end)) { + ++end; + } + *end = kNullCh; // end segment here + if (start < end) { + // this segment is the value + value.Assign(start); + } + SetProperty(aOperatorData, name, value); + } + start = ++end; + } + return true; +} + +static nsresult InitOperators(void) { + // Load the property file containing the Operator Dictionary + nsresult rv; + nsCOMPtr<nsIPersistentProperties> mathfontProp; + rv = NS_LoadPersistentPropertiesFromURISpec( + getter_AddRefs(mathfontProp), + "resource://gre/res/fonts/mathfont.properties"_ns); + + if (NS_FAILED(rv)) return rv; + + // Parse the Operator Dictionary in two passes. + // The first pass is to count the number of operators; the second pass is to + // allocate the necessary space for them and to add them in the hash table. + for (int32_t pass = 1; pass <= 2; pass++) { + OperatorData dummyData; + OperatorData* operatorData = &dummyData; + nsCOMPtr<nsISimpleEnumerator> iterator; + if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) { + bool more; + uint32_t index = 0; + nsAutoCString name; + nsAutoString attributes; + while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) { + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIPropertyElement> element; + if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) { + element = do_QueryInterface(supports); + if (NS_SUCCEEDED(element->GetKey(name)) && + NS_SUCCEEDED(element->GetValue(attributes))) { + // expected key: operator.\uNNNN.{infix,postfix,prefix} + if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) { + name.Cut(0, 9); // 9 is the length of "operator."; + int32_t len = name.Length(); + nsOperatorFlags form = 0; + if (kNotFound != name.RFind(".infix")) { + form = NS_MATHML_OPERATOR_FORM_INFIX; + len -= 6; // 6 is the length of ".infix"; + } else if (kNotFound != name.RFind(".postfix")) { + form = NS_MATHML_OPERATOR_FORM_POSTFIX; + len -= 8; // 8 is the length of ".postfix"; + } else if (kNotFound != name.RFind(".prefix")) { + form = NS_MATHML_OPERATOR_FORM_PREFIX; + len -= 7; // 7 is the length of ".prefix"; + } else + continue; // input is not applicable + name.SetLength(len); + if (2 == pass) { // allocate space and start the storage + if (!gOperatorArray) { + if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED; + gOperatorArray = new OperatorData[gOperatorCount]; + } + operatorData = &gOperatorArray[index]; + } else { + form = 0; // to quickly return from SetOperator() at pass 1 + } + // See if the operator should be retained + if (SetOperator(operatorData, form, name, attributes)) { + index++; + if (1 == pass) gOperatorCount = index; + } + } + } + } + } + } + } + return NS_OK; +} + +static nsresult InitOperatorGlobals() { + gGlobalsInitialized = true; + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + gOperatorTable = new nsTHashMap<nsStringHashKey, OperatorData*>(); + if (gOperatorTable) { + rv = InitOperators(); + } + if (NS_FAILED(rv)) nsMathMLOperators::CleanUp(); + return rv; +} + +void nsMathMLOperators::CleanUp() { + if (gOperatorArray) { + delete[] gOperatorArray; + gOperatorArray = nullptr; + } + if (gOperatorTable) { + delete gOperatorTable; + gOperatorTable = nullptr; + } +} + +void nsMathMLOperators::AddRefTable(void) { gTableRefCount++; } + +void nsMathMLOperators::ReleaseTable(void) { + if (0 == --gTableRefCount) { + CleanUp(); + } +} + +static OperatorData* GetOperatorData(const nsString& aOperator, + const uint8_t aForm) { + nsAutoString key(aOperator); + key.AppendInt(aForm); + return gOperatorTable->Get(key); +} + +bool nsMathMLOperators::LookupOperator(const nsString& aOperator, + const uint8_t aForm, + nsOperatorFlags* aFlags, + float* aLeadingSpace, + float* aTrailingSpace) { + NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage"); + NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***"); + + // Operator strings must be of length 1 or 2 in UTF-16. + // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator + if (aOperator.IsEmpty() || aOperator.Length() > 2) { + return false; + } + + if (aOperator.Length() == 2) { + // Try and handle Arabic operators. + // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator + if (auto codePoint = ToUnicodeCodePoint(aOperator)) { + if (aForm == NS_MATHML_OPERATOR_FORM_POSTFIX && + (codePoint == 0x1EEF0 || codePoint == 0x1EEF1)) { + // Use category I. + // https://w3c.github.io/mathml-core/#operator-dictionary-categories-values + *aFlags = NS_MATHML_OPERATOR_FORM_POSTFIX | + NS_MATHML_OPERATOR_STRETCHY | + NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL; + *aLeadingSpace = 0; + *aTrailingSpace = 0; + return true; + } + return false; + } + + // Ignore the combining "negation" suffix for 2-character strings. + // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator + if (aOperator[1] == 0x0338 || aOperator[1] == 0x20D2) { + nsAutoString newOperator; + newOperator.Append(aOperator[0]); + return LookupOperator(newOperator, aForm, aFlags, aLeadingSpace, + aTrailingSpace); + } + } + + if (!gGlobalsInitialized) { + InitOperatorGlobals(); + } + if (gOperatorTable) { + if (OperatorData* data = GetOperatorData(aOperator, aForm)) { + NS_ASSERTION(data->mStr.Equals(aOperator), "bad setup"); + *aFlags = data->mFlags; + *aLeadingSpace = data->mLeadingSpace; + *aTrailingSpace = data->mTrailingSpace; + return true; + } + } + + return false; +} + +bool nsMathMLOperators::LookupOperatorWithFallback(const nsString& aOperator, + const uint8_t aForm, + nsOperatorFlags* aFlags, + float* aLeadingSpace, + float* aTrailingSpace) { + if (LookupOperator(aOperator, aForm, aFlags, aLeadingSpace, aTrailingSpace)) { + return true; + } + for (const auto& form : + {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX, + NS_MATHML_OPERATOR_FORM_PREFIX}) { + if (form == aForm) { + // This form was tried above, skip it. + continue; + } + if (LookupOperator(aOperator, form, aFlags, aLeadingSpace, + aTrailingSpace)) { + return true; + } + } + return false; +} + +/* static */ +bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) { + if (auto codePoint = ToUnicodeCodePoint(aOperator)) { + return mozilla::intl::UnicodeProperties::IsMirrored(codePoint); + } + return false; +} + +/* static */ +bool nsMathMLOperators::IsIntegralOperator(const nsString& aOperator) { + if (auto codePoint = ToUnicodeCodePoint(aOperator)) { + return (0x222B <= codePoint && codePoint <= 0x2233) || + (0x2A0B <= codePoint && codePoint <= 0x2A1C); + } + return false; +} + +/* static */ +nsStretchDirection nsMathMLOperators::GetStretchyDirection( + const nsString& aOperator) { + // Search any entry for that operator and return the corresponding direction. + // It is assumed that all the forms have same direction. + for (const auto& form : + {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX, + NS_MATHML_OPERATOR_FORM_PREFIX}) { + nsOperatorFlags flags; + float dummy; + if (nsMathMLOperators::LookupOperator(aOperator, form, &flags, &dummy, + &dummy)) { + if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) { + return NS_STRETCH_DIRECTION_VERTICAL; + } + if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) { + return NS_STRETCH_DIRECTION_HORIZONTAL; + } + } + } + return NS_STRETCH_DIRECTION_UNSUPPORTED; +} |