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