layout/mathml/nsMathMLOperators.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial