|
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/. */ |
|
5 |
|
6 #include "nsMathMLOperators.h" |
|
7 #include "nsCOMPtr.h" |
|
8 #include "nsDataHashtable.h" |
|
9 #include "nsHashKeys.h" |
|
10 #include "nsTArray.h" |
|
11 |
|
12 #include "nsIPersistentProperties2.h" |
|
13 #include "nsNetUtil.h" |
|
14 #include "nsCRT.h" |
|
15 |
|
16 // operator dictionary entry |
|
17 struct OperatorData { |
|
18 OperatorData(void) |
|
19 : mFlags(0), |
|
20 mLeadingSpace(0.0f), |
|
21 mTrailingSpace(0.0f) |
|
22 { |
|
23 } |
|
24 |
|
25 // member data |
|
26 nsString mStr; |
|
27 nsOperatorFlags mFlags; |
|
28 float mLeadingSpace; // unit is em |
|
29 float mTrailingSpace; // unit is em |
|
30 }; |
|
31 |
|
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; |
|
37 |
|
38 static const char16_t kDashCh = char16_t('#'); |
|
39 static const char16_t kColonCh = char16_t(':'); |
|
40 |
|
41 static void |
|
42 SetBooleanProperty(OperatorData* aOperatorData, |
|
43 nsString aName) |
|
44 { |
|
45 if (aName.IsEmpty()) |
|
46 return; |
|
47 |
|
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; |
|
66 } |
|
67 |
|
68 static void |
|
69 SetProperty(OperatorData* aOperatorData, |
|
70 nsString aName, |
|
71 nsString aValue) |
|
72 { |
|
73 if (aName.IsEmpty() || aValue.IsEmpty()) |
|
74 return; |
|
75 |
|
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) |
|
80 |
|
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 |
|
94 |
|
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; |
|
99 |
|
100 if (isLeadingSpace) |
|
101 aOperatorData->mLeadingSpace = space; |
|
102 else |
|
103 aOperatorData->mTrailingSpace = space; |
|
104 } |
|
105 } |
|
106 |
|
107 static bool |
|
108 SetOperator(OperatorData* aOperatorData, |
|
109 nsOperatorFlags aForm, |
|
110 const nsCString& aOperator, |
|
111 nsString& aAttributes) |
|
112 |
|
113 { |
|
114 static const char16_t kNullCh = char16_t('\0'); |
|
115 |
|
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++; |
|
137 } |
|
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; |
|
154 } |
|
155 } |
|
156 } |
|
157 if (0 != state) return false; |
|
158 |
|
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; |
|
163 |
|
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); |
|
169 |
|
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; |
|
183 } |
|
184 end = start; |
|
185 // look for ':' |
|
186 while ((kNullCh!=*end) && (kDashCh!=*end) && !nsCRT::IsAsciiSpace(*end) && |
|
187 (kColonCh!=*end)) { |
|
188 ++end; |
|
189 } |
|
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); |
|
196 } |
|
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; |
|
205 } |
|
206 *end = kNullCh; // end segment here |
|
207 if (start < end) { |
|
208 // this segment is the value |
|
209 value.Assign(start); |
|
210 } |
|
211 SetProperty(aOperatorData, name, value); |
|
212 } |
|
213 start = ++end; |
|
214 } |
|
215 return true; |
|
216 } |
|
217 |
|
218 static nsresult |
|
219 InitOperators(void) |
|
220 { |
|
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; |
|
227 |
|
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"; |
|
255 } |
|
256 else if (kNotFound != name.RFind(".postfix")) { |
|
257 form = NS_MATHML_OPERATOR_FORM_POSTFIX; |
|
258 len -= 8; // 8 is the length of ".postfix"; |
|
259 } |
|
260 else if (kNotFound != name.RFind(".prefix")) { |
|
261 form = NS_MATHML_OPERATOR_FORM_PREFIX; |
|
262 len -= 7; // 7 is the length of ".prefix"; |
|
263 } |
|
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; |
|
271 } |
|
272 operatorData = &gOperatorArray[index]; |
|
273 } |
|
274 else { |
|
275 form = 0; // to quickly return from SetOperator() at pass 1 |
|
276 } |
|
277 // See if the operator should be retained |
|
278 if (SetOperator(operatorData, form, name, attributes)) { |
|
279 index++; |
|
280 if (1 == pass) gOperatorCount = index; |
|
281 } |
|
282 } |
|
283 } |
|
284 } |
|
285 } |
|
286 } |
|
287 } |
|
288 return NS_OK; |
|
289 } |
|
290 |
|
291 static nsresult |
|
292 InitGlobals() |
|
293 { |
|
294 gGlobalsInitialized = true; |
|
295 nsresult rv = NS_ERROR_OUT_OF_MEMORY; |
|
296 gOperatorTable = new nsDataHashtable<nsStringHashKey, OperatorData*>(); |
|
297 if (gOperatorTable) { |
|
298 rv = InitOperators(); |
|
299 } |
|
300 if (NS_FAILED(rv)) |
|
301 nsMathMLOperators::CleanUp(); |
|
302 return rv; |
|
303 } |
|
304 |
|
305 void |
|
306 nsMathMLOperators::CleanUp() |
|
307 { |
|
308 if (gOperatorArray) { |
|
309 delete[] gOperatorArray; |
|
310 gOperatorArray = nullptr; |
|
311 } |
|
312 if (gOperatorTable) { |
|
313 delete gOperatorTable; |
|
314 gOperatorTable = nullptr; |
|
315 } |
|
316 } |
|
317 |
|
318 void |
|
319 nsMathMLOperators::AddRefTable(void) |
|
320 { |
|
321 gTableRefCount++; |
|
322 } |
|
323 |
|
324 void |
|
325 nsMathMLOperators::ReleaseTable(void) |
|
326 { |
|
327 if (0 == --gTableRefCount) { |
|
328 CleanUp(); |
|
329 } |
|
330 } |
|
331 |
|
332 static OperatorData* |
|
333 GetOperatorData(const nsString& aOperator, nsOperatorFlags aForm) |
|
334 { |
|
335 nsAutoString key(aOperator); |
|
336 key.AppendInt(aForm); |
|
337 return gOperatorTable->Get(key); |
|
338 } |
|
339 |
|
340 bool |
|
341 nsMathMLOperators::LookupOperator(const nsString& aOperator, |
|
342 const nsOperatorFlags aForm, |
|
343 nsOperatorFlags* aFlags, |
|
344 float* aLeadingSpace, |
|
345 float* aTrailingSpace) |
|
346 { |
|
347 if (!gGlobalsInitialized) { |
|
348 InitGlobals(); |
|
349 } |
|
350 if (gOperatorTable) { |
|
351 NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage"); |
|
352 NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***"); |
|
353 |
|
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. |
|
358 |
|
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); |
|
370 } |
|
371 } |
|
372 } |
|
373 } |
|
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; |
|
381 } |
|
382 } |
|
383 return false; |
|
384 } |
|
385 |
|
386 void |
|
387 nsMathMLOperators::LookupOperators(const nsString& aOperator, |
|
388 nsOperatorFlags* aFlags, |
|
389 float* aLeadingSpace, |
|
390 float* aTrailingSpace) |
|
391 { |
|
392 if (!gGlobalsInitialized) { |
|
393 InitGlobals(); |
|
394 } |
|
395 |
|
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; |
|
399 |
|
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; |
|
403 |
|
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; |
|
407 |
|
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; |
|
415 } |
|
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; |
|
421 } |
|
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; |
|
427 } |
|
428 } |
|
429 } |
|
430 |
|
431 /* static */ bool |
|
432 nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) |
|
433 { |
|
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); |
|
443 } |
|
444 |
|
445 /* static */ nsStretchDirection |
|
446 nsMathMLOperators::GetStretchyDirection(const nsString& aOperator) |
|
447 { |
|
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); |
|
456 |
|
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; |
|
463 } |
|
464 } |