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
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
42 #include "nsHashtable.h"
43 #include "nsVoidArray.h"
45 #include "nsIComponentManager.h"
46 #include "nsIPersistentProperties2.h"
47 #include "nsNetUtil.h"
50 #include "nsMathMLOperators.h"
52 // operator dictionary entry
63 nsOperatorFlags mFlags
;
64 float mLeftSpace
; // unit is em
65 float mRightSpace
; // unit is em
70 "If the operator does not occur in the dictionary with the specified form,
71 the renderer should use one of the forms which is available there, in the
72 order of preference: infix, postfix, prefix."
74 The following variable will be used to keep track of all possible forms
75 encountered in the Operator Dictionary.
77 static OperatorData
* gOperatorFound
[4];
79 static PRInt32 gTableRefCount
= 0;
80 static PRInt32 gOperatorCount
= 0;
81 static OperatorData
* gOperatorArray
= nsnull
;
82 static nsHashtable
* gOperatorTable
= nsnull
;
83 static nsVoidArray
* gStretchyOperatorArray
= nsnull
;
84 static nsStringArray
* gInvariantCharArray
= nsnull
;
85 static PRBool gInitialized
= PR_FALSE
;
87 static const PRUnichar kNullCh
= PRUnichar('\0');
88 static const PRUnichar kDashCh
= PRUnichar('#');
89 static const PRUnichar kEqualCh
= PRUnichar('=');
90 static const PRUnichar kColonCh
= PRUnichar(':');
92 static const char* const kMathVariant_name
[] = {
100 "sans-serif-bold-italic",
110 SetProperty(OperatorData
* aOperatorData
,
114 if (!aName
.Length() || !aValue
.Length())
117 // XXX These ones are not kept in the dictionary
118 // Support for these requires nsString member variables
119 // maxsize (default: infinity)
120 // minsize (default: 1)
122 if (aValue
.EqualsLiteral("true")) {
123 // see if we should enable flags with default value=false
124 if (aName
.EqualsLiteral("fence"))
125 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_FENCE
;
126 else if (aName
.EqualsLiteral("accent"))
127 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_ACCENT
;
128 else if (aName
.EqualsLiteral("largeop"))
129 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_LARGEOP
;
130 else if (aName
.EqualsLiteral("separator"))
131 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_SEPARATOR
;
132 else if (aName
.EqualsLiteral("movablelimits"))
133 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_MOVABLELIMITS
;
135 else if (aValue
.EqualsLiteral("false")) {
136 // see if we should disable flags with default value=true
137 if (aName
.EqualsLiteral("symmetric"))
138 aOperatorData
->mFlags
&= ~NS_MATHML_OPERATOR_SYMMETRIC
;
140 else if (aName
.EqualsLiteral("stretchy") &&
141 (1 == aOperatorData
->mStr
.Length())) {
142 if (aValue
.EqualsLiteral("vertical"))
143 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_STRETCHY_VERT
;
144 else if (aValue
.EqualsLiteral("horizontal"))
145 aOperatorData
->mFlags
|= NS_MATHML_OPERATOR_STRETCHY_HORIZ
;
146 else return; // invalid value
147 if (kNotFound
== nsMathMLOperators::FindStretchyOperator(aOperatorData
->mStr
[0])) {
148 gStretchyOperatorArray
->AppendElement(aOperatorData
);
155 if (aName
.EqualsLiteral("lspace"))
156 isLeftSpace
= PR_TRUE
;
157 else if (aName
.EqualsLiteral("rspace"))
158 isLeftSpace
= PR_FALSE
;
159 else return; // input is not applicable
161 // See if it is a numeric value (unit is assumed to be 'em')
162 if (nsCRT::IsAsciiDigit(aValue
[0])) {
164 space
= aValue
.ToFloat(&error
);
167 // See if it is one of the 'namedspace' (ranging 1/18em...7/18em)
168 else if (aValue
.EqualsLiteral("veryverythinmathspace")) i
= 1;
169 else if (aValue
.EqualsLiteral("verythinmathspace")) i
= 2;
170 else if (aValue
.EqualsLiteral("thinmathspace")) i
= 3;
171 else if (aValue
.EqualsLiteral("mediummathspace")) i
= 4;
172 else if (aValue
.EqualsLiteral("thickmathspace")) i
= 5;
173 else if (aValue
.EqualsLiteral("verythickmathspace")) i
= 6;
174 else if (aValue
.EqualsLiteral("veryverythickmathspace")) i
= 7;
176 if (0 != i
) // it was a namedspace value
177 space
= float(i
)/float(18);
180 aOperatorData
->mLeftSpace
= space
;
182 aOperatorData
->mRightSpace
= space
;
187 SetOperator(OperatorData
* aOperatorData
,
188 nsOperatorFlags aForm
,
189 const nsCString
& aOperator
,
190 nsString
& aAttributes
)
193 // aOperator is in the expanded format \uNNNN\uNNNN ...
194 // First compress these Unicode points to the internal nsString format
196 nsAutoString name
, value
;
197 PRInt32 len
= aOperator
.Length();
198 PRUnichar c
= aOperator
[i
++];
208 if (('u' != c
) && ('U' != c
))
216 if (('0' <= c
) && (c
<= '9'))
217 uchar
= (uchar
<< 4) | (c
- '0');
218 else if (('a' <= c
) && (c
<= 'f'))
219 uchar
= (uchar
<< 4) | (c
- 'a' + 0x0a);
220 else if (('A' <= c
) && (c
<= 'F'))
221 uchar
= (uchar
<< 4) | (c
- 'A' + 0x0a);
222 else return PR_FALSE
;
234 if (0 != state
) return PR_FALSE
;
236 // Quick return when the caller doesn't care about the attributes and just wants
237 // to know if this is a valid operator (this is the case at the first pass of the
238 // parsing of the dictionary in InitOperators())
239 if (!aForm
) return PR_TRUE
;
241 // Add operator to hash table (symmetric="true" by default for all operators)
242 aOperatorData
->mFlags
|= aForm
| NS_MATHML_OPERATOR_SYMMETRIC
;
243 aOperatorData
->mStr
.Assign(value
);
244 value
.AppendInt(aForm
, 10);
245 nsStringKey
key(value
);
246 gOperatorTable
->Put(&key
, aOperatorData
);
249 NS_LossyConvertUTF16toASCII
str(aAttributes
);
251 // Loop over the space-delimited list of attributes to get the name:value pairs
252 aAttributes
.Append(kNullCh
); // put an extra null at the end
253 PRUnichar
* start
= aAttributes
.BeginWriting();
254 PRUnichar
* end
= start
;
255 while ((kNullCh
!= *start
) && (kDashCh
!= *start
)) {
258 // skip leading space, the dash amounts to the end of the line
259 while ((kNullCh
!=*start
) && (kDashCh
!=*start
) && nsCRT::IsAsciiSpace(*start
)) {
263 // look for ':' or '='
264 while ((kNullCh
!=*end
) && (kDashCh
!=*end
) && (kColonCh
!=*end
) && (kEqualCh
!=*end
)) {
267 if ((kColonCh
!=*end
) && (kEqualCh
!=*end
)) {
269 printf("Bad MathML operator: %s\n", str
.get());
273 *end
= kNullCh
; // end segment here
274 // this segment is the name
279 // look for space or end of line
280 while ((kNullCh
!=*end
) && (kDashCh
!=*start
) && !nsCRT::IsAsciiSpace(*end
)) {
283 *end
= kNullCh
; // end segment here
284 // this segment is the value
288 SetProperty(aOperatorData
, name
, value
);
297 // Load the property file containing the Operator Dictionary
299 nsCOMPtr
<nsIPersistentProperties
> mathfontProp
;
300 rv
= NS_LoadPersistentPropertiesFromURISpec(getter_AddRefs(mathfontProp
),
301 NS_LITERAL_CSTRING("resource://gre/res/fonts/mathfont.properties"));
302 if (NS_FAILED(rv
)) return rv
;
304 // Get the list of invariant chars
305 for (PRInt32 i
= 0; i
< eMATHVARIANT_COUNT
; ++i
) {
306 nsCAutoString
key(NS_LITERAL_CSTRING("mathvariant."));
307 key
.Append(kMathVariant_name
[i
]);
309 mathfontProp
->GetStringProperty(key
, value
);
310 gInvariantCharArray
->AppendString(value
); // i.e., gInvariantCharArray[i] holds this list
313 // Parse the Operator Dictionary in two passes.
314 // The first pass is to count the number of operators; the second pass is to
315 // allocate the necessary space for them and to add them in the hash table.
316 for (PRInt32 pass
= 1; pass
<= 2; pass
++) {
317 OperatorData dummyData
;
318 OperatorData
* operatorData
= &dummyData
;
319 nsCOMPtr
<nsISimpleEnumerator
> iterator
;
320 if (NS_SUCCEEDED(mathfontProp
->Enumerate(getter_AddRefs(iterator
)))) {
324 nsAutoString attributes
;
325 while ((NS_SUCCEEDED(iterator
->HasMoreElements(&more
))) && more
) {
326 nsCOMPtr
<nsIPropertyElement
> element
;
327 if (NS_SUCCEEDED(iterator
->GetNext(getter_AddRefs(element
)))) {
328 if (NS_SUCCEEDED(element
->GetKey(name
)) &&
329 NS_SUCCEEDED(element
->GetValue(attributes
))) {
330 // expected key: operator.\uNNNN.{infix,postfix,prefix}
331 if ((21 <= name
.Length()) && (0 == name
.Find("operator.\\u"))) {
332 name
.Cut(0, 9); // 9 is the length of "operator.";
333 PRInt32 len
= name
.Length();
334 nsOperatorFlags form
= 0;
335 if (kNotFound
!= name
.RFind(".infix")) {
336 form
= NS_MATHML_OPERATOR_FORM_INFIX
;
337 len
-= 6; // 6 is the length of ".infix";
339 else if (kNotFound
!= name
.RFind(".postfix")) {
340 form
= NS_MATHML_OPERATOR_FORM_POSTFIX
;
341 len
-= 8; // 8 is the length of ".postfix";
343 else if (kNotFound
!= name
.RFind(".prefix")) {
344 form
= NS_MATHML_OPERATOR_FORM_PREFIX
;
345 len
-= 7; // 7 is the length of ".prefix";
347 else continue; // input is not applicable
349 if (2 == pass
) { // allocate space and start the storage
350 if (!gOperatorArray
) {
351 if (0 == gOperatorCount
) return NS_ERROR_UNEXPECTED
;
352 gOperatorArray
= new OperatorData
[gOperatorCount
];
353 if (!gOperatorArray
) return NS_ERROR_OUT_OF_MEMORY
;
355 operatorData
= &gOperatorArray
[index
];
358 form
= 0; // to quickly return from SetOperator() at pass 1
360 // See if the operator should be retained
361 if (SetOperator(operatorData
, form
, name
, attributes
)) {
363 if (1 == pass
) gOperatorCount
= index
;
377 gInitialized
= PR_TRUE
;
378 nsresult rv
= NS_ERROR_OUT_OF_MEMORY
;
379 gInvariantCharArray
= new nsStringArray();
380 gStretchyOperatorArray
= new nsVoidArray();
381 if (gInvariantCharArray
&& gStretchyOperatorArray
) {
382 gOperatorTable
= new nsHashtable();
383 if (gOperatorTable
) {
384 rv
= InitOperators();
388 nsMathMLOperators::CleanUp();
393 nsMathMLOperators::CleanUp()
395 if (gInvariantCharArray
) {
396 delete gInvariantCharArray
;
397 gInvariantCharArray
= nsnull
;
399 if (gOperatorArray
) {
400 delete[] gOperatorArray
;
401 gOperatorArray
= nsnull
;
403 if (gStretchyOperatorArray
) {
404 delete gStretchyOperatorArray
;
405 gStretchyOperatorArray
= nsnull
;
407 if (gOperatorTable
) {
408 delete gOperatorTable
;
409 gOperatorTable
= nsnull
;
414 nsMathMLOperators::AddRefTable(void)
420 nsMathMLOperators::ReleaseTable(void)
422 if (0 == --gTableRefCount
) {
428 nsMathMLOperators::LookupOperator(const nsString
& aOperator
,
429 const nsOperatorFlags aForm
,
430 nsOperatorFlags
* aFlags
,
437 if (gOperatorTable
) {
438 NS_ASSERTION(aFlags
&& aLeftSpace
&& aRightSpace
, "bad usage");
439 NS_ASSERTION(aForm
>=0 && aForm
<4, "*** invalid call ***");
442 PRInt32 form
= NS_MATHML_OPERATOR_GET_FORM(aForm
);
443 gOperatorFound
[NS_MATHML_OPERATOR_FORM_INFIX
] = nsnull
;
444 gOperatorFound
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = nsnull
;
445 gOperatorFound
[NS_MATHML_OPERATOR_FORM_PREFIX
] = nsnull
;
447 nsAutoString
key(aOperator
);
448 key
.AppendInt(form
, 10);
449 nsStringKey
hkey(key
);
450 gOperatorFound
[form
] = found
= (OperatorData
*)gOperatorTable
->Get(&hkey
);
452 // If not found, check if the operator exists perhaps in a different form,
453 // in the order of preference: infix, postfix, prefix
455 if (form
!= NS_MATHML_OPERATOR_FORM_INFIX
) {
456 form
= NS_MATHML_OPERATOR_FORM_INFIX
;
457 key
.Assign(aOperator
);
458 key
.AppendInt(form
, 10);
459 nsStringKey
hashkey(key
);
460 gOperatorFound
[form
] = found
= (OperatorData
*)gOperatorTable
->Get(&hashkey
);
463 if (form
!= NS_MATHML_OPERATOR_FORM_POSTFIX
) {
464 form
= NS_MATHML_OPERATOR_FORM_POSTFIX
;
465 key
.Assign(aOperator
);
466 key
.AppendInt(form
, 10);
467 nsStringKey
hashkey(key
);
468 gOperatorFound
[form
] = found
= (OperatorData
*)gOperatorTable
->Get(&hashkey
);
471 if (form
!= NS_MATHML_OPERATOR_FORM_PREFIX
) {
472 form
= NS_MATHML_OPERATOR_FORM_PREFIX
;
473 key
.Assign(aOperator
);
474 key
.AppendInt(form
, 10);
475 nsStringKey
hashkey(key
);
476 gOperatorFound
[form
] = found
= (OperatorData
*)gOperatorTable
->Get(&hashkey
);
482 NS_ASSERTION(found
->mStr
.Equals(aOperator
), "bad setup");
483 *aLeftSpace
= found
->mLeftSpace
;
484 *aRightSpace
= found
->mRightSpace
;
485 *aFlags
&= ~NS_MATHML_OPERATOR_FORM
; // clear the form bits
486 *aFlags
|= found
->mFlags
; // just add bits without overwriting
494 nsMathMLOperators::LookupOperators(const nsString
& aOperator
,
495 nsOperatorFlags
* aFlags
,
503 aFlags
[NS_MATHML_OPERATOR_FORM_INFIX
] = 0;
504 aLeftSpace
[NS_MATHML_OPERATOR_FORM_INFIX
] = 0.0f
;
505 aRightSpace
[NS_MATHML_OPERATOR_FORM_INFIX
] = 0.0f
;
507 aFlags
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = 0;
508 aLeftSpace
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = 0.0f
;
509 aRightSpace
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = 0.0f
;
511 aFlags
[NS_MATHML_OPERATOR_FORM_PREFIX
] = 0;
512 aLeftSpace
[NS_MATHML_OPERATOR_FORM_PREFIX
] = 0.0f
;
513 aRightSpace
[NS_MATHML_OPERATOR_FORM_PREFIX
] = 0.0f
;
515 if (gOperatorTable
) {
516 // a lookup with form=0 will put all the variants in gOperatorFound[]
518 nsOperatorFlags flags
= 0;
519 LookupOperator(aOperator
, /*form=*/0, &flags
, &dummy
, &dummy
);
520 // if the operator was found, gOperatorFound contains all its variants
522 found
= gOperatorFound
[NS_MATHML_OPERATOR_FORM_INFIX
];
524 aFlags
[NS_MATHML_OPERATOR_FORM_INFIX
] = found
->mFlags
;
525 aLeftSpace
[NS_MATHML_OPERATOR_FORM_INFIX
] = found
->mLeftSpace
;
526 aRightSpace
[NS_MATHML_OPERATOR_FORM_INFIX
] = found
->mRightSpace
;
528 found
= gOperatorFound
[NS_MATHML_OPERATOR_FORM_POSTFIX
];
530 aFlags
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = found
->mFlags
;
531 aLeftSpace
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = found
->mLeftSpace
;
532 aRightSpace
[NS_MATHML_OPERATOR_FORM_POSTFIX
] = found
->mRightSpace
;
534 found
= gOperatorFound
[NS_MATHML_OPERATOR_FORM_PREFIX
];
536 aFlags
[NS_MATHML_OPERATOR_FORM_PREFIX
] = found
->mFlags
;
537 aLeftSpace
[NS_MATHML_OPERATOR_FORM_PREFIX
] = found
->mLeftSpace
;
538 aRightSpace
[NS_MATHML_OPERATOR_FORM_PREFIX
] = found
->mRightSpace
;
544 nsMathMLOperators::IsMutableOperator(const nsString
& aOperator
)
549 // lookup all the variants of the operator and return true if there
550 // is a variant that is stretchy or largeop
551 nsOperatorFlags flags
[4];
552 float lspace
[4], rspace
[4];
553 nsMathMLOperators::LookupOperators(aOperator
, flags
, lspace
, rspace
);
554 nsOperatorFlags allFlags
=
555 flags
[NS_MATHML_OPERATOR_FORM_INFIX
] |
556 flags
[NS_MATHML_OPERATOR_FORM_POSTFIX
] |
557 flags
[NS_MATHML_OPERATOR_FORM_PREFIX
];
558 return NS_MATHML_OPERATOR_IS_STRETCHY(allFlags
) ||
559 NS_MATHML_OPERATOR_IS_LARGEOP(allFlags
);
563 nsMathMLOperators::CountStretchyOperator()
568 return (gStretchyOperatorArray
) ? gStretchyOperatorArray
->Count() : 0;
572 nsMathMLOperators::FindStretchyOperator(PRUnichar aOperator
)
577 if (gStretchyOperatorArray
) {
578 for (PRInt32 k
= 0; k
< gStretchyOperatorArray
->Count(); k
++) {
579 OperatorData
* data
= (OperatorData
*)gStretchyOperatorArray
->ElementAt(k
);
580 if (data
&& (aOperator
== data
->mStr
[0])) {
589 nsMathMLOperators::GetStretchyDirectionAt(PRInt32 aIndex
)
591 NS_ASSERTION(gStretchyOperatorArray
, "invalid call");
592 if (gStretchyOperatorArray
) {
593 NS_ASSERTION(aIndex
< gStretchyOperatorArray
->Count(), "invalid call");
594 OperatorData
* data
= (OperatorData
*)gStretchyOperatorArray
->ElementAt(aIndex
);
596 if (NS_MATHML_OPERATOR_IS_STRETCHY_VERT(data
->mFlags
))
597 return NS_STRETCH_DIRECTION_VERTICAL
;
598 else if (NS_MATHML_OPERATOR_IS_STRETCHY_HORIZ(data
->mFlags
))
599 return NS_STRETCH_DIRECTION_HORIZONTAL
;
600 NS_ASSERTION(PR_FALSE
, "*** bad setup ***");
603 return NS_STRETCH_DIRECTION_UNSUPPORTED
;
607 nsMathMLOperators::DisableStretchyOperatorAt(PRInt32 aIndex
)
609 NS_ASSERTION(gStretchyOperatorArray
, "invalid call");
610 if (gStretchyOperatorArray
) {
611 NS_ASSERTION(aIndex
< gStretchyOperatorArray
->Count(), "invalid call");
612 gStretchyOperatorArray
->ReplaceElementAt(nsnull
, aIndex
);
616 /* static */ eMATHVARIANT
617 nsMathMLOperators::LookupInvariantChar(const nsAString
& aChar
)
622 if (gInvariantCharArray
) {
623 for (PRInt32 i
= gInvariantCharArray
->Count()-1; i
>= 0; --i
) {
624 nsString
* list
= gInvariantCharArray
->StringAt(i
);
625 nsString::const_iterator start
, end
;
626 list
->BeginReading(start
);
627 list
->EndReading(end
);
628 // Style-invariant characters are at offset 3*j + 1.
629 if (FindInReadable(aChar
, start
, end
) &&
630 start
.size_backward() % 3 == 1) {
631 return eMATHVARIANT(i
);
635 return eMATHVARIANT_NONE
;
638 /* static */ const nsDependentSubstring
639 nsMathMLOperators::TransformVariantChar(const PRUnichar
& aChar
,
640 eMATHVARIANT aVariant
)
645 if (gInvariantCharArray
) {
646 nsString
* list
= gInvariantCharArray
->StringAt(aVariant
);
647 PRInt32 index
= list
->FindChar(aChar
);
648 // BMP characters are at offset 3*j
649 if (index
!= kNotFound
&& index
% 3 == 0 && list
->Length() - index
>= 2 ) {
650 // The style-invariant character is the next character
651 // (and list should contain padding if the next character is in the BMP).
653 PRUint32 len
= NS_IS_HIGH_SURROGATE(list
->CharAt(index
)) ? 2 : 1;
654 return nsDependentSubstring(*list
, index
, len
);
657 return nsDependentSubstring(&aChar
, &aChar
+ 1);