gfx/2d/DrawTargetCG.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     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 #include "BorrowedContext.h"
     6 #include "DataSurfaceHelpers.h"
     7 #include "DrawTargetCG.h"
     8 #include "Logging.h"
     9 #include "SourceSurfaceCG.h"
    10 #include "Rect.h"
    11 #include "ScaledFontMac.h"
    12 #include "Tools.h"
    13 #include <vector>
    14 #include <algorithm>
    15 #include "MacIOSurface.h"
    16 #include "FilterNodeSoftware.h"
    17 #include "mozilla/Assertions.h"
    18 #include "mozilla/Types.h" // for decltype
    19 #include "mozilla/FloatingPoint.h"
    21 using namespace std;
    23 //CG_EXTERN void CGContextSetCompositeOperation (CGContextRef, PrivateCGCompositeMode);
    25 // A private API that Cairo has been using for a long time
    26 CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
    28 namespace mozilla {
    29 namespace gfx {
    31 static CGRect RectToCGRect(Rect r)
    32 {
    33   return CGRectMake(r.x, r.y, r.width, r.height);
    34 }
    36 CGBlendMode ToBlendMode(CompositionOp op)
    37 {
    38   CGBlendMode mode;
    39   switch (op) {
    40     case CompositionOp::OP_OVER:
    41       mode = kCGBlendModeNormal;
    42       break;
    43     case CompositionOp::OP_ADD:
    44       mode = kCGBlendModePlusLighter;
    45       break;
    46     case CompositionOp::OP_ATOP:
    47       mode = kCGBlendModeSourceAtop;
    48       break;
    49     case CompositionOp::OP_OUT:
    50       mode = kCGBlendModeSourceOut;
    51       break;
    52     case CompositionOp::OP_IN:
    53       mode = kCGBlendModeSourceIn;
    54       break;
    55     case CompositionOp::OP_SOURCE:
    56       mode = kCGBlendModeCopy;
    57       break;
    58     case CompositionOp::OP_DEST_IN:
    59       mode = kCGBlendModeDestinationIn;
    60       break;
    61     case CompositionOp::OP_DEST_OUT:
    62       mode = kCGBlendModeDestinationOut;
    63       break;
    64     case CompositionOp::OP_DEST_OVER:
    65       mode = kCGBlendModeDestinationOver;
    66       break;
    67     case CompositionOp::OP_DEST_ATOP:
    68       mode = kCGBlendModeDestinationAtop;
    69       break;
    70     case CompositionOp::OP_XOR:
    71       mode = kCGBlendModeXOR;
    72       break;
    73     case CompositionOp::OP_MULTIPLY:
    74       mode = kCGBlendModeMultiply;
    75       break;
    76     case CompositionOp::OP_SCREEN:
    77       mode = kCGBlendModeScreen;
    78       break;
    79     case CompositionOp::OP_OVERLAY:
    80       mode = kCGBlendModeOverlay;
    81       break;
    82     case CompositionOp::OP_DARKEN:
    83       mode = kCGBlendModeDarken;
    84       break;
    85     case CompositionOp::OP_LIGHTEN:
    86       mode = kCGBlendModeLighten;
    87       break;
    88     case CompositionOp::OP_COLOR_DODGE:
    89       mode = kCGBlendModeColorDodge;
    90       break;
    91     case CompositionOp::OP_COLOR_BURN:
    92       mode = kCGBlendModeColorBurn;
    93       break;
    94     case CompositionOp::OP_HARD_LIGHT:
    95       mode = kCGBlendModeHardLight;
    96       break;
    97     case CompositionOp::OP_SOFT_LIGHT:
    98       mode = kCGBlendModeSoftLight;
    99       break;
   100     case CompositionOp::OP_DIFFERENCE:
   101       mode = kCGBlendModeDifference;
   102       break;
   103     case CompositionOp::OP_EXCLUSION:
   104       mode = kCGBlendModeExclusion;
   105       break;
   106     case CompositionOp::OP_HUE:
   107       mode = kCGBlendModeHue;
   108       break;
   109     case CompositionOp::OP_SATURATION:
   110       mode = kCGBlendModeSaturation;
   111       break;
   112     case CompositionOp::OP_COLOR:
   113       mode = kCGBlendModeColor;
   114       break;
   115     case CompositionOp::OP_LUMINOSITY:
   116       mode = kCGBlendModeLuminosity;
   117       break;
   118       /*
   119     case OP_CLEAR:
   120       mode = kCGBlendModeClear;
   121       break;*/
   122     default:
   123       mode = kCGBlendModeNormal;
   124   }
   125   return mode;
   126 }
   128 static CGInterpolationQuality
   129 InterpolationQualityFromFilter(Filter aFilter)
   130 {
   131   switch (aFilter) {
   132     default:
   133     case Filter::LINEAR:
   134       return kCGInterpolationLow;
   135     case Filter::POINT:
   136       return kCGInterpolationNone;
   137     case Filter::GOOD:
   138       return kCGInterpolationDefault;
   139   }
   140 }
   143 DrawTargetCG::DrawTargetCG() : mCg(nullptr), mSnapshot(nullptr)
   144 {
   145 }
   147 DrawTargetCG::~DrawTargetCG()
   148 {
   149   MarkChanged();
   151   // We need to conditionally release these because Init can fail without initializing these.
   152   if (mColorSpace)
   153     CGColorSpaceRelease(mColorSpace);
   154   if (mCg)
   155     CGContextRelease(mCg);
   156 }
   158 BackendType
   159 DrawTargetCG::GetType() const
   160 {
   161   // It may be worth spliting Bitmap and IOSurface DrawTarget
   162   // into seperate classes.
   163   if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
   164     return BackendType::COREGRAPHICS_ACCELERATED;
   165   } else {
   166     return BackendType::COREGRAPHICS;
   167   }
   168 }
   170 TemporaryRef<SourceSurface>
   171 DrawTargetCG::Snapshot()
   172 {
   173   if (!mSnapshot) {
   174     if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
   175       return new SourceSurfaceCGIOSurfaceContext(this);
   176     } else {
   177       mSnapshot = new SourceSurfaceCGBitmapContext(this);
   178     }
   179   }
   181   return mSnapshot;
   182 }
   184 TemporaryRef<DrawTarget>
   185 DrawTargetCG::CreateSimilarDrawTarget(const IntSize &aSize, SurfaceFormat aFormat) const
   186 {
   187   // XXX: in thebes we use CGLayers to do this kind of thing. It probably makes sense
   188   // to add that in somehow, but at a higher level
   189   RefPtr<DrawTargetCG> newTarget = new DrawTargetCG();
   190   if (newTarget->Init(GetType(), aSize, aFormat)) {
   191     return newTarget;
   192   } else {
   193     return nullptr;
   194   }
   195 }
   197 TemporaryRef<SourceSurface>
   198 DrawTargetCG::CreateSourceSurfaceFromData(unsigned char *aData,
   199                                            const IntSize &aSize,
   200                                            int32_t aStride,
   201                                            SurfaceFormat aFormat) const
   202 {
   203   RefPtr<SourceSurfaceCG> newSurf = new SourceSurfaceCG();
   205  if (!newSurf->InitFromData(aData, aSize, aStride, aFormat)) {
   206     return nullptr;
   207   }
   209   return newSurf;
   210 }
   212 // This function returns a retained CGImage that needs to be released after
   213 // use. The reason for this is that we want to either reuse an existing CGImage
   214 // or create a new one.
   215 static CGImageRef
   216 GetRetainedImageFromSourceSurface(SourceSurface *aSurface)
   217 {
   218   if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE)
   219     return CGImageRetain(static_cast<SourceSurfaceCG*>(aSurface)->GetImage());
   220   else if (aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT)
   221     return CGImageRetain(static_cast<SourceSurfaceCGContext*>(aSurface)->GetImage());
   223   if (aSurface->GetType() == SurfaceType::DATA) {
   224     DataSourceSurface* dataSource = static_cast<DataSourceSurface*>(aSurface);
   225     return CreateCGImage(nullptr, dataSource->GetData(), dataSource->GetSize(),
   226                          dataSource->Stride(), dataSource->GetFormat());
   227   }
   229   MOZ_CRASH("unsupported source surface");
   230 }
   232 TemporaryRef<SourceSurface>
   233 DrawTargetCG::OptimizeSourceSurface(SourceSurface *aSurface) const
   234 {
   235   if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
   236       aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT) {
   237     return aSurface;
   238   }
   239   return aSurface->GetDataSurface();
   240 }
   242 class UnboundnessFixer
   243 {
   244     CGRect mClipBounds;
   245     CGLayerRef mLayer;
   246     CGContextRef mCg;
   247   public:
   248     UnboundnessFixer() : mCg(nullptr) {}
   250     CGContextRef Check(CGContextRef baseCg, CompositionOp blend, const Rect* maskBounds = nullptr)
   251     {
   252       if (!IsOperatorBoundByMask(blend)) {
   253         mClipBounds = CGContextGetClipBoundingBox(baseCg);
   254         // If we're entirely clipped out or if the drawing operation covers the entire clip then
   255         // we don't need to create a temporary surface.
   256         if (CGRectIsEmpty(mClipBounds) ||
   257             (maskBounds && maskBounds->Contains(CGRectToRect(mClipBounds)))) {
   258           return baseCg;
   259         }
   261         // TransparencyLayers aren't blended using the blend mode so
   262         // we are forced to use CGLayers
   264         //XXX: The size here is in default user space units, of the layer relative to the graphics context.
   265         // is the clip bounds still correct if, for example, we have a scale applied to the context?
   266         mLayer = CGLayerCreateWithContext(baseCg, mClipBounds.size, nullptr);
   267         mCg = CGLayerGetContext(mLayer);
   268         // CGContext's default to have the origin at the bottom left
   269         // so flip it to the top left and adjust for the origin
   270         // of the layer
   271         CGContextTranslateCTM(mCg, -mClipBounds.origin.x, mClipBounds.origin.y + mClipBounds.size.height);
   272         CGContextScaleCTM(mCg, 1, -1);
   274         return mCg;
   275       } else {
   276         return baseCg;
   277       }
   278     }
   280     void Fix(CGContextRef baseCg)
   281     {
   282         if (mCg) {
   283             CGContextTranslateCTM(baseCg, 0, mClipBounds.size.height);
   284             CGContextScaleCTM(baseCg, 1, -1);
   285             mClipBounds.origin.y *= -1;
   286             CGContextDrawLayerAtPoint(baseCg, mClipBounds.origin, mLayer);
   287             CGContextRelease(mCg);
   288         }
   289     }
   290 };
   292 void
   293 DrawTargetCG::DrawSurface(SourceSurface *aSurface,
   294                            const Rect &aDest,
   295                            const Rect &aSource,
   296                            const DrawSurfaceOptions &aSurfOptions,
   297                            const DrawOptions &aDrawOptions)
   298 {
   299   MarkChanged();
   301   CGContextSaveGState(mCg);
   303   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   304   UnboundnessFixer fixer;
   305   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aDest);
   306   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
   307   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   309   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
   311   CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(aSurfOptions.mFilter));
   313   CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
   315   if (aSurfOptions.mFilter == Filter::POINT) {
   316     CGImageRef subimage = CGImageCreateWithImageInRect(image, RectToCGRect(aSource));
   317     CGImageRelease(image);
   319     CGContextScaleCTM(cg, 1, -1);
   321     CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + aDest.height),
   322                                     aDest.width, aDest.height);
   324     CGContextDrawImage(cg, flippedRect, subimage);
   325     CGImageRelease(subimage);
   326   } else {
   327     CGRect destRect = CGRectMake(aDest.x, aDest.y, aDest.width, aDest.height);
   328     CGContextClipToRect(cg, destRect);
   330     float xScale = aSource.width / aDest.width;
   331     float yScale = aSource.height / aDest.height;
   332     CGContextTranslateCTM(cg, aDest.x - aSource.x / xScale, aDest.y - aSource.y / yScale);
   334     CGRect adjustedDestRect = CGRectMake(0, 0, CGImageGetWidth(image) / xScale,
   335                                          CGImageGetHeight(image) / yScale);
   337     CGContextTranslateCTM(cg, 0, CGRectGetHeight(adjustedDestRect));
   338     CGContextScaleCTM(cg, 1, -1);
   340     CGContextDrawImage(cg, adjustedDestRect, image);
   341     CGImageRelease(image);
   342   }
   344   fixer.Fix(mCg);
   346   CGContextRestoreGState(mCg);
   347 }
   349 TemporaryRef<FilterNode>
   350 DrawTargetCG::CreateFilter(FilterType aType)
   351 {
   352   return FilterNodeSoftware::Create(aType);
   353 }
   355 void
   356 DrawTargetCG::DrawFilter(FilterNode *aNode,
   357                          const Rect &aSourceRect,
   358                          const Point &aDestPoint,
   359                          const DrawOptions &aOptions)
   360 {
   361   FilterNodeSoftware* filter = static_cast<FilterNodeSoftware*>(aNode);
   362   filter->Draw(this, aSourceRect, aDestPoint, aOptions);
   363 }
   365 static CGColorRef ColorToCGColor(CGColorSpaceRef aColorSpace, const Color& aColor)
   366 {
   367   CGFloat components[4] = {aColor.r, aColor.g, aColor.b, aColor.a};
   368   return CGColorCreate(aColorSpace, components);
   369 }
   371 class GradientStopsCG : public GradientStops
   372 {
   373   public:
   374   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsCG)
   375   //XXX: The skia backend uses a vector and passes in aNumStops. It should do better
   376   GradientStopsCG(GradientStop* aStops, uint32_t aNumStops, ExtendMode aExtendMode)
   377   {
   378     mExtend = aExtendMode;
   379     if (aExtendMode == ExtendMode::CLAMP) {
   380       //XXX: do the stops need to be in any particular order?
   381       // what should we do about the color space here? we certainly shouldn't be
   382       // recreating it all the time
   383       std::vector<CGFloat> colors;
   384       std::vector<CGFloat> offsets;
   385       colors.reserve(aNumStops*4);
   386       offsets.reserve(aNumStops);
   388       for (uint32_t i = 0; i < aNumStops; i++) {
   389         colors.push_back(aStops[i].color.r);
   390         colors.push_back(aStops[i].color.g);
   391         colors.push_back(aStops[i].color.b);
   392         colors.push_back(aStops[i].color.a);
   394         offsets.push_back(aStops[i].offset);
   395       }
   397       CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
   398       mGradient = CGGradientCreateWithColorComponents(colorSpace,
   399                                                       &colors.front(),
   400                                                       &offsets.front(),
   401                                                       aNumStops);
   402       CGColorSpaceRelease(colorSpace);
   403     } else {
   404       mGradient = nullptr;
   405       mStops.reserve(aNumStops);
   406       for (uint32_t i = 0; i < aNumStops; i++) {
   407         mStops.push_back(aStops[i]);
   408       }
   409     }
   411   }
   412   virtual ~GradientStopsCG() {
   413     if (mGradient)
   414         CGGradientRelease(mGradient);
   415   }
   416   // Will always report BackendType::COREGRAPHICS, but it is compatible
   417   // with BackendType::COREGRAPHICS_ACCELERATED
   418   BackendType GetBackendType() const { return BackendType::COREGRAPHICS; }
   419   // XXX this should be a union
   420   CGGradientRef mGradient;
   421   std::vector<GradientStop> mStops;
   422   ExtendMode mExtend;
   423 };
   425 TemporaryRef<GradientStops>
   426 DrawTargetCG::CreateGradientStops(GradientStop *aStops, uint32_t aNumStops,
   427                                   ExtendMode aExtendMode) const
   428 {
   429   return new GradientStopsCG(aStops, aNumStops, aExtendMode);
   430 }
   432 static void
   433 UpdateLinearParametersToIncludePoint(double *min_t, double *max_t,
   434                                      CGPoint *start,
   435                                      double dx, double dy,
   436                                      double x, double y)
   437 {
   438   MOZ_ASSERT(IsFinite(x) && IsFinite(y));
   440   /**
   441    * Compute a parameter t such that a line perpendicular to the (dx,dy)
   442    * vector, passing through (start->x + dx*t, start->y + dy*t), also
   443    * passes through (x,y).
   444    *
   445    * Let px = x - start->x, py = y - start->y.
   446    * t is given by
   447    *   (px - dx*t)*dx + (py - dy*t)*dy = 0
   448    *
   449    * Solving for t we get
   450    *   numerator = dx*px + dy*py
   451    *   denominator = dx^2 + dy^2
   452    *   t = numerator/denominator
   453    *
   454    * In CalculateRepeatingGradientParams we know the length of (dx,dy)
   455    * is not zero. (This is checked in DrawLinearRepeatingGradient.)
   456    */
   457   double px = x - start->x;
   458   double py = y - start->y;
   459   double numerator = dx * px + dy * py;
   460   double denominator = dx * dx + dy * dy;
   461   double t = numerator / denominator;
   463   if (*min_t > t) {
   464     *min_t = t;
   465   }
   466   if (*max_t < t) {
   467     *max_t = t;
   468   }
   469 }
   471 /**
   472  * Repeat the gradient line such that lines extended perpendicular to the
   473  * gradient line at both start and end would completely enclose the drawing
   474  * extents.
   475  */
   476 static void
   477 CalculateRepeatingGradientParams(CGPoint *aStart, CGPoint *aEnd,
   478                                  CGRect aExtents, int *aRepeatCount)
   479 {
   480   double t_min = INFINITY;
   481   double t_max = -INFINITY;
   482   double dx = aEnd->x - aStart->x;
   483   double dy = aEnd->y - aStart->y;
   485   double bounds_x1 = aExtents.origin.x;
   486   double bounds_y1 = aExtents.origin.y;
   487   double bounds_x2 = aExtents.origin.x + aExtents.size.width;
   488   double bounds_y2 = aExtents.origin.y + aExtents.size.height;
   490   UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
   491                                        bounds_x1, bounds_y1);
   492   UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
   493                                        bounds_x2, bounds_y1);
   494   UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
   495                                        bounds_x2, bounds_y2);
   496   UpdateLinearParametersToIncludePoint(&t_min, &t_max, aStart, dx, dy,
   497                                        bounds_x1, bounds_y2);
   499   MOZ_ASSERT(!isinf(t_min) && !isinf(t_max),
   500              "The first call to UpdateLinearParametersToIncludePoint should have made t_min and t_max non-infinite.");
   502   // Move t_min and t_max to the nearest usable integer to try to avoid
   503   // subtle variations due to numerical instability, especially accidentally
   504   // cutting off a pixel. Extending the gradient repetitions is always safe.
   505   t_min = floor (t_min);
   506   t_max = ceil (t_max);
   507   aEnd->x = aStart->x + dx * t_max;
   508   aEnd->y = aStart->y + dy * t_max;
   509   aStart->x = aStart->x + dx * t_min;
   510   aStart->y = aStart->y + dy * t_min;
   512   *aRepeatCount = t_max - t_min;
   513 }
   515 static void
   516 DrawLinearRepeatingGradient(CGContextRef cg, const LinearGradientPattern &aPattern, const CGRect &aExtents)
   517 {
   518   GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
   519   CGPoint startPoint = { aPattern.mBegin.x, aPattern.mBegin.y };
   520   CGPoint endPoint = { aPattern.mEnd.x, aPattern.mEnd.y };
   522   int repeatCount = 1;
   523   // if we don't have a line then we can't extend it
   524   if (aPattern.mEnd.x != aPattern.mBegin.x ||
   525       aPattern.mEnd.y != aPattern.mBegin.y) {
   526     CalculateRepeatingGradientParams(&startPoint, &endPoint, aExtents,
   527                                      &repeatCount);
   528   }
   530   double scale = 1./repeatCount;
   532   std::vector<CGFloat> colors;
   533   std::vector<CGFloat> offsets;
   534   colors.reserve(stops->mStops.size()*repeatCount*4);
   535   offsets.reserve(stops->mStops.size()*repeatCount);
   537   for (int j = 0; j < repeatCount; j++) {
   538     for (uint32_t i = 0; i < stops->mStops.size(); i++) {
   539       colors.push_back(stops->mStops[i].color.r);
   540       colors.push_back(stops->mStops[i].color.g);
   541       colors.push_back(stops->mStops[i].color.b);
   542       colors.push_back(stops->mStops[i].color.a);
   544       offsets.push_back((stops->mStops[i].offset + j)*scale);
   545     }
   546   }
   548   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
   549   CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
   550                                                                &colors.front(),
   551                                                                &offsets.front(),
   552                                                                repeatCount*stops->mStops.size());
   553   CGColorSpaceRelease(colorSpace);
   555   CGContextDrawLinearGradient(cg, gradient, startPoint, endPoint,
   556                               kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
   557   CGGradientRelease(gradient);
   558 }
   560 static CGPoint CGRectTopLeft(CGRect a)
   561 { return a.origin; }
   562 static CGPoint CGRectBottomLeft(CGRect a)
   563 { return CGPointMake(a.origin.x, a.origin.y + a.size.height); }
   564 static CGPoint CGRectTopRight(CGRect a)
   565 { return CGPointMake(a.origin.x + a.size.width, a.origin.y); }
   566 static CGPoint CGRectBottomRight(CGRect a)
   567 { return CGPointMake(a.origin.x + a.size.width, a.origin.y + a.size.height); }
   569 static CGFloat
   570 CGPointDistance(CGPoint a, CGPoint b)
   571 {
   572   return hypot(a.x-b.x, a.y-b.y);
   573 }
   575 static void
   576 DrawRadialRepeatingGradient(CGContextRef cg, const RadialGradientPattern &aPattern, const CGRect &aExtents)
   577 {
   578   GradientStopsCG *stops = static_cast<GradientStopsCG*>(aPattern.mStops.get());
   579   CGPoint startCenter = { aPattern.mCenter1.x, aPattern.mCenter1.y };
   580   CGFloat startRadius = aPattern.mRadius1;
   581   CGPoint endCenter   = { aPattern.mCenter2.x, aPattern.mCenter2.y };
   582   CGFloat endRadius   = aPattern.mRadius2;
   584   // find the maximum distance from endCenter to a corner of aExtents
   585   CGFloat minimumEndRadius = endRadius;
   586   minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopLeft(aExtents)));
   587   minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomLeft(aExtents)));
   588   minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectTopRight(aExtents)));
   589   minimumEndRadius = max(minimumEndRadius, CGPointDistance(endCenter, CGRectBottomRight(aExtents)));
   591   CGFloat length = endRadius - startRadius;
   592   int repeatCount = 1;
   593   while (endRadius < minimumEndRadius) {
   594     endRadius += length;
   595     repeatCount++;
   596   }
   598   while (startRadius-length >= 0) {
   599     startRadius -= length;
   600     repeatCount++;
   601   }
   603   double scale = 1./repeatCount;
   605   std::vector<CGFloat> colors;
   606   std::vector<CGFloat> offsets;
   607   colors.reserve(stops->mStops.size()*repeatCount*4);
   608   offsets.reserve(stops->mStops.size()*repeatCount);
   609   for (int j = 0; j < repeatCount; j++) {
   610     for (uint32_t i = 0; i < stops->mStops.size(); i++) {
   611       colors.push_back(stops->mStops[i].color.r);
   612       colors.push_back(stops->mStops[i].color.g);
   613       colors.push_back(stops->mStops[i].color.b);
   614       colors.push_back(stops->mStops[i].color.a);
   616       offsets.push_back((stops->mStops[i].offset + j)*scale);
   617     }
   618   }
   620   CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
   621   CGGradientRef gradient = CGGradientCreateWithColorComponents(colorSpace,
   622                                                                &colors.front(),
   623                                                                &offsets.front(),
   624                                                                repeatCount*stops->mStops.size());
   625   CGColorSpaceRelease(colorSpace);
   627   //XXX: are there degenerate radial gradients that we should avoid drawing?
   628   CGContextDrawRadialGradient(cg, gradient, startCenter, startRadius, endCenter, endRadius,
   629                               kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
   630   CGGradientRelease(gradient);
   631 }
   633 static void
   634 DrawGradient(CGContextRef cg, const Pattern &aPattern, const CGRect &aExtents)
   635 {
   636   if (CGRectIsEmpty(aExtents)) {
   637     return;
   638   }
   640   if (aPattern.GetType() == PatternType::LINEAR_GRADIENT) {
   641     const LinearGradientPattern& pat = static_cast<const LinearGradientPattern&>(aPattern);
   642     GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
   643     CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
   644     if (stops->mExtend == ExtendMode::CLAMP) {
   646       // XXX: we should take the m out of the properties of LinearGradientPatterns
   647       CGPoint startPoint = { pat.mBegin.x, pat.mBegin.y };
   648       CGPoint endPoint   = { pat.mEnd.x,   pat.mEnd.y };
   650       // Canvas spec states that we should avoid drawing degenerate gradients (XXX: should this be in common code?)
   651       //if (startPoint.x == endPoint.x && startPoint.y == endPoint.y)
   652       //  return;
   654       CGContextDrawLinearGradient(cg, stops->mGradient, startPoint, endPoint,
   655                                   kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
   656     } else if (stops->mExtend == ExtendMode::REPEAT) {
   657       DrawLinearRepeatingGradient(cg, pat, aExtents);
   658     }
   659   } else if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
   660     const RadialGradientPattern& pat = static_cast<const RadialGradientPattern&>(aPattern);
   661     CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
   662     GradientStopsCG *stops = static_cast<GradientStopsCG*>(pat.mStops.get());
   663     if (stops->mExtend == ExtendMode::CLAMP) {
   665       // XXX: we should take the m out of the properties of RadialGradientPatterns
   666       CGPoint startCenter = { pat.mCenter1.x, pat.mCenter1.y };
   667       CGFloat startRadius = pat.mRadius1;
   668       CGPoint endCenter   = { pat.mCenter2.x, pat.mCenter2.y };
   669       CGFloat endRadius   = pat.mRadius2;
   671       //XXX: are there degenerate radial gradients that we should avoid drawing?
   672       CGContextDrawRadialGradient(cg, stops->mGradient, startCenter, startRadius, endCenter, endRadius,
   673                                   kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
   674     } else if (stops->mExtend == ExtendMode::REPEAT) {
   675       DrawRadialRepeatingGradient(cg, pat, aExtents);
   676     }
   677   } else {
   678     assert(0);
   679   }
   681 }
   683 static void
   684 drawPattern(void *info, CGContextRef context)
   685 {
   686   CGImageRef image = static_cast<CGImageRef>(info);
   687   CGRect rect = {{0, 0},
   688     {static_cast<CGFloat>(CGImageGetWidth(image)),
   689      static_cast<CGFloat>(CGImageGetHeight(image))}};
   690   CGContextDrawImage(context, rect, image);
   691 }
   693 static void
   694 releaseInfo(void *info)
   695 {
   696   CGImageRef image = static_cast<CGImageRef>(info);
   697   CGImageRelease(image);
   698 }
   700 CGPatternCallbacks patternCallbacks = {
   701   0,
   702   drawPattern,
   703   releaseInfo
   704 };
   706 static bool
   707 isGradient(const Pattern &aPattern)
   708 {
   709   return aPattern.GetType() == PatternType::LINEAR_GRADIENT || aPattern.GetType() == PatternType::RADIAL_GRADIENT;
   710 }
   712 /* CoreGraphics patterns ignore the userspace transform so
   713  * we need to multiply it in */
   714 static CGPatternRef
   715 CreateCGPattern(const Pattern &aPattern, CGAffineTransform aUserSpace)
   716 {
   717   const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
   718   // XXX: is .get correct here?
   719   CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
   720   CGFloat xStep, yStep;
   721   switch (pat.mExtendMode) {
   722     case ExtendMode::CLAMP:
   723       // The 1 << 22 comes from Webkit see Pattern::createPlatformPattern() in PatternCG.cpp for more info
   724       xStep = static_cast<CGFloat>(1 << 22);
   725       yStep = static_cast<CGFloat>(1 << 22);
   726       break;
   727     case ExtendMode::REFLECT:
   728       assert(0);
   729     case ExtendMode::REPEAT:
   730       xStep = static_cast<CGFloat>(CGImageGetWidth(image));
   731       yStep = static_cast<CGFloat>(CGImageGetHeight(image));
   732       // webkit uses wkCGPatternCreateWithImageAndTransform a wrapper around CGPatternCreateWithImage2
   733       // this is done to avoid pixel-cracking along pattern boundaries
   734       // (see https://bugs.webkit.org/show_bug.cgi?id=53055)
   735       // typedef enum {
   736       //    wkPatternTilingNoDistortion,
   737       //    wkPatternTilingConstantSpacingMinimalDistortion,
   738       //    wkPatternTilingConstantSpacing
   739       // } wkPatternTiling;
   740       // extern CGPatternRef (*wkCGPatternCreateWithImageAndTransform)(CGImageRef, CGAffineTransform, int);
   741   }
   743   //XXX: We should be using CGContextDrawTiledImage when we can. Even though it
   744   // creates a pattern, it seems to go down a faster path than using a delegate
   745   // like we do below
   746   CGRect bounds = {
   747     {0, 0,},
   748     {static_cast<CGFloat>(CGImageGetWidth(image)), static_cast<CGFloat>(CGImageGetHeight(image))}
   749   };
   750   CGAffineTransform transform =
   751       CGAffineTransformConcat(CGAffineTransformConcat(CGAffineTransformMakeScale(1,
   752                                                                                  -1),
   753                                                       GfxMatrixToCGAffineTransform(pat.mMatrix)),
   754                               aUserSpace);
   755   transform = CGAffineTransformTranslate(transform, 0, -static_cast<float>(CGImageGetHeight(image)));
   756   return CGPatternCreate(image, bounds, transform, xStep, yStep, kCGPatternTilingConstantSpacing,
   757                          true, &patternCallbacks);
   758 }
   760 static void
   761 SetFillFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
   762 {
   763   assert(!isGradient(aPattern));
   764   if (aPattern.GetType() == PatternType::COLOR) {
   766     const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
   767     //XXX: we should cache colors
   768     CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
   769     CGContextSetFillColorWithColor(cg, cgcolor);
   770     CGColorRelease(cgcolor);
   771   } else if (aPattern.GetType() == PatternType::SURFACE) {
   773     CGColorSpaceRef patternSpace;
   774     patternSpace = CGColorSpaceCreatePattern (nullptr);
   775     CGContextSetFillColorSpace(cg, patternSpace);
   776     CGColorSpaceRelease(patternSpace);
   778     CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
   779     const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
   780     CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
   781     CGFloat alpha = 1.;
   782     CGContextSetFillPattern(cg, pattern, &alpha);
   783     CGPatternRelease(pattern);
   784   }
   785 }
   787 static void
   788 SetStrokeFromPattern(CGContextRef cg, CGColorSpaceRef aColorSpace, const Pattern &aPattern)
   789 {
   790   assert(!isGradient(aPattern));
   791   if (aPattern.GetType() == PatternType::COLOR) {
   792     const Color& color = static_cast<const ColorPattern&>(aPattern).mColor;
   793     //XXX: we should cache colors
   794     CGColorRef cgcolor = ColorToCGColor(aColorSpace, color);
   795     CGContextSetStrokeColorWithColor(cg, cgcolor);
   796     CGColorRelease(cgcolor);
   797   } else if (aPattern.GetType() == PatternType::SURFACE) {
   798     CGColorSpaceRef patternSpace;
   799     patternSpace = CGColorSpaceCreatePattern (nullptr);
   800     CGContextSetStrokeColorSpace(cg, patternSpace);
   801     CGColorSpaceRelease(patternSpace);
   803     CGPatternRef pattern = CreateCGPattern(aPattern, CGContextGetCTM(cg));
   804     const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
   805     CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
   806     CGFloat alpha = 1.;
   807     CGContextSetStrokePattern(cg, pattern, &alpha);
   808     CGPatternRelease(pattern);
   809   }
   811 }
   813 void
   814 DrawTargetCG::MaskSurface(const Pattern &aSource,
   815                           SourceSurface *aMask,
   816                           Point aOffset,
   817                           const DrawOptions &aDrawOptions)
   818 {
   819   MarkChanged();
   821   CGContextSaveGState(mCg);
   823   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   824   UnboundnessFixer fixer;
   825   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   826   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
   827   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   829   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
   830   CGImageRef image = GetRetainedImageFromSourceSurface(aMask);
   832   // use a negative-y so that the mask image draws right ways up
   833   CGContextScaleCTM(cg, 1, -1);
   835   IntSize size = aMask->GetSize();
   837   CGContextClipToMask(cg, CGRectMake(aOffset.x, -(aOffset.y + size.height), size.width, size.height), image);
   839   CGContextScaleCTM(cg, 1, -1);
   840   if (isGradient(aSource)) {
   841     // we shouldn't need to clip to an additional rectangle
   842     // as the cliping to the mask should be sufficient.
   843     DrawGradient(cg, aSource, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
   844   } else {
   845     SetFillFromPattern(cg, mColorSpace, aSource);
   846     CGContextFillRect(cg, CGRectMake(aOffset.x, aOffset.y, size.width, size.height));
   847   }
   849   CGImageRelease(image);
   851   fixer.Fix(mCg);
   853   CGContextRestoreGState(mCg);
   854 }
   858 void
   859 DrawTargetCG::FillRect(const Rect &aRect,
   860                         const Pattern &aPattern,
   861                         const DrawOptions &aDrawOptions)
   862 {
   863   MarkChanged();
   865   CGContextSaveGState(mCg);
   867   UnboundnessFixer fixer;
   868   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp, &aRect);
   869   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   870   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   871   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   873   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
   875   if (isGradient(aPattern)) {
   876     CGContextClipToRect(cg, RectToCGRect(aRect));
   877     CGRect clipBounds = CGContextGetClipBoundingBox(cg);
   878     DrawGradient(cg, aPattern, clipBounds);
   879   } else {
   880     if (aPattern.GetType() == PatternType::SURFACE && static_cast<const SurfacePattern&>(aPattern).mExtendMode != ExtendMode::REPEAT) {
   881       // SetFillFromPattern can handle this case but using CGContextDrawImage
   882       // should give us better performance, better output, smaller PDF and
   883       // matches what cairo does.
   884       const SurfacePattern& pat = static_cast<const SurfacePattern&>(aPattern);
   885       CGImageRef image = GetRetainedImageFromSourceSurface(pat.mSurface.get());
   886       CGContextClipToRect(cg, RectToCGRect(aRect));
   887       CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(pat.mMatrix));
   888       CGContextTranslateCTM(cg, 0, CGImageGetHeight(image));
   889       CGContextScaleCTM(cg, 1, -1);
   891       CGRect imageRect = CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image));
   893       CGContextSetInterpolationQuality(cg, InterpolationQualityFromFilter(pat.mFilter));
   895       CGContextDrawImage(cg, imageRect, image);
   896       CGImageRelease(image);
   897     } else {
   898       SetFillFromPattern(cg, mColorSpace, aPattern);
   899       CGContextFillRect(cg, RectToCGRect(aRect));
   900     }
   901   }
   903   fixer.Fix(mCg);
   904   CGContextRestoreGState(mCg);
   905 }
   907 void
   908 DrawTargetCG::StrokeLine(const Point &p1, const Point &p2, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
   909 {
   910   if (!std::isfinite(p1.x) ||
   911       !std::isfinite(p1.y) ||
   912       !std::isfinite(p2.x) ||
   913       !std::isfinite(p2.y)) {
   914     return;
   915   }
   917   MarkChanged();
   919   CGContextSaveGState(mCg);
   921   UnboundnessFixer fixer;
   922   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   923   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   924   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   925   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   927   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
   929   CGContextBeginPath(cg);
   930   CGContextMoveToPoint(cg, p1.x, p1.y);
   931   CGContextAddLineToPoint(cg, p2.x, p2.y);
   933   SetStrokeOptions(cg, aStrokeOptions);
   935   if (isGradient(aPattern)) {
   936     CGContextReplacePathWithStrokedPath(cg);
   937     CGRect extents = CGContextGetPathBoundingBox(cg);
   938     //XXX: should we use EO clip here?
   939     CGContextClip(cg);
   940     DrawGradient(cg, aPattern, extents);
   941   } else {
   942     SetStrokeFromPattern(cg, mColorSpace, aPattern);
   943     CGContextStrokePath(cg);
   944   }
   946   fixer.Fix(mCg);
   947   CGContextRestoreGState(mCg);
   948 }
   950 void
   951 DrawTargetCG::StrokeRect(const Rect &aRect,
   952                          const Pattern &aPattern,
   953                          const StrokeOptions &aStrokeOptions,
   954                          const DrawOptions &aDrawOptions)
   955 {
   956   if (!aRect.IsFinite()) {
   957     return;
   958   }
   960   MarkChanged();
   962   CGContextSaveGState(mCg);
   964   UnboundnessFixer fixer;
   965   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
   966   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
   967   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
   968   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
   970   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
   972   SetStrokeOptions(cg, aStrokeOptions);
   974   if (isGradient(aPattern)) {
   975     // There's no CGContextClipStrokeRect so we do it by hand
   976     CGContextBeginPath(cg);
   977     CGContextAddRect(cg, RectToCGRect(aRect));
   978     CGContextReplacePathWithStrokedPath(cg);
   979     CGRect extents = CGContextGetPathBoundingBox(cg);
   980     //XXX: should we use EO clip here?
   981     CGContextClip(cg);
   982     DrawGradient(cg, aPattern, extents);
   983   } else {
   984     SetStrokeFromPattern(cg, mColorSpace, aPattern);
   985     CGContextStrokeRect(cg, RectToCGRect(aRect));
   986   }
   988   fixer.Fix(mCg);
   989   CGContextRestoreGState(mCg);
   990 }
   993 void
   994 DrawTargetCG::ClearRect(const Rect &aRect)
   995 {
   996   MarkChanged();
   998   CGContextSaveGState(mCg);
   999   CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
  1001   CGContextClearRect(mCg, RectToCGRect(aRect));
  1003   CGContextRestoreGState(mCg);
  1006 void
  1007 DrawTargetCG::Stroke(const Path *aPath, const Pattern &aPattern, const StrokeOptions &aStrokeOptions, const DrawOptions &aDrawOptions)
  1009   if (!aPath->GetBounds().IsFinite()) {
  1010     return;
  1013   MarkChanged();
  1015   CGContextSaveGState(mCg);
  1017   UnboundnessFixer fixer;
  1018   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
  1019   CGContextSetAlpha(mCg, aDrawOptions.mAlpha);
  1020   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
  1021   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
  1023   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
  1026   CGContextBeginPath(cg);
  1028   assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
  1029   const PathCG *cgPath = static_cast<const PathCG*>(aPath);
  1030   CGContextAddPath(cg, cgPath->GetPath());
  1032   SetStrokeOptions(cg, aStrokeOptions);
  1034   if (isGradient(aPattern)) {
  1035     CGContextReplacePathWithStrokedPath(cg);
  1036     CGRect extents = CGContextGetPathBoundingBox(cg);
  1037     //XXX: should we use EO clip here?
  1038     CGContextClip(cg);
  1039     DrawGradient(cg, aPattern, extents);
  1040   } else {
  1041     // XXX: we could put fill mode into the path fill rule if we wanted
  1043     SetStrokeFromPattern(cg, mColorSpace, aPattern);
  1044     CGContextStrokePath(cg);
  1047   fixer.Fix(mCg);
  1048   CGContextRestoreGState(mCg);
  1051 void
  1052 DrawTargetCG::Fill(const Path *aPath, const Pattern &aPattern, const DrawOptions &aDrawOptions)
  1054   MarkChanged();
  1056   assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
  1058   CGContextSaveGState(mCg);
  1060   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
  1061   UnboundnessFixer fixer;
  1062   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
  1063   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
  1064   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
  1066   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
  1068   CGContextBeginPath(cg);
  1069   // XXX: we could put fill mode into the path fill rule if we wanted
  1070   const PathCG *cgPath = static_cast<const PathCG*>(aPath);
  1072   if (isGradient(aPattern)) {
  1073     // setup a clip to draw the gradient through
  1074     CGRect extents;
  1075     if (CGPathIsEmpty(cgPath->GetPath())) {
  1076       // Adding an empty path will cause us not to clip
  1077       // so clip everything explicitly
  1078       CGContextClipToRect(mCg, CGRectZero);
  1079       extents = CGRectZero;
  1080     } else {
  1081       CGContextAddPath(cg, cgPath->GetPath());
  1082       extents = CGContextGetPathBoundingBox(cg);
  1083       if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
  1084         CGContextEOClip(mCg);
  1085       else
  1086         CGContextClip(mCg);
  1089     DrawGradient(cg, aPattern, extents);
  1090   } else {
  1091     CGContextAddPath(cg, cgPath->GetPath());
  1093     SetFillFromPattern(cg, mColorSpace, aPattern);
  1095     if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
  1096       CGContextEOFillPath(cg);
  1097     else
  1098       CGContextFillPath(cg);
  1101   fixer.Fix(mCg);
  1102   CGContextRestoreGState(mCg);
  1105 CGRect ComputeGlyphsExtents(CGRect *bboxes, CGPoint *positions, CFIndex count, float scale)
  1107   CGFloat x1, x2, y1, y2;
  1108   if (count < 1)
  1109     return CGRectZero;
  1111   x1 = bboxes[0].origin.x + positions[0].x;
  1112   x2 = bboxes[0].origin.x + positions[0].x + scale*bboxes[0].size.width;
  1113   y1 = bboxes[0].origin.y + positions[0].y;
  1114   y2 = bboxes[0].origin.y + positions[0].y + scale*bboxes[0].size.height;
  1116   // accumulate max and minimum coordinates
  1117   for (int i = 1; i < count; i++) {
  1118     x1 = min(x1, bboxes[i].origin.x + positions[i].x);
  1119     y1 = min(y1, bboxes[i].origin.y + positions[i].y);
  1120     x2 = max(x2, bboxes[i].origin.x + positions[i].x + scale*bboxes[i].size.width);
  1121     y2 = max(y2, bboxes[i].origin.y + positions[i].y + scale*bboxes[i].size.height);
  1124   CGRect extents = {{x1, y1}, {x2-x1, y2-y1}};
  1125   return extents;
  1129 void
  1130 DrawTargetCG::FillGlyphs(ScaledFont *aFont, const GlyphBuffer &aBuffer, const Pattern &aPattern, const DrawOptions &aDrawOptions,
  1131                          const GlyphRenderingOptions*)
  1133   MarkChanged();
  1135   assert(aBuffer.mNumGlyphs);
  1136   CGContextSaveGState(mCg);
  1138   CGContextSetBlendMode(mCg, ToBlendMode(aDrawOptions.mCompositionOp));
  1139   UnboundnessFixer fixer;
  1140   CGContextRef cg = fixer.Check(mCg, aDrawOptions.mCompositionOp);
  1141   CGContextSetAlpha(cg, aDrawOptions.mAlpha);
  1142   CGContextSetShouldAntialias(cg, aDrawOptions.mAntialiasMode != AntialiasMode::NONE);
  1143   if (aDrawOptions.mAntialiasMode != AntialiasMode::DEFAULT) {
  1144     CGContextSetShouldSmoothFonts(cg, aDrawOptions.mAntialiasMode == AntialiasMode::SUBPIXEL);
  1147   CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(mTransform));
  1149   ScaledFontMac* macFont = static_cast<ScaledFontMac*>(aFont);
  1151   //XXX: we should use a stack vector here when we have a class like that
  1152   std::vector<CGGlyph> glyphs;
  1153   std::vector<CGPoint> positions;
  1154   glyphs.resize(aBuffer.mNumGlyphs);
  1155   positions.resize(aBuffer.mNumGlyphs);
  1157   // Handle the flip
  1158   CGContextScaleCTM(cg, 1, -1);
  1159   // CGContextSetTextMatrix works differently with kCGTextClip && kCGTextFill
  1160   // It seems that it transforms the positions with TextFill and not with TextClip
  1161   // Therefore we'll avoid it. See also:
  1162   // http://cgit.freedesktop.org/cairo/commit/?id=9c0d761bfcdd28d52c83d74f46dd3c709ae0fa69
  1164   for (unsigned int i = 0; i < aBuffer.mNumGlyphs; i++) {
  1165     glyphs[i] = aBuffer.mGlyphs[i].mIndex;
  1166     // XXX: CGPointMake might not be inlined
  1167     positions[i] = CGPointMake(aBuffer.mGlyphs[i].mPosition.x,
  1168                               -aBuffer.mGlyphs[i].mPosition.y);
  1171   //XXX: CGContextShowGlyphsAtPositions is 10.5+ for older versions use CGContextShowGlyphsWithAdvances
  1172   if (isGradient(aPattern)) {
  1173     CGContextSetTextDrawingMode(cg, kCGTextClip);
  1174     CGRect extents;
  1175     if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
  1176       CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
  1177       CTFontGetBoundingRectsForGlyphs(macFont->mCTFont, kCTFontDefaultOrientation,
  1178                                       &glyphs.front(), bboxes, aBuffer.mNumGlyphs);
  1179       extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, 1.0f);
  1180       ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
  1181                                          &positions.front(), aBuffer.mNumGlyphs, cg);
  1182       delete bboxes;
  1183     } else {
  1184       CGRect *bboxes = new CGRect[aBuffer.mNumGlyphs];
  1185       CGFontGetGlyphBBoxes(macFont->mFont, &glyphs.front(), aBuffer.mNumGlyphs, bboxes);
  1186       extents = ComputeGlyphsExtents(bboxes, &positions.front(), aBuffer.mNumGlyphs, macFont->mSize);
  1188       CGContextSetFont(cg, macFont->mFont);
  1189       CGContextSetFontSize(cg, macFont->mSize);
  1190       CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
  1191                                      aBuffer.mNumGlyphs);
  1192       delete bboxes;
  1194     CGContextScaleCTM(cg, 1, -1);
  1195     DrawGradient(cg, aPattern, extents);
  1196   } else {
  1197     //XXX: with CoreGraphics we can stroke text directly instead of going
  1198     // through GetPath. It would be nice to add support for using that
  1199     CGContextSetTextDrawingMode(cg, kCGTextFill);
  1200     SetFillFromPattern(cg, mColorSpace, aPattern);
  1201     if (ScaledFontMac::CTFontDrawGlyphsPtr != nullptr) {
  1202       ScaledFontMac::CTFontDrawGlyphsPtr(macFont->mCTFont, &glyphs.front(),
  1203                                          &positions.front(),
  1204                                          aBuffer.mNumGlyphs, cg);
  1205     } else {
  1206       CGContextSetFont(cg, macFont->mFont);
  1207       CGContextSetFontSize(cg, macFont->mSize);
  1208       CGContextShowGlyphsAtPositions(cg, &glyphs.front(), &positions.front(),
  1209                                      aBuffer.mNumGlyphs);
  1213   fixer.Fix(mCg);
  1214   CGContextRestoreGState(cg);
  1217 extern "C" {
  1218 void
  1219 CGContextResetClip(CGContextRef);
  1220 };
  1222 void
  1223 DrawTargetCG::CopySurface(SourceSurface *aSurface,
  1224                           const IntRect& aSourceRect,
  1225                           const IntPoint &aDestination)
  1227   MarkChanged();
  1229   if (aSurface->GetType() == SurfaceType::COREGRAPHICS_IMAGE ||
  1230       aSurface->GetType() == SurfaceType::COREGRAPHICS_CGCONTEXT ||
  1231       aSurface->GetType() == SurfaceType::DATA) {
  1232     CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
  1234     // XXX: it might be more efficient for us to do the copy directly if we have access to the bits
  1236     CGContextSaveGState(mCg);
  1238     // CopySurface ignores the clip, so we need to use private API to temporarily reset it
  1239     CGContextResetClip(mCg);
  1240     CGRect destRect = CGRectMake(aDestination.x, aDestination.y,
  1241                                  aSourceRect.width, aSourceRect.height);
  1242     CGContextClipToRect(mCg, destRect);
  1244     CGContextSetBlendMode(mCg, kCGBlendModeCopy);
  1246     CGContextScaleCTM(mCg, 1, -1);
  1248     CGRect flippedRect = CGRectMake(aDestination.x - aSourceRect.x, -(aDestination.y - aSourceRect.y + double(CGImageGetHeight(image))),
  1249                                     CGImageGetWidth(image), CGImageGetHeight(image));
  1251     // Quartz seems to copy A8 surfaces incorrectly if we don't initialize them
  1252     // to transparent first.
  1253     if (mFormat == SurfaceFormat::A8) {
  1254       CGContextClearRect(mCg, flippedRect);
  1256     CGContextDrawImage(mCg, flippedRect, image);
  1258     CGContextRestoreGState(mCg);
  1259     CGImageRelease(image);
  1263 void
  1264 DrawTargetCG::DrawSurfaceWithShadow(SourceSurface *aSurface, const Point &aDest, const Color &aColor, const Point &aOffset, Float aSigma, CompositionOp aOperator)
  1266   MarkChanged();
  1268   CGImageRef image = GetRetainedImageFromSourceSurface(aSurface);
  1270   IntSize size = aSurface->GetSize();
  1271   CGContextSaveGState(mCg);
  1272   //XXX do we need to do the fixup here?
  1273   CGContextSetBlendMode(mCg, ToBlendMode(aOperator));
  1275   CGContextScaleCTM(mCg, 1, -1);
  1277   CGRect flippedRect = CGRectMake(aDest.x, -(aDest.y + size.height),
  1278                                   size.width, size.height);
  1280   CGColorRef color = ColorToCGColor(mColorSpace, aColor);
  1281   CGSize offset = {aOffset.x, -aOffset.y};
  1282   // CoreGraphics needs twice sigma as it's amount of blur
  1283   CGContextSetShadowWithColor(mCg, offset, 2*aSigma, color);
  1284   CGColorRelease(color);
  1286   CGContextDrawImage(mCg, flippedRect, image);
  1288   CGImageRelease(image);
  1289   CGContextRestoreGState(mCg);
  1293 bool
  1294 DrawTargetCG::Init(BackendType aType,
  1295                    unsigned char* aData,
  1296                    const IntSize &aSize,
  1297                    int32_t aStride,
  1298                    SurfaceFormat aFormat)
  1300   // XXX: we should come up with some consistent semantics for dealing
  1301   // with zero area drawtargets
  1302   if (aSize.width <= 0 || aSize.height <= 0 ||
  1303       // 32767 is the maximum size supported by cairo
  1304       // we clamp to that to make it easier to interoperate
  1305       aSize.width > 32767 || aSize.height > 32767) {
  1306     gfxWarning() << "Failed to Init() DrawTargetCG because of bad size.";
  1307     mColorSpace = nullptr;
  1308     mCg = nullptr;
  1309     return false;
  1312   //XXX: handle SurfaceFormat
  1314   //XXX: we'd be better off reusing the Colorspace across draw targets
  1315   mColorSpace = CGColorSpaceCreateDeviceRGB();
  1317   if (aData == nullptr && aType != BackendType::COREGRAPHICS_ACCELERATED) {
  1318     // XXX: Currently, Init implicitly clears, that can often be a waste of time
  1319     size_t bufLen = BufferSizeFromStrideAndHeight(aStride, aSize.height);
  1320     if (bufLen == 0) {
  1321       mColorSpace = nullptr;
  1322       mCg = nullptr;
  1323       return false;
  1325     static_assert(sizeof(decltype(mData[0])) == 1,
  1326                   "mData.Realloc() takes an object count, so its objects must be 1-byte sized if we use bufLen");
  1327     mData.Realloc(/* actually an object count */ bufLen);
  1328     aData = static_cast<unsigned char*>(mData);
  1329     memset(aData, 0, bufLen);
  1332   mSize = aSize;
  1334   if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
  1335     RefPtr<MacIOSurface> ioSurface = MacIOSurface::CreateIOSurface(aSize.width, aSize.height);
  1336     mCg = ioSurface->CreateIOSurfaceContext();
  1337     // If we don't have the symbol for 'CreateIOSurfaceContext' mCg will be null
  1338     // and we will fallback to software below
  1341   mFormat = SurfaceFormat::B8G8R8A8;
  1343   if (!mCg || aType == BackendType::COREGRAPHICS) {
  1344     int bitsPerComponent = 8;
  1346     CGBitmapInfo bitinfo;
  1347     if (aFormat == SurfaceFormat::A8) {
  1348       if (mColorSpace)
  1349         CGColorSpaceRelease(mColorSpace);
  1350       mColorSpace = nullptr;
  1351       bitinfo = kCGImageAlphaOnly;
  1352       mFormat = SurfaceFormat::A8;
  1353     } else {
  1354       bitinfo = kCGBitmapByteOrder32Host;
  1355       if (aFormat == SurfaceFormat::B8G8R8X8) {
  1356         bitinfo |= kCGImageAlphaNoneSkipFirst;
  1357         mFormat = aFormat;
  1358       } else {
  1359         bitinfo |= kCGImageAlphaPremultipliedFirst;
  1362     // XXX: what should we do if this fails?
  1363     mCg = CGBitmapContextCreate (aData,
  1364                                  mSize.width,
  1365                                  mSize.height,
  1366                                  bitsPerComponent,
  1367                                  aStride,
  1368                                  mColorSpace,
  1369                                  bitinfo);
  1372   assert(mCg);
  1373   // CGContext's default to have the origin at the bottom left
  1374   // so flip it to the top left
  1375   CGContextTranslateCTM(mCg, 0, mSize.height);
  1376   CGContextScaleCTM(mCg, 1, -1);
  1377   // See Bug 722164 for performance details
  1378   // Medium or higher quality lead to expensive interpolation
  1379   // for canvas we want to use low quality interpolation
  1380   // to have competitive performance with other canvas
  1381   // implementation.
  1382   // XXX: Create input parameter to control interpolation and
  1383   //      use the default for content.
  1384   CGContextSetInterpolationQuality(mCg, kCGInterpolationLow);
  1387   if (aType == BackendType::COREGRAPHICS_ACCELERATED) {
  1388     // The bitmap backend uses callac to clear, we can't do that without
  1389     // reading back the surface. This should trigger something equivilent
  1390     // to glClear.
  1391     ClearRect(Rect(0, 0, mSize.width, mSize.height));
  1394   return true;
  1397 void
  1398 DrawTargetCG::Flush()
  1400   if (GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE) {
  1401     CGContextFlush(mCg);
  1405 bool
  1406 DrawTargetCG::Init(CGContextRef cgContext, const IntSize &aSize)
  1408   // XXX: we should come up with some consistent semantics for dealing
  1409   // with zero area drawtargets
  1410   if (aSize.width == 0 || aSize.height == 0) {
  1411     mColorSpace = nullptr;
  1412     mCg = nullptr;
  1413     return false;
  1416   //XXX: handle SurfaceFormat
  1418   //XXX: we'd be better off reusing the Colorspace across draw targets
  1419   mColorSpace = CGColorSpaceCreateDeviceRGB();
  1421   mSize = aSize;
  1423   mCg = cgContext;
  1424   CGContextRetain(mCg);
  1426   assert(mCg);
  1428   // CGContext's default to have the origin at the bottom left.
  1429   // However, currently the only use of this function is to construct a
  1430   // DrawTargetCG around a CGContextRef from a cairo quartz surface which
  1431   // already has it's origin adjusted.
  1432   //
  1433   // CGContextTranslateCTM(mCg, 0, mSize.height);
  1434   // CGContextScaleCTM(mCg, 1, -1);
  1436   mFormat = SurfaceFormat::B8G8R8A8;
  1437   if (GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) {
  1438     CGColorSpaceRef colorspace;
  1439     CGBitmapInfo bitinfo = CGBitmapContextGetBitmapInfo(mCg);
  1440     colorspace = CGBitmapContextGetColorSpace (mCg);
  1441     if (CGColorSpaceGetNumberOfComponents(colorspace) == 1) {
  1442       mFormat = SurfaceFormat::A8;
  1443     } else if ((bitinfo & kCGBitmapAlphaInfoMask) == kCGImageAlphaNoneSkipFirst) {
  1444       mFormat = SurfaceFormat::B8G8R8X8;
  1448   return true;
  1451 bool
  1452 DrawTargetCG::Init(BackendType aType, const IntSize &aSize, SurfaceFormat &aFormat)
  1454   int32_t stride = GetAlignedStride<16>(aSize.width * BytesPerPixel(aFormat));
  1456   // Calling Init with aData == nullptr will allocate.
  1457   return Init(aType, nullptr, aSize, stride, aFormat);
  1460 TemporaryRef<PathBuilder>
  1461 DrawTargetCG::CreatePathBuilder(FillRule aFillRule) const
  1463   RefPtr<PathBuilderCG> pb = new PathBuilderCG(aFillRule);
  1464   return pb;
  1467 void*
  1468 DrawTargetCG::GetNativeSurface(NativeSurfaceType aType)
  1470   if ((aType == NativeSurfaceType::CGCONTEXT && GetContextType(mCg) == CG_CONTEXT_TYPE_BITMAP) ||
  1471       (aType == NativeSurfaceType::CGCONTEXT_ACCELERATED && GetContextType(mCg) == CG_CONTEXT_TYPE_IOSURFACE)) {
  1472     return mCg;
  1473   } else {
  1474     return nullptr;
  1478 void
  1479 DrawTargetCG::Mask(const Pattern &aSource,
  1480                    const Pattern &aMask,
  1481                    const DrawOptions &aDrawOptions)
  1483   MarkChanged();
  1485   CGContextSaveGState(mCg);
  1487   if (isGradient(aMask)) {
  1488     assert(0);
  1489   } else {
  1490     if (aMask.GetType() == PatternType::COLOR) {
  1491       DrawOptions drawOptions(aDrawOptions);
  1492       const Color& color = static_cast<const ColorPattern&>(aMask).mColor;
  1493       drawOptions.mAlpha *= color.a;
  1494       assert(0);
  1495       // XXX: we need to get a rect that when transformed covers the entire surface
  1496       //Rect
  1497       //FillRect(rect, aSource, drawOptions);
  1498     } else if (aMask.GetType() == PatternType::SURFACE) {
  1499       const SurfacePattern& pat = static_cast<const SurfacePattern&>(aMask);
  1500       CGImageRef mask = GetRetainedImageFromSourceSurface(pat.mSurface.get());
  1501       Rect rect(0,0, CGImageGetWidth(mask), CGImageGetHeight(mask));
  1502       // XXX: probably we need to do some flipping of the image or something
  1503       CGContextClipToMask(mCg, RectToCGRect(rect), mask);
  1504       FillRect(rect, aSource, aDrawOptions);
  1505       CGImageRelease(mask);
  1509   CGContextRestoreGState(mCg);
  1512 void
  1513 DrawTargetCG::PushClipRect(const Rect &aRect)
  1515   CGContextSaveGState(mCg);
  1517   /* We go through a bit of trouble to temporarilly set the transform
  1518    * while we add the path */
  1519   CGAffineTransform previousTransform = CGContextGetCTM(mCg);
  1520   CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
  1521   CGContextClipToRect(mCg, RectToCGRect(aRect));
  1522   CGContextSetCTM(mCg, previousTransform);
  1526 void
  1527 DrawTargetCG::PushClip(const Path *aPath)
  1529   CGContextSaveGState(mCg);
  1531   CGContextBeginPath(mCg);
  1532   assert(aPath->GetBackendType() == BackendType::COREGRAPHICS);
  1534   const PathCG *cgPath = static_cast<const PathCG*>(aPath);
  1536   // Weirdly, CoreGraphics clips empty paths as all shown
  1537   // but emtpy rects as all clipped.  We detect this situation and
  1538   // workaround it appropriately
  1539   if (CGPathIsEmpty(cgPath->GetPath())) {
  1540     // XXX: should we return here?
  1541     CGContextClipToRect(mCg, CGRectZero);
  1545   /* We go through a bit of trouble to temporarilly set the transform
  1546    * while we add the path. XXX: this could be improved if we keep
  1547    * the CTM as resident state on the DrawTarget. */
  1548   CGContextSaveGState(mCg);
  1549   CGContextConcatCTM(mCg, GfxMatrixToCGAffineTransform(mTransform));
  1550   CGContextAddPath(mCg, cgPath->GetPath());
  1551   CGContextRestoreGState(mCg);
  1553   if (cgPath->GetFillRule() == FillRule::FILL_EVEN_ODD)
  1554     CGContextEOClip(mCg);
  1555   else
  1556     CGContextClip(mCg);
  1559 void
  1560 DrawTargetCG::PopClip()
  1562   CGContextRestoreGState(mCg);
  1565 void
  1566 DrawTargetCG::MarkChanged()
  1568   if (mSnapshot) {
  1569     if (mSnapshot->refCount() > 1) {
  1570       // We only need to worry about snapshots that someone else knows about
  1571       mSnapshot->DrawTargetWillChange();
  1573     mSnapshot = nullptr;
  1577 CGContextRef
  1578 BorrowedCGContext::BorrowCGContextFromDrawTarget(DrawTarget *aDT)
  1580   if (aDT->GetType() == BackendType::COREGRAPHICS || aDT->GetType() == BackendType::COREGRAPHICS_ACCELERATED) {
  1581     DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
  1582     cgDT->MarkChanged();
  1584     // swap out the context
  1585     CGContextRef cg = cgDT->mCg;
  1586     cgDT->mCg = nullptr;
  1588     // save the state to make it easier for callers to avoid mucking with things
  1589     CGContextSaveGState(cg);
  1591     CGContextConcatCTM(cg, GfxMatrixToCGAffineTransform(cgDT->mTransform));
  1593     return cg;
  1595   return nullptr;
  1598 void
  1599 BorrowedCGContext::ReturnCGContextToDrawTarget(DrawTarget *aDT, CGContextRef cg)
  1601   DrawTargetCG* cgDT = static_cast<DrawTargetCG*>(aDT);
  1603   CGContextRestoreGState(cg);
  1604   cgDT->mCg = cg;

mercurial