Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsRenderingContext.h"
7 #include <string.h> // for strlen
8 #include <algorithm> // for min
9 #include "gfxColor.h" // for gfxRGBA
10 #include "gfxMatrix.h" // for gfxMatrix
11 #include "gfxPoint.h" // for gfxPoint, gfxSize
12 #include "gfxRect.h" // for gfxRect
13 #include "gfxTypes.h" // for gfxFloat
14 #include "mozilla/gfx/BasePoint.h" // for BasePoint
15 #include "mozilla/mozalloc.h" // for operator delete[], etc
16 #include "nsBoundingMetrics.h" // for nsBoundingMetrics
17 #include "nsCharTraits.h" // for NS_IS_LOW_SURROGATE
18 #include "nsDebug.h" // for NS_ERROR
19 #include "nsPoint.h" // for nsPoint
20 #include "nsRect.h" // for nsRect, nsIntRect
21 #include "nsRegion.h" // for nsIntRegionRectIterator, etc
23 class gfxASurface;
25 // XXXTodo: rename FORM_TWIPS to FROM_APPUNITS
26 #define FROM_TWIPS(_x) ((gfxFloat)((_x)/(mP2A)))
27 #define FROM_TWIPS_INT(_x) (NSToIntRound((gfxFloat)((_x)/(mP2A))))
28 #define TO_TWIPS(_x) ((nscoord)((_x)*(mP2A)))
29 #define GFX_RECT_FROM_TWIPS_RECT(_r) (gfxRect(FROM_TWIPS((_r).x), FROM_TWIPS((_r).y), FROM_TWIPS((_r).width), FROM_TWIPS((_r).height)))
31 // Hard limit substring lengths to 8000 characters ... this lets us statically
32 // size the cluster buffer array in FindSafeLength
33 #define MAX_GFX_TEXT_BUF_SIZE 8000
35 static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength,
36 uint32_t aMaxChunkLength)
37 {
38 if (aLength <= aMaxChunkLength)
39 return aLength;
41 int32_t len = aMaxChunkLength;
43 // Ensure that we don't break inside a surrogate pair
44 while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
45 len--;
46 }
47 if (len == 0) {
48 // We don't want our caller to go into an infinite loop, so don't
49 // return zero. It's hard to imagine how we could actually get here
50 // unless there are languages that allow clusters of arbitrary size.
51 // If there are and someone feeds us a 500+ character cluster, too
52 // bad.
53 return aMaxChunkLength;
54 }
55 return len;
56 }
58 static int32_t FindSafeLength(const char *aString, uint32_t aLength,
59 uint32_t aMaxChunkLength)
60 {
61 // Since it's ASCII, we don't need to worry about clusters or RTL
62 return std::min(aLength, aMaxChunkLength);
63 }
65 //////////////////////////////////////////////////////////////////////
66 //// nsRenderingContext
68 void
69 nsRenderingContext::Init(nsDeviceContext* aContext,
70 gfxASurface *aThebesSurface)
71 {
72 Init(aContext, new gfxContext(aThebesSurface));
73 }
75 void
76 nsRenderingContext::Init(nsDeviceContext* aContext,
77 gfxContext *aThebesContext)
78 {
79 mDeviceContext = aContext;
80 mThebes = aThebesContext;
82 mThebes->SetLineWidth(1.0);
83 mP2A = mDeviceContext->AppUnitsPerDevPixel();
84 }
86 void
87 nsRenderingContext::Init(nsDeviceContext* aContext,
88 DrawTarget *aDrawTarget)
89 {
90 Init(aContext, new gfxContext(aDrawTarget));
91 }
93 //
94 // graphics state
95 //
97 void
98 nsRenderingContext::PushState()
99 {
100 mThebes->Save();
101 }
103 void
104 nsRenderingContext::PopState()
105 {
106 mThebes->Restore();
107 }
109 void
110 nsRenderingContext::IntersectClip(const nsRect& aRect)
111 {
112 mThebes->NewPath();
113 gfxRect clipRect(GFX_RECT_FROM_TWIPS_RECT(aRect));
114 if (mThebes->UserToDevicePixelSnapped(clipRect, true)) {
115 gfxMatrix mat(mThebes->CurrentMatrix());
116 mat.Invert();
117 clipRect = mat.Transform(clipRect);
118 mThebes->Rectangle(clipRect);
119 } else {
120 mThebes->Rectangle(clipRect);
121 }
123 mThebes->Clip();
124 }
126 void
127 nsRenderingContext::SetClip(const nsIntRegion& aRegion)
128 {
129 // Region is in device coords, no transformation. This should
130 // only be called when there is no transform in place, when we we
131 // just start painting a widget. The region is set by the platform
132 // paint routine. Therefore, there is no option to intersect with
133 // an existing clip.
135 gfxMatrix mat = mThebes->CurrentMatrix();
136 mThebes->IdentityMatrix();
138 mThebes->ResetClip();
140 mThebes->NewPath();
141 nsIntRegionRectIterator iter(aRegion);
142 const nsIntRect* rect;
143 while ((rect = iter.Next())) {
144 mThebes->Rectangle(gfxRect(rect->x, rect->y, rect->width, rect->height),
145 true);
146 }
147 mThebes->Clip();
148 mThebes->SetMatrix(mat);
149 }
151 void
152 nsRenderingContext::SetLineStyle(nsLineStyle aLineStyle)
153 {
154 switch (aLineStyle) {
155 case nsLineStyle_kSolid:
156 mThebes->SetDash(gfxContext::gfxLineSolid);
157 break;
158 case nsLineStyle_kDashed:
159 mThebes->SetDash(gfxContext::gfxLineDashed);
160 break;
161 case nsLineStyle_kDotted:
162 mThebes->SetDash(gfxContext::gfxLineDotted);
163 break;
164 case nsLineStyle_kNone:
165 default:
166 // nothing uses kNone
167 NS_ERROR("SetLineStyle: Invalid line style");
168 break;
169 }
170 }
173 void
174 nsRenderingContext::SetColor(nscolor aColor)
175 {
176 /* This sets the color assuming the sRGB color space, since that's
177 * what all CSS colors are defined to be in by the spec.
178 */
179 mThebes->SetColor(gfxRGBA(aColor));
180 }
182 void
183 nsRenderingContext::Translate(const nsPoint& aPt)
184 {
185 mThebes->Translate(gfxPoint(FROM_TWIPS(aPt.x), FROM_TWIPS(aPt.y)));
186 }
188 void
189 nsRenderingContext::Scale(float aSx, float aSy)
190 {
191 mThebes->Scale(aSx, aSy);
192 }
194 //
195 // shapes
196 //
198 void
199 nsRenderingContext::DrawLine(const nsPoint& aStartPt, const nsPoint& aEndPt)
200 {
201 DrawLine(aStartPt.x, aStartPt.y, aEndPt.x, aEndPt.y);
202 }
204 void
205 nsRenderingContext::DrawLine(nscoord aX0, nscoord aY0,
206 nscoord aX1, nscoord aY1)
207 {
208 gfxPoint p0 = gfxPoint(FROM_TWIPS(aX0), FROM_TWIPS(aY0));
209 gfxPoint p1 = gfxPoint(FROM_TWIPS(aX1), FROM_TWIPS(aY1));
211 // we can't draw thick lines with gfx, so we always assume we want
212 // pixel-aligned lines if the rendering context is at 1.0 scale
213 gfxMatrix savedMatrix = mThebes->CurrentMatrix();
214 if (!savedMatrix.HasNonTranslation()) {
215 p0 = mThebes->UserToDevice(p0);
216 p1 = mThebes->UserToDevice(p1);
218 p0.Round();
219 p1.Round();
221 mThebes->IdentityMatrix();
223 mThebes->NewPath();
225 // snap straight lines
226 if (p0.x == p1.x) {
227 mThebes->Line(p0 + gfxPoint(0.5, 0),
228 p1 + gfxPoint(0.5, 0));
229 } else if (p0.y == p1.y) {
230 mThebes->Line(p0 + gfxPoint(0, 0.5),
231 p1 + gfxPoint(0, 0.5));
232 } else {
233 mThebes->Line(p0, p1);
234 }
236 mThebes->Stroke();
238 mThebes->SetMatrix(savedMatrix);
239 } else {
240 mThebes->NewPath();
241 mThebes->Line(p0, p1);
242 mThebes->Stroke();
243 }
244 }
246 void
247 nsRenderingContext::DrawRect(const nsRect& aRect)
248 {
249 mThebes->NewPath();
250 mThebes->Rectangle(GFX_RECT_FROM_TWIPS_RECT(aRect), true);
251 mThebes->Stroke();
252 }
254 void
255 nsRenderingContext::DrawRect(nscoord aX, nscoord aY,
256 nscoord aWidth, nscoord aHeight)
257 {
258 DrawRect(nsRect(aX, aY, aWidth, aHeight));
259 }
262 /* Clamp r to (0,0) (2^23,2^23)
263 * these are to be device coordinates.
264 *
265 * Returns false if the rectangle is completely out of bounds,
266 * true otherwise.
267 *
268 * This function assumes that it will be called with a rectangle being
269 * drawn into a surface with an identity transformation matrix; that
270 * is, anything above or to the left of (0,0) will be offscreen.
271 *
272 * First it checks if the rectangle is entirely beyond
273 * CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
274 * false is returned.
275 *
276 * Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
277 * and adjusts the width and height appropriately. For example, a
278 * rectangle from (0,-5) with dimensions (5,10) will become a
279 * rectangle from (0,0) with dimensions (5,5).
280 *
281 * If after negative x/y adjustment to 0, either the width or height
282 * is negative, then the rectangle is completely offscreen, and
283 * nothing is drawn -- false is returned.
284 *
285 * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
286 * the width and height are clamped such x+width or y+height are equal
287 * to CAIRO_COORD_MAX, and true is returned.
288 */
289 #define CAIRO_COORD_MAX (double(0x7fffff))
291 static bool
292 ConditionRect(gfxRect& r) {
293 // if either x or y is way out of bounds;
294 // note that we don't handle negative w/h here
295 if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX)
296 return false;
298 if (r.X() < 0.0) {
299 r.width += r.X();
300 if (r.width < 0.0)
301 return false;
302 r.x = 0.0;
303 }
305 if (r.XMost() > CAIRO_COORD_MAX) {
306 r.width = CAIRO_COORD_MAX - r.X();
307 }
309 if (r.Y() < 0.0) {
310 r.height += r.Y();
311 if (r.Height() < 0.0)
312 return false;
314 r.y = 0.0;
315 }
317 if (r.YMost() > CAIRO_COORD_MAX) {
318 r.height = CAIRO_COORD_MAX - r.Y();
319 }
320 return true;
321 }
323 void
324 nsRenderingContext::FillRect(const nsRect& aRect)
325 {
326 gfxRect r(GFX_RECT_FROM_TWIPS_RECT(aRect));
328 /* Clamp coordinates to work around a design bug in cairo */
329 nscoord bigval = (nscoord)(CAIRO_COORD_MAX*mP2A);
330 if (aRect.width > bigval ||
331 aRect.height > bigval ||
332 aRect.x < -bigval ||
333 aRect.x > bigval ||
334 aRect.y < -bigval ||
335 aRect.y > bigval)
336 {
337 gfxMatrix mat = mThebes->CurrentMatrix();
339 r = mat.Transform(r);
341 if (!ConditionRect(r))
342 return;
344 mThebes->IdentityMatrix();
345 mThebes->NewPath();
347 mThebes->Rectangle(r, true);
348 mThebes->Fill();
349 mThebes->SetMatrix(mat);
350 }
352 mThebes->NewPath();
353 mThebes->Rectangle(r, true);
354 mThebes->Fill();
355 }
357 void
358 nsRenderingContext::FillRect(nscoord aX, nscoord aY,
359 nscoord aWidth, nscoord aHeight)
360 {
361 FillRect(nsRect(aX, aY, aWidth, aHeight));
362 }
364 void
365 nsRenderingContext::InvertRect(const nsRect& aRect)
366 {
367 gfxContext::GraphicsOperator lastOp = mThebes->CurrentOperator();
369 mThebes->SetOperator(gfxContext::OPERATOR_XOR);
370 FillRect(aRect);
371 mThebes->SetOperator(lastOp);
372 }
374 void
375 nsRenderingContext::DrawEllipse(nscoord aX, nscoord aY,
376 nscoord aWidth, nscoord aHeight)
377 {
378 mThebes->NewPath();
379 mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0,
380 FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0),
381 gfxSize(FROM_TWIPS(aWidth),
382 FROM_TWIPS(aHeight)));
383 mThebes->Stroke();
384 }
386 void
387 nsRenderingContext::FillEllipse(const nsRect& aRect)
388 {
389 FillEllipse(aRect.x, aRect.y, aRect.width, aRect.height);
390 }
392 void
393 nsRenderingContext::FillEllipse(nscoord aX, nscoord aY,
394 nscoord aWidth, nscoord aHeight)
395 {
396 mThebes->NewPath();
397 mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0,
398 FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0),
399 gfxSize(FROM_TWIPS(aWidth),
400 FROM_TWIPS(aHeight)));
401 mThebes->Fill();
402 }
404 void
405 nsRenderingContext::FillPolygon(const nsPoint twPoints[], int32_t aNumPoints)
406 {
407 if (aNumPoints == 0)
408 return;
410 nsAutoArrayPtr<gfxPoint> pxPoints(new gfxPoint[aNumPoints]);
412 for (int i = 0; i < aNumPoints; i++) {
413 pxPoints[i].x = FROM_TWIPS(twPoints[i].x);
414 pxPoints[i].y = FROM_TWIPS(twPoints[i].y);
415 }
417 mThebes->NewPath();
418 mThebes->Polygon(pxPoints, aNumPoints);
419 mThebes->Fill();
420 }
422 //
423 // text
424 //
426 void
427 nsRenderingContext::SetTextRunRTL(bool aIsRTL)
428 {
429 mFontMetrics->SetTextRunRTL(aIsRTL);
430 }
432 void
433 nsRenderingContext::SetFont(nsFontMetrics *aFontMetrics)
434 {
435 mFontMetrics = aFontMetrics;
436 }
438 int32_t
439 nsRenderingContext::GetMaxChunkLength()
440 {
441 if (!mFontMetrics)
442 return 1;
443 return std::min(mFontMetrics->GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
444 }
446 nscoord
447 nsRenderingContext::GetWidth(char aC)
448 {
449 if (aC == ' ' && mFontMetrics) {
450 return mFontMetrics->SpaceWidth();
451 }
453 return GetWidth(&aC, 1);
454 }
456 nscoord
457 nsRenderingContext::GetWidth(char16_t aC)
458 {
459 return GetWidth(&aC, 1);
460 }
462 nscoord
463 nsRenderingContext::GetWidth(const nsString& aString)
464 {
465 return GetWidth(aString.get(), aString.Length());
466 }
468 nscoord
469 nsRenderingContext::GetWidth(const char* aString)
470 {
471 return GetWidth(aString, strlen(aString));
472 }
474 nscoord
475 nsRenderingContext::GetWidth(const char* aString, uint32_t aLength)
476 {
477 uint32_t maxChunkLength = GetMaxChunkLength();
478 nscoord width = 0;
479 while (aLength > 0) {
480 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
481 width += mFontMetrics->GetWidth(aString, len, this);
482 aLength -= len;
483 aString += len;
484 }
485 return width;
486 }
488 nscoord
489 nsRenderingContext::GetWidth(const char16_t *aString, uint32_t aLength)
490 {
491 uint32_t maxChunkLength = GetMaxChunkLength();
492 nscoord width = 0;
493 while (aLength > 0) {
494 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
495 width += mFontMetrics->GetWidth(aString, len, this);
496 aLength -= len;
497 aString += len;
498 }
499 return width;
500 }
502 nsBoundingMetrics
503 nsRenderingContext::GetBoundingMetrics(const char16_t* aString,
504 uint32_t aLength)
505 {
506 uint32_t maxChunkLength = GetMaxChunkLength();
507 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
508 // Assign directly in the first iteration. This ensures that
509 // negative ascent/descent can be returned and the left bearing
510 // is properly initialized.
511 nsBoundingMetrics totalMetrics
512 = mFontMetrics->GetBoundingMetrics(aString, len, this);
513 aLength -= len;
514 aString += len;
516 while (aLength > 0) {
517 len = FindSafeLength(aString, aLength, maxChunkLength);
518 nsBoundingMetrics metrics
519 = mFontMetrics->GetBoundingMetrics(aString, len, this);
520 totalMetrics += metrics;
521 aLength -= len;
522 aString += len;
523 }
524 return totalMetrics;
525 }
527 void
528 nsRenderingContext::DrawString(const char *aString, uint32_t aLength,
529 nscoord aX, nscoord aY)
530 {
531 uint32_t maxChunkLength = GetMaxChunkLength();
532 while (aLength > 0) {
533 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
534 mFontMetrics->DrawString(aString, len, aX, aY, this);
535 aLength -= len;
537 if (aLength > 0) {
538 nscoord width = mFontMetrics->GetWidth(aString, len, this);
539 aX += width;
540 aString += len;
541 }
542 }
543 }
545 void
546 nsRenderingContext::DrawString(const nsString& aString, nscoord aX, nscoord aY)
547 {
548 DrawString(aString.get(), aString.Length(), aX, aY);
549 }
551 void
552 nsRenderingContext::DrawString(const char16_t *aString, uint32_t aLength,
553 nscoord aX, nscoord aY)
554 {
555 uint32_t maxChunkLength = GetMaxChunkLength();
556 if (aLength <= maxChunkLength) {
557 mFontMetrics->DrawString(aString, aLength, aX, aY, this, this);
558 return;
559 }
561 bool isRTL = mFontMetrics->GetTextRunRTL();
563 // If we're drawing right to left, we must start at the end.
564 if (isRTL) {
565 aX += GetWidth(aString, aLength);
566 }
568 while (aLength > 0) {
569 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
570 nscoord width = mFontMetrics->GetWidth(aString, len, this);
571 if (isRTL) {
572 aX -= width;
573 }
574 mFontMetrics->DrawString(aString, len, aX, aY, this, this);
575 if (!isRTL) {
576 aX += width;
577 }
578 aLength -= len;
579 aString += len;
580 }
581 }