1 /*
2  * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
3  * Copyright (C) 2008 Eric Seidel <eric@webkit.org>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #define _USE_MATH_DEFINES 1
28 #include "config.h"
29 #include "GraphicsContextCG.h"
30 
31 #include "AffineTransform.h"
32 #include "FloatConversion.h"
33 #include "GraphicsContextPlatformPrivateCG.h"
34 #include "ImageBuffer.h"
35 #include "KURL.h"
36 #include "Path.h"
37 #include "Pattern.h"
38 #include "ShadowBlur.h"
39 
40 #include <CoreGraphics/CoreGraphics.h>
41 #include <wtf/MathExtras.h>
42 #include <wtf/OwnArrayPtr.h>
43 #include <wtf/RetainPtr.h>
44 #include <wtf/UnusedParam.h>
45 
46 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
47 #include "WebCoreSystemInterface.h"
48 #endif
49 
50 #if PLATFORM(WIN)
51 #include <WebKitSystemInterface/WebKitSystemInterface.h>
52 #endif
53 
54 #if PLATFORM(MAC) || (PLATFORM(CHROMIUM) && OS(DARWIN))
55 
56 #ifndef BUILDING_ON_LEOPARD
57 // Building on 10.6 or later: kCGInterpolationMedium is defined in the CGInterpolationQuality enum.
58 #define HAVE_CG_INTERPOLATION_MEDIUM 1
59 #endif
60 
61 #ifndef TARGETING_LEOPARD
62 // Targeting 10.6 or later: use kCGInterpolationMedium.
63 #define WTF_USE_CG_INTERPOLATION_MEDIUM 1
64 #endif
65 
66 #endif
67 
68 // Undocumented CGContextSetCTM function, available at least since 10.4.
69 extern "C" {
70     CG_EXTERN void CGContextSetCTM(CGContextRef, CGAffineTransform);
71 };
72 
73 using namespace std;
74 
75 namespace WebCore {
76 
setCGFillColor(CGContextRef context,const Color & color,ColorSpace colorSpace)77 static void setCGFillColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
78 {
79     CGContextSetFillColorWithColor(context, cachedCGColor(color, colorSpace));
80 }
81 
setCGStrokeColor(CGContextRef context,const Color & color,ColorSpace colorSpace)82 static void setCGStrokeColor(CGContextRef context, const Color& color, ColorSpace colorSpace)
83 {
84     CGContextSetStrokeColorWithColor(context, cachedCGColor(color, colorSpace));
85 }
86 
deviceRGBColorSpaceRef()87 CGColorSpaceRef deviceRGBColorSpaceRef()
88 {
89     static CGColorSpaceRef deviceSpace = CGColorSpaceCreateDeviceRGB();
90     return deviceSpace;
91 }
92 
sRGBColorSpaceRef()93 CGColorSpaceRef sRGBColorSpaceRef()
94 {
95     // FIXME: Windows should be able to use kCGColorSpaceSRGB, this is tracked by http://webkit.org/b/31363.
96 #if PLATFORM(WIN)
97     return deviceRGBColorSpaceRef();
98 #else
99     static CGColorSpaceRef sRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
100     return sRGBSpace;
101 #endif
102 }
103 
linearRGBColorSpaceRef()104 CGColorSpaceRef linearRGBColorSpaceRef()
105 {
106     // FIXME: Windows should be able to use kCGColorSpaceGenericRGBLinear, this is tracked by http://webkit.org/b/31363.
107 #if PLATFORM(WIN)
108     return deviceRGBColorSpaceRef();
109 #else
110     static CGColorSpaceRef linearRGBSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGBLinear);
111     return linearRGBSpace;
112 #endif
113 }
114 
platformInit(CGContextRef cgContext)115 void GraphicsContext::platformInit(CGContextRef cgContext)
116 {
117     m_data = new GraphicsContextPlatformPrivate(cgContext);
118     setPaintingDisabled(!cgContext);
119     if (cgContext) {
120         // Make sure the context starts in sync with our state.
121         setPlatformFillColor(fillColor(), fillColorSpace());
122         setPlatformStrokeColor(strokeColor(), strokeColorSpace());
123     }
124 }
125 
platformDestroy()126 void GraphicsContext::platformDestroy()
127 {
128     delete m_data;
129 }
130 
platformContext() const131 CGContextRef GraphicsContext::platformContext() const
132 {
133     ASSERT(!paintingDisabled());
134     ASSERT(m_data->m_cgContext);
135     return m_data->m_cgContext.get();
136 }
137 
savePlatformState()138 void GraphicsContext::savePlatformState()
139 {
140     // Note: Do not use this function within this class implementation, since we want to avoid the extra
141     // save of the secondary context (in GraphicsContextPlatformPrivateCG.h).
142     CGContextSaveGState(platformContext());
143     m_data->save();
144 }
145 
restorePlatformState()146 void GraphicsContext::restorePlatformState()
147 {
148     // Note: Do not use this function within this class implementation, since we want to avoid the extra
149     // restore of the secondary context (in GraphicsContextPlatformPrivateCG.h).
150     CGContextRestoreGState(platformContext());
151     m_data->restore();
152     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
153 }
154 
155 // Draws a filled rectangle with a stroked border.
drawRect(const IntRect & rect)156 void GraphicsContext::drawRect(const IntRect& rect)
157 {
158     // FIXME: this function does not handle patterns and gradients
159     // like drawPath does, it probably should.
160     if (paintingDisabled())
161         return;
162 
163     CGContextRef context = platformContext();
164 
165     CGContextFillRect(context, rect);
166 
167     if (strokeStyle() != NoStroke) {
168         // We do a fill of four rects to simulate the stroke of a border.
169         Color oldFillColor = fillColor();
170         if (oldFillColor != strokeColor())
171             setCGFillColor(context, strokeColor(), strokeColorSpace());
172         CGRect rects[4] = {
173             FloatRect(rect.x(), rect.y(), rect.width(), 1),
174             FloatRect(rect.x(), rect.maxY() - 1, rect.width(), 1),
175             FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
176             FloatRect(rect.maxX() - 1, rect.y() + 1, 1, rect.height() - 2)
177         };
178         CGContextFillRects(context, rects, 4);
179         if (oldFillColor != strokeColor())
180             setCGFillColor(context, oldFillColor, fillColorSpace());
181     }
182 }
183 
184 // This is only used to draw borders.
drawLine(const IntPoint & point1,const IntPoint & point2)185 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
186 {
187     if (paintingDisabled())
188         return;
189 
190     if (strokeStyle() == NoStroke)
191         return;
192 
193     float width = strokeThickness();
194 
195     FloatPoint p1 = point1;
196     FloatPoint p2 = point2;
197     bool isVerticalLine = (p1.x() == p2.x());
198 
199     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
200     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
201     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
202     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
203     if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
204         if (isVerticalLine) {
205             p1.move(0, width);
206             p2.move(0, -width);
207         } else {
208             p1.move(width, 0);
209             p2.move(-width, 0);
210         }
211     }
212 
213     if (((int)width) % 2) {
214         if (isVerticalLine) {
215             // We're a vertical line.  Adjust our x.
216             p1.move(0.5f, 0.0f);
217             p2.move(0.5f, 0.0f);
218         } else {
219             // We're a horizontal line. Adjust our y.
220             p1.move(0.0f, 0.5f);
221             p2.move(0.0f, 0.5f);
222         }
223     }
224 
225     int patWidth = 0;
226     switch (strokeStyle()) {
227     case NoStroke:
228     case SolidStroke:
229         break;
230     case DottedStroke:
231         patWidth = (int)width;
232         break;
233     case DashedStroke:
234         patWidth = 3 * (int)width;
235         break;
236     }
237 
238     CGContextRef context = platformContext();
239 
240     if (shouldAntialias())
241         CGContextSetShouldAntialias(context, false);
242 
243     if (patWidth) {
244         CGContextSaveGState(context);
245 
246         // Do a rect fill of our endpoints.  This ensures we always have the
247         // appearance of being a border.  We then draw the actual dotted/dashed line.
248         setCGFillColor(context, strokeColor(), strokeColorSpace());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
249         if (isVerticalLine) {
250             CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
251             CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
252         } else {
253             CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
254             CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
255         }
256 
257         // Example: 80 pixels with a width of 30 pixels.
258         // Remainder is 20.  The maximum pixels of line we could paint
259         // will be 50 pixels.
260         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
261         int remainder = distance % patWidth;
262         int coverage = distance - remainder;
263         int numSegments = coverage / patWidth;
264 
265         float patternOffset = 0.0f;
266         // Special case 1px dotted borders for speed.
267         if (patWidth == 1)
268             patternOffset = 1.0f;
269         else {
270             bool evenNumberOfSegments = !(numSegments % 2);
271             if (remainder)
272                 evenNumberOfSegments = !evenNumberOfSegments;
273             if (evenNumberOfSegments) {
274                 if (remainder) {
275                     patternOffset += patWidth - remainder;
276                     patternOffset += remainder / 2;
277                 } else
278                     patternOffset = patWidth / 2;
279             } else {
280                 if (remainder)
281                     patternOffset = (patWidth - remainder)/2;
282             }
283         }
284 
285         const CGFloat dottedLine[2] = { patWidth, patWidth };
286         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
287     }
288 
289     CGContextBeginPath(context);
290     CGContextMoveToPoint(context, p1.x(), p1.y());
291     CGContextAddLineToPoint(context, p2.x(), p2.y());
292 
293     CGContextStrokePath(context);
294 
295     if (patWidth)
296         CGContextRestoreGState(context);
297 
298     if (shouldAntialias())
299         CGContextSetShouldAntialias(context, true);
300 }
301 
302 // This method is only used to draw the little circles used in lists.
drawEllipse(const IntRect & rect)303 void GraphicsContext::drawEllipse(const IntRect& rect)
304 {
305     if (paintingDisabled())
306         return;
307 
308     Path path;
309     path.addEllipse(rect);
310     drawPath(path);
311 }
312 
313 
strokeArc(const IntRect & rect,int startAngle,int angleSpan)314 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
315 {
316     if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f)
317         return;
318 
319     CGContextRef context = platformContext();
320     CGContextSaveGState(context);
321     CGContextBeginPath(context);
322     CGContextSetShouldAntialias(context, false);
323 
324     int x = rect.x();
325     int y = rect.y();
326     float w = (float)rect.width();
327     float h = (float)rect.height();
328     float scaleFactor = h / w;
329     float reverseScaleFactor = w / h;
330 
331     if (w != h)
332         scale(FloatSize(1, scaleFactor));
333 
334     float hRadius = w / 2;
335     float vRadius = h / 2;
336     float fa = startAngle;
337     float falen =  fa + angleSpan;
338     float start = -fa * piFloat / 180.0f;
339     float end = -falen * piFloat / 180.0f;
340     CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);
341 
342     if (w != h)
343         scale(FloatSize(1, reverseScaleFactor));
344 
345     float width = strokeThickness();
346     int patWidth = 0;
347 
348     switch (strokeStyle()) {
349     case DottedStroke:
350         patWidth = (int)(width / 2);
351         break;
352     case DashedStroke:
353         patWidth = 3 * (int)(width / 2);
354         break;
355     default:
356         break;
357     }
358 
359     if (patWidth) {
360         // Example: 80 pixels with a width of 30 pixels.
361         // Remainder is 20.  The maximum pixels of line we could paint
362         // will be 50 pixels.
363         int distance;
364         if (hRadius == vRadius)
365             distance = static_cast<int>((piFloat * hRadius) / 2.0f);
366         else // We are elliptical and will have to estimate the distance
367             distance = static_cast<int>((piFloat * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0f)) / 2.0f);
368 
369         int remainder = distance % patWidth;
370         int coverage = distance - remainder;
371         int numSegments = coverage / patWidth;
372 
373         float patternOffset = 0.0f;
374         // Special case 1px dotted borders for speed.
375         if (patWidth == 1)
376             patternOffset = 1.0f;
377         else {
378             bool evenNumberOfSegments = !(numSegments % 2);
379             if (remainder)
380                 evenNumberOfSegments = !evenNumberOfSegments;
381             if (evenNumberOfSegments) {
382                 if (remainder) {
383                     patternOffset += patWidth - remainder;
384                     patternOffset += remainder / 2.0f;
385                 } else
386                     patternOffset = patWidth / 2.0f;
387             } else {
388                 if (remainder)
389                     patternOffset = (patWidth - remainder) / 2.0f;
390             }
391         }
392 
393         const CGFloat dottedLine[2] = { patWidth, patWidth };
394         CGContextSetLineDash(context, patternOffset, dottedLine, 2);
395     }
396 
397     CGContextStrokePath(context);
398 
399     CGContextRestoreGState(context);
400 }
401 
addConvexPolygonToPath(Path & path,size_t numberOfPoints,const FloatPoint * points)402 static void addConvexPolygonToPath(Path& path, size_t numberOfPoints, const FloatPoint* points)
403 {
404     ASSERT(numberOfPoints > 0);
405 
406     path.moveTo(points[0]);
407     for (size_t i = 1; i < numberOfPoints; ++i)
408         path.addLineTo(points[i]);
409     path.closeSubpath();
410 }
411 
drawConvexPolygon(size_t numberOfPoints,const FloatPoint * points,bool antialiased)412 void GraphicsContext::drawConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialiased)
413 {
414     if (paintingDisabled())
415         return;
416 
417     if (numberOfPoints <= 1)
418         return;
419 
420     CGContextRef context = platformContext();
421 
422     if (antialiased != shouldAntialias())
423         CGContextSetShouldAntialias(context, antialiased);
424 
425     Path path;
426     addConvexPolygonToPath(path, numberOfPoints, points);
427     drawPath(path);
428 
429     if (antialiased != shouldAntialias())
430         CGContextSetShouldAntialias(context, shouldAntialias());
431 }
432 
clipConvexPolygon(size_t numberOfPoints,const FloatPoint * points,bool antialias)433 void GraphicsContext::clipConvexPolygon(size_t numberOfPoints, const FloatPoint* points, bool antialias)
434 {
435     if (paintingDisabled())
436         return;
437 
438     if (numberOfPoints <= 1)
439         return;
440 
441     CGContextRef context = platformContext();
442 
443     if (antialias != shouldAntialias())
444         CGContextSetShouldAntialias(context, antialias);
445 
446     Path path;
447     addConvexPolygonToPath(path, numberOfPoints, points);
448     clipPath(path, RULE_NONZERO);
449 
450     if (antialias != shouldAntialias())
451         CGContextSetShouldAntialias(context, shouldAntialias());
452 }
453 
applyStrokePattern()454 void GraphicsContext::applyStrokePattern()
455 {
456     CGContextRef cgContext = platformContext();
457 
458     RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.strokePattern->createPlatformPattern(getCTM()));
459     if (!platformPattern)
460         return;
461 
462     RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0));
463     CGContextSetStrokeColorSpace(cgContext, patternSpace.get());
464 
465     const CGFloat patternAlpha = 1;
466     CGContextSetStrokePattern(cgContext, platformPattern.get(), &patternAlpha);
467 }
468 
applyFillPattern()469 void GraphicsContext::applyFillPattern()
470 {
471     CGContextRef cgContext = platformContext();
472 
473     RetainPtr<CGPatternRef> platformPattern(AdoptCF, m_state.fillPattern->createPlatformPattern(getCTM()));
474     if (!platformPattern)
475         return;
476 
477     RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0));
478     CGContextSetFillColorSpace(cgContext, patternSpace.get());
479 
480     const CGFloat patternAlpha = 1;
481     CGContextSetFillPattern(cgContext, platformPattern.get(), &patternAlpha);
482 }
483 
calculateDrawingMode(const GraphicsContextState & state,CGPathDrawingMode & mode)484 static inline bool calculateDrawingMode(const GraphicsContextState& state, CGPathDrawingMode& mode)
485 {
486     bool shouldFill = state.fillPattern || state.fillColor.alpha();
487     bool shouldStroke = state.strokePattern || (state.strokeStyle != NoStroke && state.strokeColor.alpha());
488     bool useEOFill = state.fillRule == RULE_EVENODD;
489 
490     if (shouldFill) {
491         if (shouldStroke) {
492             if (useEOFill)
493                 mode = kCGPathEOFillStroke;
494             else
495                 mode = kCGPathFillStroke;
496         } else { // fill, no stroke
497             if (useEOFill)
498                 mode = kCGPathEOFill;
499             else
500                 mode = kCGPathFill;
501         }
502     } else {
503         // Setting mode to kCGPathStroke even if shouldStroke is false. In that case, we return false and mode will not be used,
504         // but the compiler will not complain about an uninitialized variable.
505         mode = kCGPathStroke;
506     }
507 
508     return shouldFill || shouldStroke;
509 }
510 
drawPath(const Path & path)511 void GraphicsContext::drawPath(const Path& path)
512 {
513     if (paintingDisabled())
514         return;
515 
516     CGContextRef context = platformContext();
517     const GraphicsContextState& state = m_state;
518 
519     if (state.fillGradient || state.strokeGradient) {
520         // We don't have any optimized way to fill & stroke a path using gradients
521         // FIXME: Be smarter about this.
522         fillPath(path);
523         strokePath(path);
524         return;
525     }
526 
527     CGContextBeginPath(context);
528     CGContextAddPath(context, path.platformPath());
529 
530     if (state.fillPattern)
531         applyFillPattern();
532     if (state.strokePattern)
533         applyStrokePattern();
534 
535     CGPathDrawingMode drawingMode;
536     if (calculateDrawingMode(state, drawingMode))
537         CGContextDrawPath(context, drawingMode);
538 }
539 
fillPathWithFillRule(CGContextRef context,WindRule fillRule)540 static inline void fillPathWithFillRule(CGContextRef context, WindRule fillRule)
541 {
542     if (fillRule == RULE_EVENODD)
543         CGContextEOFillPath(context);
544     else
545         CGContextFillPath(context);
546 }
547 
fillPath(const Path & path)548 void GraphicsContext::fillPath(const Path& path)
549 {
550     if (paintingDisabled())
551         return;
552 
553     CGContextRef context = platformContext();
554 
555     if (m_state.fillGradient) {
556         if (hasShadow()) {
557             FloatRect rect = path.boundingRect();
558             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
559             CGContextRef layerContext = CGLayerGetContext(layer);
560 
561             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
562             CGContextBeginPath(layerContext);
563             CGContextAddPath(layerContext, path.platformPath());
564             CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
565 
566             if (fillRule() == RULE_EVENODD)
567                 CGContextEOClip(layerContext);
568             else
569                 CGContextClip(layerContext);
570 
571             m_state.fillGradient->paint(layerContext);
572             CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer);
573             CGLayerRelease(layer);
574         } else {
575             CGContextBeginPath(context);
576             CGContextAddPath(context, path.platformPath());
577             CGContextSaveGState(context);
578             CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
579 
580             if (fillRule() == RULE_EVENODD)
581                 CGContextEOClip(context);
582             else
583                 CGContextClip(context);
584 
585             m_state.fillGradient->paint(this);
586             CGContextRestoreGState(context);
587         }
588 
589         return;
590     }
591 
592     CGContextBeginPath(context);
593     CGContextAddPath(context, path.platformPath());
594 
595     if (m_state.fillPattern)
596         applyFillPattern();
597     fillPathWithFillRule(context, fillRule());
598 }
599 
strokePath(const Path & path)600 void GraphicsContext::strokePath(const Path& path)
601 {
602     if (paintingDisabled())
603         return;
604 
605     CGContextRef context = platformContext();
606 
607     CGContextBeginPath(context);
608     CGContextAddPath(context, path.platformPath());
609 
610     if (m_state.strokeGradient) {
611         if (hasShadow()) {
612             FloatRect rect = path.boundingRect();
613             float lineWidth = strokeThickness();
614             float doubleLineWidth = lineWidth * 2;
615             float layerWidth = ceilf(rect.width() + doubleLineWidth);
616             float layerHeight = ceilf(rect.height() + doubleLineWidth);
617 
618             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0);
619             CGContextRef layerContext = CGLayerGetContext(layer);
620             CGContextSetLineWidth(layerContext, lineWidth);
621 
622             // Compensate for the line width, otherwise the layer's top-left corner would be
623             // aligned with the rect's top-left corner. This would result in leaving pixels out of
624             // the layer on the left and top sides.
625             float translationX = lineWidth - rect.x();
626             float translationY = lineWidth - rect.y();
627             CGContextTranslateCTM(layerContext, translationX, translationY);
628 
629             CGContextAddPath(layerContext, path.platformPath());
630             CGContextReplacePathWithStrokedPath(layerContext);
631             CGContextClip(layerContext);
632             CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
633             m_state.strokeGradient->paint(layerContext);
634 
635             float destinationX = roundf(rect.x() - lineWidth);
636             float destinationY = roundf(rect.y() - lineWidth);
637             CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer);
638             CGLayerRelease(layer);
639         } else {
640             CGContextSaveGState(context);
641             CGContextReplacePathWithStrokedPath(context);
642             CGContextClip(context);
643             CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
644             m_state.strokeGradient->paint(this);
645             CGContextRestoreGState(context);
646         }
647         return;
648     }
649 
650     if (m_state.strokePattern)
651         applyStrokePattern();
652     CGContextStrokePath(context);
653 }
654 
radiusToLegacyRadius(float radius)655 static float radiusToLegacyRadius(float radius)
656 {
657     return radius > 8 ? 8 + 4 * sqrt((radius - 8) / 2) : radius;
658 }
659 
hasBlurredShadow(const GraphicsContextState & state)660 static bool hasBlurredShadow(const GraphicsContextState& state)
661 {
662     return state.shadowColor.isValid() && state.shadowColor.alpha() && state.shadowBlur;
663 }
664 
fillRect(const FloatRect & rect)665 void GraphicsContext::fillRect(const FloatRect& rect)
666 {
667     if (paintingDisabled())
668         return;
669 
670     CGContextRef context = platformContext();
671 
672     if (m_state.fillGradient) {
673         CGContextSaveGState(context);
674         if (hasShadow()) {
675             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(rect.width(), rect.height()), 0);
676             CGContextRef layerContext = CGLayerGetContext(layer);
677 
678             CGContextTranslateCTM(layerContext, -rect.x(), -rect.y());
679             CGContextAddRect(layerContext, rect);
680             CGContextClip(layerContext);
681 
682             CGContextConcatCTM(layerContext, m_state.fillGradient->gradientSpaceTransform());
683             m_state.fillGradient->paint(layerContext);
684             CGContextDrawLayerAtPoint(context, CGPointMake(rect.x(), rect.y()), layer);
685             CGLayerRelease(layer);
686         } else {
687             CGContextClipToRect(context, rect);
688             CGContextConcatCTM(context, m_state.fillGradient->gradientSpaceTransform());
689             m_state.fillGradient->paint(this);
690         }
691         CGContextRestoreGState(context);
692         return;
693     }
694 
695     if (m_state.fillPattern)
696         applyFillPattern();
697 
698     bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
699     if (drawOwnShadow) {
700         float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur;
701         // Turn off CG shadows.
702         CGContextSaveGState(context);
703         CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
704 
705         ShadowBlur contextShadow(FloatSize(shadowBlur, shadowBlur), m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace);
706         contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii());
707     }
708 
709     CGContextFillRect(context, rect);
710 
711     if (drawOwnShadow)
712         CGContextRestoreGState(context);
713 }
714 
fillRect(const FloatRect & rect,const Color & color,ColorSpace colorSpace)715 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
716 {
717     if (paintingDisabled())
718         return;
719 
720     CGContextRef context = platformContext();
721     Color oldFillColor = fillColor();
722     ColorSpace oldColorSpace = fillColorSpace();
723 
724     if (oldFillColor != color || oldColorSpace != colorSpace)
725         setCGFillColor(context, color, colorSpace);
726 
727     bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
728     if (drawOwnShadow) {
729         float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur;
730         // Turn off CG shadows.
731         CGContextSaveGState(context);
732         CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
733 
734         ShadowBlur contextShadow(FloatSize(shadowBlur, shadowBlur), m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace);
735         contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii());
736     }
737 
738     CGContextFillRect(context, rect);
739 
740     if (drawOwnShadow)
741         CGContextRestoreGState(context);
742 
743     if (oldFillColor != color || oldColorSpace != colorSpace)
744         setCGFillColor(context, oldFillColor, oldColorSpace);
745 }
746 
fillRoundedRect(const IntRect & rect,const IntSize & topLeft,const IntSize & topRight,const IntSize & bottomLeft,const IntSize & bottomRight,const Color & color,ColorSpace colorSpace)747 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace)
748 {
749     if (paintingDisabled())
750         return;
751 
752     CGContextRef context = platformContext();
753     Color oldFillColor = fillColor();
754     ColorSpace oldColorSpace = fillColorSpace();
755 
756     if (oldFillColor != color || oldColorSpace != colorSpace)
757         setCGFillColor(context, color, colorSpace);
758 
759     bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms; // Don't use ShadowBlur for canvas yet.
760     if (drawOwnShadow) {
761         float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur;
762 
763         // Turn off CG shadows.
764         CGContextSaveGState(context);
765         CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
766 
767         ShadowBlur contextShadow(FloatSize(shadowBlur, shadowBlur), m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace);
768         contextShadow.drawRectShadow(this, rect, RoundedIntRect::Radii(topLeft, topRight, bottomLeft, bottomRight));
769     }
770 
771     bool equalWidths = (topLeft.width() == topRight.width() && topRight.width() == bottomLeft.width() && bottomLeft.width() == bottomRight.width());
772     bool equalHeights = (topLeft.height() == bottomLeft.height() && bottomLeft.height() == topRight.height() && topRight.height() == bottomRight.height());
773     if (equalWidths && equalHeights && topLeft.width() * 2 == rect.width() && topLeft.height() * 2 == rect.height())
774         CGContextFillEllipseInRect(context, rect);
775     else {
776         Path path;
777         path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight);
778         fillPath(path);
779     }
780 
781     if (drawOwnShadow)
782         CGContextRestoreGState(context);
783 
784     if (oldFillColor != color || oldColorSpace != colorSpace)
785         setCGFillColor(context, oldFillColor, oldColorSpace);
786 }
787 
fillRectWithRoundedHole(const IntRect & rect,const RoundedIntRect & roundedHoleRect,const Color & color,ColorSpace colorSpace)788 void GraphicsContext::fillRectWithRoundedHole(const IntRect& rect, const RoundedIntRect& roundedHoleRect, const Color& color, ColorSpace colorSpace)
789 {
790     if (paintingDisabled())
791         return;
792 
793     CGContextRef context = platformContext();
794 
795     Path path;
796     path.addRect(rect);
797 
798     if (!roundedHoleRect.radii().isZero())
799         path.addRoundedRect(roundedHoleRect);
800     else
801         path.addRect(roundedHoleRect.rect());
802 
803     WindRule oldFillRule = fillRule();
804     Color oldFillColor = fillColor();
805     ColorSpace oldFillColorSpace = fillColorSpace();
806 
807     setFillRule(RULE_EVENODD);
808     setFillColor(color, colorSpace);
809 
810     // fillRectWithRoundedHole() assumes that the edges of rect are clipped out, so we only care about shadows cast around inside the hole.
811     bool drawOwnShadow = !isAcceleratedContext() && hasBlurredShadow(m_state) && !m_state.shadowsIgnoreTransforms;
812     if (drawOwnShadow) {
813         float shadowBlur = m_state.shadowsUseLegacyRadius ? radiusToLegacyRadius(m_state.shadowBlur) : m_state.shadowBlur;
814 
815         // Turn off CG shadows.
816         CGContextSaveGState(context);
817         CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
818 
819         ShadowBlur contextShadow(FloatSize(shadowBlur, shadowBlur), m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace);
820         contextShadow.drawInsetShadow(this, rect, roundedHoleRect.rect(), roundedHoleRect.radii());
821     }
822 
823     fillPath(path);
824 
825     if (drawOwnShadow)
826         CGContextRestoreGState(context);
827 
828     setFillRule(oldFillRule);
829     setFillColor(oldFillColor, oldFillColorSpace);
830 }
831 
clip(const FloatRect & rect)832 void GraphicsContext::clip(const FloatRect& rect)
833 {
834     if (paintingDisabled())
835         return;
836     CGContextClipToRect(platformContext(), rect);
837     m_data->clip(rect);
838 }
839 
clipOut(const IntRect & rect)840 void GraphicsContext::clipOut(const IntRect& rect)
841 {
842     if (paintingDisabled())
843         return;
844 
845     CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
846     CGContextBeginPath(platformContext());
847     CGContextAddRects(platformContext(), rects, 2);
848     CGContextEOClip(platformContext());
849 }
850 
clipPath(const Path & path,WindRule clipRule)851 void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
852 {
853     if (paintingDisabled())
854         return;
855 
856     if (path.isEmpty())
857         return;
858 
859     CGContextRef context = platformContext();
860 
861     CGContextBeginPath(platformContext());
862     CGContextAddPath(platformContext(), path.platformPath());
863 
864     if (clipRule == RULE_EVENODD)
865         CGContextEOClip(context);
866     else
867         CGContextClip(context);
868 }
869 
clipBounds() const870 IntRect GraphicsContext::clipBounds() const
871 {
872     return enclosingIntRect(CGContextGetClipBoundingBox(platformContext()));
873 }
874 
addInnerRoundedRectClip(const IntRect & rect,int thickness)875 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
876 {
877     if (paintingDisabled())
878         return;
879 
880     clip(rect);
881     CGContextRef context = platformContext();
882 
883     // Add outer ellipse
884     CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
885     // Add inner ellipse.
886     CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
887         rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
888 
889     CGContextEOClip(context);
890 }
891 
beginTransparencyLayer(float opacity)892 void GraphicsContext::beginTransparencyLayer(float opacity)
893 {
894     if (paintingDisabled())
895         return;
896     CGContextRef context = platformContext();
897     CGContextSaveGState(context);
898     CGContextSetAlpha(context, opacity);
899     CGContextBeginTransparencyLayer(context, 0);
900     m_data->beginTransparencyLayer();
901     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
902 }
903 
endTransparencyLayer()904 void GraphicsContext::endTransparencyLayer()
905 {
906     if (paintingDisabled())
907         return;
908     CGContextRef context = platformContext();
909     CGContextEndTransparencyLayer(context);
910     CGContextRestoreGState(context);
911     m_data->endTransparencyLayer();
912     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
913 }
914 
setPlatformShadow(const FloatSize & offset,float blur,const Color & color,ColorSpace colorSpace)915 void GraphicsContext::setPlatformShadow(const FloatSize& offset, float blur, const Color& color, ColorSpace colorSpace)
916 {
917     if (paintingDisabled())
918         return;
919 
920     // FIXME: we could avoid the shadow setup cost when we know we'll render the shadow ourselves.
921 
922     CGFloat xOffset = offset.width();
923     CGFloat yOffset = offset.height();
924     CGFloat blurRadius = blur;
925     CGContextRef context = platformContext();
926 
927     if (!m_state.shadowsIgnoreTransforms) {
928         CGAffineTransform userToBaseCTM = wkGetUserToBaseCTM(context);
929 
930         CGFloat A = userToBaseCTM.a * userToBaseCTM.a + userToBaseCTM.b * userToBaseCTM.b;
931         CGFloat B = userToBaseCTM.a * userToBaseCTM.c + userToBaseCTM.b * userToBaseCTM.d;
932         CGFloat C = B;
933         CGFloat D = userToBaseCTM.c * userToBaseCTM.c + userToBaseCTM.d * userToBaseCTM.d;
934 
935         CGFloat smallEigenvalue = narrowPrecisionToCGFloat(sqrt(0.5 * ((A + D) - sqrt(4 * B * C + (A - D) * (A - D)))));
936 
937         blurRadius = blur * smallEigenvalue;
938 
939         CGSize offsetInBaseSpace = CGSizeApplyAffineTransform(offset, userToBaseCTM);
940 
941         xOffset = offsetInBaseSpace.width;
942         yOffset = offsetInBaseSpace.height;
943     }
944 
945     // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
946     blurRadius = min(blurRadius, narrowPrecisionToCGFloat(1000.0));
947 
948     // Work around <rdar://problem/5539388> by ensuring that the offsets will get truncated
949     // to the desired integer.
950     static const CGFloat extraShadowOffset = narrowPrecisionToCGFloat(1.0 / 128);
951     if (xOffset > 0)
952         xOffset += extraShadowOffset;
953     else if (xOffset < 0)
954         xOffset -= extraShadowOffset;
955 
956     if (yOffset > 0)
957         yOffset += extraShadowOffset;
958     else if (yOffset < 0)
959         yOffset -= extraShadowOffset;
960 
961     // Check for an invalid color, as this means that the color was not set for the shadow
962     // and we should therefore just use the default shadow color.
963     if (!color.isValid())
964         CGContextSetShadow(context, CGSizeMake(xOffset, yOffset), blurRadius);
965     else
966         CGContextSetShadowWithColor(context, CGSizeMake(xOffset, yOffset), blurRadius, cachedCGColor(color, colorSpace));
967 }
968 
clearPlatformShadow()969 void GraphicsContext::clearPlatformShadow()
970 {
971     if (paintingDisabled())
972         return;
973     CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
974 }
975 
setMiterLimit(float limit)976 void GraphicsContext::setMiterLimit(float limit)
977 {
978     if (paintingDisabled())
979         return;
980     CGContextSetMiterLimit(platformContext(), limit);
981 }
982 
setAlpha(float alpha)983 void GraphicsContext::setAlpha(float alpha)
984 {
985     if (paintingDisabled())
986         return;
987     CGContextSetAlpha(platformContext(), alpha);
988 }
989 
clearRect(const FloatRect & r)990 void GraphicsContext::clearRect(const FloatRect& r)
991 {
992     if (paintingDisabled())
993         return;
994     CGContextClearRect(platformContext(), r);
995 }
996 
strokeRect(const FloatRect & rect,float lineWidth)997 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
998 {
999     if (paintingDisabled())
1000         return;
1001 
1002     CGContextRef context = platformContext();
1003 
1004     if (m_state.strokeGradient) {
1005         if (hasShadow()) {
1006             const float doubleLineWidth = lineWidth * 2;
1007             const float layerWidth = ceilf(rect.width() + doubleLineWidth);
1008             const float layerHeight = ceilf(rect.height() + doubleLineWidth);
1009             CGLayerRef layer = CGLayerCreateWithContext(context, CGSizeMake(layerWidth, layerHeight), 0);
1010 
1011             CGContextRef layerContext = CGLayerGetContext(layer);
1012             m_state.strokeThickness = lineWidth;
1013             CGContextSetLineWidth(layerContext, lineWidth);
1014 
1015             // Compensate for the line width, otherwise the layer's top-left corner would be
1016             // aligned with the rect's top-left corner. This would result in leaving pixels out of
1017             // the layer on the left and top sides.
1018             const float translationX = lineWidth - rect.x();
1019             const float translationY = lineWidth - rect.y();
1020             CGContextTranslateCTM(layerContext, translationX, translationY);
1021 
1022             CGContextAddRect(layerContext, rect);
1023             CGContextReplacePathWithStrokedPath(layerContext);
1024             CGContextClip(layerContext);
1025             CGContextConcatCTM(layerContext, m_state.strokeGradient->gradientSpaceTransform());
1026             m_state.strokeGradient->paint(layerContext);
1027 
1028             const float destinationX = roundf(rect.x() - lineWidth);
1029             const float destinationY = roundf(rect.y() - lineWidth);
1030             CGContextDrawLayerAtPoint(context, CGPointMake(destinationX, destinationY), layer);
1031             CGLayerRelease(layer);
1032         } else {
1033             CGContextSaveGState(context);
1034             setStrokeThickness(lineWidth);
1035             CGContextAddRect(context, rect);
1036             CGContextReplacePathWithStrokedPath(context);
1037             CGContextClip(context);
1038             CGContextConcatCTM(context, m_state.strokeGradient->gradientSpaceTransform());
1039             m_state.strokeGradient->paint(this);
1040             CGContextRestoreGState(context);
1041         }
1042         return;
1043     }
1044 
1045     if (m_state.strokePattern)
1046         applyStrokePattern();
1047     CGContextStrokeRectWithWidth(context, rect, lineWidth);
1048 }
1049 
setLineCap(LineCap cap)1050 void GraphicsContext::setLineCap(LineCap cap)
1051 {
1052     if (paintingDisabled())
1053         return;
1054     switch (cap) {
1055     case ButtCap:
1056         CGContextSetLineCap(platformContext(), kCGLineCapButt);
1057         break;
1058     case RoundCap:
1059         CGContextSetLineCap(platformContext(), kCGLineCapRound);
1060         break;
1061     case SquareCap:
1062         CGContextSetLineCap(platformContext(), kCGLineCapSquare);
1063         break;
1064     }
1065 }
1066 
setLineDash(const DashArray & dashes,float dashOffset)1067 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
1068 {
1069     CGContextSetLineDash(platformContext(), dashOffset, dashes.data(), dashes.size());
1070 }
1071 
setLineJoin(LineJoin join)1072 void GraphicsContext::setLineJoin(LineJoin join)
1073 {
1074     if (paintingDisabled())
1075         return;
1076     switch (join) {
1077     case MiterJoin:
1078         CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
1079         break;
1080     case RoundJoin:
1081         CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
1082         break;
1083     case BevelJoin:
1084         CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
1085         break;
1086     }
1087 }
1088 
clip(const Path & path)1089 void GraphicsContext::clip(const Path& path)
1090 {
1091     if (paintingDisabled())
1092         return;
1093     CGContextRef context = platformContext();
1094 
1095     // CGContextClip does nothing if the path is empty, so in this case, we
1096     // instead clip against a zero rect to reduce the clipping region to
1097     // nothing - which is the intended behavior of clip() if the path is empty.
1098     if (path.isEmpty())
1099         CGContextClipToRect(context, CGRectZero);
1100     else {
1101         CGContextBeginPath(context);
1102         CGContextAddPath(context, path.platformPath());
1103         CGContextClip(context);
1104     }
1105     m_data->clip(path);
1106 }
1107 
canvasClip(const Path & path)1108 void GraphicsContext::canvasClip(const Path& path)
1109 {
1110     clip(path);
1111 }
1112 
clipOut(const Path & path)1113 void GraphicsContext::clipOut(const Path& path)
1114 {
1115     if (paintingDisabled())
1116         return;
1117 
1118     CGContextBeginPath(platformContext());
1119     CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
1120     CGContextAddPath(platformContext(), path.platformPath());
1121     CGContextEOClip(platformContext());
1122 }
1123 
scale(const FloatSize & size)1124 void GraphicsContext::scale(const FloatSize& size)
1125 {
1126     if (paintingDisabled())
1127         return;
1128     CGContextScaleCTM(platformContext(), size.width(), size.height());
1129     m_data->scale(size);
1130     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1131 }
1132 
rotate(float angle)1133 void GraphicsContext::rotate(float angle)
1134 {
1135     if (paintingDisabled())
1136         return;
1137     CGContextRotateCTM(platformContext(), angle);
1138     m_data->rotate(angle);
1139     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1140 }
1141 
translate(float x,float y)1142 void GraphicsContext::translate(float x, float y)
1143 {
1144     if (paintingDisabled())
1145         return;
1146     CGContextTranslateCTM(platformContext(), x, y);
1147     m_data->translate(x, y);
1148     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1149 }
1150 
concatCTM(const AffineTransform & transform)1151 void GraphicsContext::concatCTM(const AffineTransform& transform)
1152 {
1153     if (paintingDisabled())
1154         return;
1155     CGContextConcatCTM(platformContext(), transform);
1156     m_data->concatCTM(transform);
1157     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1158 }
1159 
setCTM(const AffineTransform & transform)1160 void GraphicsContext::setCTM(const AffineTransform& transform)
1161 {
1162     if (paintingDisabled())
1163         return;
1164     CGContextSetCTM(platformContext(), transform);
1165     m_data->setCTM(transform);
1166     m_data->m_userToDeviceTransformKnownToBeIdentity = false;
1167 }
1168 
getCTM() const1169 AffineTransform GraphicsContext::getCTM() const
1170 {
1171     CGAffineTransform t = CGContextGetCTM(platformContext());
1172     return AffineTransform(t.a, t.b, t.c, t.d, t.tx, t.ty);
1173 }
1174 
roundToDevicePixels(const FloatRect & rect,RoundingMode roundingMode)1175 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode roundingMode)
1176 {
1177 #if PLATFORM(CHROMIUM)
1178     return rect;
1179 #else
1180     // It is not enough just to round to pixels in device space. The rotation part of the
1181     // affine transform matrix to device space can mess with this conversion if we have a
1182     // rotating image like the hands of the world clock widget. We just need the scale, so
1183     // we get the affine transform matrix and extract the scale.
1184 
1185     if (m_data->m_userToDeviceTransformKnownToBeIdentity)
1186         return rect;
1187 
1188     CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
1189     if (CGAffineTransformIsIdentity(deviceMatrix)) {
1190         m_data->m_userToDeviceTransformKnownToBeIdentity = true;
1191         return rect;
1192     }
1193 
1194     float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
1195     float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);
1196 
1197     CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
1198     CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
1199         (rect.y() + rect.height()) * deviceScaleY);
1200 
1201     deviceOrigin.x = roundf(deviceOrigin.x);
1202     deviceOrigin.y = roundf(deviceOrigin.y);
1203     if (roundingMode == RoundAllSides) {
1204         deviceLowerRight.x = roundf(deviceLowerRight.x);
1205         deviceLowerRight.y = roundf(deviceLowerRight.y);
1206     } else {
1207         deviceLowerRight.x = deviceOrigin.x + roundf(rect.width() * deviceScaleX);
1208         deviceLowerRight.y = deviceOrigin.y + roundf(rect.height() * deviceScaleY);
1209     }
1210 
1211     // Don't let the height or width round to 0 unless either was originally 0
1212     if (deviceOrigin.y == deviceLowerRight.y && rect.height())
1213         deviceLowerRight.y += 1;
1214     if (deviceOrigin.x == deviceLowerRight.x && rect.width())
1215         deviceLowerRight.x += 1;
1216 
1217     FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
1218     FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
1219     return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
1220 #endif
1221 }
1222 
drawLineForText(const FloatPoint & point,float width,bool printing)1223 void GraphicsContext::drawLineForText(const FloatPoint& point, float width, bool printing)
1224 {
1225     if (paintingDisabled())
1226         return;
1227 
1228     if (width <= 0)
1229         return;
1230 
1231     float x = point.x();
1232     float y = point.y();
1233     float lineLength = width;
1234 
1235     // Use a minimum thickness of 0.5 in user space.
1236     // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
1237     float thickness = max(strokeThickness(), 0.5f);
1238 
1239     bool restoreAntialiasMode = false;
1240 
1241     if (!printing) {
1242         // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
1243         float adjustedThickness = max(thickness, 1.0f);
1244 
1245         // FIXME: This should be done a better way.
1246         // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
1247         // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
1248         // in device space will make the underlines too thick.
1249         CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness), RoundOriginAndDimensions);
1250         if (lineRect.size.height < thickness * 2.0) {
1251             x = lineRect.origin.x;
1252             y = lineRect.origin.y;
1253             lineLength = lineRect.size.width;
1254             thickness = lineRect.size.height;
1255             if (shouldAntialias()) {
1256                 CGContextSetShouldAntialias(platformContext(), false);
1257                 restoreAntialiasMode = true;
1258             }
1259         }
1260     }
1261 
1262     if (fillColor() != strokeColor())
1263         setCGFillColor(platformContext(), strokeColor(), strokeColorSpace());
1264     CGContextFillRect(platformContext(), CGRectMake(x, y, lineLength, thickness));
1265     if (fillColor() != strokeColor())
1266         setCGFillColor(platformContext(), fillColor(), fillColorSpace());
1267 
1268     if (restoreAntialiasMode)
1269         CGContextSetShouldAntialias(platformContext(), true);
1270 }
1271 
setURLForRect(const KURL & link,const IntRect & destRect)1272 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
1273 {
1274     if (paintingDisabled())
1275         return;
1276 
1277     RetainPtr<CFURLRef> urlRef(AdoptCF, link.createCFURL());
1278     if (!urlRef)
1279         return;
1280 
1281     CGContextRef context = platformContext();
1282 
1283     // Get the bounding box to handle clipping.
1284     CGRect box = CGContextGetClipBoundingBox(context);
1285 
1286     IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1287     IntRect rect = destRect;
1288     rect.intersect(intBox);
1289 
1290     CGPDFContextSetURLForRect(context, urlRef.get(),
1291         CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1292 }
1293 
setImageInterpolationQuality(InterpolationQuality mode)1294 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1295 {
1296     if (paintingDisabled())
1297         return;
1298 
1299     CGInterpolationQuality quality = kCGInterpolationDefault;
1300     switch (mode) {
1301     case InterpolationDefault:
1302         quality = kCGInterpolationDefault;
1303         break;
1304     case InterpolationNone:
1305         quality = kCGInterpolationNone;
1306         break;
1307     case InterpolationLow:
1308         quality = kCGInterpolationLow;
1309         break;
1310 
1311     // Fall through to InterpolationHigh if kCGInterpolationMedium is not usable.
1312     case InterpolationMedium:
1313 #if USE(CG_INTERPOLATION_MEDIUM)
1314         quality = kCGInterpolationMedium;
1315         break;
1316 #endif
1317     case InterpolationHigh:
1318         quality = kCGInterpolationHigh;
1319         break;
1320     }
1321     CGContextSetInterpolationQuality(platformContext(), quality);
1322 }
1323 
imageInterpolationQuality() const1324 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1325 {
1326     if (paintingDisabled())
1327         return InterpolationDefault;
1328 
1329     CGInterpolationQuality quality = CGContextGetInterpolationQuality(platformContext());
1330     switch (quality) {
1331     case kCGInterpolationDefault:
1332         return InterpolationDefault;
1333     case kCGInterpolationNone:
1334         return InterpolationNone;
1335     case kCGInterpolationLow:
1336         return InterpolationLow;
1337 #if HAVE(CG_INTERPOLATION_MEDIUM)
1338     // kCGInterpolationMedium is known to be present in the CGInterpolationQuality enum.
1339     case kCGInterpolationMedium:
1340 #if USE(CG_INTERPOLATION_MEDIUM)
1341         // Only map to InterpolationMedium if targeting a system that understands it.
1342         return InterpolationMedium;
1343 #else
1344         return InterpolationDefault;
1345 #endif  // USE(CG_INTERPOLATION_MEDIUM)
1346 #endif  // HAVE(CG_INTERPOLATION_MEDIUM)
1347     case kCGInterpolationHigh:
1348         return InterpolationHigh;
1349     }
1350     return InterpolationDefault;
1351 }
1352 
setAllowsFontSmoothing(bool allowsFontSmoothing)1353 void GraphicsContext::setAllowsFontSmoothing(bool allowsFontSmoothing)
1354 {
1355     UNUSED_PARAM(allowsFontSmoothing);
1356 #if !defined(BUILDING_ON_LEOPARD)
1357     CGContextRef context = platformContext();
1358     CGContextSetAllowsFontSmoothing(context, allowsFontSmoothing);
1359 #endif
1360 }
1361 
setIsCALayerContext(bool isLayerContext)1362 void GraphicsContext::setIsCALayerContext(bool isLayerContext)
1363 {
1364     if (isLayerContext)
1365         m_data->m_contextFlags |= IsLayerCGContext;
1366     else
1367         m_data->m_contextFlags &= ~IsLayerCGContext;
1368 }
1369 
isCALayerContext() const1370 bool GraphicsContext::isCALayerContext() const
1371 {
1372     return m_data->m_contextFlags & IsLayerCGContext;
1373 }
1374 
setIsAcceleratedContext(bool isAccelerated)1375 void GraphicsContext::setIsAcceleratedContext(bool isAccelerated)
1376 {
1377     if (isAccelerated)
1378         m_data->m_contextFlags |= IsAcceleratedCGContext;
1379     else
1380         m_data->m_contextFlags &= ~IsAcceleratedCGContext;
1381 }
1382 
isAcceleratedContext() const1383 bool GraphicsContext::isAcceleratedContext() const
1384 {
1385     return m_data->m_contextFlags & IsAcceleratedCGContext;
1386 }
1387 
setPlatformTextDrawingMode(TextDrawingModeFlags mode)1388 void GraphicsContext::setPlatformTextDrawingMode(TextDrawingModeFlags mode)
1389 {
1390     if (paintingDisabled())
1391         return;
1392 
1393     // Wow, wish CG had used bits here.
1394     CGContextRef context = platformContext();
1395     switch (mode) {
1396     case TextModeInvisible:
1397         CGContextSetTextDrawingMode(context, kCGTextInvisible);
1398         break;
1399     case TextModeFill:
1400         CGContextSetTextDrawingMode(context, kCGTextFill);
1401         break;
1402     case TextModeStroke:
1403         CGContextSetTextDrawingMode(context, kCGTextStroke);
1404         break;
1405     case TextModeFill | TextModeStroke:
1406         CGContextSetTextDrawingMode(context, kCGTextFillStroke);
1407         break;
1408     case TextModeClip:
1409         CGContextSetTextDrawingMode(context, kCGTextClip);
1410         break;
1411     case TextModeFill | TextModeClip:
1412         CGContextSetTextDrawingMode(context, kCGTextFillClip);
1413         break;
1414     case TextModeStroke | TextModeClip:
1415         CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
1416         break;
1417     case TextModeFill | TextModeStroke | TextModeClip:
1418         CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
1419         break;
1420     default:
1421         break;
1422     }
1423 }
1424 
setPlatformStrokeColor(const Color & color,ColorSpace colorSpace)1425 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
1426 {
1427     if (paintingDisabled())
1428         return;
1429     setCGStrokeColor(platformContext(), color, colorSpace);
1430 }
1431 
setPlatformStrokeThickness(float thickness)1432 void GraphicsContext::setPlatformStrokeThickness(float thickness)
1433 {
1434     if (paintingDisabled())
1435         return;
1436     CGContextSetLineWidth(platformContext(), thickness);
1437 }
1438 
setPlatformFillColor(const Color & color,ColorSpace colorSpace)1439 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
1440 {
1441     if (paintingDisabled())
1442         return;
1443     setCGFillColor(platformContext(), color, colorSpace);
1444 }
1445 
setPlatformShouldAntialias(bool enable)1446 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1447 {
1448     if (paintingDisabled())
1449         return;
1450     CGContextSetShouldAntialias(platformContext(), enable);
1451 }
1452 
setPlatformShouldSmoothFonts(bool enable)1453 void GraphicsContext::setPlatformShouldSmoothFonts(bool enable)
1454 {
1455     if (paintingDisabled())
1456         return;
1457     CGContextSetShouldSmoothFonts(platformContext(), enable);
1458 }
1459 
setPlatformCompositeOperation(CompositeOperator mode)1460 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode)
1461 {
1462     if (paintingDisabled())
1463         return;
1464 
1465     CGBlendMode target = kCGBlendModeNormal;
1466     switch (mode) {
1467     case CompositeClear:
1468         target = kCGBlendModeClear;
1469         break;
1470     case CompositeCopy:
1471         target = kCGBlendModeCopy;
1472         break;
1473     case CompositeSourceOver:
1474         //kCGBlendModeNormal
1475         break;
1476     case CompositeSourceIn:
1477         target = kCGBlendModeSourceIn;
1478         break;
1479     case CompositeSourceOut:
1480         target = kCGBlendModeSourceOut;
1481         break;
1482     case CompositeSourceAtop:
1483         target = kCGBlendModeSourceAtop;
1484         break;
1485     case CompositeDestinationOver:
1486         target = kCGBlendModeDestinationOver;
1487         break;
1488     case CompositeDestinationIn:
1489         target = kCGBlendModeDestinationIn;
1490         break;
1491     case CompositeDestinationOut:
1492         target = kCGBlendModeDestinationOut;
1493         break;
1494     case CompositeDestinationAtop:
1495         target = kCGBlendModeDestinationAtop;
1496         break;
1497     case CompositeXOR:
1498         target = kCGBlendModeXOR;
1499         break;
1500     case CompositePlusDarker:
1501         target = kCGBlendModePlusDarker;
1502         break;
1503     case CompositeHighlight:
1504         // currently unsupported
1505         break;
1506     case CompositePlusLighter:
1507         target = kCGBlendModePlusLighter;
1508         break;
1509     }
1510     CGContextSetBlendMode(platformContext(), target);
1511 }
1512 
1513 }
1514