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