Bumping manifests a=b2g-bump
[gecko.git] / gfx / src / nsRenderingContext.cpp
blob464296078aa0dbb51fc31c5e543c64a0b8e8998f
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 // XXXTodo: rename FORM_TWIPS to FROM_APPUNITS
24 #define FROM_TWIPS(_x) ((gfxFloat)((_x)/(mP2A)))
25 #define FROM_TWIPS_INT(_x) (NSToIntRound((gfxFloat)((_x)/(mP2A))))
26 #define TO_TWIPS(_x) ((nscoord)((_x)*(mP2A)))
27 #define GFX_RECT_FROM_TWIPS_RECT(_r) (gfxRect(FROM_TWIPS((_r).x), FROM_TWIPS((_r).y), FROM_TWIPS((_r).width), FROM_TWIPS((_r).height)))
29 // Hard limit substring lengths to 8000 characters ... this lets us statically
30 // size the cluster buffer array in FindSafeLength
31 #define MAX_GFX_TEXT_BUF_SIZE 8000
33 static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength,
34 uint32_t aMaxChunkLength)
36 if (aLength <= aMaxChunkLength)
37 return aLength;
39 int32_t len = aMaxChunkLength;
41 // Ensure that we don't break inside a surrogate pair
42 while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
43 len--;
45 if (len == 0) {
46 // We don't want our caller to go into an infinite loop, so don't
47 // return zero. It's hard to imagine how we could actually get here
48 // unless there are languages that allow clusters of arbitrary size.
49 // If there are and someone feeds us a 500+ character cluster, too
50 // bad.
51 return aMaxChunkLength;
53 return len;
56 static int32_t FindSafeLength(const char *aString, uint32_t aLength,
57 uint32_t aMaxChunkLength)
59 // Since it's ASCII, we don't need to worry about clusters or RTL
60 return std::min(aLength, aMaxChunkLength);
63 //////////////////////////////////////////////////////////////////////
64 //// nsRenderingContext
66 void
67 nsRenderingContext::Init(nsDeviceContext* aContext,
68 gfxContext *aThebesContext)
70 mDeviceContext = aContext;
71 mThebes = aThebesContext;
73 mThebes->SetLineWidth(1.0);
74 mP2A = mDeviceContext->AppUnitsPerDevPixel();
77 void
78 nsRenderingContext::Init(nsDeviceContext* aContext,
79 DrawTarget *aDrawTarget)
81 Init(aContext, new gfxContext(aDrawTarget));
85 // graphics state
88 void
89 nsRenderingContext::PushState()
91 mThebes->Save();
94 void
95 nsRenderingContext::PopState()
97 mThebes->Restore();
100 void
101 nsRenderingContext::IntersectClip(const nsRect& aRect)
103 mThebes->NewPath();
104 gfxRect clipRect(GFX_RECT_FROM_TWIPS_RECT(aRect));
105 if (mThebes->UserToDevicePixelSnapped(clipRect, true)) {
106 gfxMatrix mat(mThebes->CurrentMatrix());
107 mat.Invert();
108 clipRect = mat.Transform(clipRect);
109 mThebes->Rectangle(clipRect);
110 } else {
111 mThebes->Rectangle(clipRect);
114 mThebes->Clip();
117 void
118 nsRenderingContext::SetClip(const nsIntRegion& aRegion)
120 // Region is in device coords, no transformation. This should
121 // only be called when there is no transform in place, when we we
122 // just start painting a widget. The region is set by the platform
123 // paint routine. Therefore, there is no option to intersect with
124 // an existing clip.
126 gfxMatrix mat = mThebes->CurrentMatrix();
127 mThebes->IdentityMatrix();
129 mThebes->ResetClip();
131 mThebes->NewPath();
132 nsIntRegionRectIterator iter(aRegion);
133 const nsIntRect* rect;
134 while ((rect = iter.Next())) {
135 mThebes->Rectangle(gfxRect(rect->x, rect->y, rect->width, rect->height),
136 true);
138 mThebes->Clip();
139 mThebes->SetMatrix(mat);
142 void
143 nsRenderingContext::SetLineStyle(nsLineStyle aLineStyle)
145 switch (aLineStyle) {
146 case nsLineStyle_kSolid:
147 mThebes->SetDash(gfxContext::gfxLineSolid);
148 break;
149 case nsLineStyle_kDashed:
150 mThebes->SetDash(gfxContext::gfxLineDashed);
151 break;
152 case nsLineStyle_kDotted:
153 mThebes->SetDash(gfxContext::gfxLineDotted);
154 break;
155 case nsLineStyle_kNone:
156 default:
157 // nothing uses kNone
158 NS_ERROR("SetLineStyle: Invalid line style");
159 break;
164 void
165 nsRenderingContext::SetColor(nscolor aColor)
167 /* This sets the color assuming the sRGB color space, since that's
168 * what all CSS colors are defined to be in by the spec.
170 mThebes->SetColor(gfxRGBA(aColor));
173 void
174 nsRenderingContext::Translate(const nsPoint& aPt)
176 mThebes->Translate(gfxPoint(FROM_TWIPS(aPt.x), FROM_TWIPS(aPt.y)));
179 void
180 nsRenderingContext::Scale(float aSx, float aSy)
182 mThebes->Scale(aSx, aSy);
186 // shapes
189 void
190 nsRenderingContext::DrawLine(const nsPoint& aStartPt, const nsPoint& aEndPt)
192 DrawLine(aStartPt.x, aStartPt.y, aEndPt.x, aEndPt.y);
195 void
196 nsRenderingContext::DrawLine(nscoord aX0, nscoord aY0,
197 nscoord aX1, nscoord aY1)
199 gfxPoint p0 = gfxPoint(FROM_TWIPS(aX0), FROM_TWIPS(aY0));
200 gfxPoint p1 = gfxPoint(FROM_TWIPS(aX1), FROM_TWIPS(aY1));
202 // we can't draw thick lines with gfx, so we always assume we want
203 // pixel-aligned lines if the rendering context is at 1.0 scale
204 gfxMatrix savedMatrix = mThebes->CurrentMatrix();
205 if (!savedMatrix.HasNonTranslation()) {
206 p0 = mThebes->UserToDevice(p0);
207 p1 = mThebes->UserToDevice(p1);
209 p0.Round();
210 p1.Round();
212 mThebes->IdentityMatrix();
214 mThebes->NewPath();
216 // snap straight lines
217 if (p0.x == p1.x) {
218 mThebes->Line(p0 + gfxPoint(0.5, 0),
219 p1 + gfxPoint(0.5, 0));
220 } else if (p0.y == p1.y) {
221 mThebes->Line(p0 + gfxPoint(0, 0.5),
222 p1 + gfxPoint(0, 0.5));
223 } else {
224 mThebes->Line(p0, p1);
227 mThebes->Stroke();
229 mThebes->SetMatrix(savedMatrix);
230 } else {
231 mThebes->NewPath();
232 mThebes->Line(p0, p1);
233 mThebes->Stroke();
237 void
238 nsRenderingContext::DrawRect(const nsRect& aRect)
240 mThebes->NewPath();
241 mThebes->Rectangle(GFX_RECT_FROM_TWIPS_RECT(aRect), true);
242 mThebes->Stroke();
245 void
246 nsRenderingContext::DrawRect(nscoord aX, nscoord aY,
247 nscoord aWidth, nscoord aHeight)
249 DrawRect(nsRect(aX, aY, aWidth, aHeight));
253 /* Clamp r to (0,0) (2^23,2^23)
254 * these are to be device coordinates.
256 * Returns false if the rectangle is completely out of bounds,
257 * true otherwise.
259 * This function assumes that it will be called with a rectangle being
260 * drawn into a surface with an identity transformation matrix; that
261 * is, anything above or to the left of (0,0) will be offscreen.
263 * First it checks if the rectangle is entirely beyond
264 * CAIRO_COORD_MAX; if so, it can't ever appear on the screen --
265 * false is returned.
267 * Then it shifts any rectangles with x/y < 0 so that x and y are = 0,
268 * and adjusts the width and height appropriately. For example, a
269 * rectangle from (0,-5) with dimensions (5,10) will become a
270 * rectangle from (0,0) with dimensions (5,5).
272 * If after negative x/y adjustment to 0, either the width or height
273 * is negative, then the rectangle is completely offscreen, and
274 * nothing is drawn -- false is returned.
276 * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX,
277 * the width and height are clamped such x+width or y+height are equal
278 * to CAIRO_COORD_MAX, and true is returned.
280 #define CAIRO_COORD_MAX (double(0x7fffff))
282 static bool
283 ConditionRect(gfxRect& r) {
284 // if either x or y is way out of bounds;
285 // note that we don't handle negative w/h here
286 if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX)
287 return false;
289 if (r.X() < 0.0) {
290 r.width += r.X();
291 if (r.width < 0.0)
292 return false;
293 r.x = 0.0;
296 if (r.XMost() > CAIRO_COORD_MAX) {
297 r.width = CAIRO_COORD_MAX - r.X();
300 if (r.Y() < 0.0) {
301 r.height += r.Y();
302 if (r.Height() < 0.0)
303 return false;
305 r.y = 0.0;
308 if (r.YMost() > CAIRO_COORD_MAX) {
309 r.height = CAIRO_COORD_MAX - r.Y();
311 return true;
314 void
315 nsRenderingContext::FillRect(const nsRect& aRect)
317 gfxRect r(GFX_RECT_FROM_TWIPS_RECT(aRect));
319 /* Clamp coordinates to work around a design bug in cairo */
320 nscoord bigval = (nscoord)(CAIRO_COORD_MAX*mP2A);
321 if (aRect.width > bigval ||
322 aRect.height > bigval ||
323 aRect.x < -bigval ||
324 aRect.x > bigval ||
325 aRect.y < -bigval ||
326 aRect.y > bigval)
328 gfxMatrix mat = mThebes->CurrentMatrix();
330 r = mat.Transform(r);
332 if (!ConditionRect(r))
333 return;
335 mThebes->IdentityMatrix();
336 mThebes->NewPath();
338 mThebes->Rectangle(r, true);
339 mThebes->Fill();
340 mThebes->SetMatrix(mat);
343 mThebes->NewPath();
344 mThebes->Rectangle(r, true);
345 mThebes->Fill();
348 void
349 nsRenderingContext::FillRect(nscoord aX, nscoord aY,
350 nscoord aWidth, nscoord aHeight)
352 FillRect(nsRect(aX, aY, aWidth, aHeight));
355 void
356 nsRenderingContext::InvertRect(const nsRect& aRect)
358 gfxContext::GraphicsOperator lastOp = mThebes->CurrentOperator();
360 mThebes->SetOperator(gfxContext::OPERATOR_XOR);
361 FillRect(aRect);
362 mThebes->SetOperator(lastOp);
365 void
366 nsRenderingContext::DrawEllipse(nscoord aX, nscoord aY,
367 nscoord aWidth, nscoord aHeight)
369 mThebes->NewPath();
370 mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0,
371 FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0),
372 gfxSize(FROM_TWIPS(aWidth),
373 FROM_TWIPS(aHeight)));
374 mThebes->Stroke();
377 void
378 nsRenderingContext::FillEllipse(const nsRect& aRect)
380 FillEllipse(aRect.x, aRect.y, aRect.width, aRect.height);
383 void
384 nsRenderingContext::FillEllipse(nscoord aX, nscoord aY,
385 nscoord aWidth, nscoord aHeight)
387 mThebes->NewPath();
388 mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0,
389 FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0),
390 gfxSize(FROM_TWIPS(aWidth),
391 FROM_TWIPS(aHeight)));
392 mThebes->Fill();
395 void
396 nsRenderingContext::FillPolygon(const nsPoint twPoints[], int32_t aNumPoints)
398 if (aNumPoints == 0)
399 return;
401 nsAutoArrayPtr<gfxPoint> pxPoints(new gfxPoint[aNumPoints]);
403 for (int i = 0; i < aNumPoints; i++) {
404 pxPoints[i].x = FROM_TWIPS(twPoints[i].x);
405 pxPoints[i].y = FROM_TWIPS(twPoints[i].y);
408 mThebes->NewPath();
409 mThebes->Polygon(pxPoints, aNumPoints);
410 mThebes->Fill();
414 // text
417 void
418 nsRenderingContext::SetTextRunRTL(bool aIsRTL)
420 mFontMetrics->SetTextRunRTL(aIsRTL);
423 void
424 nsRenderingContext::SetFont(nsFontMetrics *aFontMetrics)
426 mFontMetrics = aFontMetrics;
429 int32_t
430 nsRenderingContext::GetMaxChunkLength()
432 if (!mFontMetrics)
433 return 1;
434 return std::min(mFontMetrics->GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
437 nscoord
438 nsRenderingContext::GetWidth(char aC)
440 if (aC == ' ' && mFontMetrics) {
441 return mFontMetrics->SpaceWidth();
444 return GetWidth(&aC, 1);
447 nscoord
448 nsRenderingContext::GetWidth(char16_t aC)
450 return GetWidth(&aC, 1);
453 nscoord
454 nsRenderingContext::GetWidth(const nsString& aString)
456 return GetWidth(aString.get(), aString.Length());
459 nscoord
460 nsRenderingContext::GetWidth(const char* aString)
462 return GetWidth(aString, strlen(aString));
465 nscoord
466 nsRenderingContext::GetWidth(const char* aString, uint32_t aLength)
468 uint32_t maxChunkLength = GetMaxChunkLength();
469 nscoord width = 0;
470 while (aLength > 0) {
471 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
472 width += mFontMetrics->GetWidth(aString, len, this);
473 aLength -= len;
474 aString += len;
476 return width;
479 nscoord
480 nsRenderingContext::GetWidth(const char16_t *aString, uint32_t aLength)
482 uint32_t maxChunkLength = GetMaxChunkLength();
483 nscoord width = 0;
484 while (aLength > 0) {
485 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
486 width += mFontMetrics->GetWidth(aString, len, this);
487 aLength -= len;
488 aString += len;
490 return width;
493 nsBoundingMetrics
494 nsRenderingContext::GetBoundingMetrics(const char16_t* aString,
495 uint32_t aLength)
497 uint32_t maxChunkLength = GetMaxChunkLength();
498 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
499 // Assign directly in the first iteration. This ensures that
500 // negative ascent/descent can be returned and the left bearing
501 // is properly initialized.
502 nsBoundingMetrics totalMetrics
503 = mFontMetrics->GetBoundingMetrics(aString, len, this);
504 aLength -= len;
505 aString += len;
507 while (aLength > 0) {
508 len = FindSafeLength(aString, aLength, maxChunkLength);
509 nsBoundingMetrics metrics
510 = mFontMetrics->GetBoundingMetrics(aString, len, this);
511 totalMetrics += metrics;
512 aLength -= len;
513 aString += len;
515 return totalMetrics;
518 void
519 nsRenderingContext::DrawString(const char *aString, uint32_t aLength,
520 nscoord aX, nscoord aY)
522 uint32_t maxChunkLength = GetMaxChunkLength();
523 while (aLength > 0) {
524 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
525 mFontMetrics->DrawString(aString, len, aX, aY, this);
526 aLength -= len;
528 if (aLength > 0) {
529 nscoord width = mFontMetrics->GetWidth(aString, len, this);
530 aX += width;
531 aString += len;
536 void
537 nsRenderingContext::DrawString(const nsString& aString, nscoord aX, nscoord aY)
539 DrawString(aString.get(), aString.Length(), aX, aY);
542 void
543 nsRenderingContext::DrawString(const char16_t *aString, uint32_t aLength,
544 nscoord aX, nscoord aY)
546 uint32_t maxChunkLength = GetMaxChunkLength();
547 if (aLength <= maxChunkLength) {
548 mFontMetrics->DrawString(aString, aLength, aX, aY, this, this);
549 return;
552 bool isRTL = mFontMetrics->GetTextRunRTL();
554 // If we're drawing right to left, we must start at the end.
555 if (isRTL) {
556 aX += GetWidth(aString, aLength);
559 while (aLength > 0) {
560 int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
561 nscoord width = mFontMetrics->GetWidth(aString, len, this);
562 if (isRTL) {
563 aX -= width;
565 mFontMetrics->DrawString(aString, len, aX, aY, this, this);
566 if (!isRTL) {
567 aX += width;
569 aLength -= len;
570 aString += len;