1/*
2 * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
4 * Copyright (C) 2008, 2009 Dirk Schulze <krit@webkit.org>
5 * Copyright (C) 2008 Nuanti Ltd.
6 * Copyright (C) 2009 Brent Fulgham <bfulgham@webkit.org>
7 * Copyright (C) 2010, 2011 Igalia S.L.
8 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
9 * Copyright (C) 2012, Intel Corporation
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 *    notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in the
18 *    documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33#include "config.h"
34#include "GraphicsContext.h"
35
36#if USE(CAIRO)
37
38#include "AffineTransform.h"
39#include "CairoUtilities.h"
40#include "DrawErrorUnderline.h"
41#include "FloatConversion.h"
42#include "FloatRect.h"
43#include "Font.h"
44#include "GraphicsContextPlatformPrivateCairo.h"
45#include "IntRect.h"
46#include "NotImplemented.h"
47#include "OwnPtrCairo.h"
48#include "Path.h"
49#include "Pattern.h"
50#include "PlatformContextCairo.h"
51#include "PlatformPathCairo.h"
52#include "RefPtrCairo.h"
53#include "ShadowBlur.h"
54#include "SimpleFontData.h"
55#include "TransformationMatrix.h"
56#include <cairo.h>
57#include <math.h>
58#include <stdio.h>
59#include <wtf/MathExtras.h>
60
61#if PLATFORM(GTK)
62#include <gdk/gdk.h>
63#elif PLATFORM(WIN)
64#include <cairo-win32.h>
65#endif
66
67using namespace std;
68
69namespace WebCore {
70
71// A helper which quickly fills a rectangle with a simple color fill.
72static inline void fillRectWithColor(cairo_t* cr, const FloatRect& rect, const Color& color)
73{
74    if (!color.alpha() && cairo_get_operator(cr) == CAIRO_OPERATOR_OVER)
75        return;
76    setSourceRGBAFromColor(cr, color);
77    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
78    cairo_fill(cr);
79}
80
81static void addConvexPolygonToContext(cairo_t* context, size_t numPoints, const FloatPoint* points)
82{
83    cairo_move_to(context, points[0].x(), points[0].y());
84    for (size_t i = 1; i < numPoints; i++)
85        cairo_line_to(context, points[i].x(), points[i].y());
86    cairo_close_path(context);
87}
88
89enum PathDrawingStyle {
90    Fill = 1,
91    Stroke = 2,
92    FillAndStroke = Fill + Stroke
93};
94
95static inline void drawPathShadow(GraphicsContext* context, PathDrawingStyle drawingStyle)
96{
97    ShadowBlur& shadow = context->platformContext()->shadowBlur();
98    if (shadow.type() == ShadowBlur::NoShadow)
99        return;
100
101    // Calculate the extents of the rendered solid paths.
102    cairo_t* cairoContext = context->platformContext()->cr();
103    OwnPtr<cairo_path_t> path = adoptPtr(cairo_copy_path(cairoContext));
104
105    FloatRect solidFigureExtents;
106    double x0 = 0;
107    double x1 = 0;
108    double y0 = 0;
109    double y1 = 0;
110    if (drawingStyle & Stroke) {
111        cairo_stroke_extents(cairoContext, &x0, &y0, &x1, &y1);
112        solidFigureExtents = FloatRect(x0, y0, x1 - x0, y1 - y0);
113    }
114    if (drawingStyle & Fill) {
115        cairo_fill_extents(cairoContext, &x0, &y0, &x1, &y1);
116        FloatRect fillExtents(x0, y0, x1 - x0, y1 - y0);
117        solidFigureExtents.unite(fillExtents);
118    }
119
120    GraphicsContext* shadowContext = shadow.beginShadowLayer(context, solidFigureExtents);
121    if (!shadowContext)
122        return;
123
124    cairo_t* cairoShadowContext = shadowContext->platformContext()->cr();
125
126    // It's important to copy the context properties to the new shadow
127    // context to preserve things such as the fill rule and stroke width.
128    copyContextProperties(cairoContext, cairoShadowContext);
129
130    if (drawingStyle & Fill) {
131        cairo_save(cairoShadowContext);
132        cairo_append_path(cairoShadowContext, path.get());
133        shadowContext->platformContext()->prepareForFilling(context->state(), PlatformContextCairo::NoAdjustment);
134        cairo_fill(cairoShadowContext);
135        cairo_restore(cairoShadowContext);
136    }
137
138    if (drawingStyle & Stroke) {
139        cairo_append_path(cairoShadowContext, path.get());
140        shadowContext->platformContext()->prepareForStroking(context->state(), PlatformContextCairo::DoNotPreserveAlpha);
141        cairo_stroke(cairoShadowContext);
142    }
143
144    // The original path may still be hanging around on the context and endShadowLayer
145    // will take care of properly creating a path to draw the result shadow. We remove the path
146    // temporarily and then restore it.
147    // See: https://bugs.webkit.org/show_bug.cgi?id=108897
148    cairo_new_path(cairoContext);
149    shadow.endShadowLayer(context);
150    cairo_append_path(cairoContext, path.get());
151}
152
153static inline void shadowAndFillCurrentCairoPath(GraphicsContext* context)
154{
155    cairo_t* cr = context->platformContext()->cr();
156    cairo_save(cr);
157
158    drawPathShadow(context, Fill);
159
160    context->platformContext()->prepareForFilling(context->state(), PlatformContextCairo::AdjustPatternForGlobalAlpha);
161    cairo_fill(cr);
162
163    cairo_restore(cr);
164}
165
166static inline void shadowAndStrokeCurrentCairoPath(GraphicsContext* context)
167{
168    drawPathShadow(context, Stroke);
169    context->platformContext()->prepareForStroking(context->state());
170    cairo_stroke(context->platformContext()->cr());
171}
172
173GraphicsContext::GraphicsContext(cairo_t* cr)
174    : m_updatingControlTints(false),
175      m_transparencyCount(0)
176{
177    m_data = new GraphicsContextPlatformPrivateToplevel(new PlatformContextCairo(cr));
178}
179
180void GraphicsContext::platformInit(PlatformContextCairo* platformContext)
181{
182    m_data = new GraphicsContextPlatformPrivate(platformContext);
183    if (platformContext)
184        m_data->syncContext(platformContext->cr());
185    else
186        setPaintingDisabled(true);
187}
188
189void GraphicsContext::platformDestroy()
190{
191    delete m_data;
192}
193
194AffineTransform GraphicsContext::getCTM(IncludeDeviceScale) const
195{
196    if (paintingDisabled())
197        return AffineTransform();
198
199    cairo_t* cr = platformContext()->cr();
200    cairo_matrix_t m;
201    cairo_get_matrix(cr, &m);
202    return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0);
203}
204
205PlatformContextCairo* GraphicsContext::platformContext() const
206{
207    return m_data->platformContext;
208}
209
210void GraphicsContext::savePlatformState()
211{
212    platformContext()->save();
213    m_data->save();
214}
215
216void GraphicsContext::restorePlatformState()
217{
218    platformContext()->restore();
219    m_data->restore();
220
221    platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur),
222                                                    m_state.shadowOffset,
223                                                    m_state.shadowColor,
224                                                    m_state.shadowColorSpace,
225                                                    m_state.shadowsIgnoreTransforms);
226}
227
228// Draws a filled rectangle with a stroked border.
229void GraphicsContext::drawRect(const IntRect& rect)
230{
231    if (paintingDisabled())
232        return;
233
234    ASSERT(!rect.isEmpty());
235
236    cairo_t* cr = platformContext()->cr();
237    cairo_save(cr);
238
239    fillRectWithColor(cr, rect, fillColor());
240
241    if (strokeStyle() != NoStroke) {
242        setSourceRGBAFromColor(cr, strokeColor());
243        FloatRect r(rect);
244        r.inflate(-.5f);
245        cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
246        cairo_set_line_width(cr, 1.0);
247        cairo_stroke(cr);
248    }
249
250    cairo_restore(cr);
251}
252
253static double calculateStrokePatternOffset(int distance, int patternWidth)
254{
255    // Example: 80 pixels with a width of 30 pixels. Remainder is 20.
256    // The maximum pixels of line we could paint will be 50 pixels.
257    int remainder = distance % patternWidth;
258    int numSegments = (distance - remainder) / patternWidth;
259
260    // Special case 1px dotted borders for speed.
261    if (patternWidth == 1)
262        return 1;
263
264    bool evenNumberOfSegments = !(numSegments % 2);
265    if (remainder)
266        evenNumberOfSegments = !evenNumberOfSegments;
267
268    if (evenNumberOfSegments) {
269        if (remainder)
270            return (patternWidth - remainder) + (remainder / 2);
271        return patternWidth / 2;
272    }
273
274    // Odd number of segments.
275    if (remainder)
276        return (patternWidth - remainder) / 2.f;
277    return 0;
278}
279
280static void drawLineOnCairoContext(GraphicsContext* graphicsContext, cairo_t* context, const FloatPoint& point1, const FloatPoint& point2)
281{
282    StrokeStyle style = graphicsContext->strokeStyle();
283    if (style == NoStroke)
284        return;
285
286    const Color& strokeColor = graphicsContext->strokeColor();
287    int strokeThickness = floorf(graphicsContext->strokeThickness());
288    if (graphicsContext->strokeThickness() < 1)
289        strokeThickness = 1;
290
291    int patternWidth = 0;
292    if (style == DottedStroke)
293        patternWidth = strokeThickness;
294    else if (style == DashedStroke)
295        patternWidth = 3 * strokeThickness;
296
297    bool isVerticalLine = point1.x() == point2.x();
298    FloatPoint point1OnPixelBoundaries = point1;
299    FloatPoint point2OnPixelBoundaries = point2;
300    GraphicsContext::adjustLineToPixelBoundaries(point1OnPixelBoundaries, point2OnPixelBoundaries, strokeThickness, style);
301
302    if (patternWidth) {
303        // Do a rect fill of our endpoints.  This ensures we always have the
304        // appearance of being a border.  We then draw the actual dotted/dashed line.
305        FloatRect firstRect(point1OnPixelBoundaries, FloatSize(strokeThickness, strokeThickness));
306        FloatRect secondRect(point2OnPixelBoundaries, FloatSize(strokeThickness, strokeThickness));
307        if (isVerticalLine) {
308            firstRect.move(-strokeThickness / 2, -strokeThickness);
309            secondRect.move(-strokeThickness / 2, 0);
310        } else {
311            firstRect.move(-strokeThickness, -strokeThickness / 2);
312            secondRect.move(0, -strokeThickness / 2);
313        }
314        fillRectWithColor(context, firstRect, strokeColor);
315        fillRectWithColor(context, secondRect, strokeColor);
316
317        int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2 * strokeThickness;
318        double patternOffset = calculateStrokePatternOffset(distance, patternWidth);
319        double patternWidthAsDouble = patternWidth;
320        cairo_set_dash(context, &patternWidthAsDouble, 1, patternOffset);
321    }
322
323    setSourceRGBAFromColor(context, strokeColor);
324    cairo_set_line_width(context, strokeThickness);
325    cairo_move_to(context, point1OnPixelBoundaries.x(), point1OnPixelBoundaries.y());
326    cairo_line_to(context, point2OnPixelBoundaries.x(), point2OnPixelBoundaries.y());
327    cairo_stroke(context);
328}
329
330// This is only used to draw borders, so we should not draw shadows.
331void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
332{
333    if (paintingDisabled())
334        return;
335
336    cairo_t* cairoContext = platformContext()->cr();
337    cairo_save(cairoContext);
338    drawLineOnCairoContext(this, cairoContext, point1, point2);
339    cairo_restore(cairoContext);
340}
341
342// This method is only used to draw the little circles used in lists.
343void GraphicsContext::drawEllipse(const IntRect& rect)
344{
345    if (paintingDisabled())
346        return;
347
348    cairo_t* cr = platformContext()->cr();
349    cairo_save(cr);
350    float yRadius = .5 * rect.height();
351    float xRadius = .5 * rect.width();
352    cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
353    cairo_scale(cr, xRadius, yRadius);
354    cairo_arc(cr, 0., 0., 1., 0., 2 * piFloat);
355    cairo_restore(cr);
356
357    if (fillColor().alpha()) {
358        setSourceRGBAFromColor(cr, fillColor());
359        cairo_fill_preserve(cr);
360    }
361
362    if (strokeStyle() != NoStroke) {
363        setSourceRGBAFromColor(cr, strokeColor());
364        cairo_set_line_width(cr, strokeThickness());
365        cairo_stroke(cr);
366    } else
367        cairo_new_path(cr);
368}
369
370void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
371{
372    if (paintingDisabled())
373        return;
374
375    if (npoints <= 1)
376        return;
377
378    cairo_t* cr = platformContext()->cr();
379
380    cairo_save(cr);
381    cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
382    addConvexPolygonToContext(cr, npoints, points);
383
384    if (fillColor().alpha()) {
385        setSourceRGBAFromColor(cr, fillColor());
386        cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
387        cairo_fill_preserve(cr);
388    }
389
390    if (strokeStyle() != NoStroke) {
391        setSourceRGBAFromColor(cr, strokeColor());
392        cairo_set_line_width(cr, strokeThickness());
393        cairo_stroke(cr);
394    } else
395        cairo_new_path(cr);
396
397    cairo_restore(cr);
398}
399
400void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased)
401{
402    if (paintingDisabled())
403        return;
404
405    if (numPoints <= 1)
406        return;
407
408    cairo_t* cr = platformContext()->cr();
409
410    cairo_new_path(cr);
411    cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
412    cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr);
413
414    cairo_set_antialias(cr, antialiased ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
415    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
416    addConvexPolygonToContext(cr, numPoints, points);
417    cairo_clip(cr);
418
419    cairo_set_antialias(cr, savedAntialiasRule);
420    cairo_set_fill_rule(cr, savedFillRule);
421}
422
423void GraphicsContext::fillPath(const Path& path)
424{
425    if (paintingDisabled() || path.isEmpty())
426        return;
427
428    cairo_t* cr = platformContext()->cr();
429    setPathOnCairoContext(cr, path.platformPath()->context());
430    shadowAndFillCurrentCairoPath(this);
431}
432
433void GraphicsContext::strokePath(const Path& path)
434{
435    if (paintingDisabled() || path.isEmpty())
436        return;
437
438    cairo_t* cr = platformContext()->cr();
439    setPathOnCairoContext(cr, path.platformPath()->context());
440    shadowAndStrokeCurrentCairoPath(this);
441}
442
443void GraphicsContext::fillRect(const FloatRect& rect)
444{
445    if (paintingDisabled())
446        return;
447
448    cairo_t* cr = platformContext()->cr();
449    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
450    shadowAndFillCurrentCairoPath(this);
451}
452
453void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace)
454{
455    if (paintingDisabled())
456        return;
457
458    if (hasShadow())
459        platformContext()->shadowBlur().drawRectShadow(this, rect, RoundedRect::Radii());
460
461    fillRectWithColor(platformContext()->cr(), rect, color);
462}
463
464void GraphicsContext::clip(const FloatRect& rect)
465{
466    if (paintingDisabled())
467        return;
468
469    cairo_t* cr = platformContext()->cr();
470    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
471    cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
472    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
473    // The rectangular clip function is traditionally not expected to
474    // antialias. If we don't force antialiased clipping here,
475    // edge fringe artifacts may occur at the layer edges
476    // when a transformation is applied to the GraphicsContext
477    // while drawing the transformed layer.
478    cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr);
479    cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
480    cairo_clip(cr);
481    cairo_set_fill_rule(cr, savedFillRule);
482    cairo_set_antialias(cr, savedAntialiasRule);
483    m_data->clip(rect);
484}
485
486void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
487{
488    if (paintingDisabled())
489        return;
490
491    cairo_t* cr = platformContext()->cr();
492    if (!path.isNull())
493        setPathOnCairoContext(cr, path.platformPath()->context());
494    cairo_set_fill_rule(cr, clipRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
495    cairo_clip(cr);
496}
497
498IntRect GraphicsContext::clipBounds() const
499{
500    double x1, x2, y1, y2;
501    cairo_clip_extents(platformContext()->cr(), &x1, &y1, &x2, &y2);
502    return enclosingIntRect(FloatRect(x1, y1, x2 - x1, y2 - y1));
503}
504
505static inline void adjustFocusRingColor(Color& color)
506{
507#if !PLATFORM(GTK)
508    // Force the alpha to 50%.  This matches what the Mac does with outline rings.
509    color.setRGB(makeRGBA(color.red(), color.green(), color.blue(), 127));
510#endif
511}
512
513static inline void adjustFocusRingLineWidth(int& width)
514{
515#if PLATFORM(GTK)
516    width = 2;
517#else
518    UNUSED_PARAM(width);
519#endif
520}
521
522static inline StrokeStyle focusRingStrokeStyle()
523{
524#if PLATFORM(GTK)
525    return DottedStroke;
526#else
527    return SolidStroke;
528#endif
529}
530
531void GraphicsContext::drawFocusRing(const Path& path, int width, int /* offset */, const Color& color)
532{
533    // FIXME: We should draw paths that describe a rectangle with rounded corners
534    // so as to be consistent with how we draw rectangular focus rings.
535    Color ringColor = color;
536    adjustFocusRingColor(ringColor);
537    adjustFocusRingLineWidth(width);
538
539    cairo_t* cr = platformContext()->cr();
540    cairo_save(cr);
541    appendWebCorePathToCairoContext(cr, path);
542    setSourceRGBAFromColor(cr, ringColor);
543    cairo_set_line_width(cr, width);
544    setPlatformStrokeStyle(focusRingStrokeStyle());
545    cairo_stroke(cr);
546    cairo_restore(cr);
547}
548
549void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int /* offset */, const Color& color)
550{
551    if (paintingDisabled())
552        return;
553
554    unsigned rectCount = rects.size();
555
556    cairo_t* cr = platformContext()->cr();
557    cairo_save(cr);
558    cairo_push_group(cr);
559    cairo_new_path(cr);
560
561#if PLATFORM(GTK)
562#ifdef GTK_API_VERSION_2
563    GdkRegion* reg = gdk_region_new();
564#else
565    cairo_region_t* reg = cairo_region_create();
566#endif
567
568    for (unsigned i = 0; i < rectCount; i++) {
569#ifdef GTK_API_VERSION_2
570        GdkRectangle rect = rects[i];
571        gdk_region_union_with_rect(reg, &rect);
572#else
573        cairo_rectangle_int_t rect = rects[i];
574        cairo_region_union_rectangle(reg, &rect);
575#endif
576    }
577    gdk_cairo_region(cr, reg);
578#ifdef GTK_API_VERSION_2
579    gdk_region_destroy(reg);
580#else
581    cairo_region_destroy(reg);
582#endif
583#else
584    int radius = (width - 1) / 2;
585    Path path;
586    for (unsigned i = 0; i < rectCount; ++i) {
587        if (i > 0)
588            path.clear();
589        path.addRoundedRect(rects[i], FloatSize(radius, radius));
590        appendWebCorePathToCairoContext(cr, path);
591    }
592#endif
593    Color ringColor = color;
594    adjustFocusRingColor(ringColor);
595    adjustFocusRingLineWidth(width);
596    setSourceRGBAFromColor(cr, ringColor);
597    cairo_set_line_width(cr, width);
598    setPlatformStrokeStyle(focusRingStrokeStyle());
599
600    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
601    cairo_stroke_preserve(cr);
602
603    cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
604    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
605    cairo_fill(cr);
606
607    cairo_pop_group_to_source(cr);
608    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
609    cairo_paint(cr);
610    cairo_restore(cr);
611}
612
613void GraphicsContext::drawLineForText(const FloatPoint& origin, float width, bool)
614{
615    if (paintingDisabled())
616        return;
617
618    cairo_t* cairoContext = platformContext()->cr();
619    cairo_save(cairoContext);
620
621    // This bumping of <1 stroke thicknesses matches the one in drawLineOnCairoContext.
622    FloatPoint endPoint(origin + IntSize(width, 0));
623    FloatRect lineExtents(origin, FloatSize(width, strokeThickness()));
624
625    ShadowBlur& shadow = platformContext()->shadowBlur();
626    if (GraphicsContext* shadowContext = shadow.beginShadowLayer(this, lineExtents)) {
627        drawLineOnCairoContext(this, shadowContext->platformContext()->cr(), origin, endPoint);
628        shadow.endShadowLayer(this);
629    }
630
631    drawLineOnCairoContext(this, cairoContext, origin, endPoint);
632    cairo_restore(cairoContext);
633}
634
635void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& origin, float width, DocumentMarkerLineStyle style)
636{
637    if (paintingDisabled())
638        return;
639
640    cairo_t* cr = platformContext()->cr();
641    cairo_save(cr);
642
643    switch (style) {
644    case DocumentMarkerSpellingLineStyle:
645        cairo_set_source_rgb(cr, 1, 0, 0);
646        break;
647    case DocumentMarkerGrammarLineStyle:
648        cairo_set_source_rgb(cr, 0, 1, 0);
649        break;
650    default:
651        cairo_restore(cr);
652        return;
653    }
654
655    drawErrorUnderline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness);
656
657    cairo_restore(cr);
658}
659
660FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect, RoundingMode)
661{
662    FloatRect result;
663    double x = frect.x();
664    double y = frect.y();
665    cairo_t* cr = platformContext()->cr();
666    cairo_user_to_device(cr, &x, &y);
667    x = round(x);
668    y = round(y);
669    cairo_device_to_user(cr, &x, &y);
670    result.setX(narrowPrecisionToFloat(x));
671    result.setY(narrowPrecisionToFloat(y));
672
673    // We must ensure width and height are at least 1 (or -1) when
674    // we're given float values in the range between 0 and 1 (or -1 and 0).
675    double width = frect.width();
676    double height = frect.height();
677    cairo_user_to_device_distance(cr, &width, &height);
678    if (width > -1 && width < 0)
679        width = -1;
680    else if (width > 0 && width < 1)
681        width = 1;
682    else
683        width = round(width);
684    if (height > -1 && width < 0)
685        height = -1;
686    else if (height > 0 && height < 1)
687        height = 1;
688    else
689        height = round(height);
690    cairo_device_to_user_distance(cr, &width, &height);
691    result.setWidth(narrowPrecisionToFloat(width));
692    result.setHeight(narrowPrecisionToFloat(height));
693
694    return result;
695}
696
697void GraphicsContext::translate(float x, float y)
698{
699    if (paintingDisabled())
700        return;
701
702    cairo_t* cr = platformContext()->cr();
703    cairo_translate(cr, x, y);
704    m_data->translate(x, y);
705}
706
707void GraphicsContext::setPlatformFillColor(const Color&, ColorSpace)
708{
709    // Cairo contexts can't hold separate fill and stroke colors
710    // so we set them just before we actually fill or stroke
711}
712
713void GraphicsContext::setPlatformStrokeColor(const Color&, ColorSpace)
714{
715    // Cairo contexts can't hold separate fill and stroke colors
716    // so we set them just before we actually fill or stroke
717}
718
719void GraphicsContext::setPlatformStrokeThickness(float strokeThickness)
720{
721    if (paintingDisabled())
722        return;
723
724    cairo_set_line_width(platformContext()->cr(), strokeThickness);
725}
726
727void GraphicsContext::setPlatformStrokeStyle(StrokeStyle strokeStyle)
728{
729    static double dashPattern[] = {5.0, 5.0};
730    static double dotPattern[] = {1.0, 1.0};
731
732    if (paintingDisabled())
733        return;
734
735    switch (strokeStyle) {
736    case NoStroke:
737        // FIXME: is it the right way to emulate NoStroke?
738        cairo_set_line_width(platformContext()->cr(), 0);
739        break;
740    case SolidStroke:
741#if ENABLE(CSS3_TEXT)
742    case DoubleStroke:
743    case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94110 - Needs platform support.
744#endif // CSS3_TEXT
745        cairo_set_dash(platformContext()->cr(), 0, 0, 0);
746        break;
747    case DottedStroke:
748        cairo_set_dash(platformContext()->cr(), dotPattern, 2, 0);
749        break;
750    case DashedStroke:
751        cairo_set_dash(platformContext()->cr(), dashPattern, 2, 0);
752        break;
753    }
754}
755
756void GraphicsContext::setURLForRect(const KURL&, const IntRect&)
757{
758    notImplemented();
759}
760
761void GraphicsContext::concatCTM(const AffineTransform& transform)
762{
763    if (paintingDisabled())
764        return;
765
766    cairo_t* cr = platformContext()->cr();
767    const cairo_matrix_t matrix = cairo_matrix_t(transform);
768    cairo_transform(cr, &matrix);
769    m_data->concatCTM(transform);
770}
771
772void GraphicsContext::setCTM(const AffineTransform& transform)
773{
774    if (paintingDisabled())
775        return;
776
777    cairo_t* cr = platformContext()->cr();
778    const cairo_matrix_t matrix = cairo_matrix_t(transform);
779    cairo_set_matrix(cr, &matrix);
780    m_data->setCTM(transform);
781}
782
783void GraphicsContext::setPlatformShadow(FloatSize const& size, float, Color const&, ColorSpace)
784{
785    if (paintingDisabled())
786        return;
787
788    if (m_state.shadowsIgnoreTransforms) {
789        // Meaning that this graphics context is associated with a CanvasRenderingContext
790        // We flip the height since CG and HTML5 Canvas have opposite Y axis
791        m_state.shadowOffset = FloatSize(size.width(), -size.height());
792    }
793
794    // Cairo doesn't support shadows natively, they are drawn manually in the draw* functions using ShadowBlur.
795    platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur),
796                                                    m_state.shadowOffset,
797                                                    m_state.shadowColor,
798                                                    m_state.shadowColorSpace,
799                                                    m_state.shadowsIgnoreTransforms);
800}
801
802void GraphicsContext::clearPlatformShadow()
803{
804    if (paintingDisabled())
805        return;
806
807    platformContext()->shadowBlur().clear();
808}
809
810void GraphicsContext::beginPlatformTransparencyLayer(float opacity)
811{
812    if (paintingDisabled())
813        return;
814
815    cairo_t* cr = platformContext()->cr();
816    cairo_push_group(cr);
817    m_data->layers.append(opacity);
818}
819
820void GraphicsContext::endPlatformTransparencyLayer()
821{
822    if (paintingDisabled())
823        return;
824
825    cairo_t* cr = platformContext()->cr();
826
827    cairo_pop_group_to_source(cr);
828    cairo_paint_with_alpha(cr, m_data->layers.last());
829    m_data->layers.removeLast();
830}
831
832bool GraphicsContext::supportsTransparencyLayers()
833{
834    return true;
835}
836
837void GraphicsContext::clearRect(const FloatRect& rect)
838{
839    if (paintingDisabled())
840        return;
841
842    cairo_t* cr = platformContext()->cr();
843
844    cairo_save(cr);
845    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
846    cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
847    cairo_fill(cr);
848    cairo_restore(cr);
849}
850
851void GraphicsContext::strokeRect(const FloatRect& rect, float width)
852{
853    if (paintingDisabled())
854        return;
855
856    cairo_t* cr = platformContext()->cr();
857    cairo_save(cr);
858    cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
859    cairo_set_line_width(cr, width);
860    shadowAndStrokeCurrentCairoPath(this);
861    cairo_restore(cr);
862}
863
864void GraphicsContext::setLineCap(LineCap lineCap)
865{
866    if (paintingDisabled())
867        return;
868
869    cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT;
870    switch (lineCap) {
871    case ButtCap:
872        // no-op
873        break;
874    case RoundCap:
875        cairoCap = CAIRO_LINE_CAP_ROUND;
876        break;
877    case SquareCap:
878        cairoCap = CAIRO_LINE_CAP_SQUARE;
879        break;
880    }
881    cairo_set_line_cap(platformContext()->cr(), cairoCap);
882}
883
884void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
885{
886    cairo_set_dash(platformContext()->cr(), dashes.data(), dashes.size(), dashOffset);
887}
888
889void GraphicsContext::setLineJoin(LineJoin lineJoin)
890{
891    if (paintingDisabled())
892        return;
893
894    cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER;
895    switch (lineJoin) {
896    case MiterJoin:
897        // no-op
898        break;
899    case RoundJoin:
900        cairoJoin = CAIRO_LINE_JOIN_ROUND;
901        break;
902    case BevelJoin:
903        cairoJoin = CAIRO_LINE_JOIN_BEVEL;
904        break;
905    }
906    cairo_set_line_join(platformContext()->cr(), cairoJoin);
907}
908
909void GraphicsContext::setMiterLimit(float miter)
910{
911    if (paintingDisabled())
912        return;
913
914    cairo_set_miter_limit(platformContext()->cr(), miter);
915}
916
917void GraphicsContext::setAlpha(float alpha)
918{
919    platformContext()->setGlobalAlpha(alpha);
920}
921
922void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op, BlendMode blendOp)
923{
924    if (paintingDisabled())
925        return;
926
927    cairo_operator_t cairo_op;
928    if (blendOp == BlendModeNormal)
929        cairo_op = toCairoOperator(op);
930    else
931        cairo_op = toCairoOperator(blendOp);
932
933    cairo_set_operator(platformContext()->cr(), cairo_op);
934}
935
936void GraphicsContext::clip(const Path& path, WindRule windRule)
937{
938    if (paintingDisabled())
939        return;
940
941    cairo_t* cr = platformContext()->cr();
942    OwnPtr<cairo_path_t> pathCopy;
943    if (!path.isNull()) {
944        pathCopy = adoptPtr(cairo_copy_path(path.platformPath()->context()));
945        cairo_append_path(cr, pathCopy.get());
946    }
947    cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
948    if (windRule == RULE_NONZERO)
949        cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
950    else
951        cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
952    cairo_clip(cr);
953    cairo_set_fill_rule(cr, savedFillRule);
954    m_data->clip(path);
955}
956
957void GraphicsContext::canvasClip(const Path& path, WindRule windRule)
958{
959    clip(path, windRule);
960}
961
962void GraphicsContext::clipOut(const Path& path)
963{
964    if (paintingDisabled())
965        return;
966
967    cairo_t* cr = platformContext()->cr();
968    double x1, y1, x2, y2;
969    cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
970    cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
971    appendWebCorePathToCairoContext(cr, path);
972
973    cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
974    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
975    cairo_clip(cr);
976    cairo_set_fill_rule(cr, savedFillRule);
977}
978
979void GraphicsContext::rotate(float radians)
980{
981    if (paintingDisabled())
982        return;
983
984    cairo_rotate(platformContext()->cr(), radians);
985    m_data->rotate(radians);
986}
987
988void GraphicsContext::scale(const FloatSize& size)
989{
990    if (paintingDisabled())
991        return;
992
993    cairo_scale(platformContext()->cr(), size.width(), size.height());
994    m_data->scale(size);
995}
996
997void GraphicsContext::clipOut(const IntRect& r)
998{
999    if (paintingDisabled())
1000        return;
1001
1002    cairo_t* cr = platformContext()->cr();
1003    double x1, y1, x2, y2;
1004    cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1005    cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1006    cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
1007    cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1008    cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1009    cairo_clip(cr);
1010    cairo_set_fill_rule(cr, savedFillRule);
1011}
1012
1013static inline FloatPoint getPhase(const FloatRect& dest, const FloatRect& tile)
1014{
1015    FloatPoint phase = dest.location();
1016    phase.move(-tile.x(), -tile.y());
1017
1018    return phase;
1019}
1020
1021void GraphicsContext::fillRoundedRect(const IntRect& r, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace)
1022{
1023    if (paintingDisabled())
1024        return;
1025
1026    if (hasShadow())
1027        platformContext()->shadowBlur().drawRectShadow(this, r, RoundedRect::Radii(topLeft, topRight, bottomLeft, bottomRight));
1028
1029    cairo_t* cr = platformContext()->cr();
1030    cairo_save(cr);
1031    Path path;
1032    path.addRoundedRect(r, topLeft, topRight, bottomLeft, bottomRight);
1033    appendWebCorePathToCairoContext(cr, path);
1034    setSourceRGBAFromColor(cr, color);
1035    cairo_fill(cr);
1036    cairo_restore(cr);
1037}
1038
1039#if PLATFORM(GTK)
1040void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose)
1041{
1042    m_data->expose = expose;
1043}
1044
1045GdkEventExpose* GraphicsContext::gdkExposeEvent() const
1046{
1047    return m_data->expose;
1048}
1049
1050GdkWindow* GraphicsContext::gdkWindow() const
1051{
1052    if (!m_data->expose)
1053        return 0;
1054
1055    return m_data->expose->window;
1056}
1057#endif
1058
1059void GraphicsContext::setPlatformShouldAntialias(bool enable)
1060{
1061    if (paintingDisabled())
1062        return;
1063
1064    // When true, use the default Cairo backend antialias mode (usually this
1065    // enables standard 'grayscale' antialiasing); false to explicitly disable
1066    // antialiasing. This is the same strategy as used in drawConvexPolygon().
1067    cairo_set_antialias(platformContext()->cr(), enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1068}
1069
1070void GraphicsContext::setImageInterpolationQuality(InterpolationQuality quality)
1071{
1072    platformContext()->setImageInterpolationQuality(quality);
1073}
1074
1075InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1076{
1077    return platformContext()->imageInterpolationQuality();
1078}
1079
1080bool GraphicsContext::isAcceleratedContext() const
1081{
1082    return cairo_surface_get_type(cairo_get_target(platformContext()->cr())) == CAIRO_SURFACE_TYPE_GL;
1083}
1084
1085#if ENABLE(3D_RENDERING) && USE(TEXTURE_MAPPER)
1086TransformationMatrix GraphicsContext::get3DTransform() const
1087{
1088    // FIXME: Can we approximate the transformation better than this?
1089    return getCTM().toTransformationMatrix();
1090}
1091
1092void GraphicsContext::concat3DTransform(const TransformationMatrix& transform)
1093{
1094    concatCTM(transform.toAffineTransform());
1095}
1096
1097void GraphicsContext::set3DTransform(const TransformationMatrix& transform)
1098{
1099    setCTM(transform.toAffineTransform());
1100}
1101#endif
1102
1103} // namespace WebCore
1104
1105#endif // USE(CAIRO)
1106