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