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 #if !defined(BUILDING_ON_TIGER) && !defined(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 #if !defined(TARGETING_TIGER) && !defined(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) || defined(BUILDING_ON_TIGER)
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) || defined(BUILDING_ON_TIGER)
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(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(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(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(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_TIGER) && !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
1460 #ifndef BUILDING_ON_TIGER // Tiger's setPlatformCompositeOperation() is defined in GraphicsContextMac.mm.
setPlatformCompositeOperation(CompositeOperator mode)1461 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator mode)
1462 {
1463 if (paintingDisabled())
1464 return;
1465
1466 CGBlendMode target = kCGBlendModeNormal;
1467 switch (mode) {
1468 case CompositeClear:
1469 target = kCGBlendModeClear;
1470 break;
1471 case CompositeCopy:
1472 target = kCGBlendModeCopy;
1473 break;
1474 case CompositeSourceOver:
1475 //kCGBlendModeNormal
1476 break;
1477 case CompositeSourceIn:
1478 target = kCGBlendModeSourceIn;
1479 break;
1480 case CompositeSourceOut:
1481 target = kCGBlendModeSourceOut;
1482 break;
1483 case CompositeSourceAtop:
1484 target = kCGBlendModeSourceAtop;
1485 break;
1486 case CompositeDestinationOver:
1487 target = kCGBlendModeDestinationOver;
1488 break;
1489 case CompositeDestinationIn:
1490 target = kCGBlendModeDestinationIn;
1491 break;
1492 case CompositeDestinationOut:
1493 target = kCGBlendModeDestinationOut;
1494 break;
1495 case CompositeDestinationAtop:
1496 target = kCGBlendModeDestinationAtop;
1497 break;
1498 case CompositeXOR:
1499 target = kCGBlendModeXOR;
1500 break;
1501 case CompositePlusDarker:
1502 target = kCGBlendModePlusDarker;
1503 break;
1504 case CompositeHighlight:
1505 // currently unsupported
1506 break;
1507 case CompositePlusLighter:
1508 target = kCGBlendModePlusLighter;
1509 break;
1510 }
1511 CGContextSetBlendMode(platformContext(), target);
1512 }
1513 #endif
1514
1515 }
1516