michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMathMLOperators.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsDataHashtable.h" michael@0: #include "nsHashKeys.h" michael@0: #include "nsTArray.h" michael@0: michael@0: #include "nsIPersistentProperties2.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCRT.h" michael@0: michael@0: // operator dictionary entry michael@0: struct OperatorData { michael@0: OperatorData(void) michael@0: : mFlags(0), michael@0: mLeadingSpace(0.0f), michael@0: mTrailingSpace(0.0f) michael@0: { michael@0: } michael@0: michael@0: // member data michael@0: nsString mStr; michael@0: nsOperatorFlags mFlags; michael@0: float mLeadingSpace; // unit is em michael@0: float mTrailingSpace; // unit is em michael@0: }; michael@0: michael@0: static int32_t gTableRefCount = 0; michael@0: static uint32_t gOperatorCount = 0; michael@0: static OperatorData* gOperatorArray = nullptr; michael@0: static nsDataHashtable* gOperatorTable = nullptr; michael@0: static bool gGlobalsInitialized = false; michael@0: michael@0: static const char16_t kDashCh = char16_t('#'); michael@0: static const char16_t kColonCh = char16_t(':'); michael@0: michael@0: static void michael@0: SetBooleanProperty(OperatorData* aOperatorData, michael@0: nsString aName) michael@0: { michael@0: if (aName.IsEmpty()) michael@0: return; michael@0: michael@0: if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length())) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY; michael@0: else if (aName.EqualsLiteral("fence")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE; michael@0: else if (aName.EqualsLiteral("accent")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT; michael@0: else if (aName.EqualsLiteral("largeop")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP; michael@0: else if (aName.EqualsLiteral("separator")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR; michael@0: else if (aName.EqualsLiteral("movablelimits")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS; michael@0: else if (aName.EqualsLiteral("symmetric")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC; michael@0: else if (aName.EqualsLiteral("integral")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL; michael@0: else if (aName.EqualsLiteral("mirrorable")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_MIRRORABLE; michael@0: } michael@0: michael@0: static void michael@0: SetProperty(OperatorData* aOperatorData, michael@0: nsString aName, michael@0: nsString aValue) michael@0: { michael@0: if (aName.IsEmpty() || aValue.IsEmpty()) michael@0: return; michael@0: michael@0: // XXX These ones are not kept in the dictionary michael@0: // Support for these requires nsString member variables michael@0: // maxsize (default: infinity) michael@0: // minsize (default: 1) michael@0: michael@0: if (aName.EqualsLiteral("direction")) { michael@0: if (aValue.EqualsLiteral("vertical")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL; michael@0: else if (aValue.EqualsLiteral("horizontal")) michael@0: aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL; michael@0: else return; // invalid value michael@0: } else { michael@0: bool isLeadingSpace; michael@0: if (aName.EqualsLiteral("lspace")) michael@0: isLeadingSpace = true; michael@0: else if (aName.EqualsLiteral("rspace")) michael@0: isLeadingSpace = false; michael@0: else return; // input is not applicable michael@0: michael@0: // aValue is assumed to be a digit from 0 to 7 michael@0: nsresult error = NS_OK; michael@0: float space = aValue.ToFloat(&error) / 18.0; michael@0: if (NS_FAILED(error)) return; michael@0: michael@0: if (isLeadingSpace) michael@0: aOperatorData->mLeadingSpace = space; michael@0: else michael@0: aOperatorData->mTrailingSpace = space; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: SetOperator(OperatorData* aOperatorData, michael@0: nsOperatorFlags aForm, michael@0: const nsCString& aOperator, michael@0: nsString& aAttributes) michael@0: michael@0: { michael@0: static const char16_t kNullCh = char16_t('\0'); michael@0: michael@0: // aOperator is in the expanded format \uNNNN\uNNNN ... michael@0: // First compress these Unicode points to the internal nsString format michael@0: int32_t i = 0; michael@0: nsAutoString name, value; michael@0: int32_t len = aOperator.Length(); michael@0: char16_t c = aOperator[i++]; michael@0: uint32_t state = 0; michael@0: char16_t uchar = 0; michael@0: while (i <= len) { michael@0: if (0 == state) { michael@0: if (c != '\\') michael@0: return false; michael@0: if (i < len) michael@0: c = aOperator[i]; michael@0: i++; michael@0: if (('u' != c) && ('U' != c)) michael@0: return false; michael@0: if (i < len) michael@0: c = aOperator[i]; michael@0: i++; michael@0: state++; michael@0: } michael@0: else { michael@0: if (('0' <= c) && (c <= '9')) michael@0: uchar = (uchar << 4) | (c - '0'); michael@0: else if (('a' <= c) && (c <= 'f')) michael@0: uchar = (uchar << 4) | (c - 'a' + 0x0a); michael@0: else if (('A' <= c) && (c <= 'F')) michael@0: uchar = (uchar << 4) | (c - 'A' + 0x0a); michael@0: else return false; michael@0: if (i < len) michael@0: c = aOperator[i]; michael@0: i++; michael@0: state++; michael@0: if (5 == state) { michael@0: value.Append(uchar); michael@0: uchar = 0; michael@0: state = 0; michael@0: } michael@0: } michael@0: } michael@0: if (0 != state) return false; michael@0: michael@0: // Quick return when the caller doesn't care about the attributes and just wants michael@0: // to know if this is a valid operator (this is the case at the first pass of the michael@0: // parsing of the dictionary in InitOperators()) michael@0: if (!aForm) return true; michael@0: michael@0: // Add operator to hash table michael@0: aOperatorData->mFlags |= aForm; michael@0: aOperatorData->mStr.Assign(value); michael@0: value.AppendInt(aForm, 10); michael@0: gOperatorTable->Put(value, aOperatorData); michael@0: michael@0: #ifdef DEBUG michael@0: NS_LossyConvertUTF16toASCII str(aAttributes); michael@0: #endif michael@0: // Loop over the space-delimited list of attributes to get the name:value pairs michael@0: aAttributes.Append(kNullCh); // put an extra null at the end michael@0: char16_t* start = aAttributes.BeginWriting(); michael@0: char16_t* end = start; michael@0: while ((kNullCh != *start) && (kDashCh != *start)) { michael@0: name.SetLength(0); michael@0: value.SetLength(0); michael@0: // skip leading space, the dash amounts to the end of the line michael@0: while ((kNullCh!=*start) && (kDashCh!=*start) && nsCRT::IsAsciiSpace(*start)) { michael@0: ++start; michael@0: } michael@0: end = start; michael@0: // look for ':' michael@0: while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) && michael@0: (kColonCh!=*end)) { michael@0: ++end; michael@0: } michael@0: // If ':' is not found, then it's a boolean property michael@0: bool IsBooleanProperty = (kColonCh != *end); michael@0: *end = kNullCh; // end segment here michael@0: // this segment is the name michael@0: if (start < end) { michael@0: name.Assign(start); michael@0: } michael@0: if (IsBooleanProperty) { michael@0: SetBooleanProperty(aOperatorData, name); michael@0: } else { michael@0: start = ++end; michael@0: // look for space or end of line michael@0: while ((kNullCh!=*end) && (kDashCh!=*end) && michael@0: !nsCRT::IsAsciiSpace(*end)) { michael@0: ++end; michael@0: } michael@0: *end = kNullCh; // end segment here michael@0: if (start < end) { michael@0: // this segment is the value michael@0: value.Assign(start); michael@0: } michael@0: SetProperty(aOperatorData, name, value); michael@0: } michael@0: start = ++end; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static nsresult michael@0: InitOperators(void) michael@0: { michael@0: // Load the property file containing the Operator Dictionary michael@0: nsresult rv; michael@0: nsCOMPtr mathfontProp; michael@0: rv = NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(mathfontProp), michael@0: NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties")); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Parse the Operator Dictionary in two passes. michael@0: // The first pass is to count the number of operators; the second pass is to michael@0: // allocate the necessary space for them and to add them in the hash table. michael@0: for (int32_t pass = 1; pass <= 2; pass++) { michael@0: OperatorData dummyData; michael@0: OperatorData* operatorData = &dummyData; michael@0: nsCOMPtr iterator; michael@0: if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) { michael@0: bool more; michael@0: uint32_t index = 0; michael@0: nsAutoCString name; michael@0: nsAutoString attributes; michael@0: while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) { michael@0: nsCOMPtr supports; michael@0: nsCOMPtr element; michael@0: if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) { michael@0: element = do_QueryInterface(supports); michael@0: if (NS_SUCCEEDED(element->GetKey(name)) && michael@0: NS_SUCCEEDED(element->GetValue(attributes))) { michael@0: // expected key: operator.\uNNNN.{infix,postfix,prefix} michael@0: if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) { michael@0: name.Cut(0, 9); // 9 is the length of "operator."; michael@0: int32_t len = name.Length(); michael@0: nsOperatorFlags form = 0; michael@0: if (kNotFound != name.RFind(".infix")) { michael@0: form = NS_MATHML_OPERATOR_FORM_INFIX; michael@0: len -= 6; // 6 is the length of ".infix"; michael@0: } michael@0: else if (kNotFound != name.RFind(".postfix")) { michael@0: form = NS_MATHML_OPERATOR_FORM_POSTFIX; michael@0: len -= 8; // 8 is the length of ".postfix"; michael@0: } michael@0: else if (kNotFound != name.RFind(".prefix")) { michael@0: form = NS_MATHML_OPERATOR_FORM_PREFIX; michael@0: len -= 7; // 7 is the length of ".prefix"; michael@0: } michael@0: else continue; // input is not applicable michael@0: name.SetLength(len); michael@0: if (2 == pass) { // allocate space and start the storage michael@0: if (!gOperatorArray) { michael@0: if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED; michael@0: gOperatorArray = new OperatorData[gOperatorCount]; michael@0: if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: operatorData = &gOperatorArray[index]; michael@0: } michael@0: else { michael@0: form = 0; // to quickly return from SetOperator() at pass 1 michael@0: } michael@0: // See if the operator should be retained michael@0: if (SetOperator(operatorData, form, name, attributes)) { michael@0: index++; michael@0: if (1 == pass) gOperatorCount = index; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsresult michael@0: InitGlobals() michael@0: { michael@0: gGlobalsInitialized = true; michael@0: nsresult rv = NS_ERROR_OUT_OF_MEMORY; michael@0: gOperatorTable = new nsDataHashtable(); michael@0: if (gOperatorTable) { michael@0: rv = InitOperators(); michael@0: } michael@0: if (NS_FAILED(rv)) michael@0: nsMathMLOperators::CleanUp(); michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsMathMLOperators::CleanUp() michael@0: { michael@0: if (gOperatorArray) { michael@0: delete[] gOperatorArray; michael@0: gOperatorArray = nullptr; michael@0: } michael@0: if (gOperatorTable) { michael@0: delete gOperatorTable; michael@0: gOperatorTable = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsMathMLOperators::AddRefTable(void) michael@0: { michael@0: gTableRefCount++; michael@0: } michael@0: michael@0: void michael@0: nsMathMLOperators::ReleaseTable(void) michael@0: { michael@0: if (0 == --gTableRefCount) { michael@0: CleanUp(); michael@0: } michael@0: } michael@0: michael@0: static OperatorData* michael@0: GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm) michael@0: { michael@0: nsAutoString key(aOperator); michael@0: key.AppendInt(aForm); michael@0: return gOperatorTable->Get(key); michael@0: } michael@0: michael@0: bool michael@0: nsMathMLOperators::LookupOperator(const nsString& aOperator, michael@0: const nsOperatorFlags aForm, michael@0: nsOperatorFlags* aFlags, michael@0: float* aLeadingSpace, michael@0: float* aTrailingSpace) michael@0: { michael@0: if (!gGlobalsInitialized) { michael@0: InitGlobals(); michael@0: } michael@0: if (gOperatorTable) { michael@0: NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage"); michael@0: NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***"); michael@0: michael@0: // The MathML REC says: michael@0: // If the operator does not occur in the dictionary with the specified form, michael@0: // the renderer should use one of the forms which is available there, in the michael@0: // order of preference: infix, postfix, prefix. michael@0: michael@0: OperatorData* found; michael@0: int32_t form = NS_MATHML_OPERATOR_GET_FORM(aForm); michael@0: if (!(found = GetOperatorData(aOperator, form))) { michael@0: if (form == NS_MATHML_OPERATOR_FORM_INFIX || michael@0: !(found = michael@0: GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) { michael@0: if (form == NS_MATHML_OPERATOR_FORM_POSTFIX || michael@0: !(found = michael@0: GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) { michael@0: if (form != NS_MATHML_OPERATOR_FORM_PREFIX) { michael@0: found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (found) { michael@0: NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup"); michael@0: *aLeadingSpace = found->mLeadingSpace; michael@0: *aTrailingSpace = found->mTrailingSpace; michael@0: *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits michael@0: *aFlags |= found->mFlags; // just add bits without overwriting michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsMathMLOperators::LookupOperators(const nsString& aOperator, michael@0: nsOperatorFlags* aFlags, michael@0: float* aLeadingSpace, michael@0: float* aTrailingSpace) michael@0: { michael@0: if (!gGlobalsInitialized) { michael@0: InitGlobals(); michael@0: } michael@0: michael@0: aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f; michael@0: michael@0: aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f; michael@0: michael@0: aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f; michael@0: michael@0: if (gOperatorTable) { michael@0: OperatorData* found; michael@0: found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX); michael@0: if (found) { michael@0: aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeadingSpace; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mTrailingSpace; michael@0: } michael@0: found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX); michael@0: if (found) { michael@0: aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeadingSpace; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mTrailingSpace; michael@0: } michael@0: found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX); michael@0: if (found) { michael@0: aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags; michael@0: aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeadingSpace; michael@0: aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mTrailingSpace; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) michael@0: { michael@0: // LookupOperator will search infix, postfix and prefix forms of aOperator and michael@0: // return the first form found. It is assumed that all these forms have same michael@0: // mirrorability. michael@0: nsOperatorFlags flags = 0; michael@0: float dummy; michael@0: nsMathMLOperators::LookupOperator(aOperator, michael@0: NS_MATHML_OPERATOR_FORM_INFIX, michael@0: &flags, &dummy, &dummy); michael@0: return NS_MATHML_OPERATOR_IS_MIRRORABLE(flags); michael@0: } michael@0: michael@0: /* static */ nsStretchDirection michael@0: nsMathMLOperators::GetStretchyDirection(const nsString& aOperator) michael@0: { michael@0: // LookupOperator will search infix, postfix and prefix forms of aOperator and michael@0: // return the first form found. It is assumed that all these forms have same michael@0: // direction. michael@0: nsOperatorFlags flags = 0; michael@0: float dummy; michael@0: nsMathMLOperators::LookupOperator(aOperator, michael@0: NS_MATHML_OPERATOR_FORM_INFIX, michael@0: &flags, &dummy, &dummy); michael@0: michael@0: if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) { michael@0: return NS_STRETCH_DIRECTION_VERTICAL; michael@0: } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) { michael@0: return NS_STRETCH_DIRECTION_HORIZONTAL; michael@0: } else { michael@0: return NS_STRETCH_DIRECTION_UNSUPPORTED; michael@0: } michael@0: }