GraphicsContextCG.cpp 27.6 KB
Newer Older
darin's avatar
darin committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
 * Copyright (C) 2003, 2004, 2005, 2006 Apple Computer, Inc.  All rights reserved.
 *
 * 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
37
38
39
40

#ifdef SVG_SUPPORT
#include "KRenderingDeviceQuartz.h"
#endif

hyatt's avatar
hyatt committed
41
#include <GraphicsContextPlatformPrivate.h> // FIXME: Temporary.
darin's avatar
darin committed
42
43
44
45
46

using namespace std;

namespace WebCore {

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
GraphicsContext::GraphicsContext(CGContextRef cgContext)
    : m_common(createGraphicsContextPrivate())
    , m_data(new GraphicsContextPlatformPrivate(cgContext))
{
    setPaintingDisabled(!cgContext);
}

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
78

darin's avatar
darin committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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);
}

darin's avatar
darin committed
93
94
void GraphicsContext::savePlatformState()
{
hyatt's avatar
hyatt committed
95
96
    // 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
97
    CGContextSaveGState(platformContext());
hyatt's avatar
hyatt committed
98
    m_data->save();
darin's avatar
darin committed
99
100
101
102
}

void GraphicsContext::restorePlatformState()
{
hyatt's avatar
hyatt committed
103
104
    // 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
105
    CGContextRestoreGState(platformContext());
hyatt's avatar
hyatt committed
106
    m_data->restore();
darin's avatar
darin committed
107
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();

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

122
    if (pen().style() != Pen::NoPen) {
darin's avatar
darin committed
123
        setCGFillColor(context, pen().color());
darin's avatar
darin committed
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
        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);
    }
}

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

    Pen::PenStyle penStyle = pen().style();
141
    if (penStyle == Pen::NoPen)
darin's avatar
darin committed
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
        return;
    float width = pen().width();
    if (width < 1)
        width = 1;

    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.
    if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
        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;
    switch (penStyle) {
        case Pen::NoPen:
        case Pen::SolidLine:
            break;
        case Pen::DotLine:
            patWidth = (int)width;
            break;
        case Pen::DashLine:
            patWidth = 3 * (int)width;
            break;
    }

    CGContextRef context = platformContext();

    CGContextSaveGState(context);

darin's avatar
darin committed
194
    setCGStrokeColor(context, pen().color());
darin's avatar
darin committed
195
196

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

darin's avatar
darin committed
198
199
200
    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.
darin's avatar
darin committed
201
        setCGFillColor(context, pen().color());
darin's avatar
darin committed
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        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);
    }
    
    CGContextSetLineWidth(context, width);

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

    CGContextStrokePath(context);

    CGContextRestoreGState(context);
}

// 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;
    CGContextAddArc(context, rect.x() + r, rect.y() + r, r, 0, 2*M_PI, true);
    CGContextClosePath(context);

    if (fillColor().alpha()) {
darin's avatar
darin committed
271
        setCGFillColor(context, fillColor());
darin's avatar
darin committed
272
273
        if (pen().style() != Pen::NoPen) {
            // stroke and fill
darin's avatar
darin committed
274
            setCGStrokeColor(context, pen().color());
darin's avatar
darin committed
275
276
277
278
279
280
281
282
            unsigned penWidth = pen().width();
            if (penWidth == 0) 
                penWidth++;
            CGContextSetLineWidth(context, penWidth);
            CGContextDrawPath(context, kCGPathFillStroke);
        } else
            CGContextFillPath(context);
    } else if (pen().style() != Pen::NoPen) {
darin's avatar
darin committed
283
        setCGStrokeColor(context, pen().color());
darin's avatar
darin committed
284
285
286
287
288
289
290
291
292
        unsigned penWidth = pen().width();
        if (penWidth == 0) 
            penWidth++;
        CGContextSetLineWidth(context, penWidth);
        CGContextStrokePath(context);
    }
}


bdakin's avatar
bdakin committed
293
void GraphicsContext::drawArc(const IntRect& rect, float thickness, int startAngle, int angleSpan)
darin's avatar
darin committed
294
295
296
297
{ 
    if (paintingDisabled())
        return;
    
bdakin's avatar
bdakin committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    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));
    
    if (pen().style() == Pen::NoPen) {
        setCGStrokeColor(context, fillColor());
        CGContextSetLineWidth(context, thickness);
        CGContextStrokePath(context);
    } else {
        Pen::PenStyle penStyle = pen().style();
        float width = pen().width();
        if (width < 1)
            width = 1;
        int patWidth = 0;
darin's avatar
darin committed
334
        
bdakin's avatar
bdakin committed
335
336
337
338
339
340
341
342
343
344
345
346
347
        switch (penStyle) {
            case Pen::NoPen:
            case Pen::SolidLine:
                break;
            case Pen::DotLine:
                patWidth = (int)(width / 2);
                break;
            case Pen::DashLine:
                patWidth = 3 * (int)(width / 2);
                break;
        }

        CGContextSaveGState(context);
darin's avatar
darin committed
348
        setCGStrokeColor(context, pen().color());
bdakin's avatar
bdakin committed
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388

        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;
            
            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);
        }
    
        CGContextSetLineWidth(context, width);
darin's avatar
darin committed
389
        CGContextStrokePath(context);
bdakin's avatar
bdakin committed
390
        CGContextRestoreGState(context);
darin's avatar
darin committed
391
    }
bdakin's avatar
bdakin committed
392
393
    
    CGContextRestoreGState(context);
darin's avatar
darin committed
394
395
}

adele's avatar
adele committed
396
void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
darin's avatar
darin committed
397
398
399
400
401
402
403
404
405
406
407
{
    if (paintingDisabled())
        return;

    if (npoints <= 1)
        return;

    CGContextRef context = platformContext();

    CGContextSaveGState(context);

adele's avatar
adele committed
408
    CGContextSetShouldAntialias(context, shouldAntialias);
darin's avatar
darin committed
409
410
411
    
    CGContextBeginPath(context);
    CGContextMoveToPoint(context, points[0].x(), points[0].y());
412
    for (size_t i = 1; i < npoints; i++)
darin's avatar
darin committed
413
414
415
416
        CGContextAddLineToPoint(context, points[i].x(), points[i].y());
    CGContextClosePath(context);

    if (fillColor().alpha()) {
darin's avatar
darin committed
417
        setCGFillColor(context, fillColor());
darin's avatar
darin committed
418
419
420
421
        CGContextEOFillPath(context);
    }

    if (pen().style() != Pen::NoPen) {
darin's avatar
darin committed
422
        setCGStrokeColor(context, pen().color());
darin's avatar
darin committed
423
424
425
426
427
428
429
430
        CGContextSetLineWidth(context, pen().width());
        CGContextStrokePath(context);
    }

    CGContextRestoreGState(context);
}

void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
431
432
433
434
435
436
437
438
439
440
441
{
    if (paintingDisabled())
        return;
    if (color.alpha()) {
        CGContextRef context = platformContext();
        setCGFillColor(context, color);
        CGContextFillRect(context, rect);
    }
}

void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
darin's avatar
darin committed
442
443
444
445
446
{
    if (paintingDisabled())
        return;
    if (color.alpha()) {
        CGContextRef context = platformContext();
darin's avatar
darin committed
447
        setCGFillColor(context, color);
darin's avatar
darin committed
448
449
450
451
        CGContextFillRect(context, rect);
    }
}

hyatt's avatar
hyatt committed
452
void GraphicsContext::clip(const IntRect& rect)
darin's avatar
darin committed
453
454
455
456
{
    if (paintingDisabled())
        return;
    CGContextClipToRect(platformContext(), rect);
hyatt's avatar
hyatt committed
457
    m_data->clip(rect);
darin's avatar
darin committed
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
}

void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
    const IntSize& bottomLeft, const IntSize& bottomRight)
{
    if (paintingDisabled())
        return;

    // Need sufficient width and height to contain these curves.  Sanity check our top/bottom
    // values and our width/height values to make sure the curves can all fit.
    int requiredWidth = max(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
    if (requiredWidth > rect.width())
        return;
    int requiredHeight = max(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
    if (requiredHeight > rect.height())
        return;
 
    // Clip to our rect.
hyatt's avatar
hyatt committed
476
    clip(rect);
darin's avatar
darin committed
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513

    // OK, the curves can fit.
    CGContextRef context = platformContext();
    
    // Add the four ellipses to the path.  Technically this really isn't good enough, since we could end up
    // not clipping the other 3/4 of the ellipse we don't care about.  We're relying on the fact that for
    // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
    // be subsumed by the other).
    CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
    CGContextAddEllipseInRect(context, CGRectMake(rect.right() - topRight.width() * 2, rect.y(),
                                                  topRight.width() * 2, topRight.height() * 2));
    CGContextAddEllipseInRect(context, CGRectMake(rect.x(), rect.bottom() - bottomLeft.height() * 2,
                                                  bottomLeft.width() * 2, bottomLeft.height() * 2));
    CGContextAddEllipseInRect(context, CGRectMake(rect.right() - bottomRight.width() * 2,
                                                  rect.bottom() - bottomRight.height() * 2,
                                                  bottomRight.width() * 2, bottomRight.height() * 2));
    
    // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
    CGContextAddRect(context, CGRectMake(rect.x() + topLeft.width(), rect.y(),
                                         rect.width() - topLeft.width() - topRight.width(),
                                         max(topLeft.height(), topRight.height())));
    CGContextAddRect(context, CGRectMake(rect.x() + bottomLeft.width(), 
                                         rect.bottom() - max(bottomLeft.height(), bottomRight.height()),
                                         rect.width() - bottomLeft.width() - bottomRight.width(),
                                         max(bottomLeft.height(), bottomRight.height())));
    CGContextAddRect(context, CGRectMake(rect.x(), rect.y() + topLeft.height(),
                                         max(topLeft.width(), bottomLeft.width()), rect.height() - topLeft.height() - bottomLeft.height()));
    CGContextAddRect(context, CGRectMake(rect.right() - max(topRight.width(), bottomRight.width()),
                                         rect.y() + topRight.height(),
                                         max(topRight.width(), bottomRight.width()), rect.height() - topRight.height() - bottomRight.height()));
    CGContextAddRect(context, CGRectMake(rect.x() + max(topLeft.width(), bottomLeft.width()),
                                         rect.y() + max(topLeft.height(), topRight.height()),
                                         rect.width() - max(topLeft.width(), bottomLeft.width()) - max(topRight.width(), bottomRight.width()),
                                         rect.height() - max(topLeft.height(), topRight.height()) - max(bottomLeft.height(), bottomRight.height())));
    CGContextClip(context);
}

bdakin's avatar
bdakin committed
514
515
516
517
518
void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
{
    if (paintingDisabled())
        return;

hyatt's avatar
hyatt committed
519
    clip(rect);
bdakin's avatar
bdakin committed
520
521
522
523
524
525
526
527
528
529
530
    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);
}

ddkilzer's avatar
ddkilzer committed
531
#ifdef SVG_SUPPORT
darin's avatar
darin committed
532
533
534
535
536
537
538
539
540
541
542
543
544
545
KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
{
    return new KRenderingDeviceContextQuartz(platformContext());
}
#endif

void GraphicsContext::beginTransparencyLayer(float opacity)
{
    if (paintingDisabled())
        return;
    CGContextRef context = platformContext();
    CGContextSaveGState(context);
    CGContextSetAlpha(context, opacity);
    CGContextBeginTransparencyLayer(context, 0);
hyatt's avatar
hyatt committed
546
    m_data->beginTransparencyLayer();
darin's avatar
darin committed
547
548
549
550
551
552
553
554
555
}

void GraphicsContext::endTransparencyLayer()
{
    if (paintingDisabled())
        return;
    CGContextRef context = platformContext();
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
hyatt's avatar
hyatt committed
556
    m_data->endTransparencyLayer();
darin's avatar
darin committed
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
}

void GraphicsContext::setShadow(const IntSize& size, int blur, const Color& color)
{
    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
582
    CGContextSetShadowWithColor(platformContext(), CGSizeZero, 0, 0);
darin's avatar
darin committed
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
}

void GraphicsContext::setLineWidth(float width)
{
    if (paintingDisabled())
        return;
    CGContextSetLineWidth(platformContext(), width);
}

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;
    }
}

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
662
    m_data->clip(path);
darin's avatar
darin committed
663
664
665
666
667
668
669
}

void GraphicsContext::scale(const FloatSize& size)
{
    if (paintingDisabled())
        return;
    CGContextScaleCTM(platformContext(), size.width(), size.height());
hyatt's avatar
hyatt committed
670
    m_data->scale(size);
darin's avatar
darin committed
671
672
673
674
675
676
677
}

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

thatcher's avatar
thatcher committed
681
void GraphicsContext::translate(float x, float y)
darin's avatar
darin committed
682
683
684
{
    if (paintingDisabled())
        return;
ggaren's avatar
ggaren committed
685
    CGContextTranslateCTM(platformContext(), x, y);
hyatt's avatar
hyatt committed
686
    m_data->translate(x, y);
darin's avatar
darin committed
687
688
}

689
690
691
692
693
void GraphicsContext::concatCTM(const AffineTransform& transform)
{
    if (paintingDisabled())
        return;
    CGContextConcatCTM(platformContext(), transform);
hyatt's avatar
hyatt committed
694
    m_data->concatCTM(transform);
695
696
}

bdakin's avatar
bdakin committed
697
698
FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
{
bdakin's avatar
bdakin committed
699
700
701
702
703
704
705
706
707
708
709
    // 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());
    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
710
711
712
713
714

    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
715
716
    
    // Don't let the height or width round to 0 unless either was originally 0
mjs's avatar
mjs committed
717
718
719
720
721
    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
722
723
    FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x / deviceScaleX, deviceOrigin.y / deviceScaleY);
    FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x / deviceScaleX, deviceLowerRight.y / deviceScaleY);
mjs's avatar
mjs committed
724
    return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
bdakin's avatar
bdakin committed
725
726
}

727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)
{
    if (paintingDisabled())
        return;
    
    // Note: This function assumes that point.x and point.y are integers (and that's currently always the case).
    float x = point.x();
    float y = point.y() + yOffset;

    // Leave 1.0 in user space between the baseline of the text and the top of the underline.
    // FIXME: Is this the right distance for space above the underline? Even for thick underlines on large sized text?
    y += 1;

    float thickness = pen().width();
    if (printing) {
        // When printing, use a minimum thickness of 0.5 in user space.
        // See bugzilla bug 4255 for details of why 0.5 is the right minimum thickness to use while printing.
        if (thickness < 0.5)
            thickness = 0.5;

        // When printing, use antialiasing instead of putting things on integral pixel boundaries.
    } else {
        // On screen, use a minimum thickness of 1.0 in user space (later rounded to an integral number in device space).
        if (thickness < 1)
            thickness = 1;

        // On screen, round all parameters to integer boundaries in device space.
bdakin's avatar
bdakin committed
754
        CGRect lineRect = roundToDevicePixels(FloatRect(x, y, width, thickness));
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
        x = lineRect.origin.x;
        y = lineRect.origin.y;
        width = (int)(lineRect.size.width);
        thickness = lineRect.size.height;
    }

    // FIXME: How about using a rectangle fill instead of drawing a line?
    CGContextSaveGState(platformContext());

    setCGStrokeColor(platformContext(), pen().color());
    
    CGContextSetLineWidth(platformContext(), thickness);
    CGContextSetShouldAntialias(platformContext(), printing);

    float halfThickness = thickness / 2;

    CGPoint linePoints[2];
    linePoints[0].x = x + halfThickness;
    linePoints[0].y = y + halfThickness;
    linePoints[1].x = x + width - halfThickness;
    linePoints[1].y = y + halfThickness;
    CGContextStrokeLineSegments(platformContext(), linePoints, 2);

    CGContextRestoreGState(platformContext());
}

781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
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);
    }
}

darin's avatar
darin committed
804
}
805
806

#endif // PLATFORM(CG)