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
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.
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 ***** */
43 #include "nsHashtable.h"
46 #include "nsIComponentManager.h"
47 #include "nsIPersistentProperties2.h"
48 #include "nsNetUtil.h"
51 #include "nsMathMLOperators.h"
53 // operator dictionary entry
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
[] = {
88 "sans-serif-bold-italic",
98 SetBooleanProperty(OperatorData
* aOperatorData
,
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
;
123 SetProperty(OperatorData
* aOperatorData
,
127 if (aName
.IsEmpty() || aValue
.IsEmpty())
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
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
151 float space
= aValue
.ToFloat(&error
) / 18.0;
155 aOperatorData
->mLeftSpace
= space
;
157 aOperatorData
->mRightSpace
= space
;
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
171 nsAutoString name
, value
;
172 PRInt32 len
= aOperator
.Length();
173 PRUnichar c
= aOperator
[i
++];
183 if (('u' != c
) && ('U' != c
))
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
;
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
);
224 NS_LossyConvertUTF16toASCII
str(aAttributes
);
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
)) {
233 // skip leading space, the dash amounts to the end of the line
234 while ((kNullCh
!=*start
) && (kDashCh
!=*start
) && nsCRT::IsAsciiSpace(*start
)) {
239 while ((kNullCh
!=*end
) && (kDashCh
!=*end
) && !nsCRT::IsAsciiSpace(*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
250 if (IsBooleanProperty
) {
251 SetBooleanProperty(aOperatorData
, name
);
254 // look for space or end of line
255 while ((kNullCh
!=*end
) && (kDashCh
!=*end
) &&
256 !nsCRT::IsAsciiSpace(*end
)) {
259 *end
= kNullCh
; // end segment here
261 // this segment is the value
264 SetProperty(aOperatorData
, name
, value
);
274 // Load the property file containing the Operator Dictionary
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
]);
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
)))) {
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
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
];
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
)) {
340 if (1 == pass
) gOperatorCount
= index
;
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();
364 nsMathMLOperators::CleanUp();
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
;
386 nsMathMLOperators::AddRefTable(void)
392 nsMathMLOperators::ReleaseTable(void)
394 if (0 == --gTableRefCount
) {
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
);
409 nsMathMLOperators::LookupOperator(const nsString
& aOperator
,
410 const nsOperatorFlags aForm
,
411 nsOperatorFlags
* aFlags
,
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.
428 PRInt32 form
= NS_MATHML_OPERATOR_GET_FORM(aForm
);
429 if (!(found
= GetOperatorData(aOperator
, form
))) {
430 if (form
== NS_MATHML_OPERATOR_FORM_INFIX
||
432 GetOperatorData(aOperator
, NS_MATHML_OPERATOR_FORM_INFIX
))) {
433 if (form
== NS_MATHML_OPERATOR_FORM_POSTFIX
||
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
);
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
455 nsMathMLOperators::LookupOperators(const nsString
& aOperator
,
456 nsOperatorFlags
* aFlags
,
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
) {
478 found
= GetOperatorData(aOperator
, NS_MATHML_OPERATOR_FORM_INFIX
);
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
);
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
);
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
;
500 nsMathMLOperators::IsMutableOperator(const nsString
& aOperator
)
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
524 nsOperatorFlags flags
= 0;
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
;
535 return NS_STRETCH_DIRECTION_UNSUPPORTED
;
539 /* static */ eMATHVARIANT
540 nsMathMLOperators::LookupInvariantChar(const nsAString
& aChar
)
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
)
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).
576 PRUint32 len
= NS_IS_HIGH_SURROGATE(list
.CharAt(index
)) ? 2 : 1;
577 return nsDependentSubstring(list
, index
, len
);
580 return nsDependentSubstring(&aChar
, &aChar
+ 1);