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