fune/layout/mathml/nsMathMLOperators.cpp
Simon Giesecke ad01a10a3b Bug 1634281 - Use nsTHashMap instead of nsDataHashtable. r=xpcom-reviewers,necko-reviewers,jgilbert,nika,valentin
Note that this patch only transforms the use of the nsDataHashtable type alias
to a directly equivalent use of nsTHashMap. It does not change the specification
of the hash key type to make use of the key class deduction that nsTHashMap
allows for in some cases. That can be done in a separate step, but requires more
attention.

Differential Revision: https://phabricator.services.mozilla.com/D106008
2021-03-10 10:47:47 +00:00

426 lines
15 KiB
C++

/* -*- 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 "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 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->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,
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;
}
}