1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/gfx/src/nsRenderingContext.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,581 @@ 1.4 +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsRenderingContext.h" 1.10 +#include <string.h> // for strlen 1.11 +#include <algorithm> // for min 1.12 +#include "gfxColor.h" // for gfxRGBA 1.13 +#include "gfxMatrix.h" // for gfxMatrix 1.14 +#include "gfxPoint.h" // for gfxPoint, gfxSize 1.15 +#include "gfxRect.h" // for gfxRect 1.16 +#include "gfxTypes.h" // for gfxFloat 1.17 +#include "mozilla/gfx/BasePoint.h" // for BasePoint 1.18 +#include "mozilla/mozalloc.h" // for operator delete[], etc 1.19 +#include "nsBoundingMetrics.h" // for nsBoundingMetrics 1.20 +#include "nsCharTraits.h" // for NS_IS_LOW_SURROGATE 1.21 +#include "nsDebug.h" // for NS_ERROR 1.22 +#include "nsPoint.h" // for nsPoint 1.23 +#include "nsRect.h" // for nsRect, nsIntRect 1.24 +#include "nsRegion.h" // for nsIntRegionRectIterator, etc 1.25 + 1.26 +class gfxASurface; 1.27 + 1.28 +// XXXTodo: rename FORM_TWIPS to FROM_APPUNITS 1.29 +#define FROM_TWIPS(_x) ((gfxFloat)((_x)/(mP2A))) 1.30 +#define FROM_TWIPS_INT(_x) (NSToIntRound((gfxFloat)((_x)/(mP2A)))) 1.31 +#define TO_TWIPS(_x) ((nscoord)((_x)*(mP2A))) 1.32 +#define GFX_RECT_FROM_TWIPS_RECT(_r) (gfxRect(FROM_TWIPS((_r).x), FROM_TWIPS((_r).y), FROM_TWIPS((_r).width), FROM_TWIPS((_r).height))) 1.33 + 1.34 +// Hard limit substring lengths to 8000 characters ... this lets us statically 1.35 +// size the cluster buffer array in FindSafeLength 1.36 +#define MAX_GFX_TEXT_BUF_SIZE 8000 1.37 + 1.38 +static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength, 1.39 + uint32_t aMaxChunkLength) 1.40 +{ 1.41 + if (aLength <= aMaxChunkLength) 1.42 + return aLength; 1.43 + 1.44 + int32_t len = aMaxChunkLength; 1.45 + 1.46 + // Ensure that we don't break inside a surrogate pair 1.47 + while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) { 1.48 + len--; 1.49 + } 1.50 + if (len == 0) { 1.51 + // We don't want our caller to go into an infinite loop, so don't 1.52 + // return zero. It's hard to imagine how we could actually get here 1.53 + // unless there are languages that allow clusters of arbitrary size. 1.54 + // If there are and someone feeds us a 500+ character cluster, too 1.55 + // bad. 1.56 + return aMaxChunkLength; 1.57 + } 1.58 + return len; 1.59 +} 1.60 + 1.61 +static int32_t FindSafeLength(const char *aString, uint32_t aLength, 1.62 + uint32_t aMaxChunkLength) 1.63 +{ 1.64 + // Since it's ASCII, we don't need to worry about clusters or RTL 1.65 + return std::min(aLength, aMaxChunkLength); 1.66 +} 1.67 + 1.68 +////////////////////////////////////////////////////////////////////// 1.69 +//// nsRenderingContext 1.70 + 1.71 +void 1.72 +nsRenderingContext::Init(nsDeviceContext* aContext, 1.73 + gfxASurface *aThebesSurface) 1.74 +{ 1.75 + Init(aContext, new gfxContext(aThebesSurface)); 1.76 +} 1.77 + 1.78 +void 1.79 +nsRenderingContext::Init(nsDeviceContext* aContext, 1.80 + gfxContext *aThebesContext) 1.81 +{ 1.82 + mDeviceContext = aContext; 1.83 + mThebes = aThebesContext; 1.84 + 1.85 + mThebes->SetLineWidth(1.0); 1.86 + mP2A = mDeviceContext->AppUnitsPerDevPixel(); 1.87 +} 1.88 + 1.89 +void 1.90 +nsRenderingContext::Init(nsDeviceContext* aContext, 1.91 + DrawTarget *aDrawTarget) 1.92 +{ 1.93 + Init(aContext, new gfxContext(aDrawTarget)); 1.94 +} 1.95 + 1.96 +// 1.97 +// graphics state 1.98 +// 1.99 + 1.100 +void 1.101 +nsRenderingContext::PushState() 1.102 +{ 1.103 + mThebes->Save(); 1.104 +} 1.105 + 1.106 +void 1.107 +nsRenderingContext::PopState() 1.108 +{ 1.109 + mThebes->Restore(); 1.110 +} 1.111 + 1.112 +void 1.113 +nsRenderingContext::IntersectClip(const nsRect& aRect) 1.114 +{ 1.115 + mThebes->NewPath(); 1.116 + gfxRect clipRect(GFX_RECT_FROM_TWIPS_RECT(aRect)); 1.117 + if (mThebes->UserToDevicePixelSnapped(clipRect, true)) { 1.118 + gfxMatrix mat(mThebes->CurrentMatrix()); 1.119 + mat.Invert(); 1.120 + clipRect = mat.Transform(clipRect); 1.121 + mThebes->Rectangle(clipRect); 1.122 + } else { 1.123 + mThebes->Rectangle(clipRect); 1.124 + } 1.125 + 1.126 + mThebes->Clip(); 1.127 +} 1.128 + 1.129 +void 1.130 +nsRenderingContext::SetClip(const nsIntRegion& aRegion) 1.131 +{ 1.132 + // Region is in device coords, no transformation. This should 1.133 + // only be called when there is no transform in place, when we we 1.134 + // just start painting a widget. The region is set by the platform 1.135 + // paint routine. Therefore, there is no option to intersect with 1.136 + // an existing clip. 1.137 + 1.138 + gfxMatrix mat = mThebes->CurrentMatrix(); 1.139 + mThebes->IdentityMatrix(); 1.140 + 1.141 + mThebes->ResetClip(); 1.142 + 1.143 + mThebes->NewPath(); 1.144 + nsIntRegionRectIterator iter(aRegion); 1.145 + const nsIntRect* rect; 1.146 + while ((rect = iter.Next())) { 1.147 + mThebes->Rectangle(gfxRect(rect->x, rect->y, rect->width, rect->height), 1.148 + true); 1.149 + } 1.150 + mThebes->Clip(); 1.151 + mThebes->SetMatrix(mat); 1.152 +} 1.153 + 1.154 +void 1.155 +nsRenderingContext::SetLineStyle(nsLineStyle aLineStyle) 1.156 +{ 1.157 + switch (aLineStyle) { 1.158 + case nsLineStyle_kSolid: 1.159 + mThebes->SetDash(gfxContext::gfxLineSolid); 1.160 + break; 1.161 + case nsLineStyle_kDashed: 1.162 + mThebes->SetDash(gfxContext::gfxLineDashed); 1.163 + break; 1.164 + case nsLineStyle_kDotted: 1.165 + mThebes->SetDash(gfxContext::gfxLineDotted); 1.166 + break; 1.167 + case nsLineStyle_kNone: 1.168 + default: 1.169 + // nothing uses kNone 1.170 + NS_ERROR("SetLineStyle: Invalid line style"); 1.171 + break; 1.172 + } 1.173 +} 1.174 + 1.175 + 1.176 +void 1.177 +nsRenderingContext::SetColor(nscolor aColor) 1.178 +{ 1.179 + /* This sets the color assuming the sRGB color space, since that's 1.180 + * what all CSS colors are defined to be in by the spec. 1.181 + */ 1.182 + mThebes->SetColor(gfxRGBA(aColor)); 1.183 +} 1.184 + 1.185 +void 1.186 +nsRenderingContext::Translate(const nsPoint& aPt) 1.187 +{ 1.188 + mThebes->Translate(gfxPoint(FROM_TWIPS(aPt.x), FROM_TWIPS(aPt.y))); 1.189 +} 1.190 + 1.191 +void 1.192 +nsRenderingContext::Scale(float aSx, float aSy) 1.193 +{ 1.194 + mThebes->Scale(aSx, aSy); 1.195 +} 1.196 + 1.197 +// 1.198 +// shapes 1.199 +// 1.200 + 1.201 +void 1.202 +nsRenderingContext::DrawLine(const nsPoint& aStartPt, const nsPoint& aEndPt) 1.203 +{ 1.204 + DrawLine(aStartPt.x, aStartPt.y, aEndPt.x, aEndPt.y); 1.205 +} 1.206 + 1.207 +void 1.208 +nsRenderingContext::DrawLine(nscoord aX0, nscoord aY0, 1.209 + nscoord aX1, nscoord aY1) 1.210 +{ 1.211 + gfxPoint p0 = gfxPoint(FROM_TWIPS(aX0), FROM_TWIPS(aY0)); 1.212 + gfxPoint p1 = gfxPoint(FROM_TWIPS(aX1), FROM_TWIPS(aY1)); 1.213 + 1.214 + // we can't draw thick lines with gfx, so we always assume we want 1.215 + // pixel-aligned lines if the rendering context is at 1.0 scale 1.216 + gfxMatrix savedMatrix = mThebes->CurrentMatrix(); 1.217 + if (!savedMatrix.HasNonTranslation()) { 1.218 + p0 = mThebes->UserToDevice(p0); 1.219 + p1 = mThebes->UserToDevice(p1); 1.220 + 1.221 + p0.Round(); 1.222 + p1.Round(); 1.223 + 1.224 + mThebes->IdentityMatrix(); 1.225 + 1.226 + mThebes->NewPath(); 1.227 + 1.228 + // snap straight lines 1.229 + if (p0.x == p1.x) { 1.230 + mThebes->Line(p0 + gfxPoint(0.5, 0), 1.231 + p1 + gfxPoint(0.5, 0)); 1.232 + } else if (p0.y == p1.y) { 1.233 + mThebes->Line(p0 + gfxPoint(0, 0.5), 1.234 + p1 + gfxPoint(0, 0.5)); 1.235 + } else { 1.236 + mThebes->Line(p0, p1); 1.237 + } 1.238 + 1.239 + mThebes->Stroke(); 1.240 + 1.241 + mThebes->SetMatrix(savedMatrix); 1.242 + } else { 1.243 + mThebes->NewPath(); 1.244 + mThebes->Line(p0, p1); 1.245 + mThebes->Stroke(); 1.246 + } 1.247 +} 1.248 + 1.249 +void 1.250 +nsRenderingContext::DrawRect(const nsRect& aRect) 1.251 +{ 1.252 + mThebes->NewPath(); 1.253 + mThebes->Rectangle(GFX_RECT_FROM_TWIPS_RECT(aRect), true); 1.254 + mThebes->Stroke(); 1.255 +} 1.256 + 1.257 +void 1.258 +nsRenderingContext::DrawRect(nscoord aX, nscoord aY, 1.259 + nscoord aWidth, nscoord aHeight) 1.260 +{ 1.261 + DrawRect(nsRect(aX, aY, aWidth, aHeight)); 1.262 +} 1.263 + 1.264 + 1.265 +/* Clamp r to (0,0) (2^23,2^23) 1.266 + * these are to be device coordinates. 1.267 + * 1.268 + * Returns false if the rectangle is completely out of bounds, 1.269 + * true otherwise. 1.270 + * 1.271 + * This function assumes that it will be called with a rectangle being 1.272 + * drawn into a surface with an identity transformation matrix; that 1.273 + * is, anything above or to the left of (0,0) will be offscreen. 1.274 + * 1.275 + * First it checks if the rectangle is entirely beyond 1.276 + * CAIRO_COORD_MAX; if so, it can't ever appear on the screen -- 1.277 + * false is returned. 1.278 + * 1.279 + * Then it shifts any rectangles with x/y < 0 so that x and y are = 0, 1.280 + * and adjusts the width and height appropriately. For example, a 1.281 + * rectangle from (0,-5) with dimensions (5,10) will become a 1.282 + * rectangle from (0,0) with dimensions (5,5). 1.283 + * 1.284 + * If after negative x/y adjustment to 0, either the width or height 1.285 + * is negative, then the rectangle is completely offscreen, and 1.286 + * nothing is drawn -- false is returned. 1.287 + * 1.288 + * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX, 1.289 + * the width and height are clamped such x+width or y+height are equal 1.290 + * to CAIRO_COORD_MAX, and true is returned. 1.291 + */ 1.292 +#define CAIRO_COORD_MAX (double(0x7fffff)) 1.293 + 1.294 +static bool 1.295 +ConditionRect(gfxRect& r) { 1.296 + // if either x or y is way out of bounds; 1.297 + // note that we don't handle negative w/h here 1.298 + if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) 1.299 + return false; 1.300 + 1.301 + if (r.X() < 0.0) { 1.302 + r.width += r.X(); 1.303 + if (r.width < 0.0) 1.304 + return false; 1.305 + r.x = 0.0; 1.306 + } 1.307 + 1.308 + if (r.XMost() > CAIRO_COORD_MAX) { 1.309 + r.width = CAIRO_COORD_MAX - r.X(); 1.310 + } 1.311 + 1.312 + if (r.Y() < 0.0) { 1.313 + r.height += r.Y(); 1.314 + if (r.Height() < 0.0) 1.315 + return false; 1.316 + 1.317 + r.y = 0.0; 1.318 + } 1.319 + 1.320 + if (r.YMost() > CAIRO_COORD_MAX) { 1.321 + r.height = CAIRO_COORD_MAX - r.Y(); 1.322 + } 1.323 + return true; 1.324 +} 1.325 + 1.326 +void 1.327 +nsRenderingContext::FillRect(const nsRect& aRect) 1.328 +{ 1.329 + gfxRect r(GFX_RECT_FROM_TWIPS_RECT(aRect)); 1.330 + 1.331 + /* Clamp coordinates to work around a design bug in cairo */ 1.332 + nscoord bigval = (nscoord)(CAIRO_COORD_MAX*mP2A); 1.333 + if (aRect.width > bigval || 1.334 + aRect.height > bigval || 1.335 + aRect.x < -bigval || 1.336 + aRect.x > bigval || 1.337 + aRect.y < -bigval || 1.338 + aRect.y > bigval) 1.339 + { 1.340 + gfxMatrix mat = mThebes->CurrentMatrix(); 1.341 + 1.342 + r = mat.Transform(r); 1.343 + 1.344 + if (!ConditionRect(r)) 1.345 + return; 1.346 + 1.347 + mThebes->IdentityMatrix(); 1.348 + mThebes->NewPath(); 1.349 + 1.350 + mThebes->Rectangle(r, true); 1.351 + mThebes->Fill(); 1.352 + mThebes->SetMatrix(mat); 1.353 + } 1.354 + 1.355 + mThebes->NewPath(); 1.356 + mThebes->Rectangle(r, true); 1.357 + mThebes->Fill(); 1.358 +} 1.359 + 1.360 +void 1.361 +nsRenderingContext::FillRect(nscoord aX, nscoord aY, 1.362 + nscoord aWidth, nscoord aHeight) 1.363 +{ 1.364 + FillRect(nsRect(aX, aY, aWidth, aHeight)); 1.365 +} 1.366 + 1.367 +void 1.368 +nsRenderingContext::InvertRect(const nsRect& aRect) 1.369 +{ 1.370 + gfxContext::GraphicsOperator lastOp = mThebes->CurrentOperator(); 1.371 + 1.372 + mThebes->SetOperator(gfxContext::OPERATOR_XOR); 1.373 + FillRect(aRect); 1.374 + mThebes->SetOperator(lastOp); 1.375 +} 1.376 + 1.377 +void 1.378 +nsRenderingContext::DrawEllipse(nscoord aX, nscoord aY, 1.379 + nscoord aWidth, nscoord aHeight) 1.380 +{ 1.381 + mThebes->NewPath(); 1.382 + mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0, 1.383 + FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0), 1.384 + gfxSize(FROM_TWIPS(aWidth), 1.385 + FROM_TWIPS(aHeight))); 1.386 + mThebes->Stroke(); 1.387 +} 1.388 + 1.389 +void 1.390 +nsRenderingContext::FillEllipse(const nsRect& aRect) 1.391 +{ 1.392 + FillEllipse(aRect.x, aRect.y, aRect.width, aRect.height); 1.393 +} 1.394 + 1.395 +void 1.396 +nsRenderingContext::FillEllipse(nscoord aX, nscoord aY, 1.397 + nscoord aWidth, nscoord aHeight) 1.398 +{ 1.399 + mThebes->NewPath(); 1.400 + mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0, 1.401 + FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0), 1.402 + gfxSize(FROM_TWIPS(aWidth), 1.403 + FROM_TWIPS(aHeight))); 1.404 + mThebes->Fill(); 1.405 +} 1.406 + 1.407 +void 1.408 +nsRenderingContext::FillPolygon(const nsPoint twPoints[], int32_t aNumPoints) 1.409 +{ 1.410 + if (aNumPoints == 0) 1.411 + return; 1.412 + 1.413 + nsAutoArrayPtr<gfxPoint> pxPoints(new gfxPoint[aNumPoints]); 1.414 + 1.415 + for (int i = 0; i < aNumPoints; i++) { 1.416 + pxPoints[i].x = FROM_TWIPS(twPoints[i].x); 1.417 + pxPoints[i].y = FROM_TWIPS(twPoints[i].y); 1.418 + } 1.419 + 1.420 + mThebes->NewPath(); 1.421 + mThebes->Polygon(pxPoints, aNumPoints); 1.422 + mThebes->Fill(); 1.423 +} 1.424 + 1.425 +// 1.426 +// text 1.427 +// 1.428 + 1.429 +void 1.430 +nsRenderingContext::SetTextRunRTL(bool aIsRTL) 1.431 +{ 1.432 + mFontMetrics->SetTextRunRTL(aIsRTL); 1.433 +} 1.434 + 1.435 +void 1.436 +nsRenderingContext::SetFont(nsFontMetrics *aFontMetrics) 1.437 +{ 1.438 + mFontMetrics = aFontMetrics; 1.439 +} 1.440 + 1.441 +int32_t 1.442 +nsRenderingContext::GetMaxChunkLength() 1.443 +{ 1.444 + if (!mFontMetrics) 1.445 + return 1; 1.446 + return std::min(mFontMetrics->GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE); 1.447 +} 1.448 + 1.449 +nscoord 1.450 +nsRenderingContext::GetWidth(char aC) 1.451 +{ 1.452 + if (aC == ' ' && mFontMetrics) { 1.453 + return mFontMetrics->SpaceWidth(); 1.454 + } 1.455 + 1.456 + return GetWidth(&aC, 1); 1.457 +} 1.458 + 1.459 +nscoord 1.460 +nsRenderingContext::GetWidth(char16_t aC) 1.461 +{ 1.462 + return GetWidth(&aC, 1); 1.463 +} 1.464 + 1.465 +nscoord 1.466 +nsRenderingContext::GetWidth(const nsString& aString) 1.467 +{ 1.468 + return GetWidth(aString.get(), aString.Length()); 1.469 +} 1.470 + 1.471 +nscoord 1.472 +nsRenderingContext::GetWidth(const char* aString) 1.473 +{ 1.474 + return GetWidth(aString, strlen(aString)); 1.475 +} 1.476 + 1.477 +nscoord 1.478 +nsRenderingContext::GetWidth(const char* aString, uint32_t aLength) 1.479 +{ 1.480 + uint32_t maxChunkLength = GetMaxChunkLength(); 1.481 + nscoord width = 0; 1.482 + while (aLength > 0) { 1.483 + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); 1.484 + width += mFontMetrics->GetWidth(aString, len, this); 1.485 + aLength -= len; 1.486 + aString += len; 1.487 + } 1.488 + return width; 1.489 +} 1.490 + 1.491 +nscoord 1.492 +nsRenderingContext::GetWidth(const char16_t *aString, uint32_t aLength) 1.493 +{ 1.494 + uint32_t maxChunkLength = GetMaxChunkLength(); 1.495 + nscoord width = 0; 1.496 + while (aLength > 0) { 1.497 + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); 1.498 + width += mFontMetrics->GetWidth(aString, len, this); 1.499 + aLength -= len; 1.500 + aString += len; 1.501 + } 1.502 + return width; 1.503 +} 1.504 + 1.505 +nsBoundingMetrics 1.506 +nsRenderingContext::GetBoundingMetrics(const char16_t* aString, 1.507 + uint32_t aLength) 1.508 +{ 1.509 + uint32_t maxChunkLength = GetMaxChunkLength(); 1.510 + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); 1.511 + // Assign directly in the first iteration. This ensures that 1.512 + // negative ascent/descent can be returned and the left bearing 1.513 + // is properly initialized. 1.514 + nsBoundingMetrics totalMetrics 1.515 + = mFontMetrics->GetBoundingMetrics(aString, len, this); 1.516 + aLength -= len; 1.517 + aString += len; 1.518 + 1.519 + while (aLength > 0) { 1.520 + len = FindSafeLength(aString, aLength, maxChunkLength); 1.521 + nsBoundingMetrics metrics 1.522 + = mFontMetrics->GetBoundingMetrics(aString, len, this); 1.523 + totalMetrics += metrics; 1.524 + aLength -= len; 1.525 + aString += len; 1.526 + } 1.527 + return totalMetrics; 1.528 +} 1.529 + 1.530 +void 1.531 +nsRenderingContext::DrawString(const char *aString, uint32_t aLength, 1.532 + nscoord aX, nscoord aY) 1.533 +{ 1.534 + uint32_t maxChunkLength = GetMaxChunkLength(); 1.535 + while (aLength > 0) { 1.536 + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); 1.537 + mFontMetrics->DrawString(aString, len, aX, aY, this); 1.538 + aLength -= len; 1.539 + 1.540 + if (aLength > 0) { 1.541 + nscoord width = mFontMetrics->GetWidth(aString, len, this); 1.542 + aX += width; 1.543 + aString += len; 1.544 + } 1.545 + } 1.546 +} 1.547 + 1.548 +void 1.549 +nsRenderingContext::DrawString(const nsString& aString, nscoord aX, nscoord aY) 1.550 +{ 1.551 + DrawString(aString.get(), aString.Length(), aX, aY); 1.552 +} 1.553 + 1.554 +void 1.555 +nsRenderingContext::DrawString(const char16_t *aString, uint32_t aLength, 1.556 + nscoord aX, nscoord aY) 1.557 +{ 1.558 + uint32_t maxChunkLength = GetMaxChunkLength(); 1.559 + if (aLength <= maxChunkLength) { 1.560 + mFontMetrics->DrawString(aString, aLength, aX, aY, this, this); 1.561 + return; 1.562 + } 1.563 + 1.564 + bool isRTL = mFontMetrics->GetTextRunRTL(); 1.565 + 1.566 + // If we're drawing right to left, we must start at the end. 1.567 + if (isRTL) { 1.568 + aX += GetWidth(aString, aLength); 1.569 + } 1.570 + 1.571 + while (aLength > 0) { 1.572 + int32_t len = FindSafeLength(aString, aLength, maxChunkLength); 1.573 + nscoord width = mFontMetrics->GetWidth(aString, len, this); 1.574 + if (isRTL) { 1.575 + aX -= width; 1.576 + } 1.577 + mFontMetrics->DrawString(aString, len, aX, aY, this, this); 1.578 + if (!isRTL) { 1.579 + aX += width; 1.580 + } 1.581 + aLength -= len; 1.582 + aString += len; 1.583 + } 1.584 +}