Bumping manifests a=b2g-bump
[gecko.git] / layout / mathml / nsMathMLOperators.cpp
blob7f1b0070a520f6b7b33d4e8c121581c77f7e8512
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsMathMLOperators.h"
7 #include "nsCOMPtr.h"
8 #include "nsDataHashtable.h"
9 #include "nsHashKeys.h"
10 #include "nsTArray.h"
12 #include "nsIPersistentProperties2.h"
13 #include "nsNetUtil.h"
14 #include "nsCRT.h"
16 // operator dictionary entry
17 struct OperatorData {
18 OperatorData(void)
19 : mFlags(0),
20 mLeadingSpace(0.0f),
21 mTrailingSpace(0.0f)
25 // member data
26 nsString mStr;
27 nsOperatorFlags mFlags;
28 float mLeadingSpace; // unit is em
29 float mTrailingSpace; // unit is em
32 static int32_t gTableRefCount = 0;
33 static uint32_t gOperatorCount = 0;
34 static OperatorData* gOperatorArray = nullptr;
35 static nsDataHashtable<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr;
36 static bool gGlobalsInitialized = false;
38 static const char16_t kDashCh = char16_t('#');
39 static const char16_t kColonCh = char16_t(':');
41 static void
42 SetBooleanProperty(OperatorData* aOperatorData,
43 nsString aName)
45 if (aName.IsEmpty())
46 return;
48 if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length()))
49 aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY;
50 else if (aName.EqualsLiteral("fence"))
51 aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE;
52 else if (aName.EqualsLiteral("accent"))
53 aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT;
54 else if (aName.EqualsLiteral("largeop"))
55 aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP;
56 else if (aName.EqualsLiteral("separator"))
57 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR;
58 else if (aName.EqualsLiteral("movablelimits"))
59 aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS;
60 else if (aName.EqualsLiteral("symmetric"))
61 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
62 else if (aName.EqualsLiteral("integral"))
63 aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL;
64 else if (aName.EqualsLiteral("mirrorable"))
65 aOperatorData->mFlags |= NS_MATHML_OPERATOR_MIRRORABLE;
68 static void
69 SetProperty(OperatorData* aOperatorData,
70 nsString aName,
71 nsString aValue)
73 if (aName.IsEmpty() || aValue.IsEmpty())
74 return;
76 // XXX These ones are not kept in the dictionary
77 // Support for these requires nsString member variables
78 // maxsize (default: infinity)
79 // minsize (default: 1)
81 if (aName.EqualsLiteral("direction")) {
82 if (aValue.EqualsLiteral("vertical"))
83 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL;
84 else if (aValue.EqualsLiteral("horizontal"))
85 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
86 else return; // invalid value
87 } else {
88 bool isLeadingSpace;
89 if (aName.EqualsLiteral("lspace"))
90 isLeadingSpace = true;
91 else if (aName.EqualsLiteral("rspace"))
92 isLeadingSpace = false;
93 else return; // input is not applicable
95 // aValue is assumed to be a digit from 0 to 7
96 nsresult error = NS_OK;
97 float space = aValue.ToFloat(&error) / 18.0;
98 if (NS_FAILED(error)) return;
100 if (isLeadingSpace)
101 aOperatorData->mLeadingSpace = space;
102 else
103 aOperatorData->mTrailingSpace = space;
107 static bool
108 SetOperator(OperatorData* aOperatorData,
109 nsOperatorFlags aForm,
110 const nsCString& aOperator,
111 nsString& aAttributes)
114 static const char16_t kNullCh = char16_t('\0');
116 // aOperator is in the expanded format \uNNNN\uNNNN ...
117 // First compress these Unicode points to the internal nsString format
118 int32_t i = 0;
119 nsAutoString name, value;
120 int32_t len = aOperator.Length();
121 char16_t c = aOperator[i++];
122 uint32_t state = 0;
123 char16_t uchar = 0;
124 while (i <= len) {
125 if (0 == state) {
126 if (c != '\\')
127 return false;
128 if (i < len)
129 c = aOperator[i];
130 i++;
131 if (('u' != c) && ('U' != c))
132 return false;
133 if (i < len)
134 c = aOperator[i];
135 i++;
136 state++;
138 else {
139 if (('0' <= c) && (c <= '9'))
140 uchar = (uchar << 4) | (c - '0');
141 else if (('a' <= c) && (c <= 'f'))
142 uchar = (uchar << 4) | (c - 'a' + 0x0a);
143 else if (('A' <= c) && (c <= 'F'))
144 uchar = (uchar << 4) | (c - 'A' + 0x0a);
145 else return false;
146 if (i < len)
147 c = aOperator[i];
148 i++;
149 state++;
150 if (5 == state) {
151 value.Append(uchar);
152 uchar = 0;
153 state = 0;
157 if (0 != state) return false;
159 // Quick return when the caller doesn't care about the attributes and just wants
160 // to know if this is a valid operator (this is the case at the first pass of the
161 // parsing of the dictionary in InitOperators())
162 if (!aForm) return true;
164 // Add operator to hash table
165 aOperatorData->mFlags |= aForm;
166 aOperatorData->mStr.Assign(value);
167 value.AppendInt(aForm, 10);
168 gOperatorTable->Put(value, aOperatorData);
170 #ifdef DEBUG
171 NS_LossyConvertUTF16toASCII str(aAttributes);
172 #endif
173 // Loop over the space-delimited list of attributes to get the name:value pairs
174 aAttributes.Append(kNullCh); // put an extra null at the end
175 char16_t* start = aAttributes.BeginWriting();
176 char16_t* end = start;
177 while ((kNullCh != *start) && (kDashCh != *start)) {
178 name.SetLength(0);
179 value.SetLength(0);
180 // skip leading space, the dash amounts to the end of the line
181 while ((kNullCh!=*start) && (kDashCh!=*start) && nsCRT::IsAsciiSpace(*start)) {
182 ++start;
184 end = start;
185 // look for ':'
186 while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) &&
187 (kColonCh!=*end)) {
188 ++end;
190 // If ':' is not found, then it's a boolean property
191 bool IsBooleanProperty = (kColonCh != *end);
192 *end = kNullCh; // end segment here
193 // this segment is the name
194 if (start < end) {
195 name.Assign(start);
197 if (IsBooleanProperty) {
198 SetBooleanProperty(aOperatorData, name);
199 } else {
200 start = ++end;
201 // look for space or end of line
202 while ((kNullCh!=*end) && (kDashCh!=*end) &&
203 !nsCRT::IsAsciiSpace(*end)) {
204 ++end;
206 *end = kNullCh; // end segment here
207 if (start < end) {
208 // this segment is the value
209 value.Assign(start);
211 SetProperty(aOperatorData, name, value);
213 start = ++end;
215 return true;
218 static nsresult
219 InitOperators(void)
221 // Load the property file containing the Operator Dictionary
222 nsresult rv;
223 nsCOMPtr<nsIPersistentProperties> mathfontProp;
224 rv = NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(mathfontProp),
225 NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));
226 if (NS_FAILED(rv)) return rv;
228 // Parse the Operator Dictionary in two passes.
229 // The first pass is to count the number of operators; the second pass is to
230 // allocate the necessary space for them and to add them in the hash table.
231 for (int32_t pass = 1; pass <= 2; pass++) {
232 OperatorData dummyData;
233 OperatorData* operatorData = &dummyData;
234 nsCOMPtr<nsISimpleEnumerator> iterator;
235 if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) {
236 bool more;
237 uint32_t index = 0;
238 nsAutoCString name;
239 nsAutoString attributes;
240 while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) {
241 nsCOMPtr<nsISupports> supports;
242 nsCOMPtr<nsIPropertyElement> element;
243 if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) {
244 element = do_QueryInterface(supports);
245 if (NS_SUCCEEDED(element->GetKey(name)) &&
246 NS_SUCCEEDED(element->GetValue(attributes))) {
247 // expected key: operator.\uNNNN.{infix,postfix,prefix}
248 if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) {
249 name.Cut(0, 9); // 9 is the length of "operator.";
250 int32_t len = name.Length();
251 nsOperatorFlags form = 0;
252 if (kNotFound != name.RFind(".infix")) {
253 form = NS_MATHML_OPERATOR_FORM_INFIX;
254 len -= 6; // 6 is the length of ".infix";
256 else if (kNotFound != name.RFind(".postfix")) {
257 form = NS_MATHML_OPERATOR_FORM_POSTFIX;
258 len -= 8; // 8 is the length of ".postfix";
260 else if (kNotFound != name.RFind(".prefix")) {
261 form = NS_MATHML_OPERATOR_FORM_PREFIX;
262 len -= 7; // 7 is the length of ".prefix";
264 else continue; // input is not applicable
265 name.SetLength(len);
266 if (2 == pass) { // allocate space and start the storage
267 if (!gOperatorArray) {
268 if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED;
269 gOperatorArray = new OperatorData[gOperatorCount];
270 if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY;
272 operatorData = &gOperatorArray[index];
274 else {
275 form = 0; // to quickly return from SetOperator() at pass 1
277 // See if the operator should be retained
278 if (SetOperator(operatorData, form, name, attributes)) {
279 index++;
280 if (1 == pass) gOperatorCount = index;
288 return NS_OK;
291 static nsresult
292 InitGlobals()
294 gGlobalsInitialized = true;
295 nsresult rv = NS_ERROR_OUT_OF_MEMORY;
296 gOperatorTable = new nsDataHashtable<nsStringHashKey, OperatorData*>();
297 if (gOperatorTable) {
298 rv = InitOperators();
300 if (NS_FAILED(rv))
301 nsMathMLOperators::CleanUp();
302 return rv;
305 void
306 nsMathMLOperators::CleanUp()
308 if (gOperatorArray) {
309 delete[] gOperatorArray;
310 gOperatorArray = nullptr;
312 if (gOperatorTable) {
313 delete gOperatorTable;
314 gOperatorTable = nullptr;
318 void
319 nsMathMLOperators::AddRefTable(void)
321 gTableRefCount++;
324 void
325 nsMathMLOperators::ReleaseTable(void)
327 if (0 == --gTableRefCount) {
328 CleanUp();
332 static OperatorData*
333 GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm)
335 nsAutoString key(aOperator);
336 key.AppendInt(aForm);
337 return gOperatorTable->Get(key);
340 bool
341 nsMathMLOperators::LookupOperator(const nsString& aOperator,
342 const nsOperatorFlags aForm,
343 nsOperatorFlags* aFlags,
344 float* aLeadingSpace,
345 float* aTrailingSpace)
347 if (!gGlobalsInitialized) {
348 InitGlobals();
350 if (gOperatorTable) {
351 NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage");
352 NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
354 // The MathML REC says:
355 // If the operator does not occur in the dictionary with the specified form,
356 // the renderer should use one of the forms which is available there, in the
357 // order of preference: infix, postfix, prefix.
359 OperatorData* found;
360 int32_t form = NS_MATHML_OPERATOR_GET_FORM(aForm);
361 if (!(found = GetOperatorData(aOperator, form))) {
362 if (form == NS_MATHML_OPERATOR_FORM_INFIX ||
363 !(found =
364 GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) {
365 if (form == NS_MATHML_OPERATOR_FORM_POSTFIX ||
366 !(found =
367 GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) {
368 if (form != NS_MATHML_OPERATOR_FORM_PREFIX) {
369 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
374 if (found) {
375 NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup");
376 *aLeadingSpace = found->mLeadingSpace;
377 *aTrailingSpace = found->mTrailingSpace;
378 *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
379 *aFlags |= found->mFlags; // just add bits without overwriting
380 return true;
383 return false;
386 void
387 nsMathMLOperators::LookupOperators(const nsString& aOperator,
388 nsOperatorFlags* aFlags,
389 float* aLeadingSpace,
390 float* aTrailingSpace)
392 if (!gGlobalsInitialized) {
393 InitGlobals();
396 aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0;
397 aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
398 aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
400 aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0;
401 aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
402 aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
404 aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0;
405 aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
406 aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
408 if (gOperatorTable) {
409 OperatorData* found;
410 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX);
411 if (found) {
412 aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags;
413 aLeadingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeadingSpace;
414 aTrailingSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mTrailingSpace;
416 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX);
417 if (found) {
418 aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags;
419 aLeadingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeadingSpace;
420 aTrailingSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mTrailingSpace;
422 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
423 if (found) {
424 aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags;
425 aLeadingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeadingSpace;
426 aTrailingSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mTrailingSpace;
431 /* static */ bool
432 nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator)
434 // LookupOperator will search infix, postfix and prefix forms of aOperator and
435 // return the first form found. It is assumed that all these forms have same
436 // mirrorability.
437 nsOperatorFlags flags = 0;
438 float dummy;
439 nsMathMLOperators::LookupOperator(aOperator,
440 NS_MATHML_OPERATOR_FORM_INFIX,
441 &flags, &dummy, &dummy);
442 return NS_MATHML_OPERATOR_IS_MIRRORABLE(flags);
445 /* static */ nsStretchDirection
446 nsMathMLOperators::GetStretchyDirection(const nsString& aOperator)
448 // LookupOperator will search infix, postfix and prefix forms of aOperator and
449 // return the first form found. It is assumed that all these forms have same
450 // direction.
451 nsOperatorFlags flags = 0;
452 float dummy;
453 nsMathMLOperators::LookupOperator(aOperator,
454 NS_MATHML_OPERATOR_FORM_INFIX,
455 &flags, &dummy, &dummy);
457 if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
458 return NS_STRETCH_DIRECTION_VERTICAL;
459 } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
460 return NS_STRETCH_DIRECTION_HORIZONTAL;
461 } else {
462 return NS_STRETCH_DIRECTION_UNSUPPORTED;