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