|
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/. */ |
|
5 |
|
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 |
|
22 |
|
23 class gfxASurface; |
|
24 |
|
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))) |
|
30 |
|
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 |
|
34 |
|
35 static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength, |
|
36 uint32_t aMaxChunkLength) |
|
37 { |
|
38 if (aLength <= aMaxChunkLength) |
|
39 return aLength; |
|
40 |
|
41 int32_t len = aMaxChunkLength; |
|
42 |
|
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 } |
|
57 |
|
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 } |
|
64 |
|
65 ////////////////////////////////////////////////////////////////////// |
|
66 //// nsRenderingContext |
|
67 |
|
68 void |
|
69 nsRenderingContext::Init(nsDeviceContext* aContext, |
|
70 gfxASurface *aThebesSurface) |
|
71 { |
|
72 Init(aContext, new gfxContext(aThebesSurface)); |
|
73 } |
|
74 |
|
75 void |
|
76 nsRenderingContext::Init(nsDeviceContext* aContext, |
|
77 gfxContext *aThebesContext) |
|
78 { |
|
79 mDeviceContext = aContext; |
|
80 mThebes = aThebesContext; |
|
81 |
|
82 mThebes->SetLineWidth(1.0); |
|
83 mP2A = mDeviceContext->AppUnitsPerDevPixel(); |
|
84 } |
|
85 |
|
86 void |
|
87 nsRenderingContext::Init(nsDeviceContext* aContext, |
|
88 DrawTarget *aDrawTarget) |
|
89 { |
|
90 Init(aContext, new gfxContext(aDrawTarget)); |
|
91 } |
|
92 |
|
93 // |
|
94 // graphics state |
|
95 // |
|
96 |
|
97 void |
|
98 nsRenderingContext::PushState() |
|
99 { |
|
100 mThebes->Save(); |
|
101 } |
|
102 |
|
103 void |
|
104 nsRenderingContext::PopState() |
|
105 { |
|
106 mThebes->Restore(); |
|
107 } |
|
108 |
|
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 } |
|
122 |
|
123 mThebes->Clip(); |
|
124 } |
|
125 |
|
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. |
|
134 |
|
135 gfxMatrix mat = mThebes->CurrentMatrix(); |
|
136 mThebes->IdentityMatrix(); |
|
137 |
|
138 mThebes->ResetClip(); |
|
139 |
|
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 } |
|
150 |
|
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 } |
|
171 |
|
172 |
|
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 } |
|
181 |
|
182 void |
|
183 nsRenderingContext::Translate(const nsPoint& aPt) |
|
184 { |
|
185 mThebes->Translate(gfxPoint(FROM_TWIPS(aPt.x), FROM_TWIPS(aPt.y))); |
|
186 } |
|
187 |
|
188 void |
|
189 nsRenderingContext::Scale(float aSx, float aSy) |
|
190 { |
|
191 mThebes->Scale(aSx, aSy); |
|
192 } |
|
193 |
|
194 // |
|
195 // shapes |
|
196 // |
|
197 |
|
198 void |
|
199 nsRenderingContext::DrawLine(const nsPoint& aStartPt, const nsPoint& aEndPt) |
|
200 { |
|
201 DrawLine(aStartPt.x, aStartPt.y, aEndPt.x, aEndPt.y); |
|
202 } |
|
203 |
|
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)); |
|
210 |
|
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); |
|
217 |
|
218 p0.Round(); |
|
219 p1.Round(); |
|
220 |
|
221 mThebes->IdentityMatrix(); |
|
222 |
|
223 mThebes->NewPath(); |
|
224 |
|
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 } |
|
235 |
|
236 mThebes->Stroke(); |
|
237 |
|
238 mThebes->SetMatrix(savedMatrix); |
|
239 } else { |
|
240 mThebes->NewPath(); |
|
241 mThebes->Line(p0, p1); |
|
242 mThebes->Stroke(); |
|
243 } |
|
244 } |
|
245 |
|
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 } |
|
253 |
|
254 void |
|
255 nsRenderingContext::DrawRect(nscoord aX, nscoord aY, |
|
256 nscoord aWidth, nscoord aHeight) |
|
257 { |
|
258 DrawRect(nsRect(aX, aY, aWidth, aHeight)); |
|
259 } |
|
260 |
|
261 |
|
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)) |
|
290 |
|
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; |
|
297 |
|
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 } |
|
304 |
|
305 if (r.XMost() > CAIRO_COORD_MAX) { |
|
306 r.width = CAIRO_COORD_MAX - r.X(); |
|
307 } |
|
308 |
|
309 if (r.Y() < 0.0) { |
|
310 r.height += r.Y(); |
|
311 if (r.Height() < 0.0) |
|
312 return false; |
|
313 |
|
314 r.y = 0.0; |
|
315 } |
|
316 |
|
317 if (r.YMost() > CAIRO_COORD_MAX) { |
|
318 r.height = CAIRO_COORD_MAX - r.Y(); |
|
319 } |
|
320 return true; |
|
321 } |
|
322 |
|
323 void |
|
324 nsRenderingContext::FillRect(const nsRect& aRect) |
|
325 { |
|
326 gfxRect r(GFX_RECT_FROM_TWIPS_RECT(aRect)); |
|
327 |
|
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(); |
|
338 |
|
339 r = mat.Transform(r); |
|
340 |
|
341 if (!ConditionRect(r)) |
|
342 return; |
|
343 |
|
344 mThebes->IdentityMatrix(); |
|
345 mThebes->NewPath(); |
|
346 |
|
347 mThebes->Rectangle(r, true); |
|
348 mThebes->Fill(); |
|
349 mThebes->SetMatrix(mat); |
|
350 } |
|
351 |
|
352 mThebes->NewPath(); |
|
353 mThebes->Rectangle(r, true); |
|
354 mThebes->Fill(); |
|
355 } |
|
356 |
|
357 void |
|
358 nsRenderingContext::FillRect(nscoord aX, nscoord aY, |
|
359 nscoord aWidth, nscoord aHeight) |
|
360 { |
|
361 FillRect(nsRect(aX, aY, aWidth, aHeight)); |
|
362 } |
|
363 |
|
364 void |
|
365 nsRenderingContext::InvertRect(const nsRect& aRect) |
|
366 { |
|
367 gfxContext::GraphicsOperator lastOp = mThebes->CurrentOperator(); |
|
368 |
|
369 mThebes->SetOperator(gfxContext::OPERATOR_XOR); |
|
370 FillRect(aRect); |
|
371 mThebes->SetOperator(lastOp); |
|
372 } |
|
373 |
|
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 } |
|
385 |
|
386 void |
|
387 nsRenderingContext::FillEllipse(const nsRect& aRect) |
|
388 { |
|
389 FillEllipse(aRect.x, aRect.y, aRect.width, aRect.height); |
|
390 } |
|
391 |
|
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 } |
|
403 |
|
404 void |
|
405 nsRenderingContext::FillPolygon(const nsPoint twPoints[], int32_t aNumPoints) |
|
406 { |
|
407 if (aNumPoints == 0) |
|
408 return; |
|
409 |
|
410 nsAutoArrayPtr<gfxPoint> pxPoints(new gfxPoint[aNumPoints]); |
|
411 |
|
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 } |
|
416 |
|
417 mThebes->NewPath(); |
|
418 mThebes->Polygon(pxPoints, aNumPoints); |
|
419 mThebes->Fill(); |
|
420 } |
|
421 |
|
422 // |
|
423 // text |
|
424 // |
|
425 |
|
426 void |
|
427 nsRenderingContext::SetTextRunRTL(bool aIsRTL) |
|
428 { |
|
429 mFontMetrics->SetTextRunRTL(aIsRTL); |
|
430 } |
|
431 |
|
432 void |
|
433 nsRenderingContext::SetFont(nsFontMetrics *aFontMetrics) |
|
434 { |
|
435 mFontMetrics = aFontMetrics; |
|
436 } |
|
437 |
|
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 } |
|
445 |
|
446 nscoord |
|
447 nsRenderingContext::GetWidth(char aC) |
|
448 { |
|
449 if (aC == ' ' && mFontMetrics) { |
|
450 return mFontMetrics->SpaceWidth(); |
|
451 } |
|
452 |
|
453 return GetWidth(&aC, 1); |
|
454 } |
|
455 |
|
456 nscoord |
|
457 nsRenderingContext::GetWidth(char16_t aC) |
|
458 { |
|
459 return GetWidth(&aC, 1); |
|
460 } |
|
461 |
|
462 nscoord |
|
463 nsRenderingContext::GetWidth(const nsString& aString) |
|
464 { |
|
465 return GetWidth(aString.get(), aString.Length()); |
|
466 } |
|
467 |
|
468 nscoord |
|
469 nsRenderingContext::GetWidth(const char* aString) |
|
470 { |
|
471 return GetWidth(aString, strlen(aString)); |
|
472 } |
|
473 |
|
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 } |
|
487 |
|
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 } |
|
501 |
|
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; |
|
515 |
|
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 } |
|
526 |
|
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; |
|
536 |
|
537 if (aLength > 0) { |
|
538 nscoord width = mFontMetrics->GetWidth(aString, len, this); |
|
539 aX += width; |
|
540 aString += len; |
|
541 } |
|
542 } |
|
543 } |
|
544 |
|
545 void |
|
546 nsRenderingContext::DrawString(const nsString& aString, nscoord aX, nscoord aY) |
|
547 { |
|
548 DrawString(aString.get(), aString.Length(), aX, aY); |
|
549 } |
|
550 |
|
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 } |
|
560 |
|
561 bool isRTL = mFontMetrics->GetTextRunRTL(); |
|
562 |
|
563 // If we're drawing right to left, we must start at the end. |
|
564 if (isRTL) { |
|
565 aX += GetWidth(aString, aLength); |
|
566 } |
|
567 |
|
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 } |