michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsRenderingContext.h" michael@0: #include // for strlen michael@0: #include // for min michael@0: #include "gfxColor.h" // for gfxRGBA michael@0: #include "gfxMatrix.h" // for gfxMatrix michael@0: #include "gfxPoint.h" // for gfxPoint, gfxSize michael@0: #include "gfxRect.h" // for gfxRect michael@0: #include "gfxTypes.h" // for gfxFloat michael@0: #include "mozilla/gfx/BasePoint.h" // for BasePoint michael@0: #include "mozilla/mozalloc.h" // for operator delete[], etc michael@0: #include "nsBoundingMetrics.h" // for nsBoundingMetrics michael@0: #include "nsCharTraits.h" // for NS_IS_LOW_SURROGATE michael@0: #include "nsDebug.h" // for NS_ERROR michael@0: #include "nsPoint.h" // for nsPoint michael@0: #include "nsRect.h" // for nsRect, nsIntRect michael@0: #include "nsRegion.h" // for nsIntRegionRectIterator, etc michael@0: michael@0: class gfxASurface; michael@0: michael@0: // XXXTodo: rename FORM_TWIPS to FROM_APPUNITS michael@0: #define FROM_TWIPS(_x) ((gfxFloat)((_x)/(mP2A))) michael@0: #define FROM_TWIPS_INT(_x) (NSToIntRound((gfxFloat)((_x)/(mP2A)))) michael@0: #define TO_TWIPS(_x) ((nscoord)((_x)*(mP2A))) michael@0: #define GFX_RECT_FROM_TWIPS_RECT(_r) (gfxRect(FROM_TWIPS((_r).x), FROM_TWIPS((_r).y), FROM_TWIPS((_r).width), FROM_TWIPS((_r).height))) michael@0: michael@0: // Hard limit substring lengths to 8000 characters ... this lets us statically michael@0: // size the cluster buffer array in FindSafeLength michael@0: #define MAX_GFX_TEXT_BUF_SIZE 8000 michael@0: michael@0: static int32_t FindSafeLength(const char16_t *aString, uint32_t aLength, michael@0: uint32_t aMaxChunkLength) michael@0: { michael@0: if (aLength <= aMaxChunkLength) michael@0: return aLength; michael@0: michael@0: int32_t len = aMaxChunkLength; michael@0: michael@0: // Ensure that we don't break inside a surrogate pair michael@0: while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) { michael@0: len--; michael@0: } michael@0: if (len == 0) { michael@0: // We don't want our caller to go into an infinite loop, so don't michael@0: // return zero. It's hard to imagine how we could actually get here michael@0: // unless there are languages that allow clusters of arbitrary size. michael@0: // If there are and someone feeds us a 500+ character cluster, too michael@0: // bad. michael@0: return aMaxChunkLength; michael@0: } michael@0: return len; michael@0: } michael@0: michael@0: static int32_t FindSafeLength(const char *aString, uint32_t aLength, michael@0: uint32_t aMaxChunkLength) michael@0: { michael@0: // Since it's ASCII, we don't need to worry about clusters or RTL michael@0: return std::min(aLength, aMaxChunkLength); michael@0: } michael@0: michael@0: ////////////////////////////////////////////////////////////////////// michael@0: //// nsRenderingContext michael@0: michael@0: void michael@0: nsRenderingContext::Init(nsDeviceContext* aContext, michael@0: gfxASurface *aThebesSurface) michael@0: { michael@0: Init(aContext, new gfxContext(aThebesSurface)); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::Init(nsDeviceContext* aContext, michael@0: gfxContext *aThebesContext) michael@0: { michael@0: mDeviceContext = aContext; michael@0: mThebes = aThebesContext; michael@0: michael@0: mThebes->SetLineWidth(1.0); michael@0: mP2A = mDeviceContext->AppUnitsPerDevPixel(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::Init(nsDeviceContext* aContext, michael@0: DrawTarget *aDrawTarget) michael@0: { michael@0: Init(aContext, new gfxContext(aDrawTarget)); michael@0: } michael@0: michael@0: // michael@0: // graphics state michael@0: // michael@0: michael@0: void michael@0: nsRenderingContext::PushState() michael@0: { michael@0: mThebes->Save(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::PopState() michael@0: { michael@0: mThebes->Restore(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::IntersectClip(const nsRect& aRect) michael@0: { michael@0: mThebes->NewPath(); michael@0: gfxRect clipRect(GFX_RECT_FROM_TWIPS_RECT(aRect)); michael@0: if (mThebes->UserToDevicePixelSnapped(clipRect, true)) { michael@0: gfxMatrix mat(mThebes->CurrentMatrix()); michael@0: mat.Invert(); michael@0: clipRect = mat.Transform(clipRect); michael@0: mThebes->Rectangle(clipRect); michael@0: } else { michael@0: mThebes->Rectangle(clipRect); michael@0: } michael@0: michael@0: mThebes->Clip(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::SetClip(const nsIntRegion& aRegion) michael@0: { michael@0: // Region is in device coords, no transformation. This should michael@0: // only be called when there is no transform in place, when we we michael@0: // just start painting a widget. The region is set by the platform michael@0: // paint routine. Therefore, there is no option to intersect with michael@0: // an existing clip. michael@0: michael@0: gfxMatrix mat = mThebes->CurrentMatrix(); michael@0: mThebes->IdentityMatrix(); michael@0: michael@0: mThebes->ResetClip(); michael@0: michael@0: mThebes->NewPath(); michael@0: nsIntRegionRectIterator iter(aRegion); michael@0: const nsIntRect* rect; michael@0: while ((rect = iter.Next())) { michael@0: mThebes->Rectangle(gfxRect(rect->x, rect->y, rect->width, rect->height), michael@0: true); michael@0: } michael@0: mThebes->Clip(); michael@0: mThebes->SetMatrix(mat); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::SetLineStyle(nsLineStyle aLineStyle) michael@0: { michael@0: switch (aLineStyle) { michael@0: case nsLineStyle_kSolid: michael@0: mThebes->SetDash(gfxContext::gfxLineSolid); michael@0: break; michael@0: case nsLineStyle_kDashed: michael@0: mThebes->SetDash(gfxContext::gfxLineDashed); michael@0: break; michael@0: case nsLineStyle_kDotted: michael@0: mThebes->SetDash(gfxContext::gfxLineDotted); michael@0: break; michael@0: case nsLineStyle_kNone: michael@0: default: michael@0: // nothing uses kNone michael@0: NS_ERROR("SetLineStyle: Invalid line style"); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsRenderingContext::SetColor(nscolor aColor) michael@0: { michael@0: /* This sets the color assuming the sRGB color space, since that's michael@0: * what all CSS colors are defined to be in by the spec. michael@0: */ michael@0: mThebes->SetColor(gfxRGBA(aColor)); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::Translate(const nsPoint& aPt) michael@0: { michael@0: mThebes->Translate(gfxPoint(FROM_TWIPS(aPt.x), FROM_TWIPS(aPt.y))); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::Scale(float aSx, float aSy) michael@0: { michael@0: mThebes->Scale(aSx, aSy); michael@0: } michael@0: michael@0: // michael@0: // shapes michael@0: // michael@0: michael@0: void michael@0: nsRenderingContext::DrawLine(const nsPoint& aStartPt, const nsPoint& aEndPt) michael@0: { michael@0: DrawLine(aStartPt.x, aStartPt.y, aEndPt.x, aEndPt.y); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawLine(nscoord aX0, nscoord aY0, michael@0: nscoord aX1, nscoord aY1) michael@0: { michael@0: gfxPoint p0 = gfxPoint(FROM_TWIPS(aX0), FROM_TWIPS(aY0)); michael@0: gfxPoint p1 = gfxPoint(FROM_TWIPS(aX1), FROM_TWIPS(aY1)); michael@0: michael@0: // we can't draw thick lines with gfx, so we always assume we want michael@0: // pixel-aligned lines if the rendering context is at 1.0 scale michael@0: gfxMatrix savedMatrix = mThebes->CurrentMatrix(); michael@0: if (!savedMatrix.HasNonTranslation()) { michael@0: p0 = mThebes->UserToDevice(p0); michael@0: p1 = mThebes->UserToDevice(p1); michael@0: michael@0: p0.Round(); michael@0: p1.Round(); michael@0: michael@0: mThebes->IdentityMatrix(); michael@0: michael@0: mThebes->NewPath(); michael@0: michael@0: // snap straight lines michael@0: if (p0.x == p1.x) { michael@0: mThebes->Line(p0 + gfxPoint(0.5, 0), michael@0: p1 + gfxPoint(0.5, 0)); michael@0: } else if (p0.y == p1.y) { michael@0: mThebes->Line(p0 + gfxPoint(0, 0.5), michael@0: p1 + gfxPoint(0, 0.5)); michael@0: } else { michael@0: mThebes->Line(p0, p1); michael@0: } michael@0: michael@0: mThebes->Stroke(); michael@0: michael@0: mThebes->SetMatrix(savedMatrix); michael@0: } else { michael@0: mThebes->NewPath(); michael@0: mThebes->Line(p0, p1); michael@0: mThebes->Stroke(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawRect(const nsRect& aRect) michael@0: { michael@0: mThebes->NewPath(); michael@0: mThebes->Rectangle(GFX_RECT_FROM_TWIPS_RECT(aRect), true); michael@0: mThebes->Stroke(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawRect(nscoord aX, nscoord aY, michael@0: nscoord aWidth, nscoord aHeight) michael@0: { michael@0: DrawRect(nsRect(aX, aY, aWidth, aHeight)); michael@0: } michael@0: michael@0: michael@0: /* Clamp r to (0,0) (2^23,2^23) michael@0: * these are to be device coordinates. michael@0: * michael@0: * Returns false if the rectangle is completely out of bounds, michael@0: * true otherwise. michael@0: * michael@0: * This function assumes that it will be called with a rectangle being michael@0: * drawn into a surface with an identity transformation matrix; that michael@0: * is, anything above or to the left of (0,0) will be offscreen. michael@0: * michael@0: * First it checks if the rectangle is entirely beyond michael@0: * CAIRO_COORD_MAX; if so, it can't ever appear on the screen -- michael@0: * false is returned. michael@0: * michael@0: * Then it shifts any rectangles with x/y < 0 so that x and y are = 0, michael@0: * and adjusts the width and height appropriately. For example, a michael@0: * rectangle from (0,-5) with dimensions (5,10) will become a michael@0: * rectangle from (0,0) with dimensions (5,5). michael@0: * michael@0: * If after negative x/y adjustment to 0, either the width or height michael@0: * is negative, then the rectangle is completely offscreen, and michael@0: * nothing is drawn -- false is returned. michael@0: * michael@0: * Finally, if x+width or y+height are greater than CAIRO_COORD_MAX, michael@0: * the width and height are clamped such x+width or y+height are equal michael@0: * to CAIRO_COORD_MAX, and true is returned. michael@0: */ michael@0: #define CAIRO_COORD_MAX (double(0x7fffff)) michael@0: michael@0: static bool michael@0: ConditionRect(gfxRect& r) { michael@0: // if either x or y is way out of bounds; michael@0: // note that we don't handle negative w/h here michael@0: if (r.X() > CAIRO_COORD_MAX || r.Y() > CAIRO_COORD_MAX) michael@0: return false; michael@0: michael@0: if (r.X() < 0.0) { michael@0: r.width += r.X(); michael@0: if (r.width < 0.0) michael@0: return false; michael@0: r.x = 0.0; michael@0: } michael@0: michael@0: if (r.XMost() > CAIRO_COORD_MAX) { michael@0: r.width = CAIRO_COORD_MAX - r.X(); michael@0: } michael@0: michael@0: if (r.Y() < 0.0) { michael@0: r.height += r.Y(); michael@0: if (r.Height() < 0.0) michael@0: return false; michael@0: michael@0: r.y = 0.0; michael@0: } michael@0: michael@0: if (r.YMost() > CAIRO_COORD_MAX) { michael@0: r.height = CAIRO_COORD_MAX - r.Y(); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::FillRect(const nsRect& aRect) michael@0: { michael@0: gfxRect r(GFX_RECT_FROM_TWIPS_RECT(aRect)); michael@0: michael@0: /* Clamp coordinates to work around a design bug in cairo */ michael@0: nscoord bigval = (nscoord)(CAIRO_COORD_MAX*mP2A); michael@0: if (aRect.width > bigval || michael@0: aRect.height > bigval || michael@0: aRect.x < -bigval || michael@0: aRect.x > bigval || michael@0: aRect.y < -bigval || michael@0: aRect.y > bigval) michael@0: { michael@0: gfxMatrix mat = mThebes->CurrentMatrix(); michael@0: michael@0: r = mat.Transform(r); michael@0: michael@0: if (!ConditionRect(r)) michael@0: return; michael@0: michael@0: mThebes->IdentityMatrix(); michael@0: mThebes->NewPath(); michael@0: michael@0: mThebes->Rectangle(r, true); michael@0: mThebes->Fill(); michael@0: mThebes->SetMatrix(mat); michael@0: } michael@0: michael@0: mThebes->NewPath(); michael@0: mThebes->Rectangle(r, true); michael@0: mThebes->Fill(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::FillRect(nscoord aX, nscoord aY, michael@0: nscoord aWidth, nscoord aHeight) michael@0: { michael@0: FillRect(nsRect(aX, aY, aWidth, aHeight)); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::InvertRect(const nsRect& aRect) michael@0: { michael@0: gfxContext::GraphicsOperator lastOp = mThebes->CurrentOperator(); michael@0: michael@0: mThebes->SetOperator(gfxContext::OPERATOR_XOR); michael@0: FillRect(aRect); michael@0: mThebes->SetOperator(lastOp); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawEllipse(nscoord aX, nscoord aY, michael@0: nscoord aWidth, nscoord aHeight) michael@0: { michael@0: mThebes->NewPath(); michael@0: mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0, michael@0: FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0), michael@0: gfxSize(FROM_TWIPS(aWidth), michael@0: FROM_TWIPS(aHeight))); michael@0: mThebes->Stroke(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::FillEllipse(const nsRect& aRect) michael@0: { michael@0: FillEllipse(aRect.x, aRect.y, aRect.width, aRect.height); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::FillEllipse(nscoord aX, nscoord aY, michael@0: nscoord aWidth, nscoord aHeight) michael@0: { michael@0: mThebes->NewPath(); michael@0: mThebes->Ellipse(gfxPoint(FROM_TWIPS(aX) + FROM_TWIPS(aWidth)/2.0, michael@0: FROM_TWIPS(aY) + FROM_TWIPS(aHeight)/2.0), michael@0: gfxSize(FROM_TWIPS(aWidth), michael@0: FROM_TWIPS(aHeight))); michael@0: mThebes->Fill(); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::FillPolygon(const nsPoint twPoints[], int32_t aNumPoints) michael@0: { michael@0: if (aNumPoints == 0) michael@0: return; michael@0: michael@0: nsAutoArrayPtr pxPoints(new gfxPoint[aNumPoints]); michael@0: michael@0: for (int i = 0; i < aNumPoints; i++) { michael@0: pxPoints[i].x = FROM_TWIPS(twPoints[i].x); michael@0: pxPoints[i].y = FROM_TWIPS(twPoints[i].y); michael@0: } michael@0: michael@0: mThebes->NewPath(); michael@0: mThebes->Polygon(pxPoints, aNumPoints); michael@0: mThebes->Fill(); michael@0: } michael@0: michael@0: // michael@0: // text michael@0: // michael@0: michael@0: void michael@0: nsRenderingContext::SetTextRunRTL(bool aIsRTL) michael@0: { michael@0: mFontMetrics->SetTextRunRTL(aIsRTL); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::SetFont(nsFontMetrics *aFontMetrics) michael@0: { michael@0: mFontMetrics = aFontMetrics; michael@0: } michael@0: michael@0: int32_t michael@0: nsRenderingContext::GetMaxChunkLength() michael@0: { michael@0: if (!mFontMetrics) michael@0: return 1; michael@0: return std::min(mFontMetrics->GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE); michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(char aC) michael@0: { michael@0: if (aC == ' ' && mFontMetrics) { michael@0: return mFontMetrics->SpaceWidth(); michael@0: } michael@0: michael@0: return GetWidth(&aC, 1); michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(char16_t aC) michael@0: { michael@0: return GetWidth(&aC, 1); michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(const nsString& aString) michael@0: { michael@0: return GetWidth(aString.get(), aString.Length()); michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(const char* aString) michael@0: { michael@0: return GetWidth(aString, strlen(aString)); michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(const char* aString, uint32_t aLength) michael@0: { michael@0: uint32_t maxChunkLength = GetMaxChunkLength(); michael@0: nscoord width = 0; michael@0: while (aLength > 0) { michael@0: int32_t len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: width += mFontMetrics->GetWidth(aString, len, this); michael@0: aLength -= len; michael@0: aString += len; michael@0: } michael@0: return width; michael@0: } michael@0: michael@0: nscoord michael@0: nsRenderingContext::GetWidth(const char16_t *aString, uint32_t aLength) michael@0: { michael@0: uint32_t maxChunkLength = GetMaxChunkLength(); michael@0: nscoord width = 0; michael@0: while (aLength > 0) { michael@0: int32_t len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: width += mFontMetrics->GetWidth(aString, len, this); michael@0: aLength -= len; michael@0: aString += len; michael@0: } michael@0: return width; michael@0: } michael@0: michael@0: nsBoundingMetrics michael@0: nsRenderingContext::GetBoundingMetrics(const char16_t* aString, michael@0: uint32_t aLength) michael@0: { michael@0: uint32_t maxChunkLength = GetMaxChunkLength(); michael@0: int32_t len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: // Assign directly in the first iteration. This ensures that michael@0: // negative ascent/descent can be returned and the left bearing michael@0: // is properly initialized. michael@0: nsBoundingMetrics totalMetrics michael@0: = mFontMetrics->GetBoundingMetrics(aString, len, this); michael@0: aLength -= len; michael@0: aString += len; michael@0: michael@0: while (aLength > 0) { michael@0: len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: nsBoundingMetrics metrics michael@0: = mFontMetrics->GetBoundingMetrics(aString, len, this); michael@0: totalMetrics += metrics; michael@0: aLength -= len; michael@0: aString += len; michael@0: } michael@0: return totalMetrics; michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawString(const char *aString, uint32_t aLength, michael@0: nscoord aX, nscoord aY) michael@0: { michael@0: uint32_t maxChunkLength = GetMaxChunkLength(); michael@0: while (aLength > 0) { michael@0: int32_t len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: mFontMetrics->DrawString(aString, len, aX, aY, this); michael@0: aLength -= len; michael@0: michael@0: if (aLength > 0) { michael@0: nscoord width = mFontMetrics->GetWidth(aString, len, this); michael@0: aX += width; michael@0: aString += len; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawString(const nsString& aString, nscoord aX, nscoord aY) michael@0: { michael@0: DrawString(aString.get(), aString.Length(), aX, aY); michael@0: } michael@0: michael@0: void michael@0: nsRenderingContext::DrawString(const char16_t *aString, uint32_t aLength, michael@0: nscoord aX, nscoord aY) michael@0: { michael@0: uint32_t maxChunkLength = GetMaxChunkLength(); michael@0: if (aLength <= maxChunkLength) { michael@0: mFontMetrics->DrawString(aString, aLength, aX, aY, this, this); michael@0: return; michael@0: } michael@0: michael@0: bool isRTL = mFontMetrics->GetTextRunRTL(); michael@0: michael@0: // If we're drawing right to left, we must start at the end. michael@0: if (isRTL) { michael@0: aX += GetWidth(aString, aLength); michael@0: } michael@0: michael@0: while (aLength > 0) { michael@0: int32_t len = FindSafeLength(aString, aLength, maxChunkLength); michael@0: nscoord width = mFontMetrics->GetWidth(aString, len, this); michael@0: if (isRTL) { michael@0: aX -= width; michael@0: } michael@0: mFontMetrics->DrawString(aString, len, aX, aY, this, this); michael@0: if (!isRTL) { michael@0: aX += width; michael@0: } michael@0: aLength -= len; michael@0: aString += len; michael@0: } michael@0: }