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