| OLD | NEW |
| 1 /* | 1 /* |
| 2 * Copyright 2012 Google Inc. | 2 * Copyright 2012 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "SkTwoPointConicalGradient.h" | 8 #include "SkTwoPointConicalGradient.h" |
| 9 | 9 |
| 10 #include "SkTwoPointConicalGradient_Gpu.h" |
| 11 |
| 10 static int valid_divide(float numer, float denom, float* ratio) { | 12 static int valid_divide(float numer, float denom, float* ratio) { |
| 11 SkASSERT(ratio); | 13 SkASSERT(ratio); |
| 12 if (0 == denom) { | 14 if (0 == denom) { |
| 13 return 0; | 15 return 0; |
| 14 } | 16 } |
| 15 *ratio = numer / denom; | 17 *ratio = numer / denom; |
| 16 return 1; | 18 return 1; |
| 17 } | 19 } |
| 18 | 20 |
| 19 // Return the number of distinct real roots, and write them into roots[] in | 21 // Return the number of distinct real roots, and write them into roots[] in |
| (...skipping 301 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 321 | 323 |
| 322 void SkTwoPointConicalGradient::flatten( | 324 void SkTwoPointConicalGradient::flatten( |
| 323 SkWriteBuffer& buffer) const { | 325 SkWriteBuffer& buffer) const { |
| 324 this->INHERITED::flatten(buffer); | 326 this->INHERITED::flatten(buffer); |
| 325 buffer.writePoint(fCenter1); | 327 buffer.writePoint(fCenter1); |
| 326 buffer.writePoint(fCenter2); | 328 buffer.writePoint(fCenter2); |
| 327 buffer.writeScalar(fRadius1); | 329 buffer.writeScalar(fRadius1); |
| 328 buffer.writeScalar(fRadius2); | 330 buffer.writeScalar(fRadius2); |
| 329 } | 331 } |
| 330 | 332 |
| 331 ///////////////////////////////////////////////////////////////////// | |
| 332 | |
| 333 #if SK_SUPPORT_GPU | 333 #if SK_SUPPORT_GPU |
| 334 | 334 |
| 335 #include "GrTBackendEffectFactory.h" | |
| 336 | |
| 337 // For brevity | |
| 338 typedef GrGLUniformManager::UniformHandle UniformHandle; | |
| 339 | |
| 340 class GrGLConical2Gradient : public GrGLGradientEffect { | |
| 341 public: | |
| 342 | |
| 343 GrGLConical2Gradient(const GrBackendEffectFactory& factory, const GrDrawEffe
ct&); | |
| 344 virtual ~GrGLConical2Gradient() { } | |
| 345 | |
| 346 virtual void emitCode(GrGLShaderBuilder*, | |
| 347 const GrDrawEffect&, | |
| 348 EffectKey, | |
| 349 const char* outputColor, | |
| 350 const char* inputColor, | |
| 351 const TransformedCoordsArray&, | |
| 352 const TextureSamplerArray&) SK_OVERRIDE; | |
| 353 virtual void setData(const GrGLUniformManager&, const GrDrawEffect&) SK_OVER
RIDE; | |
| 354 | |
| 355 static EffectKey GenKey(const GrDrawEffect&, const GrGLCaps& caps); | |
| 356 | |
| 357 protected: | |
| 358 | |
| 359 UniformHandle fParamUni; | |
| 360 | |
| 361 const char* fVSVaryingName; | |
| 362 const char* fFSVaryingName; | |
| 363 | |
| 364 bool fIsDegenerate; | |
| 365 | |
| 366 // @{ | |
| 367 /// Values last uploaded as uniforms | |
| 368 | |
| 369 SkScalar fCachedCenter; | |
| 370 SkScalar fCachedRadius; | |
| 371 SkScalar fCachedDiffRadius; | |
| 372 | |
| 373 // @} | |
| 374 | |
| 375 private: | |
| 376 | |
| 377 typedef GrGLGradientEffect INHERITED; | |
| 378 | |
| 379 }; | |
| 380 | |
| 381 ///////////////////////////////////////////////////////////////////// | |
| 382 | |
| 383 class GrConical2Gradient : public GrGradientEffect { | |
| 384 public: | |
| 385 | |
| 386 static GrEffectRef* Create(GrContext* ctx, | |
| 387 const SkTwoPointConicalGradient& shader, | |
| 388 const SkMatrix& matrix, | |
| 389 SkShader::TileMode tm) { | |
| 390 AutoEffectUnref effect(SkNEW_ARGS(GrConical2Gradient, (ctx, shader, matr
ix, tm))); | |
| 391 return CreateEffectRef(effect); | |
| 392 } | |
| 393 | |
| 394 virtual ~GrConical2Gradient() { } | |
| 395 | |
| 396 static const char* Name() { return "Two-Point Conical Gradient"; } | |
| 397 virtual const GrBackendEffectFactory& getFactory() const SK_OVERRIDE { | |
| 398 return GrTBackendEffectFactory<GrConical2Gradient>::getInstance(); | |
| 399 } | |
| 400 | |
| 401 // The radial gradient parameters can collapse to a linear (instead of quadr
atic) equation. | |
| 402 bool isDegenerate() const { return SkScalarAbs(fDiffRadius) == SkScalarAbs(f
CenterX1); } | |
| 403 SkScalar center() const { return fCenterX1; } | |
| 404 SkScalar diffRadius() const { return fDiffRadius; } | |
| 405 SkScalar radius() const { return fRadius0; } | |
| 406 | |
| 407 typedef GrGLConical2Gradient GLEffect; | |
| 408 | |
| 409 private: | |
| 410 virtual bool onIsEqual(const GrEffect& sBase) const SK_OVERRIDE { | |
| 411 const GrConical2Gradient& s = CastEffect<GrConical2Gradient>(sBase); | |
| 412 return (INHERITED::onIsEqual(sBase) && | |
| 413 this->fCenterX1 == s.fCenterX1 && | |
| 414 this->fRadius0 == s.fRadius0 && | |
| 415 this->fDiffRadius == s.fDiffRadius); | |
| 416 } | |
| 417 | |
| 418 GrConical2Gradient(GrContext* ctx, | |
| 419 const SkTwoPointConicalGradient& shader, | |
| 420 const SkMatrix& matrix, | |
| 421 SkShader::TileMode tm) | |
| 422 : INHERITED(ctx, shader, matrix, tm) | |
| 423 , fCenterX1(shader.getCenterX1()) | |
| 424 , fRadius0(shader.getStartRadius()) | |
| 425 , fDiffRadius(shader.getDiffRadius()) { | |
| 426 // We pass the linear part of the quadratic as a varying. | |
| 427 // float b = -2.0 * (fCenterX1 * x + fRadius0 * fDiffRadius * z) | |
| 428 fBTransform = this->getCoordTransform(); | |
| 429 SkMatrix& bMatrix = *fBTransform.accessMatrix(); | |
| 430 SkScalar r0dr = SkScalarMul(fRadius0, fDiffRadius); | |
| 431 bMatrix[SkMatrix::kMScaleX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMat
rix::kMScaleX]) + | |
| 432 SkScalarMul(r0dr, bMatrix[SkMatrix::
kMPersp0])); | |
| 433 bMatrix[SkMatrix::kMSkewX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMatr
ix::kMSkewX]) + | |
| 434 SkScalarMul(r0dr, bMatrix[SkMatrix::k
MPersp1])); | |
| 435 bMatrix[SkMatrix::kMTransX] = -2 * (SkScalarMul(fCenterX1, bMatrix[SkMat
rix::kMTransX]) + | |
| 436 SkScalarMul(r0dr, bMatrix[SkMatrix::
kMPersp2])); | |
| 437 this->addCoordTransform(&fBTransform); | |
| 438 } | |
| 439 | |
| 440 GR_DECLARE_EFFECT_TEST; | |
| 441 | |
| 442 // @{ | |
| 443 // Cache of values - these can change arbitrarily, EXCEPT | |
| 444 // we shouldn't change between degenerate and non-degenerate?! | |
| 445 | |
| 446 GrCoordTransform fBTransform; | |
| 447 SkScalar fCenterX1; | |
| 448 SkScalar fRadius0; | |
| 449 SkScalar fDiffRadius; | |
| 450 | |
| 451 // @} | |
| 452 | |
| 453 typedef GrGradientEffect INHERITED; | |
| 454 }; | |
| 455 | |
| 456 GR_DEFINE_EFFECT_TEST(GrConical2Gradient); | |
| 457 | |
| 458 GrEffectRef* GrConical2Gradient::TestCreate(SkRandom* random, | |
| 459 GrContext* context, | |
| 460 const GrDrawTargetCaps&, | |
| 461 GrTexture**) { | |
| 462 SkPoint center1 = {random->nextUScalar1(), random->nextUScalar1()}; | |
| 463 SkScalar radius1 = random->nextUScalar1(); | |
| 464 SkPoint center2; | |
| 465 SkScalar radius2; | |
| 466 do { | |
| 467 center2.set(random->nextUScalar1(), random->nextUScalar1()); | |
| 468 radius2 = random->nextUScalar1 (); | |
| 469 // If the circles are identical the factory will give us an empty shader
. | |
| 470 } while (radius1 == radius2 && center1 == center2); | |
| 471 | |
| 472 SkColor colors[kMaxRandomGradientColors]; | |
| 473 SkScalar stopsArray[kMaxRandomGradientColors]; | |
| 474 SkScalar* stops = stopsArray; | |
| 475 SkShader::TileMode tm; | |
| 476 int colorCount = RandomGradientParams(random, colors, &stops, &tm); | |
| 477 SkAutoTUnref<SkShader> shader(SkGradientShader::CreateTwoPointConical(center
1, radius1, | |
| 478 center
2, radius2, | |
| 479 colors
, stops, colorCount, | |
| 480 tm)); | |
| 481 SkPaint paint; | |
| 482 return shader->asNewEffect(context, paint); | |
| 483 } | |
| 484 | |
| 485 | |
| 486 ///////////////////////////////////////////////////////////////////// | |
| 487 | |
| 488 GrGLConical2Gradient::GrGLConical2Gradient(const GrBackendEffectFactory& factory
, | |
| 489 const GrDrawEffect& drawEffect) | |
| 490 : INHERITED(factory) | |
| 491 , fVSVaryingName(NULL) | |
| 492 , fFSVaryingName(NULL) | |
| 493 , fCachedCenter(SK_ScalarMax) | |
| 494 , fCachedRadius(-SK_ScalarMax) | |
| 495 , fCachedDiffRadius(-SK_ScalarMax) { | |
| 496 | |
| 497 const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>()
; | |
| 498 fIsDegenerate = data.isDegenerate(); | |
| 499 } | |
| 500 | |
| 501 void GrGLConical2Gradient::emitCode(GrGLShaderBuilder* builder, | |
| 502 const GrDrawEffect&, | |
| 503 EffectKey key, | |
| 504 const char* outputColor, | |
| 505 const char* inputColor, | |
| 506 const TransformedCoordsArray& coords, | |
| 507 const TextureSamplerArray& samplers) { | |
| 508 this->emitUniforms(builder, key); | |
| 509 fParamUni = builder->addUniformArray(GrGLShaderBuilder::kFragment_Visibility
, | |
| 510 kFloat_GrSLType, "Conical2FSParams", 6)
; | |
| 511 | |
| 512 SkString cName("c"); | |
| 513 SkString ac4Name("ac4"); | |
| 514 SkString dName("d"); | |
| 515 SkString qName("q"); | |
| 516 SkString r0Name("r0"); | |
| 517 SkString r1Name("r1"); | |
| 518 SkString tName("t"); | |
| 519 SkString p0; // 4a | |
| 520 SkString p1; // 1/a | |
| 521 SkString p2; // distance between centers | |
| 522 SkString p3; // start radius | |
| 523 SkString p4; // start radius squared | |
| 524 SkString p5; // difference in radii (r1 - r0) | |
| 525 | |
| 526 builder->getUniformVariable(fParamUni).appendArrayAccess(0, &p0); | |
| 527 builder->getUniformVariable(fParamUni).appendArrayAccess(1, &p1); | |
| 528 builder->getUniformVariable(fParamUni).appendArrayAccess(2, &p2); | |
| 529 builder->getUniformVariable(fParamUni).appendArrayAccess(3, &p3); | |
| 530 builder->getUniformVariable(fParamUni).appendArrayAccess(4, &p4); | |
| 531 builder->getUniformVariable(fParamUni).appendArrayAccess(5, &p5); | |
| 532 | |
| 533 // We interpolate the linear component in coords[1]. | |
| 534 SkASSERT(coords[0].type() == coords[1].type()); | |
| 535 const char* coords2D; | |
| 536 SkString bVar; | |
| 537 if (kVec3f_GrSLType == coords[0].type()) { | |
| 538 builder->fsCodeAppendf("\tvec3 interpolants = vec3(%s.xy, %s.x) / %s.z;\
n", | |
| 539 coords[0].c_str(), coords[1].c_str(), coords[0].c
_str()); | |
| 540 coords2D = "interpolants.xy"; | |
| 541 bVar = "interpolants.z"; | |
| 542 } else { | |
| 543 coords2D = coords[0].c_str(); | |
| 544 bVar.printf("%s.x", coords[1].c_str()); | |
| 545 } | |
| 546 | |
| 547 // output will default to transparent black (we simply won't write anything | |
| 548 // else to it if invalid, instead of discarding or returning prematurely) | |
| 549 builder->fsCodeAppendf("\t%s = vec4(0.0,0.0,0.0,0.0);\n", outputColor); | |
| 550 | |
| 551 // c = (x^2)+(y^2) - params[4] | |
| 552 builder->fsCodeAppendf("\tfloat %s = dot(%s, %s) - %s;\n", | |
| 553 cName.c_str(), coords2D, coords2D, p4.c_str()); | |
| 554 | |
| 555 // Non-degenerate case (quadratic) | |
| 556 if (!fIsDegenerate) { | |
| 557 | |
| 558 // ac4 = params[0] * c | |
| 559 builder->fsCodeAppendf("\tfloat %s = %s * %s;\n", ac4Name.c_str(), p0.c_
str(), | |
| 560 cName.c_str()); | |
| 561 | |
| 562 // d = b^2 - ac4 | |
| 563 builder->fsCodeAppendf("\tfloat %s = %s * %s - %s;\n", dName.c_str(), | |
| 564 bVar.c_str(), bVar.c_str(), ac4Name.c_str()); | |
| 565 | |
| 566 // only proceed if discriminant is >= 0 | |
| 567 builder->fsCodeAppendf("\tif (%s >= 0.0) {\n", dName.c_str()); | |
| 568 | |
| 569 // intermediate value we'll use to compute the roots | |
| 570 // q = -0.5 * (b +/- sqrt(d)) | |
| 571 builder->fsCodeAppendf("\t\tfloat %s = -0.5 * (%s + (%s < 0.0 ? -1.0 : 1
.0)" | |
| 572 " * sqrt(%s));\n", qName.c_str(), bVar.c_str(), | |
| 573 bVar.c_str(), dName.c_str()); | |
| 574 | |
| 575 // compute both roots | |
| 576 // r0 = q * params[1] | |
| 577 builder->fsCodeAppendf("\t\tfloat %s = %s * %s;\n", r0Name.c_str(), | |
| 578 qName.c_str(), p1.c_str()); | |
| 579 // r1 = c / q | |
| 580 builder->fsCodeAppendf("\t\tfloat %s = %s / %s;\n", r1Name.c_str(), | |
| 581 cName.c_str(), qName.c_str()); | |
| 582 | |
| 583 // Note: If there are two roots that both generate radius(t) > 0, the | |
| 584 // Canvas spec says to choose the larger t. | |
| 585 | |
| 586 // so we'll look at the larger one first: | |
| 587 builder->fsCodeAppendf("\t\tfloat %s = max(%s, %s);\n", tName.c_str(), | |
| 588 r0Name.c_str(), r1Name.c_str()); | |
| 589 | |
| 590 // if r(t) > 0, then we're done; t will be our x coordinate | |
| 591 builder->fsCodeAppendf("\t\tif (%s * %s + %s > 0.0) {\n", tName.c_str(), | |
| 592 p5.c_str(), p3.c_str()); | |
| 593 | |
| 594 builder->fsCodeAppend("\t\t"); | |
| 595 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, sa
mplers); | |
| 596 | |
| 597 // otherwise, if r(t) for the larger root was <= 0, try the other root | |
| 598 builder->fsCodeAppend("\t\t} else {\n"); | |
| 599 builder->fsCodeAppendf("\t\t\t%s = min(%s, %s);\n", tName.c_str(), | |
| 600 r0Name.c_str(), r1Name.c_str()); | |
| 601 | |
| 602 // if r(t) > 0 for the smaller root, then t will be our x coordinate | |
| 603 builder->fsCodeAppendf("\t\t\tif (%s * %s + %s > 0.0) {\n", | |
| 604 tName.c_str(), p5.c_str(), p3.c_str()); | |
| 605 | |
| 606 builder->fsCodeAppend("\t\t\t"); | |
| 607 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, sa
mplers); | |
| 608 | |
| 609 // end if (r(t) > 0) for smaller root | |
| 610 builder->fsCodeAppend("\t\t\t}\n"); | |
| 611 // end if (r(t) > 0), else, for larger root | |
| 612 builder->fsCodeAppend("\t\t}\n"); | |
| 613 // end if (discriminant >= 0) | |
| 614 builder->fsCodeAppend("\t}\n"); | |
| 615 } else { | |
| 616 | |
| 617 // linear case: t = -c/b | |
| 618 builder->fsCodeAppendf("\tfloat %s = -(%s / %s);\n", tName.c_str(), | |
| 619 cName.c_str(), bVar.c_str()); | |
| 620 | |
| 621 // if r(t) > 0, then t will be the x coordinate | |
| 622 builder->fsCodeAppendf("\tif (%s * %s + %s > 0.0) {\n", tName.c_str(), | |
| 623 p5.c_str(), p3.c_str()); | |
| 624 builder->fsCodeAppend("\t"); | |
| 625 this->emitColor(builder, tName.c_str(), key, outputColor, inputColor, sa
mplers); | |
| 626 builder->fsCodeAppend("\t}\n"); | |
| 627 } | |
| 628 } | |
| 629 | |
| 630 void GrGLConical2Gradient::setData(const GrGLUniformManager& uman, | |
| 631 const GrDrawEffect& drawEffect) { | |
| 632 INHERITED::setData(uman, drawEffect); | |
| 633 const GrConical2Gradient& data = drawEffect.castEffect<GrConical2Gradient>()
; | |
| 634 SkASSERT(data.isDegenerate() == fIsDegenerate); | |
| 635 SkScalar centerX1 = data.center(); | |
| 636 SkScalar radius0 = data.radius(); | |
| 637 SkScalar diffRadius = data.diffRadius(); | |
| 638 | |
| 639 if (fCachedCenter != centerX1 || | |
| 640 fCachedRadius != radius0 || | |
| 641 fCachedDiffRadius != diffRadius) { | |
| 642 | |
| 643 SkScalar a = SkScalarMul(centerX1, centerX1) - diffRadius * diffRadius; | |
| 644 | |
| 645 // When we're in the degenerate (linear) case, the second | |
| 646 // value will be INF but the program doesn't read it. (We | |
| 647 // use the same 6 uniforms even though we don't need them | |
| 648 // all in the linear case just to keep the code complexity | |
| 649 // down). | |
| 650 float values[6] = { | |
| 651 SkScalarToFloat(a * 4), | |
| 652 1.f / (SkScalarToFloat(a)), | |
| 653 SkScalarToFloat(centerX1), | |
| 654 SkScalarToFloat(radius0), | |
| 655 SkScalarToFloat(SkScalarMul(radius0, radius0)), | |
| 656 SkScalarToFloat(diffRadius) | |
| 657 }; | |
| 658 | |
| 659 uman.set1fv(fParamUni, 6, values); | |
| 660 fCachedCenter = centerX1; | |
| 661 fCachedRadius = radius0; | |
| 662 fCachedDiffRadius = diffRadius; | |
| 663 } | |
| 664 } | |
| 665 | |
| 666 GrGLEffect::EffectKey GrGLConical2Gradient::GenKey(const GrDrawEffect& drawEffec
t, | |
| 667 const GrGLCaps&) { | |
| 668 enum { | |
| 669 kIsDegenerate = 1 << kBaseKeyBitCnt, | |
| 670 }; | |
| 671 | |
| 672 EffectKey key = GenBaseGradientKey(drawEffect); | |
| 673 if (drawEffect.castEffect<GrConical2Gradient>().isDegenerate()) { | |
| 674 key |= kIsDegenerate; | |
| 675 } | |
| 676 return key; | |
| 677 } | |
| 678 | |
| 679 ///////////////////////////////////////////////////////////////////// | |
| 680 | |
| 681 GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const Sk
Paint&) const { | 335 GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext* context, const Sk
Paint&) const { |
| 682 SkASSERT(NULL != context); | 336 SkASSERT(NULL != context); |
| 683 SkASSERT(fPtsToUnit.isIdentity()); | 337 SkASSERT(fPtsToUnit.isIdentity()); |
| 684 // invert the localM, translate to center1, rotate so center2 is on x axis. | 338 // invert the localM, translate to center1, rotate so center2 is on x axis. |
| 685 SkMatrix matrix; | 339 SkMatrix matrix; |
| 686 if (!this->getLocalMatrix().invert(&matrix)) { | 340 if (!this->getLocalMatrix().invert(&matrix)) { |
| 687 return NULL; | 341 return NULL; |
| 688 } | 342 } |
| 689 matrix.postTranslate(-fCenter1.fX, -fCenter1.fY); | 343 matrix.postTranslate(-fCenter1.fX, -fCenter1.fY); |
| 690 | 344 |
| 691 SkPoint diff = fCenter2 - fCenter1; | 345 SkPoint diff = fCenter2 - fCenter1; |
| 692 SkScalar diffLen = diff.length(); | 346 SkScalar diffLen = diff.length(); |
| 693 if (0 != diffLen) { | 347 if (0 != diffLen) { |
| 694 SkScalar invDiffLen = SkScalarInvert(diffLen); | 348 SkScalar invDiffLen = SkScalarInvert(diffLen); |
| 695 SkMatrix rot; | 349 SkMatrix rot; |
| 696 rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY), | 350 rot.setSinCos(-SkScalarMul(invDiffLen, diff.fY), |
| 697 SkScalarMul(invDiffLen, diff.fX)); | 351 SkScalarMul(invDiffLen, diff.fX)); |
| 698 matrix.postConcat(rot); | 352 matrix.postConcat(rot); |
| 699 } | 353 } |
| 700 | 354 |
| 701 return GrConical2Gradient::Create(context, *this, matrix, fTileMode); | 355 return Gr2PntConicalGradientEffect::Create(context, *this, matrix, fTileMode
); |
| 702 } | 356 } |
| 703 | 357 |
| 704 #else | 358 #else |
| 705 | 359 |
| 706 GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&)
const { | 360 GrEffectRef* SkTwoPointConicalGradient::asNewEffect(GrContext*, const SkPaint&)
const { |
| 707 SkDEBUGFAIL("Should not call in GPU-less build"); | 361 SkDEBUGFAIL("Should not call in GPU-less build"); |
| 708 return NULL; | 362 return NULL; |
| 709 } | 363 } |
| 710 | 364 |
| 711 #endif | 365 #endif |
| (...skipping 16 matching lines...) Expand all Loading... |
| 728 str->appendScalar(fCenter2.fY); | 382 str->appendScalar(fCenter2.fY); |
| 729 str->append(") radius2: "); | 383 str->append(") radius2: "); |
| 730 str->appendScalar(fRadius2); | 384 str->appendScalar(fRadius2); |
| 731 str->append(" "); | 385 str->append(" "); |
| 732 | 386 |
| 733 this->INHERITED::toString(str); | 387 this->INHERITED::toString(str); |
| 734 | 388 |
| 735 str->append(")"); | 389 str->append(")"); |
| 736 } | 390 } |
| 737 #endif | 391 #endif |
| OLD | NEW |