GraphicsContextCG.cpp 26.7 KB
Newer Older
darin's avatar
darin committed
1
/*
darin's avatar
darin committed
2
 * Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
darin's avatar
darin committed
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

26
#define _USE_MATH_DEFINES 1
darin's avatar
darin committed
27
28
29
#include "config.h"
#include "GraphicsContext.h"

30
31
#if PLATFORM(CG)

32
#include "AffineTransform.h"
33
#include "KURL.h"
darin's avatar
darin committed
34
#include "Path.h"
ggaren's avatar
ggaren committed
35
#include <wtf/MathExtras.h>
36

hyatt's avatar
hyatt committed
37
#include <GraphicsContextPlatformPrivate.h> // FIXME: Temporary.
darin's avatar
darin committed
38
39
40
41
42

using namespace std;

namespace WebCore {

43
44
45
46
47
48
49
50
51
52
53
54
55
56
static void setCGFillColor(CGContextRef context, const Color& color)
{
    CGFloat red, green, blue, alpha;
    color.getRGBA(red, green, blue, alpha);
    CGContextSetRGBFillColor(context, red, green, blue, alpha);
}

static void setCGStrokeColor(CGContextRef context, const Color& color)
{
    CGFloat red, green, blue, alpha;
    color.getRGBA(red, green, blue, alpha);
    CGContextSetRGBStrokeColor(context, red, green, blue, alpha);
}

57
58
59
60
61
GraphicsContext::GraphicsContext(CGContextRef cgContext)
    : m_common(createGraphicsContextPrivate())
    , m_data(new GraphicsContextPlatformPrivate(cgContext))
{
    setPaintingDisabled(!cgContext);
62
63
64
65
66
    if (cgContext) {
        // Make sure the context starts in sync with our state.
        setPlatformFillColor(fillColor());
        setPlatformStrokeColor(strokeColor());
    }
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
}

GraphicsContext::~GraphicsContext()
{
    destroyGraphicsContextPrivate(m_common);
    delete m_data;
}

void GraphicsContext::setFocusRingClip(const IntRect& r)
{
    // This method only exists to work around bugs in Mac focus ring clipping.
    m_data->m_focusRingClip = r;
}

void GraphicsContext::clearFocusRingClip()
{
    // This method only exists to work around bugs in Mac focus ring clipping.
    m_data->m_focusRingClip = IntRect();
}

CGContextRef GraphicsContext::platformContext() const
{
    ASSERT(!paintingDisabled());
    ASSERT(m_data->m_cgContext);
    return m_data->m_cgContext;
}
darin's avatar
darin committed
93
94
95

void GraphicsContext::savePlatformState()
{
hyatt's avatar
hyatt committed
96
97
    // Note: Do not use this function within this class implementation, since we want to avoid the extra
    // save of the secondary context (in GraphicsContextPlatformPrivate.h).
darin's avatar
darin committed
98
    CGContextSaveGState(platformContext());
hyatt's avatar
hyatt committed
99
    m_data->save();
darin's avatar
darin committed
100
101
102
103
}

void GraphicsContext::restorePlatformState()
{
hyatt's avatar
hyatt committed
104
105
    // Note: Do not use this function within this class implementation, since we want to avoid the extra
    // restore of the secondary context (in GraphicsContextPlatformPrivate.h).
darin's avatar
darin committed
106
    CGContextRestoreGState(platformContext());
hyatt's avatar
hyatt committed
107
    m_data->restore();
darin's avatar
darin committed
108
109
110
111
112
113
114
115
116
117
}

// Draws a filled rectangle with a stroked border.
void GraphicsContext::drawRect(const IntRect& rect)
{
    if (paintingDisabled())
        return;

    CGContextRef context = platformContext();

118
    if (fillColor().alpha())
darin's avatar
darin committed
119
120
        CGContextFillRect(context, rect);

121
122
123
124
125
    if (strokeStyle() != NoStroke && strokeColor().alpha()) {
        // We do a fill of four rects to simulate the stroke of a border.
        Color oldFillColor = fillColor();
        if (oldFillColor != strokeColor())
            setCGFillColor(context, strokeColor());
darin's avatar
darin committed
126
127
128
129
130
131
132
        CGRect rects[4] = {
            FloatRect(rect.x(), rect.y(), rect.width(), 1),
            FloatRect(rect.x(), rect.bottom() - 1, rect.width(), 1),
            FloatRect(rect.x(), rect.y() + 1, 1, rect.height() - 2),
            FloatRect(rect.right() - 1, rect.y() + 1, 1, rect.height() - 2)
        };
        CGContextFillRects(context, rects, 4);
133
134
        if (oldFillColor != strokeColor())
            setCGFillColor(context, oldFillColor);
darin's avatar
darin committed
135
136
137
138
139
140
141
142
143
    }
}

// This is only used to draw borders.
void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
{
    if (paintingDisabled())
        return;

144
    if (strokeStyle() == NoStroke || !strokeColor().alpha())
darin's avatar
darin committed
145
        return;
146

147
    float width = strokeThickness();
darin's avatar
darin committed
148
149
150
151
152
153
154
155
156

    FloatPoint p1 = point1;
    FloatPoint p2 = point2;
    bool isVerticalLine = (p1.x() == p2.x());
    
    // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
    // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
    // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
    // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
157
    if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) {
darin's avatar
darin committed
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
        if (isVerticalLine) {
            p1.move(0, width);
            p2.move(0, -width);
        } else {
            p1.move(width, 0);
            p2.move(-width, 0);
        }
    }
    
    if (((int)width) % 2) {
        if (isVerticalLine) {
            // We're a vertical line.  Adjust our x.
            p1.move(0.5, 0);
            p2.move(0.5, 0);
        } else {
            // We're a horizontal line. Adjust our y.
            p1.move(0, 0.5);
            p2.move(0, 0.5);
        }
    }
    
    int patWidth = 0;
180
181
182
    switch (strokeStyle()) {
        case NoStroke:
        case SolidStroke:
darin's avatar
darin committed
183
            break;
184
        case DottedStroke:
darin's avatar
darin committed
185
186
            patWidth = (int)width;
            break;
187
        case DashedStroke:
darin's avatar
darin committed
188
189
190
191
            patWidth = 3 * (int)width;
            break;
    }

192
    CGContextRef context = platformContext();
193
    CGContextSaveGState(context);
darin's avatar
darin committed
194
195

    CGContextSetShouldAntialias(context, false);
darin's avatar
darin committed
196

darin's avatar
darin committed
197
198
199
    if (patWidth) {
        // Do a rect fill of our endpoints.  This ensures we always have the
        // appearance of being a border.  We then draw the actual dotted/dashed line.
200
        setCGFillColor(context, strokeColor());  // The save/restore make it safe to mutate the fill color here without setting it back to the old color.
darin's avatar
darin committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
        if (isVerticalLine) {
            CGContextFillRect(context, FloatRect(p1.x() - width / 2, p1.y() - width, width, width));
            CGContextFillRect(context, FloatRect(p2.x() - width / 2, p2.y(), width, width));
        } else {
            CGContextFillRect(context, FloatRect(p1.x() - width, p1.y() - width / 2, width, width));
            CGContextFillRect(context, FloatRect(p2.x(), p2.y() - width / 2, width, width));
        }

        // Example: 80 pixels with a width of 30 pixels.
        // Remainder is 20.  The maximum pixels of line we could paint
        // will be 50 pixels.
        int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
        int remainder = distance % patWidth;
        int coverage = distance - remainder;
        int numSegments = coverage / patWidth;

        float patternOffset = 0;
        // Special case 1px dotted borders for speed.
        if (patWidth == 1)
            patternOffset = 1.0;
        else {
            bool evenNumberOfSegments = numSegments % 2 == 0;
            if (remainder)
                evenNumberOfSegments = !evenNumberOfSegments;
            if (evenNumberOfSegments) {
                if (remainder) {
                    patternOffset += patWidth - remainder;
                    patternOffset += remainder / 2;
                } else
                    patternOffset = patWidth / 2;
            } else {
                if (remainder)
                    patternOffset = (patWidth - remainder)/2;
            }
        }
        
        const CGFloat dottedLine[2] = { patWidth, patWidth };
        CGContextSetLineDash(context, patternOffset, dottedLine, 2);
    }

    CGContextBeginPath(context);
    CGContextMoveToPoint(context, p1.x(), p1.y());
    CGContextAddLineToPoint(context, p2.x(), p2.y());

    CGContextStrokePath(context);

247
    CGContextRestoreGState(context);
darin's avatar
darin committed
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
}

// This method is only used to draw the little circles used in lists.
void GraphicsContext::drawEllipse(const IntRect& rect)
{
    // FIXME: CG added CGContextAddEllipseinRect in Tiger, so we should be able to quite easily draw an ellipse.
    // This code can only handle circles, not ellipses. But khtml only
    // uses it for circles.
    ASSERT(rect.width() == rect.height());

    if (paintingDisabled())
        return;
        
    CGContextRef context = platformContext();
    CGContextBeginPath(context);
    float r = (float)rect.width() / 2;
thatcher's avatar
thatcher committed
264
    CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, 0);
darin's avatar
darin committed
265
266
267
    CGContextClosePath(context);

    if (fillColor().alpha()) {
268
        if (strokeStyle() != NoStroke)
darin's avatar
darin committed
269
270
            // stroke and fill
            CGContextDrawPath(context, kCGPathFillStroke);
271
        else
darin's avatar
darin committed
272
            CGContextFillPath(context);
273
    } else if (strokeStyle() != NoStroke)
darin's avatar
darin committed
274
275
276
277
        CGContextStrokePath(context);
}


278
void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
darin's avatar
darin committed
279
{ 
280
    if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f || !strokeColor().alpha())
darin's avatar
darin committed
281
282
        return;
    
bdakin's avatar
bdakin committed
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    CGContextRef context = platformContext();
    CGContextSaveGState(context);
    CGContextBeginPath(context);
    CGContextSetShouldAntialias(context, false);
    
    int x = rect.x();
    int y = rect.y();
    float w = (float)rect.width();
    float h = (float)rect.height();
    float scaleFactor = h / w;
    float reverseScaleFactor = w / h;
    
    if (w != h)
        scale(FloatSize(1, scaleFactor));
    
    float hRadius = w / 2;
    float vRadius = h / 2;
    float fa = startAngle;
    float falen =  fa + angleSpan;
    float start = -fa * M_PI/180;
    float end = -falen * M_PI/180;
    CGContextAddArc(context, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, start, end, true);

    if (w != h)
        scale(FloatSize(1, reverseScaleFactor));
    
309
    
310
    float width = strokeThickness();
311
312
313
314
315
316
317
318
319
320
321
322
    int patWidth = 0;
    
    switch (strokeStyle()) {
        case DottedStroke:
            patWidth = (int)(width / 2);
            break;
        case DashedStroke:
            patWidth = 3 * (int)(width / 2);
            break;
        default:
            break;
    }
bdakin's avatar
bdakin committed
323

324
325
326
327
328
329
330
331
332
333
334
    CGContextSaveGState(context);
    
    if (patWidth) {
        // Example: 80 pixels with a width of 30 pixels.
        // Remainder is 20.  The maximum pixels of line we could paint
        // will be 50 pixels.
        int distance;
        if (hRadius == vRadius)
            distance = (int)(M_PI * hRadius) / 2;
        else // We are elliptical and will have to estimate the distance
            distance = (int)(M_PI * sqrt((hRadius * hRadius + vRadius * vRadius) / 2)) / 2;
335
        
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
        int remainder = distance % patWidth;
        int coverage = distance - remainder;
        int numSegments = coverage / patWidth;

        float patternOffset = 0;
        // Special case 1px dotted borders for speed.
        if (patWidth == 1)
            patternOffset = 1.0;
        else {
            bool evenNumberOfSegments = numSegments % 2 == 0;
            if (remainder)
                evenNumberOfSegments = !evenNumberOfSegments;
            if (evenNumberOfSegments) {
                if (remainder) {
                    patternOffset += patWidth - remainder;
                    patternOffset += remainder / 2;
                } else
                    patternOffset = patWidth / 2;
            } else {
bdakin's avatar
bdakin committed
355
                if (remainder)
356
                    patternOffset = (patWidth - remainder) / 2;
bdakin's avatar
bdakin committed
357
358
359
            }
        }
    
360
361
        const CGFloat dottedLine[2] = { patWidth, patWidth };
        CGContextSetLineDash(context, patternOffset, dottedLine, 2);
darin's avatar
darin committed
362
    }
363
364
365

    CGContextStrokePath(context);
    CGContextRestoreGState(context);
bdakin's avatar
bdakin committed
366
367
    
    CGContextRestoreGState(context);
darin's avatar
darin committed
368
369
}

adele's avatar
adele committed
370
void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
darin's avatar
darin committed
371
{
adele's avatar
adele committed
372
    if (paintingDisabled() || !fillColor().alpha() && (strokeThickness() <= 0 || strokeStyle() == NoStroke))
darin's avatar
darin committed
373
374
375
376
377
378
379
380
381
        return;

    if (npoints <= 1)
        return;

    CGContextRef context = platformContext();

    CGContextSaveGState(context);

adele's avatar
adele committed
382
    CGContextSetShouldAntialias(context, shouldAntialias);
darin's avatar
darin committed
383
384
385
    
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, points[0].x(), points[0].y());
386
    for (size_t i = 1; i < npoints; i++)
darin's avatar
darin committed
387
388
389
        CGContextAddLineToPoint(context, points[i].x(), points[i].y());
    CGContextClosePath(context);

adele's avatar
adele committed
390
391
392
393
394
395
    if (fillColor().alpha()) {
        if (strokeStyle() != NoStroke)
            CGContextDrawPath(context, kCGPathEOFillStroke);
        else
            CGContextEOFillPath(context);
    } else
darin's avatar
darin committed
396
397
398
399
400
401
        CGContextStrokePath(context);

    CGContextRestoreGState(context);
}

void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
402
403
404
405
406
{
    if (paintingDisabled())
        return;
    if (color.alpha()) {
        CGContextRef context = platformContext();
407
408
409
        Color oldFillColor = fillColor();
        if (oldFillColor != color)
            setCGFillColor(context, color);
410
        CGContextFillRect(context, rect);
411
412
        if (oldFillColor != color)
            setCGFillColor(context, oldFillColor);
413
414
415
416
    }
}

void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
darin's avatar
darin committed
417
418
419
420
421
{
    if (paintingDisabled())
        return;
    if (color.alpha()) {
        CGContextRef context = platformContext();
422
423
424
        Color oldFillColor = fillColor();
        if (oldFillColor != color)
            setCGFillColor(context, color);
darin's avatar
darin committed
425
        CGContextFillRect(context, rect);
426
427
        if (oldFillColor != color)
            setCGFillColor(context, oldFillColor);
darin's avatar
darin committed
428
429
430
    }
}

hyatt's avatar
hyatt committed
431
432
433
434
435
436
437
438
439
440
void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color)
{
    if (paintingDisabled() || !color.alpha())
        return;

    CGContextRef context = platformContext();
    Color oldFillColor = fillColor();
    if (oldFillColor != color)
        setCGFillColor(context, color);

441
    addPath(Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight));
hyatt's avatar
hyatt committed
442
    CGContextFillPath(context);
443

hyatt's avatar
hyatt committed
444
445
446
447
448
    if (oldFillColor != color)
        setCGFillColor(context, oldFillColor);
}


hyatt's avatar
hyatt committed
449
void GraphicsContext::clip(const IntRect& rect)
darin's avatar
darin committed
450
451
452
453
{
    if (paintingDisabled())
        return;
    CGContextClipToRect(platformContext(), rect);
hyatt's avatar
hyatt committed
454
    m_data->clip(rect);
darin's avatar
darin committed
455
456
}

hyatt's avatar
hyatt committed
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
void GraphicsContext::clipOut(const IntRect& rect)
{
    if (paintingDisabled())
        return;
        
    CGRect rects[2] = { CGContextGetClipBoundingBox(platformContext()), rect };
    CGContextBeginPath(platformContext());
    CGContextAddRects(platformContext(), rects, 2);
    CGContextEOClip(platformContext());
}

void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
{
    if (paintingDisabled())
        return;
        
    CGContextBeginPath(platformContext());
    CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
    CGContextAddEllipseInRect(platformContext(), rect);
    CGContextEOClip(platformContext());
}

bdakin's avatar
bdakin committed
479
480
481
482
483
void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
{
    if (paintingDisabled())
        return;

hyatt's avatar
hyatt committed
484
    clip(rect);
bdakin's avatar
bdakin committed
485
486
487
488
489
490
491
492
493
494
495
    CGContextRef context = platformContext();
    
    // Add outer ellipse
    CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), rect.width(), rect.height()));
    // Add inner ellipse.
    CGContextAddEllipseInRect(context, CGRectMake(rect.x() + thickness, rect.y() + thickness,
        rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
    
    CGContextEOClip(context);
}

darin's avatar
darin committed
496
497
498
499
500
501
502
503
void GraphicsContext::beginTransparencyLayer(float opacity)
{
    if (paintingDisabled())
        return;
    CGContextRef context = platformContext();
    CGContextSaveGState(context);
    CGContextSetAlpha(context, opacity);
    CGContextBeginTransparencyLayer(context, 0);
hyatt's avatar
hyatt committed
504
    m_data->beginTransparencyLayer();
darin's avatar
darin committed
505
506
507
508
509
510
511
512
513
}

void GraphicsContext::endTransparencyLayer()
{
    if (paintingDisabled())
        return;
    CGContextRef context = platformContext();
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
hyatt's avatar
hyatt committed
514
    m_data->endTransparencyLayer();
darin's avatar
darin committed
515
516
517
518
}

void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
{
mjs's avatar
mjs committed
519
520
521
    // Extreme "blur" values can make text drawing crash or take crazy long times, so clamp
    blur = min(blur, 1000);

darin's avatar
darin committed
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
    if (paintingDisabled())
        return;
    // Check for an invalid color, as this means that the color was not set for the shadow
    // and we should therefore just use the default shadow color.
    CGContextRef context = platformContext();
    if (!color.isValid())
        CGContextSetShadow(context, CGSizeMake(size.width(), -size.height()), blur); // y is flipped.
    else {
        CGColorRef colorCG = cgColor(color);
        CGContextSetShadowWithColor(context,
                                    CGSizeMake(size.width(), -size.height()), // y is flipped.
                                    blur, 
                                    colorCG);
        CGColorRelease(colorCG);
    }
}

void GraphicsContext::clearShadow()
{
    if (paintingDisabled())
        return;
darin's avatar
darin committed
543
    CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
darin's avatar
darin committed
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
}

void GraphicsContext::setMiterLimit(float limit)
{
    if (paintingDisabled())
        return;
    CGContextSetMiterLimit(platformContext(), limit);
}

void GraphicsContext::setAlpha(float alpha)
{
    if (paintingDisabled())
        return;
    CGContextSetAlpha(platformContext(), alpha);
}

void GraphicsContext::clearRect(const FloatRect& r)
{
    if (paintingDisabled())
        return;
    CGContextClearRect(platformContext(), r);
}

void GraphicsContext::strokeRect(const FloatRect& r, float lineWidth)
{
    if (paintingDisabled())
        return;
    CGContextStrokeRectWithWidth(platformContext(), r, lineWidth);
}

void GraphicsContext::setLineCap(LineCap cap)
{
    if (paintingDisabled())
        return;
    switch (cap) {
        case ButtCap:
            CGContextSetLineCap(platformContext(), kCGLineCapButt);
            break;
        case RoundCap:
            CGContextSetLineCap(platformContext(), kCGLineCapRound);
            break;
        case SquareCap:
            CGContextSetLineCap(platformContext(), kCGLineCapSquare);
            break;
    }
}

void GraphicsContext::setLineJoin(LineJoin join)
{
    if (paintingDisabled())
        return;
    switch (join) {
        case MiterJoin:
            CGContextSetLineJoin(platformContext(), kCGLineJoinMiter);
            break;
        case RoundJoin:
            CGContextSetLineJoin(platformContext(), kCGLineJoinRound);
            break;
        case BevelJoin:
            CGContextSetLineJoin(platformContext(), kCGLineJoinBevel);
            break;
    }
}
607
608
609
610
611
612
613
614
615
616
 
void GraphicsContext::beginPath()
{
    CGContextBeginPath(platformContext());
}

void GraphicsContext::addPath(const Path& path)
{
    CGContextAddPath(platformContext(), path.platformPath());
}
darin's avatar
darin committed
617
618
619
620
621
622
623
624
625

void GraphicsContext::clip(const Path& path)
{
    if (paintingDisabled())
        return;
    CGContextRef context = platformContext();
    CGContextBeginPath(context);
    CGContextAddPath(context, path.platformPath());
    CGContextClip(context);
hyatt's avatar
hyatt committed
626
    m_data->clip(path);
darin's avatar
darin committed
627
628
}

629
630
631
632
633
634
635
636
637
638
639
void GraphicsContext::clipOut(const Path& path)
{
    if (paintingDisabled())
        return;
        
    CGContextBeginPath(platformContext());
    CGContextAddRect(platformContext(), CGContextGetClipBoundingBox(platformContext()));
    CGContextAddPath(platformContext(), path.platformPath());
    CGContextEOClip(platformContext());
}

darin's avatar
darin committed
640
641
642
643
644
void GraphicsContext::scale(const FloatSize& size)
{
    if (paintingDisabled())
        return;
    CGContextScaleCTM(platformContext(), size.width(), size.height());
hyatt's avatar
hyatt committed
645
    m_data->scale(size);
darin's avatar
darin committed
646
647
648
649
650
651
652
}

void GraphicsContext::rotate(float angle)
{
    if (paintingDisabled())
        return;
    CGContextRotateCTM(platformContext(), angle);
hyatt's avatar
hyatt committed
653
    m_data->rotate(angle);
darin's avatar
darin committed
654
655
}

thatcher's avatar
thatcher committed
656
void GraphicsContext::translate(float x, float y)
darin's avatar
darin committed
657
658
659
{
    if (paintingDisabled())
        return;
ggaren's avatar
ggaren committed
660
    CGContextTranslateCTM(platformContext(), x, y);
hyatt's avatar
hyatt committed
661
    m_data->translate(x, y);
darin's avatar
darin committed
662
663
}

664
665
666
667
668
void GraphicsContext::concatCTM(const AffineTransform& transform)
{
    if (paintingDisabled())
        return;
    CGContextConcatCTM(platformContext(), transform);
hyatt's avatar
hyatt committed
669
    m_data->concatCTM(transform);
670
671
}

bdakin's avatar
bdakin committed
672
673
FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
{
bdakin's avatar
bdakin committed
674
675
676
677
678
    // It is not enough just to round to pixels in device space. The rotation part of the 
    // affine transform matrix to device space can mess with this conversion if we have a
    // rotating image like the hands of the world clock widget. We just need the scale, so 
    // we get the affine transform matrix and extract the scale.
    CGAffineTransform deviceMatrix = CGContextGetUserSpaceToDeviceSpaceTransform(platformContext());
679
680
681
    if (CGAffineTransformIsIdentity(deviceMatrix))
        return rect;

bdakin's avatar
bdakin committed
682
683
684
685
686
687
    float deviceScaleX = sqrtf(deviceMatrix.a * deviceMatrix.a + deviceMatrix.b * deviceMatrix.b);
    float deviceScaleY = sqrtf(deviceMatrix.c * deviceMatrix.c + deviceMatrix.d * deviceMatrix.d);

    CGPoint deviceOrigin = CGPointMake(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
    CGPoint deviceLowerRight = CGPointMake((rect.x() + rect.width()) * deviceScaleX,
        (rect.y() + rect.height()) * deviceScaleY);
mjs's avatar
mjs committed
688
689
690
691
692

    deviceOrigin.x = roundf(deviceOrigin.x);
    deviceOrigin.y = roundf(deviceOrigin.y);
    deviceLowerRight.x = roundf(deviceLowerRight.x);
    deviceLowerRight.y = roundf(deviceLowerRight.y);
bdakin's avatar
bdakin committed
693
694
    
    // Don't let the height or width round to 0 unless either was originally 0
mjs's avatar
mjs committed
695
696
697
698
699
    if (deviceOrigin.y == deviceLowerRight.y && rect.height() != 0)
        deviceLowerRight.y += 1;
    if (deviceOrigin.x == deviceLowerRight.x && rect.width() != 0)
        deviceLowerRight.x += 1;

bdakin's avatar
bdakin committed
700
701
    FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
    FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
mjs's avatar
mjs committed
702
    return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
bdakin's avatar
bdakin committed
703
704
}

darin's avatar
darin committed
705
void GraphicsContext::drawLineForText(const IntPoint& point, int width, bool printing)
706
707
708
{
    if (paintingDisabled())
        return;
bdakin's avatar
bdakin committed
709
710
711

    CGContextSaveGState(platformContext());

712
    float x = point.x();
darin's avatar
darin committed
713
    float y = point.y();
bdakin's avatar
bdakin committed
714
    float lineLength = width;
715

bdakin's avatar
bdakin committed
716
717
718
    // Use a minimum thickness of 0.5 in user space.
    // See http://bugs.webkit.org/show_bug.cgi?id=4255 for details of why 0.5 is the right minimum thickness to use.
    float thickness = max(strokeThickness(), 0.5f);
719

bdakin's avatar
bdakin committed
720
    if (!printing) {
721
        // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
bdakin's avatar
bdakin committed
722
723
724
725
726
727
728
729
730
731
732
733
734
735
        float adjustedThickness = max(thickness, 1.0f);

        // FIXME: This should be done a better way.
        // We try to round all parameters to integer boundaries in device space. If rounding pixels in device space
        // makes our thickness more than double, then there must be a shrinking-scale factor and rounding to pixels
        // in device space will make the underlines too thick.
        CGRect lineRect = roundToDevicePixels(FloatRect(x, y, lineLength, adjustedThickness));
        if (lineRect.size.height < thickness * 2.0) {
            x = lineRect.origin.x;
            y = lineRect.origin.y;
            lineLength = lineRect.size.width;
            thickness = lineRect.size.height;
            CGContextSetShouldAntialias(platformContext(), false);
        }
736
737
738
739
740
741
    }
    
    CGContextSetLineWidth(platformContext(), thickness);

    float halfThickness = thickness / 2;

bdakin's avatar
bdakin committed
742
    // FIXME: How about using a rectangle fill instead of drawing a line?
743
744
745
    CGPoint linePoints[2];
    linePoints[0].x = x + halfThickness;
    linePoints[0].y = y + halfThickness;
bdakin's avatar
bdakin committed
746
    linePoints[1].x = x + lineLength - halfThickness;
747
748
749
750
751
752
    linePoints[1].y = y + halfThickness;
    CGContextStrokeLineSegments(platformContext(), linePoints, 2);

    CGContextRestoreGState(platformContext());
}

753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
{
    if (paintingDisabled())
        return;
        
    CFURLRef urlRef = link.createCFURL();
    if (urlRef) {
        CGContextRef context = platformContext();
        
        // Get the bounding box to handle clipping.
        CGRect box = CGContextGetClipBoundingBox(context);

        IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
        IntRect rect = destRect;
        rect.intersect(intBox);

        CGPDFContextSetURLForRect(context, urlRef,
            CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));

        CFRelease(urlRef);
    }
}

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
void GraphicsContext::setPlatformTextDrawingMode(int mode)
{
    if (paintingDisabled())
        return;

    // Wow, wish CG had used bits here.
    CGContextRef context = platformContext();
    switch (mode) {
        case cTextInvisible: // Invisible
            CGContextSetTextDrawingMode(context, kCGTextInvisible);
            break;
        case cTextFill: // Fill
            CGContextSetTextDrawingMode(context, kCGTextFill);
            break;
        case cTextStroke: // Stroke
            CGContextSetTextDrawingMode(context, kCGTextStroke);
            break;
        case 3: // Fill | Stroke
            CGContextSetTextDrawingMode(context, kCGTextFillStroke);
            break;
        case cTextClip: // Clip
            CGContextSetTextDrawingMode(context, kCGTextClip);
            break;
        case 5: // Fill | Clip
            CGContextSetTextDrawingMode(context, kCGTextFillClip);
            break;
        case 6: // Stroke | Clip
            CGContextSetTextDrawingMode(context, kCGTextStrokeClip);
            break;
        case 7: // Fill | Stroke | Clip
            CGContextSetTextDrawingMode(context, kCGTextFillStrokeClip);
            break;
        default:
            break;
    }
}

813
814
815
816
817
818
819
void GraphicsContext::setPlatformStrokeColor(const Color& color)
{
    if (paintingDisabled())
        return;
    setCGStrokeColor(platformContext(), color);
}

820
821
822
823
824
825
826
void GraphicsContext::setPlatformStrokeThickness(float thickness)
{
    if (paintingDisabled())
        return;
    CGContextSetLineWidth(platformContext(), thickness);
}

827
828
829
830
831
832
833
void GraphicsContext::setPlatformFillColor(const Color& color)
{
    if (paintingDisabled())
        return;
    setCGFillColor(platformContext(), color);
}

darin's avatar
darin committed
834
}
835
836

#endif // PLATFORM(CG)