Add display-list drawing hooks to platform-specific GraphicsContext files
[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 "DisplayListRecorder.h"
41 #include "DrawErrorUnderline.h"
42 #include "FloatConversion.h"
43 #include "FloatRect.h"
44 #include "FloatRoundedRect.h"
45 #include "Font.h"
46 #include "GraphicsContextPlatformPrivateCairo.h"
47 #include "ImageBuffer.h"
48 #include "IntRect.h"
49 #include "NotImplemented.h"
50 #include "Path.h"
51 #include "Pattern.h"
52 #include "PlatformContextCairo.h"
53 #include "PlatformPathCairo.h"
54 #include "RefPtrCairo.h"
55 #include "ShadowBlur.h"
56 #include "TransformationMatrix.h"
57 #include <cairo.h>
58 #include <math.h>
59 #include <stdio.h>
60 #include <wtf/MathExtras.h>
61
62 #if PLATFORM(WIN)
63 #include <cairo-win32.h>
64 #endif
65
66 using namespace std;
67
68 namespace WebCore {
69
70 // A helper which quickly fills a rectangle with a simple color fill.
71 static inline void fillRectWithColor(cairo_t* cr, const FloatRect& rect, const Color& color)
72 {
73     if (!color.alpha() && cairo_get_operator(cr) == CAIRO_OPERATOR_OVER)
74         return;
75     setSourceRGBAFromColor(cr, color);
76     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
77     cairo_fill(cr);
78 }
79
80 static void addConvexPolygonToContext(cairo_t* context, size_t numPoints, const FloatPoint* points)
81 {
82     cairo_move_to(context, points[0].x(), points[0].y());
83     for (size_t i = 1; i < numPoints; i++)
84         cairo_line_to(context, points[i].x(), points[i].y());
85     cairo_close_path(context);
86 }
87
88 enum PathDrawingStyle { 
89     Fill = 1,
90     Stroke = 2,
91     FillAndStroke = Fill + Stroke
92 };
93
94 static inline void drawPathShadow(GraphicsContext& context, PathDrawingStyle drawingStyle)
95 {
96     ShadowBlur& shadow = context.platformContext()->shadowBlur();
97     if (shadow.type() == ShadowBlur::NoShadow)
98         return;
99
100     // Calculate the extents of the rendered solid paths.
101     cairo_t* cairoContext = context.platformContext()->cr();
102     std::unique_ptr<cairo_path_t, void(*)(cairo_path_t*)> path(cairo_copy_path(cairoContext), [](cairo_path_t* path) {
103         cairo_path_destroy(path);
104     });
105
106     FloatRect solidFigureExtents;
107     double x0 = 0;
108     double x1 = 0;
109     double y0 = 0;
110     double y1 = 0;
111     if (drawingStyle & Stroke) {
112         cairo_stroke_extents(cairoContext, &x0, &y0, &x1, &y1);
113         solidFigureExtents = FloatRect(x0, y0, x1 - x0, y1 - y0);
114     }
115     if (drawingStyle & Fill) {
116         cairo_fill_extents(cairoContext, &x0, &y0, &x1, &y1);
117         FloatRect fillExtents(x0, y0, x1 - x0, y1 - y0);
118         solidFigureExtents.unite(fillExtents);
119     }
120
121     GraphicsContext* shadowContext = shadow.beginShadowLayer(context, solidFigureExtents);
122     if (!shadowContext)
123         return;
124
125     cairo_t* cairoShadowContext = shadowContext->platformContext()->cr();
126
127     // It's important to copy the context properties to the new shadow
128     // context to preserve things such as the fill rule and stroke width.
129     copyContextProperties(cairoContext, cairoShadowContext);
130
131     if (drawingStyle & Fill) {
132         cairo_save(cairoShadowContext);
133         cairo_append_path(cairoShadowContext, path.get());
134         shadowContext->platformContext()->prepareForFilling(context.state(), PlatformContextCairo::NoAdjustment);
135         cairo_fill(cairoShadowContext);
136         cairo_restore(cairoShadowContext);
137     }
138
139     if (drawingStyle & Stroke) {
140         cairo_append_path(cairoShadowContext, path.get());
141         shadowContext->platformContext()->prepareForStroking(context.state(), PlatformContextCairo::DoNotPreserveAlpha);
142         cairo_stroke(cairoShadowContext);
143     }
144
145     // The original path may still be hanging around on the context and endShadowLayer
146     // will take care of properly creating a path to draw the result shadow. We remove the path
147     // temporarily and then restore it.
148     // See: https://bugs.webkit.org/show_bug.cgi?id=108897
149     cairo_new_path(cairoContext);
150     shadow.endShadowLayer(context);
151     cairo_append_path(cairoContext, path.get());
152 }
153
154 static inline void fillCurrentCairoPath(GraphicsContext& context)
155 {
156     cairo_t* cr = context.platformContext()->cr();
157     cairo_save(cr);
158
159     context.platformContext()->prepareForFilling(context.state(), PlatformContextCairo::AdjustPatternForGlobalAlpha);
160     cairo_fill(cr);
161
162     cairo_restore(cr);
163 }
164
165 static inline void shadowAndFillCurrentCairoPath(GraphicsContext& context)
166 {
167     drawPathShadow(context, Fill);
168     fillCurrentCairoPath(context);
169 }
170
171 static inline void shadowAndStrokeCurrentCairoPath(GraphicsContext& context)
172 {
173     drawPathShadow(context, Stroke);
174     context.platformContext()->prepareForStroking(context.state());
175     cairo_stroke(context.platformContext()->cr());
176 }
177
178 GraphicsContext::GraphicsContext(cairo_t* cr)
179 {
180     if (!cr)
181         return;
182
183     m_data = new GraphicsContextPlatformPrivateToplevel(new PlatformContextCairo(cr));
184 }
185
186 void GraphicsContext::platformInit(PlatformContextCairo* platformContext)
187 {
188     if (!platformContext)
189         return;
190
191     m_data = new GraphicsContextPlatformPrivate(platformContext);
192     m_data->syncContext(platformContext->cr());
193 }
194
195 void GraphicsContext::platformDestroy()
196 {
197     delete m_data;
198 }
199
200 AffineTransform GraphicsContext::getCTM(IncludeDeviceScale) const
201 {
202     if (paintingDisabled())
203         return AffineTransform();
204
205     if (isRecording()) {
206         WTFLogAlways("GraphicsContext::getCTM() is not yet compatible with recording contexts.");
207         return AffineTransform();
208     }
209
210     cairo_t* cr = platformContext()->cr();
211     cairo_matrix_t m;
212     cairo_get_matrix(cr, &m);
213     return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0);
214 }
215
216 PlatformContextCairo* GraphicsContext::platformContext() const
217 {
218     return m_data->platformContext;
219 }
220
221 void GraphicsContext::savePlatformState()
222 {
223     ASSERT(!isRecording());
224     platformContext()->save();
225     m_data->save();
226 }
227
228 void GraphicsContext::restorePlatformState()
229 {
230     ASSERT(!isRecording());
231     platformContext()->restore();
232     m_data->restore();
233
234     platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur),
235                                                     m_state.shadowOffset,
236                                                     m_state.shadowColor,
237                                                     m_state.shadowsIgnoreTransforms);
238 }
239
240 // Draws a filled rectangle with a stroked border.
241 void GraphicsContext::drawRect(const FloatRect& rect, float borderThickness)
242 {
243     if (paintingDisabled())
244         return;
245
246     if (isRecording()) {
247         m_displayListRecorder->drawRect(rect, borderThickness);
248         return;
249     }
250
251     ASSERT(!rect.isEmpty());
252
253     cairo_t* cr = platformContext()->cr();
254     cairo_save(cr);
255
256     fillRectWithColor(cr, rect, fillColor());
257
258     if (strokeStyle() != NoStroke) {
259         setSourceRGBAFromColor(cr, strokeColor());
260         FloatRect r(rect);
261         r.inflate(-.5f);
262         cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
263         cairo_set_line_width(cr, 1.0); // borderThickness?
264         cairo_stroke(cr);
265     }
266
267     cairo_restore(cr);
268 }
269
270 void GraphicsContext::drawNativeImage(PassNativeImagePtr imagePtr, const FloatSize& imageSize, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, ImageOrientation orientation)
271 {
272     if (paintingDisabled())
273         return;
274
275     if (isRecording()) {
276         m_displayListRecorder->drawNativeImage(imagePtr, imageSize, destRect, srcRect, op, blendMode, orientation);
277         return;
278     }
279
280     NativeImagePtr image = imagePtr;
281
282     platformContext()->save();
283
284     // Set the compositing operation.
285     if (op == CompositeSourceOver && blendMode == BlendModeNormal)
286         setCompositeOperation(CompositeCopy);
287     else
288         setCompositeOperation(op, blendMode);
289
290     FloatRect dst = destRect;
291
292     if (orientation != DefaultImageOrientation) {
293         // ImageOrientation expects the origin to be at (0, 0).
294         translate(dst.x(), dst.y());
295         dst.setLocation(FloatPoint());
296         concatCTM(orientation.transformFromDefault(dst.size()));
297         if (orientation.usesWidthAsHeight()) {
298             // The destination rectangle will have its width and height already reversed for the orientation of
299             // the image, as it was needed for page layout, so we need to reverse it back here.
300             dst = FloatRect(dst.x(), dst.y(), dst.height(), dst.width());
301         }
302     }
303
304     platformContext()->drawSurfaceToContext(image.get(), dst, srcRect, *this);
305     platformContext()->restore();
306 }
307
308 // This is only used to draw borders, so we should not draw shadows.
309 void GraphicsContext::drawLine(const FloatPoint& point1, const FloatPoint& point2)
310 {
311     if (paintingDisabled())
312         return;
313
314     if (strokeStyle() == NoStroke)
315         return;
316
317     if (isRecording()) {
318         m_displayListRecorder->drawLine(point1, point2);
319         return;
320     }
321
322     const Color& strokeColor = this->strokeColor();
323     float thickness = strokeThickness();
324     bool isVerticalLine = (point1.x() + thickness == point2.x());
325     float strokeWidth = isVerticalLine ? point2.y() - point1.y() : point2.x() - point1.x();
326     if (!thickness || !strokeWidth)
327         return;
328
329     cairo_t* cairoContext = platformContext()->cr();
330     StrokeStyle strokeStyle = this->strokeStyle();
331     float cornerWidth = 0;
332     bool drawsDashedLine = strokeStyle == DottedStroke || strokeStyle == DashedStroke;
333
334     if (drawsDashedLine) {
335         cairo_save(cairoContext);
336         // Figure out end points to ensure we always paint corners.
337         cornerWidth = strokeStyle == DottedStroke ? thickness : std::min(2 * thickness, std::max(thickness, strokeWidth / 3));
338         if (isVerticalLine) {
339             fillRectWithColor(cairoContext, FloatRect(point1.x(), point1.y(), thickness, cornerWidth), strokeColor);
340             fillRectWithColor(cairoContext, FloatRect(point1.x(), point2.y() - cornerWidth, thickness, cornerWidth), strokeColor);
341         } else {
342             fillRectWithColor(cairoContext, FloatRect(point1.x(), point1.y(), cornerWidth, thickness), strokeColor);
343             fillRectWithColor(cairoContext, FloatRect(point2.x() - cornerWidth, point1.y(), cornerWidth, thickness), strokeColor);
344         }
345         strokeWidth -= 2 * cornerWidth;
346         float patternWidth = strokeStyle == DottedStroke ? thickness : std::min(3 * thickness, std::max(thickness, strokeWidth / 3));
347         // Check if corner drawing sufficiently covers the line.
348         if (strokeWidth <= patternWidth + 1) {
349             cairo_restore(cairoContext);
350             return;
351         }
352
353         // Pattern starts with full fill and ends with the empty fill.
354         // 1. Let's start with the empty phase after the corner.
355         // 2. Check if we've got odd or even number of patterns and whether they fully cover the line.
356         // 3. In case of even number of patterns and/or remainder, move the pattern start position
357         // so that the pattern is balanced between the corners.
358         float patternOffset = patternWidth;
359         int numberOfSegments = std::floor(strokeWidth / patternWidth);
360         bool oddNumberOfSegments = numberOfSegments % 2;
361         float remainder = strokeWidth - (numberOfSegments * patternWidth);
362         if (oddNumberOfSegments && remainder)
363             patternOffset -= remainder / 2.f;
364         else if (!oddNumberOfSegments) {
365             if (remainder)
366                 patternOffset += patternOffset - (patternWidth + remainder) / 2.f;
367             else
368                 patternOffset += patternWidth / 2.f;
369         }
370         const double dashedLine[2] = { static_cast<double>(patternWidth), static_cast<double>(patternWidth) };
371         cairo_set_dash(cairoContext, dashedLine, 2, patternOffset);
372     } else {
373         setSourceRGBAFromColor(cairoContext, strokeColor);
374         if (thickness < 1)
375             cairo_set_line_width(cairoContext, 1);
376     }
377
378
379     FloatPoint p1 = point1;
380     FloatPoint p2 = point2;
381     // Center line and cut off corners for pattern patining.
382     if (isVerticalLine) {
383         float centerOffset = (p2.x() - p1.x()) / 2;
384         p1.move(centerOffset, cornerWidth);
385         p2.move(-centerOffset, -cornerWidth);
386     } else {
387         float centerOffset = (p2.y() - p1.y()) / 2;
388         p1.move(cornerWidth, centerOffset);
389         p2.move(-cornerWidth, -centerOffset);
390     }
391
392     if (shouldAntialias())
393         cairo_set_antialias(cairoContext, CAIRO_ANTIALIAS_NONE);
394
395     cairo_new_path(cairoContext);
396     cairo_move_to(cairoContext, p1.x(), p1.y());
397     cairo_line_to(cairoContext, p2.x(), p2.y());
398     cairo_stroke(cairoContext);
399     if (drawsDashedLine)
400         cairo_restore(cairoContext);
401     if (shouldAntialias())
402         cairo_set_antialias(cairoContext, CAIRO_ANTIALIAS_DEFAULT);
403 }
404
405 // This method is only used to draw the little circles used in lists.
406 void GraphicsContext::drawEllipse(const FloatRect& rect)
407 {
408     if (paintingDisabled())
409         return;
410
411     if (isRecording()) {
412         m_displayListRecorder->drawEllipse(rect);
413         return;
414     }
415
416     cairo_t* cr = platformContext()->cr();
417     cairo_save(cr);
418     float yRadius = .5 * rect.height();
419     float xRadius = .5 * rect.width();
420     cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
421     cairo_scale(cr, xRadius, yRadius);
422     cairo_arc(cr, 0., 0., 1., 0., 2 * piFloat);
423     cairo_restore(cr);
424
425     if (fillColor().alpha()) {
426         setSourceRGBAFromColor(cr, fillColor());
427         cairo_fill_preserve(cr);
428     }
429
430     if (strokeStyle() != NoStroke) {
431         setSourceRGBAFromColor(cr, strokeColor());
432         cairo_set_line_width(cr, strokeThickness());
433         cairo_stroke(cr);
434     } else
435         cairo_new_path(cr);
436 }
437
438 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
439 {
440     if (paintingDisabled())
441         return;
442
443     if (npoints <= 1)
444         return;
445
446     if (isRecording()) {
447         m_displayListRecorder->drawConvexPolygon(npoints, points, shouldAntialias);
448         return;
449     }
450
451     cairo_t* cr = platformContext()->cr();
452
453     cairo_save(cr);
454     cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
455     addConvexPolygonToContext(cr, npoints, points);
456
457     if (fillColor().alpha()) {
458         setSourceRGBAFromColor(cr, fillColor());
459         cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
460         cairo_fill_preserve(cr);
461     }
462
463     if (strokeStyle() != NoStroke) {
464         setSourceRGBAFromColor(cr, strokeColor());
465         cairo_set_line_width(cr, strokeThickness());
466         cairo_stroke(cr);
467     } else
468         cairo_new_path(cr);
469
470     cairo_restore(cr);
471 }
472
473 void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased)
474 {
475     if (paintingDisabled())
476         return;
477
478     if (numPoints <= 1)
479         return;
480
481     if (isRecording()) {
482         m_displayListRecorder->clipConvexPolygon(numPoints, points, antialiased);
483         return;
484     }
485
486     cairo_t* cr = platformContext()->cr();
487
488     cairo_new_path(cr);
489     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
490     cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr);
491
492     cairo_set_antialias(cr, antialiased ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
493     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
494     addConvexPolygonToContext(cr, numPoints, points);
495     cairo_clip(cr);
496
497     cairo_set_antialias(cr, savedAntialiasRule);
498     cairo_set_fill_rule(cr, savedFillRule);
499 }
500
501 void GraphicsContext::fillPath(const Path& path)
502 {
503     if (paintingDisabled() || path.isEmpty())
504         return;
505
506     if (isRecording()) {
507         m_displayListRecorder->fillPath(path);
508         return;
509     }
510
511     cairo_t* cr = platformContext()->cr();
512     setPathOnCairoContext(cr, path.platformPath()->context());
513     shadowAndFillCurrentCairoPath(*this);
514 }
515
516 void GraphicsContext::strokePath(const Path& path)
517 {
518     if (paintingDisabled() || path.isEmpty())
519         return;
520
521     if (isRecording()) {
522         m_displayListRecorder->strokePath(path);
523         return;
524     }
525
526     cairo_t* cr = platformContext()->cr();
527     setPathOnCairoContext(cr, path.platformPath()->context());
528     shadowAndStrokeCurrentCairoPath(*this);
529 }
530
531 void GraphicsContext::fillRect(const FloatRect& rect)
532 {
533     if (paintingDisabled())
534         return;
535
536     if (isRecording()) {
537         m_displayListRecorder->fillRect(rect);
538         return;
539     }
540
541     cairo_t* cr = platformContext()->cr();
542     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
543     shadowAndFillCurrentCairoPath(*this);
544 }
545
546 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
547 {
548     if (paintingDisabled())
549         return;
550
551     if (isRecording()) {
552         m_displayListRecorder->fillRect(rect, color);
553         return;
554     }
555
556     if (hasShadow())
557         platformContext()->shadowBlur().drawRectShadow(*this, FloatRoundedRect(rect));
558
559     fillRectWithColor(platformContext()->cr(), rect, color);
560 }
561
562 void GraphicsContext::clip(const FloatRect& rect)
563 {
564     if (paintingDisabled())
565         return;
566
567     if (isRecording()) {
568         m_displayListRecorder->clip(rect);
569         return;
570     }
571
572     cairo_t* cr = platformContext()->cr();
573     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
574     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
575     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
576     // The rectangular clip function is traditionally not expected to
577     // antialias. If we don't force antialiased clipping here,
578     // edge fringe artifacts may occur at the layer edges
579     // when a transformation is applied to the GraphicsContext
580     // while drawing the transformed layer.
581     cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr);
582     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
583     cairo_clip(cr);
584     cairo_set_fill_rule(cr, savedFillRule);
585     cairo_set_antialias(cr, savedAntialiasRule);
586     m_data->clip(rect);
587 }
588
589 void GraphicsContext::clipPath(const Path& path, WindRule clipRule)
590 {
591     if (paintingDisabled())
592         return;
593
594     if (isRecording()) {
595         m_displayListRecorder->clipPath(path, clipRule);
596         return;
597     }
598
599     cairo_t* cr = platformContext()->cr();
600     if (!path.isNull())
601         setPathOnCairoContext(cr, path.platformPath()->context());
602
603     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
604     cairo_set_fill_rule(cr, clipRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
605     cairo_clip(cr);
606     cairo_set_fill_rule(cr, savedFillRule);
607
608     m_data->clip(path);
609 }
610
611 void GraphicsContext::clipToImageBuffer(ImageBuffer& buffer, const FloatRect& destRect)
612 {
613     if (paintingDisabled())
614         return;
615
616     RefPtr<Image> image = buffer.copyImage(DontCopyBackingStore);
617     RefPtr<cairo_surface_t> surface = image->nativeImageForCurrentFrame();
618     if (surface)
619         platformContext()->pushImageMask(surface.get(), destRect);
620 }
621
622 IntRect GraphicsContext::clipBounds() const
623 {
624     if (paintingDisabled())
625         return IntRect();
626
627     if (isRecording()) {
628         WTFLogAlways("Getting the clip bounds not yet supported with display lists");
629         return IntRect(-2048, -2048, 4096, 4096); // FIXME: display lists.
630     }
631
632     double x1, x2, y1, y2;
633     cairo_clip_extents(platformContext()->cr(), &x1, &y1, &x2, &y2);
634     return enclosingIntRect(FloatRect(x1, y1, x2 - x1, y2 - y1));
635 }
636
637 static inline void adjustFocusRingColor(Color& color)
638 {
639 #if !PLATFORM(GTK)
640     // Force the alpha to 50%.  This matches what the Mac does with outline rings.
641     color.setRGB(makeRGBA(color.red(), color.green(), color.blue(), 127));
642 #else
643     UNUSED_PARAM(color);
644 #endif
645 }
646
647 static inline void adjustFocusRingLineWidth(float& width)
648 {
649 #if PLATFORM(GTK)
650     width = 2;
651 #else
652     UNUSED_PARAM(width);
653 #endif
654 }
655
656 static inline StrokeStyle focusRingStrokeStyle()
657 {
658 #if PLATFORM(GTK)
659     return DottedStroke;
660 #else
661     return SolidStroke;
662 #endif
663 }
664
665 void GraphicsContext::drawFocusRing(const Path& path, float width, float /* offset */, const Color& color)
666 {
667     // FIXME: We should draw paths that describe a rectangle with rounded corners
668     // so as to be consistent with how we draw rectangular focus rings.
669     Color ringColor = color;
670     adjustFocusRingColor(ringColor);
671     adjustFocusRingLineWidth(width);
672
673     cairo_t* cr = platformContext()->cr();
674     cairo_save(cr);
675     appendWebCorePathToCairoContext(cr, path);
676     setSourceRGBAFromColor(cr, ringColor);
677     cairo_set_line_width(cr, width);
678     setPlatformStrokeStyle(focusRingStrokeStyle());
679     cairo_stroke(cr);
680     cairo_restore(cr);
681 }
682
683 void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, float width, float /* offset */, const Color& color)
684 {
685     if (paintingDisabled())
686         return;
687
688     cairo_t* cr = platformContext()->cr();
689     cairo_save(cr);
690     cairo_push_group(cr);
691     cairo_new_path(cr);
692
693 #if PLATFORM(GTK)
694     for (const auto& rect : rects)
695         cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
696 #else
697     unsigned rectCount = rects.size();
698     int radius = (width - 1) / 2;
699     Path path;
700     for (unsigned i = 0; i < rectCount; ++i) {
701         if (i > 0)
702             path.clear();
703         path.addRoundedRect(rects[i], FloatSize(radius, radius));
704         appendWebCorePathToCairoContext(cr, path);
705     }
706 #endif
707     Color ringColor = color;
708     adjustFocusRingColor(ringColor);
709     adjustFocusRingLineWidth(width);
710     setSourceRGBAFromColor(cr, ringColor);
711     cairo_set_line_width(cr, width);
712     setPlatformStrokeStyle(focusRingStrokeStyle());
713
714     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
715     cairo_stroke_preserve(cr);
716
717     cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
718     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
719     cairo_fill(cr);
720
721     cairo_pop_group_to_source(cr);
722     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
723     cairo_paint(cr);
724     cairo_restore(cr);
725 }
726
727 void GraphicsContext::drawLineForText(const FloatPoint& origin, float width, bool printing, bool doubleUnderlines)
728 {
729     DashArray widths;
730     widths.append(width);
731     widths.append(0);
732     drawLinesForText(origin, widths, printing, doubleUnderlines);
733 }
734
735 void GraphicsContext::drawLinesForText(const FloatPoint& point, const DashArray& widths, bool printing, bool doubleUnderlines)
736 {
737     if (paintingDisabled())
738         return;
739
740     if (widths.size() <= 0)
741         return;
742
743     if (isRecording()) {
744         m_displayListRecorder->drawLinesForText(point, widths, printing, doubleUnderlines, strokeThickness());
745         return;
746     }
747
748     Color localStrokeColor(strokeColor());
749
750     FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(point, widths.last(), printing, localStrokeColor);
751
752     Vector<FloatRect, 4> dashBounds;
753     ASSERT(!(widths.size() % 2));
754     dashBounds.reserveInitialCapacity(dashBounds.size() / 2);
755     for (size_t i = 0; i < widths.size(); i += 2)
756         dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y()), FloatSize(widths[i+1] - widths[i], bounds.height())));
757
758     if (doubleUnderlines) {
759         // The space between double underlines is equal to the height of the underline
760         for (size_t i = 0; i < widths.size(); i += 2)
761             dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y() + 2 * bounds.height()), FloatSize(widths[i+1] - widths[i], bounds.height())));
762     }
763
764     cairo_t* cr = platformContext()->cr();
765     cairo_save(cr);
766
767     for (auto& dash : dashBounds)
768         fillRectWithColor(cr, dash, localStrokeColor);
769
770     cairo_restore(cr);
771 }
772
773 void GraphicsContext::updateDocumentMarkerResources()
774 {
775     // Unnecessary, since our document markers don't use resources.
776 }
777
778 void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& origin, float width, DocumentMarkerLineStyle style)
779 {
780     if (paintingDisabled())
781         return;
782
783     cairo_t* cr = platformContext()->cr();
784     cairo_save(cr);
785
786     switch (style) {
787     case DocumentMarkerSpellingLineStyle:
788         cairo_set_source_rgb(cr, 1, 0, 0);
789         break;
790     case DocumentMarkerGrammarLineStyle:
791         cairo_set_source_rgb(cr, 0, 1, 0);
792         break;
793     default:
794         cairo_restore(cr);
795         return;
796     }
797
798     drawErrorUnderline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness);
799
800     cairo_restore(cr);
801 }
802
803 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect, RoundingMode)
804 {
805     if (paintingDisabled())
806         return frect;
807
808     if (!isRecording()) {
809         WTFLogAlways("GraphicsContext::roundToDevicePixels() is not yet compatible with recording contexts.");
810         return frect;
811     }
812
813     FloatRect result;
814     double x = frect.x();
815     double y = frect.y();
816     cairo_t* cr = platformContext()->cr();
817     cairo_user_to_device(cr, &x, &y);
818     x = round(x);
819     y = round(y);
820     cairo_device_to_user(cr, &x, &y);
821     result.setX(narrowPrecisionToFloat(x));
822     result.setY(narrowPrecisionToFloat(y));
823
824     // We must ensure width and height are at least 1 (or -1) when
825     // we're given float values in the range between 0 and 1 (or -1 and 0).
826     double width = frect.width();
827     double height = frect.height();
828     cairo_user_to_device_distance(cr, &width, &height);
829     if (width > -1 && width < 0)
830         width = -1;
831     else if (width > 0 && width < 1)
832         width = 1;
833     else
834         width = round(width);
835     if (height > -1 && width < 0)
836         height = -1;
837     else if (height > 0 && height < 1)
838         height = 1;
839     else
840         height = round(height);
841     cairo_device_to_user_distance(cr, &width, &height);
842     result.setWidth(narrowPrecisionToFloat(width));
843     result.setHeight(narrowPrecisionToFloat(height));
844
845     return result;
846 }
847
848 void GraphicsContext::translate(float x, float y)
849 {
850     if (paintingDisabled())
851         return;
852
853     if (isRecording()) {
854         m_displayListRecorder->translate(x, y);
855         return;
856     }
857
858     cairo_t* cr = platformContext()->cr();
859     cairo_translate(cr, x, y);
860     m_data->translate(x, y);
861 }
862
863 void GraphicsContext::setPlatformFillColor(const Color&)
864 {
865     // Cairo contexts can't hold separate fill and stroke colors
866     // so we set them just before we actually fill or stroke
867 }
868
869 void GraphicsContext::setPlatformStrokeColor(const Color&)
870 {
871     // Cairo contexts can't hold separate fill and stroke colors
872     // so we set them just before we actually fill or stroke
873 }
874
875 void GraphicsContext::setPlatformStrokeThickness(float strokeThickness)
876 {
877     if (paintingDisabled())
878         return;
879
880     ASSERT(!isRecording());
881
882     cairo_set_line_width(platformContext()->cr(), strokeThickness);
883 }
884
885 void GraphicsContext::setPlatformStrokeStyle(StrokeStyle strokeStyle)
886 {
887     static const double dashPattern[] = { 5.0, 5.0 };
888     static const double dotPattern[] = { 1.0, 1.0 };
889
890     if (paintingDisabled())
891         return;
892
893     ASSERT(!isRecording());
894
895     switch (strokeStyle) {
896     case NoStroke:
897         // FIXME: is it the right way to emulate NoStroke?
898         cairo_set_line_width(platformContext()->cr(), 0);
899         break;
900     case SolidStroke:
901     case DoubleStroke:
902     case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94110 - Needs platform support.
903         cairo_set_dash(platformContext()->cr(), 0, 0, 0);
904         break;
905     case DottedStroke:
906         cairo_set_dash(platformContext()->cr(), dotPattern, 2, 0);
907         break;
908     case DashedStroke:
909         cairo_set_dash(platformContext()->cr(), dashPattern, 2, 0);
910         break;
911     }
912 }
913
914 void GraphicsContext::setURLForRect(const URL&, const IntRect&)
915 {
916     notImplemented();
917 }
918
919 void GraphicsContext::concatCTM(const AffineTransform& transform)
920 {
921     if (paintingDisabled())
922         return;
923
924     if (isRecording()) {
925         m_displayListRecorder->concatCTM(transform);
926         return;
927     }
928
929     cairo_t* cr = platformContext()->cr();
930     const cairo_matrix_t matrix = cairo_matrix_t(transform);
931     cairo_transform(cr, &matrix);
932     m_data->concatCTM(transform);
933 }
934
935 void GraphicsContext::setCTM(const AffineTransform& transform)
936 {
937     if (paintingDisabled())
938         return;
939
940     if (isRecording()) {
941         WTFLogAlways("GraphicsContext::setCTM() is not compatible with recording contexts.");
942         return;
943     }
944
945     cairo_t* cr = platformContext()->cr();
946     const cairo_matrix_t matrix = cairo_matrix_t(transform);
947     cairo_set_matrix(cr, &matrix);
948     m_data->setCTM(transform);
949 }
950
951 void GraphicsContext::setPlatformShadow(FloatSize const& size, float, Color const&)
952 {
953     if (paintingDisabled())
954         return;
955
956     if (m_state.shadowsIgnoreTransforms) {
957         // Meaning that this graphics context is associated with a CanvasRenderingContext
958         // We flip the height since CG and HTML5 Canvas have opposite Y axis
959         m_state.shadowOffset = FloatSize(size.width(), -size.height());
960     }
961
962     // Cairo doesn't support shadows natively, they are drawn manually in the draw* functions using ShadowBlur.
963     platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur),
964                                                     m_state.shadowOffset,
965                                                     m_state.shadowColor,
966                                                     m_state.shadowsIgnoreTransforms);
967 }
968
969 void GraphicsContext::clearPlatformShadow()
970 {
971     if (paintingDisabled())
972         return;
973
974     platformContext()->shadowBlur().clear();
975 }
976
977 void GraphicsContext::beginPlatformTransparencyLayer(float opacity)
978 {
979     if (paintingDisabled())
980         return;
981
982     ASSERT(!isRecording());
983
984     cairo_t* cr = platformContext()->cr();
985     cairo_push_group(cr);
986     m_data->layers.append(opacity);
987 }
988
989 void GraphicsContext::endPlatformTransparencyLayer()
990 {
991     if (paintingDisabled())
992         return;
993
994     ASSERT(!isRecording());
995
996     cairo_t* cr = platformContext()->cr();
997
998     cairo_pop_group_to_source(cr);
999     cairo_paint_with_alpha(cr, m_data->layers.last());
1000     m_data->layers.removeLast();
1001 }
1002
1003 bool GraphicsContext::supportsTransparencyLayers()
1004 {
1005     return true;
1006 }
1007
1008 void GraphicsContext::clearRect(const FloatRect& rect)
1009 {
1010     if (paintingDisabled())
1011         return;
1012
1013     if (isRecording()) {
1014         m_displayListRecorder->clearRect(rect);
1015         return;
1016     }
1017
1018     cairo_t* cr = platformContext()->cr();
1019
1020     cairo_save(cr);
1021     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
1022     cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
1023     cairo_fill(cr);
1024     cairo_restore(cr);
1025 }
1026
1027 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
1028 {
1029     if (paintingDisabled())
1030         return;
1031
1032     if (isRecording()) {
1033         m_displayListRecorder->strokeRect(rect, width);
1034         return;
1035     }
1036
1037     cairo_t* cr = platformContext()->cr();
1038     cairo_save(cr);
1039     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
1040     cairo_set_line_width(cr, width);
1041     shadowAndStrokeCurrentCairoPath(*this);
1042     cairo_restore(cr);
1043 }
1044
1045 void GraphicsContext::setLineCap(LineCap lineCap)
1046 {
1047     if (paintingDisabled())
1048         return;
1049
1050     if (isRecording()) {
1051         m_displayListRecorder->setLineCap(lineCap);
1052         return;
1053     }
1054
1055     cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT;
1056     switch (lineCap) {
1057     case ButtCap:
1058         // no-op
1059         break;
1060     case RoundCap:
1061         cairoCap = CAIRO_LINE_CAP_ROUND;
1062         break;
1063     case SquareCap:
1064         cairoCap = CAIRO_LINE_CAP_SQUARE;
1065         break;
1066     }
1067     cairo_set_line_cap(platformContext()->cr(), cairoCap);
1068 }
1069
1070 static inline bool isDashArrayAllZero(const DashArray& dashes)
1071 {
1072     for (auto& dash : dashes) {
1073         if (dash)
1074             return false;
1075     }
1076     return true;
1077 }
1078
1079 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
1080 {
1081     if (paintingDisabled())
1082         return;
1083
1084     if (isRecording()) {
1085         m_displayListRecorder->setLineDash(dashes, dashOffset);
1086         return;
1087     }
1088
1089     if (isDashArrayAllZero(dashes))
1090         cairo_set_dash(platformContext()->cr(), 0, 0, 0);
1091     else
1092         cairo_set_dash(platformContext()->cr(), dashes.data(), dashes.size(), dashOffset);
1093 }
1094
1095 void GraphicsContext::setLineJoin(LineJoin lineJoin)
1096 {
1097     if (paintingDisabled())
1098         return;
1099
1100     if (isRecording()) {
1101         m_displayListRecorder->setLineJoin(lineJoin);
1102         return;
1103     }
1104
1105     cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER;
1106     switch (lineJoin) {
1107     case MiterJoin:
1108         // no-op
1109         break;
1110     case RoundJoin:
1111         cairoJoin = CAIRO_LINE_JOIN_ROUND;
1112         break;
1113     case BevelJoin:
1114         cairoJoin = CAIRO_LINE_JOIN_BEVEL;
1115         break;
1116     }
1117     cairo_set_line_join(platformContext()->cr(), cairoJoin);
1118 }
1119
1120 void GraphicsContext::setMiterLimit(float miter)
1121 {
1122     if (paintingDisabled())
1123         return;
1124
1125     cairo_set_miter_limit(platformContext()->cr(), miter);
1126 }
1127
1128 void GraphicsContext::setPlatformAlpha(float alpha)
1129 {
1130     platformContext()->setGlobalAlpha(alpha);
1131 }
1132
1133 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op, BlendMode blendOp)
1134 {
1135     if (paintingDisabled())
1136         return;
1137
1138     cairo_operator_t cairo_op;
1139     if (blendOp == BlendModeNormal)
1140         cairo_op = toCairoOperator(op);
1141     else
1142         cairo_op = toCairoOperator(blendOp);
1143
1144     cairo_set_operator(platformContext()->cr(), cairo_op);
1145 }
1146
1147 void GraphicsContext::canvasClip(const Path& path, WindRule windRule)
1148 {
1149     clipPath(path, windRule);
1150 }
1151
1152 void GraphicsContext::clipOut(const Path& path)
1153 {
1154     if (paintingDisabled())
1155         return;
1156
1157     if (isRecording()) {
1158         m_displayListRecorder->clipOut(path);
1159         return;
1160     }
1161
1162     cairo_t* cr = platformContext()->cr();
1163     double x1, y1, x2, y2;
1164     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1165     cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1166     appendWebCorePathToCairoContext(cr, path);
1167
1168     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1169     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1170     cairo_clip(cr);
1171     cairo_set_fill_rule(cr, savedFillRule);
1172 }
1173
1174 void GraphicsContext::rotate(float radians)
1175 {
1176     if (paintingDisabled())
1177         return;
1178
1179     if (isRecording()) {
1180         m_displayListRecorder->rotate(radians);
1181         return;
1182     }
1183
1184     cairo_rotate(platformContext()->cr(), radians);
1185     m_data->rotate(radians);
1186 }
1187
1188 void GraphicsContext::scale(const FloatSize& size)
1189 {
1190     if (paintingDisabled())
1191         return;
1192
1193     if (isRecording()) {
1194         m_displayListRecorder->scale(size);
1195         return;
1196     }
1197
1198     cairo_scale(platformContext()->cr(), size.width(), size.height());
1199     m_data->scale(size);
1200 }
1201
1202 void GraphicsContext::clipOut(const FloatRect& r)
1203 {
1204     if (paintingDisabled())
1205         return;
1206
1207     if (isRecording()) {
1208         m_displayListRecorder->clipOut(r);
1209         return;
1210     }
1211
1212     cairo_t* cr = platformContext()->cr();
1213     double x1, y1, x2, y2;
1214     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1215     cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1216     cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
1217     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1218     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1219     cairo_clip(cr);
1220     cairo_set_fill_rule(cr, savedFillRule);
1221 }
1222
1223 void GraphicsContext::platformFillRoundedRect(const FloatRoundedRect& rect, const Color& color)
1224 {
1225     if (paintingDisabled())
1226         return;
1227
1228     ASSERT(!isRecording());
1229
1230     if (hasShadow())
1231         platformContext()->shadowBlur().drawRectShadow(*this, rect);
1232
1233     cairo_t* cr = platformContext()->cr();
1234     cairo_save(cr);
1235     Path path;
1236     path.addRoundedRect(rect);
1237     appendWebCorePathToCairoContext(cr, path);
1238     setSourceRGBAFromColor(cr, color);
1239     cairo_fill(cr);
1240     cairo_restore(cr);
1241 }
1242
1243 void GraphicsContext::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color)
1244 {
1245     if (paintingDisabled() || !color.isValid())
1246         return;
1247
1248     if (isRecording()) {
1249         m_displayListRecorder->fillRectWithRoundedHole(rect, roundedHoleRect, color);
1250         return;
1251     }
1252
1253     if (this->mustUseShadowBlur())
1254         platformContext()->shadowBlur().drawInsetShadow(*this, rect, roundedHoleRect);
1255
1256     Path path;
1257     path.addRect(rect);
1258     if (!roundedHoleRect.radii().isZero())
1259         path.addRoundedRect(roundedHoleRect);
1260     else
1261         path.addRect(roundedHoleRect.rect());
1262
1263     cairo_t* cr = platformContext()->cr();
1264     cairo_save(cr);
1265     setPathOnCairoContext(platformContext()->cr(), path.platformPath()->context());
1266     fillCurrentCairoPath(*this);
1267     cairo_restore(cr);
1268 }
1269
1270 void GraphicsContext::drawPattern(Image& image, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, const FloatRect& destRect, BlendMode blendMode)
1271 {
1272     if (paintingDisabled())
1273         return;
1274
1275     if (isRecording()) {
1276         m_displayListRecorder->drawPattern(image, tileRect, patternTransform, phase, spacing, op, destRect, blendMode);
1277         return;
1278     }
1279
1280     RefPtr<cairo_surface_t> surface = image.nativeImageForCurrentFrame();
1281     if (!surface) // If it's too early we won't have an image yet.
1282         return;
1283
1284     cairo_t* cr = platformContext()->cr();
1285     drawPatternToCairoContext(cr, surface.get(), IntSize(image.size()), tileRect, patternTransform, phase, toCairoOperator(op), destRect);
1286 }
1287
1288 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1289 {
1290     if (paintingDisabled())
1291         return;
1292
1293     ASSERT(!isRecording());
1294
1295     // When true, use the default Cairo backend antialias mode (usually this
1296     // enables standard 'grayscale' antialiasing); false to explicitly disable
1297     // antialiasing. This is the same strategy as used in drawConvexPolygon().
1298     cairo_set_antialias(platformContext()->cr(), enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1299 }
1300
1301 void GraphicsContext::setPlatformImageInterpolationQuality(InterpolationQuality quality)
1302 {
1303     ASSERT(!isRecording());
1304
1305     platformContext()->setImageInterpolationQuality(quality);
1306 }
1307
1308 bool GraphicsContext::isAcceleratedContext() const
1309 {
1310     if (isRecording())
1311         return false;
1312
1313     return cairo_surface_get_type(cairo_get_target(platformContext()->cr())) == CAIRO_SURFACE_TYPE_GL;
1314 }
1315
1316 #if ENABLE(3D_TRANSFORMS) && USE(TEXTURE_MAPPER)
1317 TransformationMatrix GraphicsContext::get3DTransform() const
1318 {
1319     // FIXME: Can we approximate the transformation better than this?
1320     return getCTM().toTransformationMatrix();
1321 }
1322
1323 void GraphicsContext::concat3DTransform(const TransformationMatrix& transform)
1324 {
1325     concatCTM(transform.toAffineTransform());
1326 }
1327
1328 void GraphicsContext::set3DTransform(const TransformationMatrix& transform)
1329 {
1330     setCTM(transform.toAffineTransform());
1331 }
1332 #endif // ENABLE(3D_TRANSFORMS) && USE(TEXTURE_MAPPER)
1333
1334 } // namespace WebCore
1335
1336 #endif // USE(CAIRO)