summaryrefslogtreecommitdiffstats
path: root/layout/mathml/nsMathMLOperators.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/mathml/nsMathMLOperators.cpp')
-rw-r--r--layout/mathml/nsMathMLOperators.cpp467
1 files changed, 467 insertions, 0 deletions
diff --git a/layout/mathml/nsMathMLOperators.cpp b/layout/mathml/nsMathMLOperators.cpp
new file mode 100644
index 000000000..bbb03014b
--- /dev/null
+++ b/layout/mathml/nsMathMLOperators.cpp
@@ -0,0 +1,467 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsDataHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+
+#include "nsIPersistentProperties2.h"
+#include "nsISimpleEnumerator.h"
+#include "nsContentUtils.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 nsDataHashtable<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
+static bool gGlobalsInitialized = false;
+
+static const char16_t kDashCh = char16_t('#');
+static const char16_t kColonCh = char16_t(':');
+
+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;
+ else if (aName.EqualsLiteral("integral"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL;
+ else if (aName.EqualsLiteral("mirrorable"))
+ aOperatorData->mFlags |= NS_MATHML_OPERATOR_MIRRORABLE;
+}
+
+static void
+SetProperty(OperatorData* aOperatorData,
+ nsString aName,
+ nsString aValue)
+{
+ if (aName.IsEmpty() || aValue.IsEmpty())
+ return;
+
+ // XXX These ones are not kept in the dictionary
+ // Support for these requires nsString member variables
+ // maxsize (default: infinity)
+ // minsize (default: 1)
+
+ 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->Put(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),
+ NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));
+
+ 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];
+ if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY;
+ }
+ 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 nsDataHashtable<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, nsOperatorFlags aForm)
+{
+ nsAutoString key(aOperator);
+ key.AppendInt(aForm);
+ return gOperatorTable->Get(key);
+}
+
+bool
+nsMathMLOperators::LookupOperator(const nsString& aOperator,
+ const nsOperatorFlags aForm,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace)
+{
+ if (!gGlobalsInitialized) {
+ InitOperatorGlobals();
+ }
+ if (gOperatorTable) {
+ NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
+ NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
+
+ // The MathML REC says:
+ // If the operator does not occur in the dictionary with the specified form,
+ // the renderer should use one of the forms which is available there, in the
+ // order of preference: infix, postfix, prefix.
+
+ OperatorData* found;
+ int32_t form = NS_MATHML_OPERATOR_GET_FORM(aForm);
+ if (!(found = GetOperatorData(aOperator, form))) {
+ if (form == NS_MATHML_OPERATOR_FORM_INFIX ||
+ !(found =
+ GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) {
+ if (form == NS_MATHML_OPERATOR_FORM_POSTFIX ||
+ !(found =
+ GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) {
+ if (form != NS_MATHML_OPERATOR_FORM_PREFIX) {
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
+ }
+ }
+ }
+ }
+ if (found) {
+ NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup");
+ *aLeadingSpace = found->mLeadingSpace;
+ *aTrailingSpace = found->mTrailingSpace;
+ *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
+ *aFlags |= found->mFlags; // just add bits without overwriting
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+nsMathMLOperators::LookupOperators(const nsString& aOperator,
+ nsOperatorFlags* aFlags,
+ float* aLeadingSpace,
+ float* aTrailingSpace)
+{
+ if (!gGlobalsInitialized) {
+ InitOperatorGlobals();
+ }
+
+ aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
+
+ aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
+
+ aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
+
+ if (gOperatorTable) {
+ OperatorData* found;
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mTrailingSpace;
+ }
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mTrailingSpace;
+ }
+ found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
+ if (found) {
+ aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags;
+ aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeadingSpace;
+ aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mTrailingSpace;
+ }
+ }
+}
+
+/* static */ bool
+nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator)
+{
+ // LookupOperator will search infix, postfix and prefix forms of aOperator and
+ // return the first form found. It is assumed that all these forms have same
+ // mirrorability.
+ nsOperatorFlags flags = 0;
+ float dummy;
+ nsMathMLOperators::LookupOperator(aOperator,
+ NS_MATHML_OPERATOR_FORM_INFIX,
+ &flags, &dummy, &dummy);
+ return NS_MATHML_OPERATOR_IS_MIRRORABLE(flags);
+}
+
+/* static */ nsStretchDirection
+nsMathMLOperators::GetStretchyDirection(const nsString& aOperator)
+{
+ // LookupOperator will search infix, postfix and prefix forms of aOperator and
+ // return the first form found. It is assumed that all these forms have same
+ // direction.
+ nsOperatorFlags flags = 0;
+ float dummy;
+ nsMathMLOperators::LookupOperator(aOperator,
+ NS_MATHML_OPERATOR_FORM_INFIX,
+ &flags, &dummy, &dummy);
+
+ if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
+ return NS_STRETCH_DIRECTION_VERTICAL;
+ } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
+ return NS_STRETCH_DIRECTION_HORIZONTAL;
+ } else {
+ return NS_STRETCH_DIRECTION_UNSUPPORTED;
+ }
+}