Merge mozilla-central and tracemonkey. (a=blockers)
[mozilla-central.git] / layout / mathml / nsMathMLOperators.cpp
blobb62ad17701e8128dbb0959eccf21e3483bf5269c
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla MathML Project.
17 * The Initial Developer of the Original Code is
18 * The University Of Queensland.
19 * Portions created by the Initial Developer are Copyright (C) 1999
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Roger B. Sidje <rbs@maths.uq.edu.au>
24 * Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
25 * Frederic Wang <fred.wang@free.fr>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsCOMPtr.h"
42 #include "nsString.h"
43 #include "nsHashtable.h"
44 #include "nsTArray.h"
46 #include "nsIComponentManager.h"
47 #include "nsIPersistentProperties2.h"
48 #include "nsNetUtil.h"
49 #include "nsCRT.h"
51 #include "nsMathMLOperators.h"
53 // operator dictionary entry
54 struct OperatorData {
55 OperatorData(void)
56 : mFlags(0),
57 mLeftSpace(0.0f),
58 mRightSpace(0.0f)
62 // member data
63 nsString mStr;
64 nsOperatorFlags mFlags;
65 float mLeftSpace; // unit is em
66 float mRightSpace; // unit is em
69 static PRInt32 gTableRefCount = 0;
70 static PRUint32 gOperatorCount = 0;
71 static OperatorData* gOperatorArray = nsnull;
72 static nsHashtable* gOperatorTable = nsnull;
73 static PRBool gInitialized = PR_FALSE;
74 static nsTArray<nsString>* gInvariantCharArray = nsnull;
76 static const PRUnichar kNullCh = PRUnichar('\0');
77 static const PRUnichar kDashCh = PRUnichar('#');
78 static const PRUnichar kColonCh = PRUnichar(':');
80 static const char* const kMathVariant_name[] = {
81 "normal",
82 "bold",
83 "italic",
84 "bold-italic",
85 "sans-serif",
86 "bold-sans-serif",
87 "sans-serif-italic",
88 "sans-serif-bold-italic",
89 "monospace",
90 "script",
91 "bold-script",
92 "fraktur",
93 "bold-fraktur",
94 "double-struck"
97 static void
98 SetBooleanProperty(OperatorData* aOperatorData,
99 nsString aName)
101 if (aName.IsEmpty())
102 return;
104 if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length()))
105 aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY;
106 else if (aName.EqualsLiteral("fence"))
107 aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE;
108 else if (aName.EqualsLiteral("accent"))
109 aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT;
110 else if (aName.EqualsLiteral("largeop"))
111 aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP;
112 else if (aName.EqualsLiteral("separator"))
113 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR;
114 else if (aName.EqualsLiteral("movablelimits"))
115 aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS;
116 else if (aName.EqualsLiteral("symmetric"))
117 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC;
118 else if (aName.EqualsLiteral("integral"))
119 aOperatorData->mFlags |= NS_MATHML_OPERATOR_INTEGRAL;
122 static void
123 SetProperty(OperatorData* aOperatorData,
124 nsString aName,
125 nsString aValue)
127 if (aName.IsEmpty() || aValue.IsEmpty())
128 return;
130 // XXX These ones are not kept in the dictionary
131 // Support for these requires nsString member variables
132 // maxsize (default: infinity)
133 // minsize (default: 1)
135 if (aName.EqualsLiteral("direction")) {
136 if (aValue.EqualsLiteral("vertical"))
137 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL;
138 else if (aValue.EqualsLiteral("horizontal"))
139 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL;
140 else return; // invalid value
141 } else {
142 PRBool isLeftSpace;
143 if (aName.EqualsLiteral("lspace"))
144 isLeftSpace = PR_TRUE;
145 else if (aName.EqualsLiteral("rspace"))
146 isLeftSpace = PR_FALSE;
147 else return; // input is not applicable
149 // aValue is assumed to be a digit from 0 to 7
150 PRInt32 error = 0;
151 float space = aValue.ToFloat(&error) / 18.0;
152 if (error) return;
154 if (isLeftSpace)
155 aOperatorData->mLeftSpace = space;
156 else
157 aOperatorData->mRightSpace = space;
161 static PRBool
162 SetOperator(OperatorData* aOperatorData,
163 nsOperatorFlags aForm,
164 const nsCString& aOperator,
165 nsString& aAttributes)
168 // aOperator is in the expanded format \uNNNN\uNNNN ...
169 // First compress these Unicode points to the internal nsString format
170 PRInt32 i = 0;
171 nsAutoString name, value;
172 PRInt32 len = aOperator.Length();
173 PRUnichar c = aOperator[i++];
174 PRUint32 state = 0;
175 PRUnichar uchar = 0;
176 while (i <= len) {
177 if (0 == state) {
178 if (c != '\\')
179 return PR_FALSE;
180 if (i < len)
181 c = aOperator[i];
182 i++;
183 if (('u' != c) && ('U' != c))
184 return PR_FALSE;
185 if (i < len)
186 c = aOperator[i];
187 i++;
188 state++;
190 else {
191 if (('0' <= c) && (c <= '9'))
192 uchar = (uchar << 4) | (c - '0');
193 else if (('a' <= c) && (c <= 'f'))
194 uchar = (uchar << 4) | (c - 'a' + 0x0a);
195 else if (('A' <= c) && (c <= 'F'))
196 uchar = (uchar << 4) | (c - 'A' + 0x0a);
197 else return PR_FALSE;
198 if (i < len)
199 c = aOperator[i];
200 i++;
201 state++;
202 if (5 == state) {
203 value.Append(uchar);
204 uchar = 0;
205 state = 0;
209 if (0 != state) return PR_FALSE;
211 // Quick return when the caller doesn't care about the attributes and just wants
212 // to know if this is a valid operator (this is the case at the first pass of the
213 // parsing of the dictionary in InitOperators())
214 if (!aForm) return PR_TRUE;
216 // Add operator to hash table
217 aOperatorData->mFlags |= aForm;
218 aOperatorData->mStr.Assign(value);
219 value.AppendInt(aForm, 10);
220 nsStringKey key(value);
221 gOperatorTable->Put(&key, aOperatorData);
223 #ifdef NS_DEBUG
224 NS_LossyConvertUTF16toASCII str(aAttributes);
225 #endif
226 // Loop over the space-delimited list of attributes to get the name:value pairs
227 aAttributes.Append(kNullCh); // put an extra null at the end
228 PRUnichar* start = aAttributes.BeginWriting();
229 PRUnichar* end = start;
230 while ((kNullCh != *start) && (kDashCh != *start)) {
231 name.SetLength(0);
232 value.SetLength(0);
233 // skip leading space, the dash amounts to the end of the line
234 while ((kNullCh!=*start) && (kDashCh!=*start) && nsCRT::IsAsciiSpace(*start)) {
235 ++start;
237 end = start;
238 // look for ':'
239 while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) &&
240 (kColonCh!=*end)) {
241 ++end;
243 // If ':' is not found, then it's a boolean property
244 PRBool IsBooleanProperty = (kColonCh != *end);
245 *end = kNullCh; // end segment here
246 // this segment is the name
247 if (start < end) {
248 name.Assign(start);
250 if (IsBooleanProperty) {
251 SetBooleanProperty(aOperatorData, name);
252 } else {
253 start = ++end;
254 // look for space or end of line
255 while ((kNullCh!=*end) && (kDashCh!=*end) &&
256 !nsCRT::IsAsciiSpace(*end)) {
257 ++end;
259 *end = kNullCh; // end segment here
260 if (start < end) {
261 // this segment is the value
262 value.Assign(start);
264 SetProperty(aOperatorData, name, value);
266 start = ++end;
268 return PR_TRUE;
271 static nsresult
272 InitOperators(void)
274 // Load the property file containing the Operator Dictionary
275 nsresult rv;
276 nsCOMPtr<nsIPersistentProperties> mathfontProp;
277 rv = NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(mathfontProp),
278 NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));
279 if (NS_FAILED(rv)) return rv;
281 // Get the list of invariant chars
282 for (PRInt32 i = 0; i < eMATHVARIANT_COUNT; ++i) {
283 nsCAutoString key(NS_LITERAL_CSTRING("mathvariant."));
284 key.Append(kMathVariant_name[i]);
285 nsAutoString value;
286 mathfontProp->GetStringProperty(key, value);
287 gInvariantCharArray->AppendElement(value); // i.e., gInvariantCharArray[i] holds this list
290 // Parse the Operator Dictionary in two passes.
291 // The first pass is to count the number of operators; the second pass is to
292 // allocate the necessary space for them and to add them in the hash table.
293 for (PRInt32 pass = 1; pass <= 2; pass++) {
294 OperatorData dummyData;
295 OperatorData* operatorData = &dummyData;
296 nsCOMPtr<nsISimpleEnumerator> iterator;
297 if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) {
298 PRBool more;
299 PRUint32 index = 0;
300 nsCAutoString name;
301 nsAutoString attributes;
302 while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) {
303 nsCOMPtr<nsIPropertyElement> element;
304 if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(element)))) {
305 if (NS_SUCCEEDED(element->GetKey(name)) &&
306 NS_SUCCEEDED(element->GetValue(attributes))) {
307 // expected key: operator.\uNNNN.{infix,postfix,prefix}
308 if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) {
309 name.Cut(0, 9); // 9 is the length of "operator.";
310 PRInt32 len = name.Length();
311 nsOperatorFlags form = 0;
312 if (kNotFound != name.RFind(".infix")) {
313 form = NS_MATHML_OPERATOR_FORM_INFIX;
314 len -= 6; // 6 is the length of ".infix";
316 else if (kNotFound != name.RFind(".postfix")) {
317 form = NS_MATHML_OPERATOR_FORM_POSTFIX;
318 len -= 8; // 8 is the length of ".postfix";
320 else if (kNotFound != name.RFind(".prefix")) {
321 form = NS_MATHML_OPERATOR_FORM_PREFIX;
322 len -= 7; // 7 is the length of ".prefix";
324 else continue; // input is not applicable
325 name.SetLength(len);
326 if (2 == pass) { // allocate space and start the storage
327 if (!gOperatorArray) {
328 if (0 == gOperatorCount) return NS_ERROR_UNEXPECTED;
329 gOperatorArray = new OperatorData[gOperatorCount];
330 if (!gOperatorArray) return NS_ERROR_OUT_OF_MEMORY;
332 operatorData = &gOperatorArray[index];
334 else {
335 form = 0; // to quickly return from SetOperator() at pass 1
337 // See if the operator should be retained
338 if (SetOperator(operatorData, form, name, attributes)) {
339 index++;
340 if (1 == pass) gOperatorCount = index;
348 return NS_OK;
351 static nsresult
352 InitGlobals()
354 gInitialized = PR_TRUE;
355 nsresult rv = NS_ERROR_OUT_OF_MEMORY;
356 gInvariantCharArray = new nsTArray<nsString>();
357 if (gInvariantCharArray) {
358 gOperatorTable = new nsHashtable();
359 if (gOperatorTable) {
360 rv = InitOperators();
363 if (NS_FAILED(rv))
364 nsMathMLOperators::CleanUp();
365 return rv;
368 void
369 nsMathMLOperators::CleanUp()
371 if (gInvariantCharArray) {
372 delete gInvariantCharArray;
373 gInvariantCharArray = nsnull;
375 if (gOperatorArray) {
376 delete[] gOperatorArray;
377 gOperatorArray = nsnull;
379 if (gOperatorTable) {
380 delete gOperatorTable;
381 gOperatorTable = nsnull;
385 void
386 nsMathMLOperators::AddRefTable(void)
388 gTableRefCount++;
391 void
392 nsMathMLOperators::ReleaseTable(void)
394 if (0 == --gTableRefCount) {
395 CleanUp();
399 static OperatorData*
400 GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm)
402 nsAutoString key(aOperator);
403 key.AppendInt(aForm);
404 nsStringKey hkey(key);
405 return (OperatorData*)gOperatorTable->Get(&hkey);
408 PRBool
409 nsMathMLOperators::LookupOperator(const nsString& aOperator,
410 const nsOperatorFlags aForm,
411 nsOperatorFlags* aFlags,
412 float* aLeftSpace,
413 float* aRightSpace)
415 if (!gInitialized) {
416 InitGlobals();
418 if (gOperatorTable) {
419 NS_ASSERTION(aFlags && aLeftSpace && aRightSpace, "bad usage");
420 NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***");
422 // The MathML REC says:
423 // If the operator does not occur in the dictionary with the specified form,
424 // the renderer should use one of the forms which is available there, in the
425 // order of preference: infix, postfix, prefix.
427 OperatorData* found;
428 PRInt32 form = NS_MATHML_OPERATOR_GET_FORM(aForm);
429 if (!(found = GetOperatorData(aOperator, form))) {
430 if (form == NS_MATHML_OPERATOR_FORM_INFIX ||
431 !(found =
432 GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX))) {
433 if (form == NS_MATHML_OPERATOR_FORM_POSTFIX ||
434 !(found =
435 GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX))) {
436 if (form != NS_MATHML_OPERATOR_FORM_PREFIX) {
437 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
442 if (found) {
443 NS_ASSERTION(found->mStr.Equals(aOperator), "bad setup");
444 *aLeftSpace = found->mLeftSpace;
445 *aRightSpace = found->mRightSpace;
446 *aFlags &= ~NS_MATHML_OPERATOR_FORM; // clear the form bits
447 *aFlags |= found->mFlags; // just add bits without overwriting
448 return PR_TRUE;
451 return PR_FALSE;
454 void
455 nsMathMLOperators::LookupOperators(const nsString& aOperator,
456 nsOperatorFlags* aFlags,
457 float* aLeftSpace,
458 float* aRightSpace)
460 if (!gInitialized) {
461 InitGlobals();
464 aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = 0;
465 aLeftSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
466 aRightSpace[NS_MATHML_OPERATOR_FORM_INFIX] = 0.0f;
468 aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0;
469 aLeftSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
470 aRightSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = 0.0f;
472 aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = 0;
473 aLeftSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
474 aRightSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = 0.0f;
476 if (gOperatorTable) {
477 OperatorData* found;
478 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_INFIX);
479 if (found) {
480 aFlags[NS_MATHML_OPERATOR_FORM_INFIX] = found->mFlags;
481 aLeftSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mLeftSpace;
482 aRightSpace[NS_MATHML_OPERATOR_FORM_INFIX] = found->mRightSpace;
484 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_POSTFIX);
485 if (found) {
486 aFlags[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mFlags;
487 aLeftSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mLeftSpace;
488 aRightSpace[NS_MATHML_OPERATOR_FORM_POSTFIX] = found->mRightSpace;
490 found = GetOperatorData(aOperator, NS_MATHML_OPERATOR_FORM_PREFIX);
491 if (found) {
492 aFlags[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mFlags;
493 aLeftSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mLeftSpace;
494 aRightSpace[NS_MATHML_OPERATOR_FORM_PREFIX] = found->mRightSpace;
499 PRBool
500 nsMathMLOperators::IsMutableOperator(const nsString& aOperator)
502 if (!gInitialized) {
503 InitGlobals();
505 // lookup all the variants of the operator and return true if there
506 // is a variant that is stretchy or largeop
507 nsOperatorFlags flags[4];
508 float lspace[4], rspace[4];
509 nsMathMLOperators::LookupOperators(aOperator, flags, lspace, rspace);
510 nsOperatorFlags allFlags =
511 flags[NS_MATHML_OPERATOR_FORM_INFIX] |
512 flags[NS_MATHML_OPERATOR_FORM_POSTFIX] |
513 flags[NS_MATHML_OPERATOR_FORM_PREFIX];
514 return NS_MATHML_OPERATOR_IS_STRETCHY(allFlags) ||
515 NS_MATHML_OPERATOR_IS_LARGEOP(allFlags);
518 /* static */ nsStretchDirection
519 nsMathMLOperators::GetStretchyDirection(const nsString& aOperator)
521 // LookupOperator will search infix, postfix and prefix forms of aOperator and
522 // return the first form found. It is assumed that all these forms have same
523 // direction.
524 nsOperatorFlags flags = 0;
525 float dummy;
526 nsMathMLOperators::LookupOperator(aOperator,
527 NS_MATHML_OPERATOR_FORM_INFIX,
528 &flags, &dummy, &dummy);
530 if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) {
531 return NS_STRETCH_DIRECTION_VERTICAL;
532 } else if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) {
533 return NS_STRETCH_DIRECTION_HORIZONTAL;
534 } else {
535 return NS_STRETCH_DIRECTION_UNSUPPORTED;
539 /* static */ eMATHVARIANT
540 nsMathMLOperators::LookupInvariantChar(const nsAString& aChar)
542 if (!gInitialized) {
543 InitGlobals();
545 if (gInvariantCharArray) {
546 for (PRInt32 i = gInvariantCharArray->Length()-1; i >= 0; --i) {
547 const nsString& list = gInvariantCharArray->ElementAt(i);
548 nsString::const_iterator start, end;
549 list.BeginReading(start);
550 list.EndReading(end);
551 // Style-invariant characters are at offset 3*j + 1.
552 if (FindInReadable(aChar, start, end) &&
553 start.size_backward() % 3 == 1) {
554 return eMATHVARIANT(i);
558 return eMATHVARIANT_NONE;
561 /* static */ const nsDependentSubstring
562 nsMathMLOperators::TransformVariantChar(const PRUnichar& aChar,
563 eMATHVARIANT aVariant)
565 if (!gInitialized) {
566 InitGlobals();
568 if (gInvariantCharArray) {
569 nsString list = gInvariantCharArray->ElementAt(aVariant);
570 PRInt32 index = list.FindChar(aChar);
571 // BMP characters are at offset 3*j
572 if (index != kNotFound && index % 3 == 0 && list.Length() - index >= 2 ) {
573 // The style-invariant character is the next character
574 // (and list should contain padding if the next character is in the BMP).
575 ++index;
576 PRUint32 len = NS_IS_HIGH_SURROGATE(list.CharAt(index)) ? 2 : 1;
577 return nsDependentSubstring(list, index, len);
580 return nsDependentSubstring(&aChar, &aChar + 1);