Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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),
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.
if (aOperator.IsEmpty() || aOperator.Length() > 2) {
return false;
}
if (aOperator.Length() == 2) {
// Try and handle Arabic operators.
if (auto codePoint = ToUnicodeCodePoint(aOperator)) {
if (aForm == NS_MATHML_OPERATOR_FORM_POSTFIX &&
(codePoint == 0x1EEF0 || codePoint == 0x1EEF1)) {
// Use category I.
*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.
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;
}