nsMathMLOperators.cpp (15570B)
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 9 #include "mozilla/StaticPrefs_mathml.h" 10 #include "mozilla/intl/UnicodeProperties.h" 11 #include "nsCOMPtr.h" 12 #include "nsCRT.h" 13 #include "nsHashKeys.h" 14 #include "nsIPersistentProperties2.h" 15 #include "nsISimpleEnumerator.h" 16 #include "nsNetUtil.h" 17 #include "nsTArray.h" 18 #include "nsTHashMap.h" 19 20 using namespace mozilla; 21 22 // operator dictionary entry 23 struct OperatorData { 24 OperatorData(void) : mFlags(0), mLeadingSpace(0.0f), mTrailingSpace(0.0f) {} 25 26 // member data 27 nsString mStr; 28 nsOperatorFlags mFlags; 29 float mLeadingSpace; // unit is em 30 float mTrailingSpace; // unit is em 31 }; 32 33 static int32_t gTableRefCount = 0; 34 static uint32_t gOperatorCount = 0; 35 static OperatorData* gOperatorArray = nullptr; 36 static nsTHashMap<nsStringHashKey, OperatorData*>* gOperatorTable = nullptr; 37 static bool gGlobalsInitialized = false; 38 39 static const char16_t kDashCh = char16_t('#'); 40 static const char16_t kColonCh = char16_t(':'); 41 42 static uint32_t ToUnicodeCodePoint(const nsString& aOperator) { 43 if (aOperator.Length() == 1) { 44 return aOperator[0]; 45 } 46 if (aOperator.Length() == 2 && 47 NS_IS_SURROGATE_PAIR(aOperator[0], aOperator[1])) { 48 return SURROGATE_TO_UCS4(aOperator[0], aOperator[1]); 49 } 50 return 0; 51 } 52 53 static void SetBooleanProperty(OperatorData* aOperatorData, nsString aName) { 54 if (aName.IsEmpty()) { 55 return; 56 } 57 58 if (aName.EqualsLiteral("stretchy") && (1 == aOperatorData->mStr.Length())) { 59 aOperatorData->mFlags |= NS_MATHML_OPERATOR_STRETCHY; 60 } else if (aName.EqualsLiteral("fence")) { 61 aOperatorData->mFlags |= NS_MATHML_OPERATOR_FENCE; 62 } else if (!StaticPrefs::mathml_operator_dictionary_accent_disabled() && 63 aName.EqualsLiteral("accent")) { 64 aOperatorData->mFlags |= NS_MATHML_OPERATOR_ACCENT; 65 } else if (aName.EqualsLiteral("largeop")) { 66 aOperatorData->mFlags |= NS_MATHML_OPERATOR_LARGEOP; 67 } else if (aName.EqualsLiteral("separator")) { 68 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SEPARATOR; 69 } else if (aName.EqualsLiteral("movablelimits")) { 70 aOperatorData->mFlags |= NS_MATHML_OPERATOR_MOVABLELIMITS; 71 } else if (aName.EqualsLiteral("symmetric")) { 72 aOperatorData->mFlags |= NS_MATHML_OPERATOR_SYMMETRIC; 73 } 74 } 75 76 static void SetProperty(OperatorData* aOperatorData, nsString aName, 77 nsString aValue) { 78 if (aName.IsEmpty() || aValue.IsEmpty()) { 79 return; 80 } 81 82 if (aName.EqualsLiteral("direction")) { 83 if (aValue.EqualsLiteral("vertical")) { 84 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_VERTICAL; 85 } else if (aValue.EqualsLiteral("horizontal")) { 86 aOperatorData->mFlags |= NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL; 87 } else { 88 return; // invalid value 89 } 90 } else { 91 bool isLeadingSpace; 92 if (aName.EqualsLiteral("lspace")) { 93 isLeadingSpace = true; 94 } else if (aName.EqualsLiteral("rspace")) { 95 isLeadingSpace = false; 96 } else { 97 return; // input is not applicable 98 } 99 100 // aValue is assumed to be a digit from 0 to 7 101 nsresult error = NS_OK; 102 float space = aValue.ToFloat(&error) / 18.0; 103 if (NS_FAILED(error)) { 104 return; 105 } 106 107 if (isLeadingSpace) { 108 aOperatorData->mLeadingSpace = space; 109 } else { 110 aOperatorData->mTrailingSpace = space; 111 } 112 } 113 } 114 115 static bool SetOperator(OperatorData* aOperatorData, nsOperatorFlags aForm, 116 const nsCString& aOperator, nsString& aAttributes) 117 118 { 119 static const char16_t kNullCh = char16_t('\0'); 120 121 // aOperator is in the expanded format \uNNNN\uNNNN ... 122 // First compress these Unicode points to the internal nsString format 123 int32_t i = 0; 124 nsAutoString name, value; 125 int32_t len = aOperator.Length(); 126 char16_t c = aOperator[i++]; 127 uint32_t state = 0; 128 char16_t uchar = 0; 129 while (i <= len) { 130 if (0 == state) { 131 if (c != '\\') { 132 return false; 133 } 134 if (i < len) { 135 c = aOperator[i]; 136 } 137 i++; 138 if (('u' != c) && ('U' != c)) { 139 return false; 140 } 141 if (i < len) { 142 c = aOperator[i]; 143 } 144 i++; 145 state++; 146 } else { 147 if (('0' <= c) && (c <= '9')) { 148 uchar = (uchar << 4) | (c - '0'); 149 } else if (('a' <= c) && (c <= 'f')) { 150 uchar = (uchar << 4) | (c - 'a' + 0x0a); 151 } else if (('A' <= c) && (c <= 'F')) { 152 uchar = (uchar << 4) | (c - 'A' + 0x0a); 153 } else { 154 return false; 155 } 156 if (i < len) { 157 c = aOperator[i]; 158 } 159 i++; 160 state++; 161 if (5 == state) { 162 value.Append(uchar); 163 uchar = 0; 164 state = 0; 165 } 166 } 167 } 168 if (0 != state) { 169 return false; 170 } 171 172 // Quick return when the caller doesn't care about the attributes and just 173 // wants to know if this is a valid operator (this is the case at the first 174 // pass of the parsing of the dictionary in InitOperators()) 175 if (!aForm) { 176 return true; 177 } 178 179 // Add operator to hash table 180 aOperatorData->mFlags |= aForm; 181 aOperatorData->mStr.Assign(value); 182 value.AppendInt(aForm, 10); 183 gOperatorTable->InsertOrUpdate(value, aOperatorData); 184 185 #ifdef DEBUG 186 NS_LossyConvertUTF16toASCII str(aAttributes); 187 #endif 188 // Loop over the space-delimited list of attributes to get the name:value 189 // pairs 190 aAttributes.Append(kNullCh); // put an extra null at the end 191 char16_t* start = aAttributes.BeginWriting(); 192 char16_t* end = start; 193 while ((kNullCh != *start) && (kDashCh != *start)) { 194 name.SetLength(0); 195 value.SetLength(0); 196 // skip leading space, the dash amounts to the end of the line 197 while ((kNullCh != *start) && (kDashCh != *start) && 198 nsCRT::IsAsciiSpace(*start)) { 199 ++start; 200 } 201 end = start; 202 // look for ':' 203 while ((kNullCh != *end) && (kDashCh != *end) && 204 !nsCRT::IsAsciiSpace(*end) && (kColonCh != *end)) { 205 ++end; 206 } 207 // If ':' is not found, then it's a boolean property 208 bool IsBooleanProperty = (kColonCh != *end); 209 *end = kNullCh; // end segment here 210 // this segment is the name 211 if (start < end) { 212 name.Assign(start); 213 } 214 if (IsBooleanProperty) { 215 SetBooleanProperty(aOperatorData, name); 216 } else { 217 start = ++end; 218 // look for space or end of line 219 while ((kNullCh != *end) && (kDashCh != *end) && 220 !nsCRT::IsAsciiSpace(*end)) { 221 ++end; 222 } 223 *end = kNullCh; // end segment here 224 if (start < end) { 225 // this segment is the value 226 value.Assign(start); 227 } 228 SetProperty(aOperatorData, name, value); 229 } 230 start = ++end; 231 } 232 return true; 233 } 234 235 static nsresult InitOperators(void) { 236 // Load the property file containing the Operator Dictionary 237 nsresult rv; 238 nsCOMPtr<nsIPersistentProperties> mathfontProp; 239 rv = NS_LoadPersistentPropertiesFromURISpec( 240 getter_AddRefs(mathfontProp), 241 "resource://gre/res/fonts/mathfont.properties"_ns); 242 243 if (NS_FAILED(rv)) { 244 return rv; 245 } 246 247 // Parse the Operator Dictionary in two passes. 248 // The first pass is to count the number of operators; the second pass is to 249 // allocate the necessary space for them and to add them in the hash table. 250 for (int32_t pass = 1; pass <= 2; pass++) { 251 OperatorData dummyData; 252 OperatorData* operatorData = &dummyData; 253 nsCOMPtr<nsISimpleEnumerator> iterator; 254 if (NS_SUCCEEDED(mathfontProp->Enumerate(getter_AddRefs(iterator)))) { 255 bool more; 256 uint32_t index = 0; 257 nsAutoCString name; 258 nsAutoString attributes; 259 while ((NS_SUCCEEDED(iterator->HasMoreElements(&more))) && more) { 260 nsCOMPtr<nsISupports> supports; 261 nsCOMPtr<nsIPropertyElement> element; 262 if (NS_SUCCEEDED(iterator->GetNext(getter_AddRefs(supports)))) { 263 element = do_QueryInterface(supports); 264 if (NS_SUCCEEDED(element->GetKey(name)) && 265 NS_SUCCEEDED(element->GetValue(attributes))) { 266 // expected key: operator.\uNNNN.{infix,postfix,prefix} 267 if ((21 <= name.Length()) && (0 == name.Find("operator.\\u"))) { 268 name.Cut(0, 9); // 9 is the length of "operator."; 269 int32_t len = name.Length(); 270 nsOperatorFlags form = 0; 271 if (kNotFound != name.RFind(".infix")) { 272 form = NS_MATHML_OPERATOR_FORM_INFIX; 273 len -= 6; // 6 is the length of ".infix"; 274 } else if (kNotFound != name.RFind(".postfix")) { 275 form = NS_MATHML_OPERATOR_FORM_POSTFIX; 276 len -= 8; // 8 is the length of ".postfix"; 277 } else if (kNotFound != name.RFind(".prefix")) { 278 form = NS_MATHML_OPERATOR_FORM_PREFIX; 279 len -= 7; // 7 is the length of ".prefix"; 280 } else { 281 continue; // input is not applicable 282 } 283 name.SetLength(len); 284 if (2 == pass) { // allocate space and start the storage 285 if (!gOperatorArray) { 286 if (0 == gOperatorCount) { 287 return NS_ERROR_UNEXPECTED; 288 } 289 gOperatorArray = new OperatorData[gOperatorCount]; 290 } 291 operatorData = &gOperatorArray[index]; 292 } else { 293 form = 0; // to quickly return from SetOperator() at pass 1 294 } 295 // See if the operator should be retained 296 if (SetOperator(operatorData, form, name, attributes)) { 297 index++; 298 if (1 == pass) { 299 gOperatorCount = index; 300 } 301 } 302 } 303 } 304 } 305 } 306 } 307 } 308 return NS_OK; 309 } 310 311 static nsresult InitOperatorGlobals() { 312 gGlobalsInitialized = true; 313 nsresult rv = NS_ERROR_OUT_OF_MEMORY; 314 gOperatorTable = new nsTHashMap<nsStringHashKey, OperatorData*>(); 315 if (gOperatorTable) { 316 rv = InitOperators(); 317 } 318 if (NS_FAILED(rv)) { 319 nsMathMLOperators::CleanUp(); 320 } 321 return rv; 322 } 323 324 void nsMathMLOperators::CleanUp() { 325 if (gOperatorArray) { 326 delete[] gOperatorArray; 327 gOperatorArray = nullptr; 328 } 329 if (gOperatorTable) { 330 delete gOperatorTable; 331 gOperatorTable = nullptr; 332 } 333 } 334 335 void nsMathMLOperators::AddRefTable(void) { gTableRefCount++; } 336 337 void nsMathMLOperators::ReleaseTable(void) { 338 if (0 == --gTableRefCount) { 339 CleanUp(); 340 } 341 } 342 343 static OperatorData* GetOperatorData(const nsString& aOperator, 344 const uint8_t aForm) { 345 nsAutoString key(aOperator); 346 key.AppendInt(aForm); 347 return gOperatorTable->Get(key); 348 } 349 350 bool nsMathMLOperators::LookupOperator(const nsString& aOperator, 351 const uint8_t aForm, 352 nsOperatorFlags* aFlags, 353 float* aLeadingSpace, 354 float* aTrailingSpace) { 355 NS_ASSERTION(aFlags && aLeadingSpace && aTrailingSpace, "bad usage"); 356 NS_ASSERTION(aForm > 0 && aForm < 4, "*** invalid call ***"); 357 358 // Operator strings must be of length 1 or 2 in UTF-16. 359 // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator 360 if (aOperator.IsEmpty() || aOperator.Length() > 2) { 361 return false; 362 } 363 364 if (aOperator.Length() == 2) { 365 // Try and handle Arabic operators. 366 // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator 367 if (auto codePoint = ToUnicodeCodePoint(aOperator)) { 368 if (aForm == NS_MATHML_OPERATOR_FORM_POSTFIX && 369 (codePoint == 0x1EEF0 || codePoint == 0x1EEF1)) { 370 // Use category I. 371 // https://w3c.github.io/mathml-core/#operator-dictionary-categories-values 372 *aFlags = NS_MATHML_OPERATOR_FORM_POSTFIX | 373 NS_MATHML_OPERATOR_STRETCHY | 374 NS_MATHML_OPERATOR_DIRECTION_HORIZONTAL; 375 *aLeadingSpace = 0; 376 *aTrailingSpace = 0; 377 return true; 378 } 379 return false; 380 } 381 382 // Ignore the combining "negation" suffix for 2-character strings. 383 // https://w3c.github.io/mathml-core/#dfn-algorithm-to-determine-the-category-of-an-operator 384 if (aOperator[1] == 0x0338 || aOperator[1] == 0x20D2) { 385 nsAutoString newOperator; 386 newOperator.Append(aOperator[0]); 387 return LookupOperator(newOperator, aForm, aFlags, aLeadingSpace, 388 aTrailingSpace); 389 } 390 } 391 392 if (!gGlobalsInitialized) { 393 InitOperatorGlobals(); 394 } 395 if (gOperatorTable) { 396 if (OperatorData* data = GetOperatorData(aOperator, aForm)) { 397 NS_ASSERTION(data->mStr.Equals(aOperator), "bad setup"); 398 *aFlags = data->mFlags; 399 *aLeadingSpace = data->mLeadingSpace; 400 *aTrailingSpace = data->mTrailingSpace; 401 return true; 402 } 403 } 404 405 return false; 406 } 407 408 bool nsMathMLOperators::LookupOperatorWithFallback(const nsString& aOperator, 409 const uint8_t aForm, 410 nsOperatorFlags* aFlags, 411 float* aLeadingSpace, 412 float* aTrailingSpace) { 413 if (LookupOperator(aOperator, aForm, aFlags, aLeadingSpace, aTrailingSpace)) { 414 return true; 415 } 416 for (const auto& form : 417 {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX, 418 NS_MATHML_OPERATOR_FORM_PREFIX}) { 419 if (form == aForm) { 420 // This form was tried above, skip it. 421 continue; 422 } 423 if (LookupOperator(aOperator, form, aFlags, aLeadingSpace, 424 aTrailingSpace)) { 425 return true; 426 } 427 } 428 return false; 429 } 430 431 /* static */ 432 bool nsMathMLOperators::IsMirrorableOperator(const nsString& aOperator) { 433 if (auto codePoint = ToUnicodeCodePoint(aOperator)) { 434 return intl::UnicodeProperties::IsMirrored(codePoint); 435 } 436 return false; 437 } 438 439 /* static */ 440 nsString nsMathMLOperators::GetMirroredOperator(const nsString& aOperator) { 441 nsString result; 442 if (auto codePoint = ToUnicodeCodePoint(aOperator)) { 443 result.Assign(intl::UnicodeProperties::CharMirror(codePoint)); 444 } 445 return result; 446 } 447 448 /* static */ 449 bool nsMathMLOperators::IsIntegralOperator(const nsString& aOperator) { 450 if (auto codePoint = ToUnicodeCodePoint(aOperator)) { 451 return (0x222B <= codePoint && codePoint <= 0x2233) || 452 (0x2A0B <= codePoint && codePoint <= 0x2A1C); 453 } 454 return false; 455 } 456 457 /* static */ 458 nsStretchDirection nsMathMLOperators::GetStretchyDirection( 459 const nsString& aOperator) { 460 // Search any entry for that operator and return the corresponding direction. 461 // It is assumed that all the forms have same direction. 462 for (const auto& form : 463 {NS_MATHML_OPERATOR_FORM_INFIX, NS_MATHML_OPERATOR_FORM_POSTFIX, 464 NS_MATHML_OPERATOR_FORM_PREFIX}) { 465 nsOperatorFlags flags; 466 float dummy; 467 if (nsMathMLOperators::LookupOperator(aOperator, form, &flags, &dummy, 468 &dummy)) { 469 if (NS_MATHML_OPERATOR_IS_DIRECTION_VERTICAL(flags)) { 470 return NS_STRETCH_DIRECTION_VERTICAL; 471 } 472 if (NS_MATHML_OPERATOR_IS_DIRECTION_HORIZONTAL(flags)) { 473 return NS_STRETCH_DIRECTION_HORIZONTAL; 474 } 475 } 476 } 477 return NS_STRETCH_DIRECTION_UNSUPPORTED; 478 }