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
)
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
])) {
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
51 return aMaxChunkLength
;
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
67 nsRenderingContext::Init(nsDeviceContext
* aContext
,
68 gfxContext
*aThebesContext
)
70 mDeviceContext
= aContext
;
71 mThebes
= aThebesContext
;
73 mThebes
->SetLineWidth(1.0);
74 mP2A
= mDeviceContext
->AppUnitsPerDevPixel();
78 nsRenderingContext::Init(nsDeviceContext
* aContext
,
79 DrawTarget
*aDrawTarget
)
81 Init(aContext
, new gfxContext(aDrawTarget
));
89 nsRenderingContext::PushState()
95 nsRenderingContext::PopState()
101 nsRenderingContext::IntersectClip(const nsRect
& aRect
)
104 gfxRect
clipRect(GFX_RECT_FROM_TWIPS_RECT(aRect
));
105 if (mThebes
->UserToDevicePixelSnapped(clipRect
, true)) {
106 gfxMatrix
mat(mThebes
->CurrentMatrix());
108 clipRect
= mat
.Transform(clipRect
);
109 mThebes
->Rectangle(clipRect
);
111 mThebes
->Rectangle(clipRect
);
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
126 gfxMatrix mat
= mThebes
->CurrentMatrix();
127 mThebes
->IdentityMatrix();
129 mThebes
->ResetClip();
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
),
139 mThebes
->SetMatrix(mat
);
143 nsRenderingContext::SetLineStyle(nsLineStyle aLineStyle
)
145 switch (aLineStyle
) {
146 case nsLineStyle_kSolid
:
147 mThebes
->SetDash(gfxContext::gfxLineSolid
);
149 case nsLineStyle_kDashed
:
150 mThebes
->SetDash(gfxContext::gfxLineDashed
);
152 case nsLineStyle_kDotted
:
153 mThebes
->SetDash(gfxContext::gfxLineDotted
);
155 case nsLineStyle_kNone
:
157 // nothing uses kNone
158 NS_ERROR("SetLineStyle: Invalid line style");
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
));
174 nsRenderingContext::Translate(const nsPoint
& aPt
)
176 mThebes
->Translate(gfxPoint(FROM_TWIPS(aPt
.x
), FROM_TWIPS(aPt
.y
)));
180 nsRenderingContext::Scale(float aSx
, float aSy
)
182 mThebes
->Scale(aSx
, aSy
);
190 nsRenderingContext::DrawLine(const nsPoint
& aStartPt
, const nsPoint
& aEndPt
)
192 DrawLine(aStartPt
.x
, aStartPt
.y
, aEndPt
.x
, aEndPt
.y
);
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
);
212 mThebes
->IdentityMatrix();
216 // snap straight lines
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));
224 mThebes
->Line(p0
, p1
);
229 mThebes
->SetMatrix(savedMatrix
);
232 mThebes
->Line(p0
, p1
);
238 nsRenderingContext::DrawRect(const nsRect
& aRect
)
241 mThebes
->Rectangle(GFX_RECT_FROM_TWIPS_RECT(aRect
), true);
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,
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 --
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))
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
)
296 if (r
.XMost() > CAIRO_COORD_MAX
) {
297 r
.width
= CAIRO_COORD_MAX
- r
.X();
302 if (r
.Height() < 0.0)
308 if (r
.YMost() > CAIRO_COORD_MAX
) {
309 r
.height
= CAIRO_COORD_MAX
- r
.Y();
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
||
328 gfxMatrix mat
= mThebes
->CurrentMatrix();
330 r
= mat
.Transform(r
);
332 if (!ConditionRect(r
))
335 mThebes
->IdentityMatrix();
338 mThebes
->Rectangle(r
, true);
340 mThebes
->SetMatrix(mat
);
344 mThebes
->Rectangle(r
, true);
349 nsRenderingContext::FillRect(nscoord aX
, nscoord aY
,
350 nscoord aWidth
, nscoord aHeight
)
352 FillRect(nsRect(aX
, aY
, aWidth
, aHeight
));
356 nsRenderingContext::InvertRect(const nsRect
& aRect
)
358 gfxContext::GraphicsOperator lastOp
= mThebes
->CurrentOperator();
360 mThebes
->SetOperator(gfxContext::OPERATOR_XOR
);
362 mThebes
->SetOperator(lastOp
);
366 nsRenderingContext::DrawEllipse(nscoord aX
, nscoord aY
,
367 nscoord aWidth
, nscoord aHeight
)
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
)));
378 nsRenderingContext::FillEllipse(const nsRect
& aRect
)
380 FillEllipse(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
384 nsRenderingContext::FillEllipse(nscoord aX
, nscoord aY
,
385 nscoord aWidth
, nscoord aHeight
)
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
)));
396 nsRenderingContext::FillPolygon(const nsPoint twPoints
[], int32_t aNumPoints
)
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
);
409 mThebes
->Polygon(pxPoints
, aNumPoints
);
418 nsRenderingContext::SetTextRunRTL(bool aIsRTL
)
420 mFontMetrics
->SetTextRunRTL(aIsRTL
);
424 nsRenderingContext::SetFont(nsFontMetrics
*aFontMetrics
)
426 mFontMetrics
= aFontMetrics
;
430 nsRenderingContext::GetMaxChunkLength()
434 return std::min(mFontMetrics
->GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE
);
438 nsRenderingContext::GetWidth(char aC
)
440 if (aC
== ' ' && mFontMetrics
) {
441 return mFontMetrics
->SpaceWidth();
444 return GetWidth(&aC
, 1);
448 nsRenderingContext::GetWidth(char16_t aC
)
450 return GetWidth(&aC
, 1);
454 nsRenderingContext::GetWidth(const nsString
& aString
)
456 return GetWidth(aString
.get(), aString
.Length());
460 nsRenderingContext::GetWidth(const char* aString
)
462 return GetWidth(aString
, strlen(aString
));
466 nsRenderingContext::GetWidth(const char* aString
, uint32_t aLength
)
468 uint32_t maxChunkLength
= GetMaxChunkLength();
470 while (aLength
> 0) {
471 int32_t len
= FindSafeLength(aString
, aLength
, maxChunkLength
);
472 width
+= mFontMetrics
->GetWidth(aString
, len
, this);
480 nsRenderingContext::GetWidth(const char16_t
*aString
, uint32_t aLength
)
482 uint32_t maxChunkLength
= GetMaxChunkLength();
484 while (aLength
> 0) {
485 int32_t len
= FindSafeLength(aString
, aLength
, maxChunkLength
);
486 width
+= mFontMetrics
->GetWidth(aString
, len
, this);
494 nsRenderingContext::GetBoundingMetrics(const char16_t
* aString
,
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);
507 while (aLength
> 0) {
508 len
= FindSafeLength(aString
, aLength
, maxChunkLength
);
509 nsBoundingMetrics metrics
510 = mFontMetrics
->GetBoundingMetrics(aString
, len
, this);
511 totalMetrics
+= metrics
;
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);
529 nscoord width
= mFontMetrics
->GetWidth(aString
, len
, this);
537 nsRenderingContext::DrawString(const nsString
& aString
, nscoord aX
, nscoord aY
)
539 DrawString(aString
.get(), aString
.Length(), aX
, aY
);
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);
552 bool isRTL
= mFontMetrics
->GetTextRunRTL();
554 // If we're drawing right to left, we must start at the end.
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);
565 mFontMetrics
->DrawString(aString
, len
, aX
, aY
, this, this);