f65ee256572b218ed7de9ff18ab6306ea3cf7947
[WebKit-https.git] / Source / WebCore / platform / graphics / cairo / CairoOperations.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 "CairoOperations.h"
35
36 #if USE(CAIRO)
37
38 #include "CairoUtilities.h"
39 #include "DrawErrorUnderline.h"
40 #include "FloatConversion.h"
41 #include "FloatRect.h"
42 #include "GraphicsContext.h"
43 #include "GraphicsContextPlatformPrivateCairo.h"
44 #include "Image.h"
45 #include "ImageBuffer.h"
46 #include "Path.h"
47 #include "PlatformContextCairo.h"
48 #include "PlatformPathCairo.h"
49 #include "ShadowBlur.h"
50 #include <algorithm>
51 #include <cairo.h>
52
53 namespace WebCore {
54 namespace Cairo {
55
56 enum PatternAdjustment { NoAdjustment, AdjustPatternForGlobalAlpha };
57 enum AlphaPreservation { DoNotPreserveAlpha, PreserveAlpha };
58
59 static void reduceSourceByAlpha(cairo_t* cr, float alpha)
60 {
61     if (alpha >= 1)
62         return;
63     cairo_push_group(cr);
64     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
65     cairo_paint_with_alpha(cr, alpha);
66     cairo_pop_group_to_source(cr);
67 }
68
69 static void prepareCairoContextSource(cairo_t* cr, cairo_pattern_t* pattern, cairo_pattern_t* gradient, const Color& color, float globalAlpha)
70 {
71     if (pattern) {
72         // Pattern source
73         cairo_set_source(cr, pattern);
74         reduceSourceByAlpha(cr, globalAlpha);
75     } else if (gradient) {
76         // Gradient source
77         cairo_set_source(cr, gradient);
78     } else {
79         // Solid color source
80         if (globalAlpha < 1)
81             setSourceRGBAFromColor(cr, colorWithOverrideAlpha(color.rgb(), color.alpha() / 255.f * globalAlpha));
82         else
83             setSourceRGBAFromColor(cr, color);
84     }
85 }
86
87 static void clipForPatternFilling(cairo_t* cr, const FloatSize& patternSize, const AffineTransform& patternTransform, bool repeatX, bool repeatY)
88 {
89     // Hold current cairo path in a variable for restoring it after configuring the pattern clip rectangle.
90     auto currentPath = cairo_copy_path(cr);
91     cairo_new_path(cr);
92
93     // Initialize clipping extent from current cairo clip extents, then shrink if needed according to pattern.
94     // Inspired by GraphicsContextQt::drawRepeatPattern.
95     double x1, y1, x2, y2;
96     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
97     FloatRect clipRect(x1, y1, x2 - x1, y2 - y1);
98
99     FloatRect patternRect = patternTransform.mapRect(FloatRect(FloatPoint(), patternSize));
100
101     if (!repeatX) {
102         clipRect.setX(patternRect.x());
103         clipRect.setWidth(patternRect.width());
104     }
105     if (!repeatY) {
106         clipRect.setY(patternRect.y());
107         clipRect.setHeight(patternRect.height());
108     }
109     if (!repeatX || !repeatY) {
110         cairo_rectangle(cr, clipRect.x(), clipRect.y(), clipRect.width(), clipRect.height());
111         cairo_clip(cr);
112     }
113
114     // Restoring cairo path.
115     cairo_append_path(cr, currentPath);
116     cairo_path_destroy(currentPath);
117 }
118
119 static void prepareForFilling(cairo_t* cr, const Cairo::FillSource& fillSource, PatternAdjustment patternAdjustment)
120 {
121     cairo_set_fill_rule(cr, fillSource.fillRule == WindRule::EvenOdd ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
122
123     bool adjustForAlpha = patternAdjustment == AdjustPatternForGlobalAlpha;
124
125     auto* gradient = fillSource.gradient.base.get();
126     if (adjustForAlpha && fillSource.gradient.alphaAdjusted)
127         gradient = fillSource.gradient.alphaAdjusted.get();
128
129     prepareCairoContextSource(cr, fillSource.pattern.object.get(), gradient,
130         fillSource.color, adjustForAlpha ? fillSource.globalAlpha : 1);
131
132     if (fillSource.pattern.object) {
133         clipForPatternFilling(cr, fillSource.pattern.size, fillSource.pattern.transform,
134             fillSource.pattern.repeatX, fillSource.pattern.repeatY);
135     }
136 }
137
138 static void prepareForStroking(cairo_t* cr, const Cairo::StrokeSource& strokeSource, AlphaPreservation alphaPreservation)
139 {
140     bool preserveAlpha = alphaPreservation == PreserveAlpha;
141
142     auto* gradient = strokeSource.gradient.base.get();
143     if (preserveAlpha && strokeSource.gradient.alphaAdjusted)
144         gradient = strokeSource.gradient.alphaAdjusted.get();
145
146     prepareCairoContextSource(cr, strokeSource.pattern.get(), gradient,
147         strokeSource.color, preserveAlpha ? strokeSource.globalAlpha : 1);
148 }
149
150 static void drawPatternToCairoContext(cairo_t* cr, cairo_pattern_t* pattern, const FloatRect& destRect, float alpha)
151 {
152     cairo_translate(cr, destRect.x(), destRect.y());
153     cairo_set_source(cr, pattern);
154     cairo_rectangle(cr, 0, 0, destRect.width(), destRect.height());
155     cairo_clip(cr);
156     cairo_paint_with_alpha(cr, std::max<float>(0, std::min<float>(1.0, alpha)));
157 }
158
159 static inline void fillRectWithColor(cairo_t* cr, const FloatRect& rect, const Color& color)
160 {
161     if (!color.isVisible() && cairo_get_operator(cr) == CAIRO_OPERATOR_OVER)
162         return;
163
164     setSourceRGBAFromColor(cr, color);
165     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
166     cairo_fill(cr);
167 }
168
169 enum PathDrawingStyle {
170     Fill = 1,
171     Stroke = 2,
172     FillAndStroke = Fill + Stroke
173 };
174
175 static void drawShadowLayerBuffer(PlatformContextCairo& platformContext, ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const ShadowState& shadowState)
176 {
177     RefPtr<Image> image = layerImage.copyImage(DontCopyBackingStore);
178     if (!image)
179         return;
180
181     if (auto surface = image->nativeImageForCurrentFrame()) {
182         drawNativeImage(platformContext, surface.get(), FloatRect(roundedIntPoint(layerOrigin), layerSize), FloatRect(FloatPoint(), layerSize), shadowState.globalCompositeOperator, BlendMode::Normal, ImageOrientation(),
183             InterpolationDefault, shadowState.globalAlpha, ShadowState());
184     }
185 }
186
187 static void fillShadowBuffer(PlatformContextCairo& platformContext, ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect& sourceRect, const ShadowState& shadowState)
188 {
189     save(platformContext);
190
191     RefPtr<Image> image = layerImage.copyImage(DontCopyBackingStore);
192     if (image) {
193         if (auto surface = image->nativeImageForCurrentFrame())
194             clipToImageBuffer(platformContext, surface.get(), FloatRect(layerOrigin, expandedIntSize(layerSize)));
195     }
196
197     FillSource fillSource;
198     fillSource.globalAlpha = shadowState.globalAlpha;
199     fillSource.color = shadowState.color;
200     fillRect(platformContext, FloatRect(layerOrigin, sourceRect.size()), fillSource, ShadowState());
201
202     restore(platformContext);
203 }
204
205 static inline void drawPathShadow(PlatformContextCairo& platformContext, const FillSource& fillSource, const StrokeSource& strokeSource, const ShadowState& shadowState, PathDrawingStyle drawingStyle)
206 {
207     ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
208     if (shadow.type() == ShadowBlur::NoShadow)
209         return;
210
211     // Calculate the extents of the rendered solid paths.
212     cairo_t* cairoContext = platformContext.cr();
213     std::unique_ptr<cairo_path_t, void(*)(cairo_path_t*)> path(cairo_copy_path(cairoContext), [](cairo_path_t* path) {
214         cairo_path_destroy(path);
215     });
216
217     FloatRect solidFigureExtents;
218     double x0 = 0;
219     double x1 = 0;
220     double y0 = 0;
221     double y1 = 0;
222     if (drawingStyle & Stroke) {
223         cairo_stroke_extents(cairoContext, &x0, &y0, &x1, &y1);
224         solidFigureExtents = FloatRect(x0, y0, x1 - x0, y1 - y0);
225     }
226     if (drawingStyle & Fill) {
227         cairo_fill_extents(cairoContext, &x0, &y0, &x1, &y1);
228         FloatRect fillExtents(x0, y0, x1 - x0, y1 - y0);
229         solidFigureExtents.unite(fillExtents);
230     }
231
232     shadow.drawShadowLayer(State::getCTM(platformContext), State::getClipBounds(platformContext), solidFigureExtents,
233         [cairoContext, drawingStyle, &path, &fillSource, &strokeSource](GraphicsContext& shadowContext)
234         {
235             cairo_t* cairoShadowContext = shadowContext.platformContext()->cr();
236
237             // It's important to copy the context properties to the new shadow
238             // context to preserve things such as the fill rule and stroke width.
239             copyContextProperties(cairoContext, cairoShadowContext);
240
241             if (drawingStyle & Fill) {
242                 cairo_save(cairoShadowContext);
243                 cairo_append_path(cairoShadowContext, path.get());
244                 prepareForFilling(cairoShadowContext, fillSource, NoAdjustment);
245                 cairo_fill(cairoShadowContext);
246                 cairo_restore(cairoShadowContext);
247             }
248
249             if (drawingStyle & Stroke) {
250                 cairo_append_path(cairoShadowContext, path.get());
251                 prepareForStroking(cairoShadowContext, strokeSource, DoNotPreserveAlpha);
252                 cairo_stroke(cairoShadowContext);
253             }
254         },
255         [&platformContext, &shadowState, &cairoContext, &path](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect&)
256         {
257             // The original path may still be hanging around on the context and endShadowLayer
258             // will take care of properly creating a path to draw the result shadow. We remove the path
259             // temporarily and then restore it.
260             // See: https://bugs.webkit.org/show_bug.cgi?id=108897
261             cairo_new_path(cairoContext);
262
263             drawShadowLayerBuffer(platformContext, layerImage, layerOrigin, layerSize, shadowState);
264
265             cairo_append_path(cairoContext, path.get());
266         });
267 }
268
269 static inline void fillCurrentCairoPath(PlatformContextCairo& platformContext, const FillSource& fillSource)
270 {
271     cairo_t* cr = platformContext.cr();
272     cairo_save(cr);
273
274     prepareForFilling(cr, fillSource, AdjustPatternForGlobalAlpha);
275     cairo_fill(cr);
276
277     cairo_restore(cr);
278 }
279
280 static inline void adjustFocusRingColor(Color& color)
281 {
282 #if !PLATFORM(GTK)
283     // Force the alpha to 50%. This matches what the Mac does with outline rings.
284     color = Color(makeRGBA(color.red(), color.green(), color.blue(), 127));
285 #else
286     UNUSED_PARAM(color);
287 #endif
288 }
289
290 static inline void adjustFocusRingLineWidth(float& width)
291 {
292 #if PLATFORM(GTK)
293     width = 2;
294 #else
295     UNUSED_PARAM(width);
296 #endif
297 }
298
299 static inline StrokeStyle focusRingStrokeStyle()
300 {
301 #if PLATFORM(GTK)
302     return DottedStroke;
303 #else
304     return SolidStroke;
305 #endif
306 }
307
308 static void drawGlyphsToContext(cairo_t* context, cairo_scaled_font_t* scaledFont, double syntheticBoldOffset, const Vector<cairo_glyph_t>& glyphs)
309 {
310     cairo_matrix_t originalTransform;
311     if (syntheticBoldOffset)
312         cairo_get_matrix(context, &originalTransform);
313
314     cairo_set_scaled_font(context, scaledFont);
315     cairo_show_glyphs(context, glyphs.data(), glyphs.size());
316
317     if (syntheticBoldOffset) {
318         cairo_translate(context, syntheticBoldOffset, 0);
319         cairo_show_glyphs(context, glyphs.data(), glyphs.size());
320
321         cairo_set_matrix(context, &originalTransform);
322     }
323 }
324
325 static void drawGlyphsShadow(PlatformContextCairo& platformContext, const ShadowState& shadowState, TextDrawingModeFlags textDrawingMode, const FloatSize& shadowOffset, const Color& shadowColor, const FloatPoint& point, cairo_scaled_font_t* scaledFont, double syntheticBoldOffset, const Vector<cairo_glyph_t>& glyphs)
326 {
327     ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
328     if (!(textDrawingMode & TextModeFill) || shadow.type() == ShadowBlur::NoShadow)
329         return;
330
331     if (!shadowState.isRequired(platformContext)) {
332         // Optimize non-blurry shadows, by just drawing text without the ShadowBlur.
333         cairo_t* context = platformContext.cr();
334         cairo_save(context);
335
336         cairo_translate(context, shadowOffset.width(), shadowOffset.height());
337         setSourceRGBAFromColor(context, shadowColor);
338         drawGlyphsToContext(context, scaledFont, syntheticBoldOffset, glyphs);
339
340         cairo_restore(context);
341         return;
342     }
343
344     cairo_text_extents_t extents;
345     cairo_scaled_font_glyph_extents(scaledFont, glyphs.data(), glyphs.size(), &extents);
346     FloatRect fontExtentsRect(point.x() + extents.x_bearing, point.y() + extents.y_bearing, extents.width, extents.height);
347
348     shadow.drawShadowLayer(State::getCTM(platformContext), State::getClipBounds(platformContext), fontExtentsRect,
349         [scaledFont, syntheticBoldOffset, &glyphs](GraphicsContext& shadowContext)
350         {
351             drawGlyphsToContext(shadowContext.platformContext()->cr(), scaledFont, syntheticBoldOffset, glyphs);
352         },
353         [&platformContext, &shadowState](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect&)
354         {
355             drawShadowLayerBuffer(platformContext, layerImage, layerOrigin, layerSize, shadowState);
356         });
357 }
358
359 static bool cairoSurfaceHasAlpha(cairo_surface_t* surface)
360 {
361     return cairo_surface_get_content(surface) != CAIRO_CONTENT_COLOR;
362 }
363
364 // FIXME: Fix GraphicsContext::computeLineBoundsAndAntialiasingModeForText()
365 // to be a static public function that operates on CTM and strokeThickness
366 // arguments instead of using an underlying GraphicsContext object.
367 FloatRect computeLineBoundsAndAntialiasingModeForText(PlatformContextCairo& platformContext, const FloatPoint& point, float width, bool printing, Color& color, float strokeThickness)
368 {
369     FloatPoint origin = point;
370     float thickness = std::max(strokeThickness, 0.5f);
371     if (printing)
372         return FloatRect(origin, FloatSize(width, thickness));
373
374     AffineTransform transform = Cairo::State::getCTM(platformContext);
375     // Just compute scale in x dimension, assuming x and y scales are equal.
376     float scale = transform.b() ? sqrtf(transform.a() * transform.a() + transform.b() * transform.b()) : transform.a();
377     if (scale < 1.0) {
378         // This code always draws a line that is at least one-pixel line high,
379         // which tends to visually overwhelm text at small scales. To counter this
380         // effect, an alpha is applied to the underline color when text is at small scales.
381         static const float minimumUnderlineAlpha = 0.4f;
382         float shade = scale > minimumUnderlineAlpha ? scale : minimumUnderlineAlpha;
383         color = color.colorWithAlphaMultipliedBy(shade);
384     }
385
386     FloatPoint devicePoint = transform.mapPoint(point);
387     // Visual overflow might occur here due to integral roundf/ceilf. visualOverflowForDecorations adjusts the overflow value for underline decoration.
388     FloatPoint deviceOrigin = FloatPoint(roundf(devicePoint.x()), ceilf(devicePoint.y()));
389     if (auto inverse = transform.inverse())
390         origin = inverse.value().mapPoint(deviceOrigin);
391     return FloatRect(origin, FloatSize(width, thickness));
392 };
393
394 // FIXME: Replace once GraphicsContext::dashedLineCornerWidthForStrokeWidth()
395 // is refactored as a static public function.
396 static float dashedLineCornerWidthForStrokeWidth(float strokeWidth, StrokeStyle strokeStyle, float strokeThickness)
397 {
398     return strokeStyle == DottedStroke ? strokeThickness : std::min(2.0f * strokeThickness, std::max(strokeThickness, strokeWidth / 3.0f));
399 }
400
401 // FIXME: Replace once GraphicsContext::dashedLinePatternWidthForStrokeWidth()
402 // is refactored as a static public function.
403 static float dashedLinePatternWidthForStrokeWidth(float strokeWidth, StrokeStyle strokeStyle, float strokeThickness)
404 {
405     return strokeStyle == DottedStroke ? strokeThickness : std::min(3.0f * strokeThickness, std::max(strokeThickness, strokeWidth / 3.0f));
406 }
407
408 // FIXME: Replace once GraphicsContext::dashedLinePatternOffsetForPatternAndStrokeWidth()
409 // is refactored as a static public function.
410 static float dashedLinePatternOffsetForPatternAndStrokeWidth(float patternWidth, float strokeWidth)
411 {
412     // Pattern starts with full fill and ends with the empty fill.
413     // 1. Let's start with the empty phase after the corner.
414     // 2. Check if we've got odd or even number of patterns and whether they fully cover the line.
415     // 3. In case of even number of patterns and/or remainder, move the pattern start position
416     // so that the pattern is balanced between the corners.
417     float patternOffset = patternWidth;
418     int numberOfSegments = std::floor(strokeWidth / patternWidth);
419     bool oddNumberOfSegments = numberOfSegments % 2;
420     float remainder = strokeWidth - (numberOfSegments * patternWidth);
421     if (oddNumberOfSegments && remainder)
422         patternOffset -= remainder / 2.0f;
423     else if (!oddNumberOfSegments) {
424         if (remainder)
425             patternOffset += patternOffset - (patternWidth + remainder) / 2.0f;
426         else
427             patternOffset += patternWidth / 2.0f;
428     }
429
430     return patternOffset;
431 }
432
433 // FIXME: Replace once GraphicsContext::centerLineAndCutOffCorners()
434 // is refactored as a static public function.
435 static Vector<FloatPoint> centerLineAndCutOffCorners(bool isVerticalLine, float cornerWidth, FloatPoint point1, FloatPoint point2)
436 {
437     // Center line and cut off corners for pattern painting.
438     if (isVerticalLine) {
439         float centerOffset = (point2.x() - point1.x()) / 2.0f;
440         point1.move(centerOffset, cornerWidth);
441         point2.move(-centerOffset, -cornerWidth);
442     } else {
443         float centerOffset = (point2.y() - point1.y()) / 2.0f;
444         point1.move(cornerWidth, centerOffset);
445         point2.move(-cornerWidth, -centerOffset);
446     }
447
448     return { point1, point2 };
449 }
450
451 namespace State {
452
453 void setStrokeThickness(PlatformContextCairo& platformContext, float strokeThickness)
454 {
455     cairo_set_line_width(platformContext.cr(), strokeThickness);
456 }
457
458 void setStrokeStyle(PlatformContextCairo& platformContext, StrokeStyle strokeStyle)
459 {
460     static const double dashPattern[] = { 5.0, 5.0 };
461     static const double dotPattern[] = { 1.0, 1.0 };
462
463     cairo_t* cr = platformContext.cr();
464     switch (strokeStyle) {
465     case NoStroke:
466         // FIXME: is it the right way to emulate NoStroke?
467         cairo_set_line_width(cr, 0);
468         break;
469     case SolidStroke:
470     case DoubleStroke:
471     case WavyStroke:
472         // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94110 - Needs platform support.
473         cairo_set_dash(cr, 0, 0, 0);
474         break;
475     case DottedStroke:
476         cairo_set_dash(cr, dotPattern, 2, 0);
477         break;
478     case DashedStroke:
479         cairo_set_dash(cr, dashPattern, 2, 0);
480         break;
481     }
482 }
483
484 void setCompositeOperation(PlatformContextCairo& platformContext, CompositeOperator compositeOperation, BlendMode blendMode)
485 {
486     cairo_set_operator(platformContext.cr(), toCairoOperator(compositeOperation, blendMode));
487 }
488
489 void setShouldAntialias(PlatformContextCairo& platformContext, bool enable)
490 {
491     // When true, use the default Cairo backend antialias mode (usually this
492     // enables standard 'grayscale' antialiasing); false to explicitly disable
493     // antialiasing.
494     cairo_set_antialias(platformContext.cr(), enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
495 }
496
497 void setCTM(PlatformContextCairo& platformContext, const AffineTransform& transform)
498 {
499     const cairo_matrix_t matrix = toCairoMatrix(transform);
500     cairo_set_matrix(platformContext.cr(), &matrix);
501
502     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
503         graphicsContextPrivate->setCTM(transform);
504 }
505
506 AffineTransform getCTM(PlatformContextCairo& platformContext)
507 {
508     cairo_matrix_t m;
509     cairo_get_matrix(platformContext.cr(), &m);
510     return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0);
511 }
512
513 IntRect getClipBounds(PlatformContextCairo& platformContext)
514 {
515     double x1, x2, y1, y2;
516     cairo_clip_extents(platformContext.cr(), &x1, &y1, &x2, &y2);
517     return enclosingIntRect(FloatRect(x1, y1, x2 - x1, y2 - y1));
518 }
519
520 FloatRect roundToDevicePixels(PlatformContextCairo& platformContext, const FloatRect& rect)
521 {
522     FloatRect result;
523     double x = rect.x();
524     double y = rect.y();
525
526     cairo_t* cr = platformContext.cr();
527     cairo_user_to_device(cr, &x, &y);
528     x = round(x);
529     y = round(y);
530     cairo_device_to_user(cr, &x, &y);
531     result.setX(narrowPrecisionToFloat(x));
532     result.setY(narrowPrecisionToFloat(y));
533
534     // We must ensure width and height are at least 1 (or -1) when
535     // we're given float values in the range between 0 and 1 (or -1 and 0).
536     double width = rect.width();
537     double height = rect.height();
538     cairo_user_to_device_distance(cr, &width, &height);
539     if (width > -1 && width < 0)
540         width = -1;
541     else if (width > 0 && width < 1)
542         width = 1;
543     else
544         width = round(width);
545     if (height > -1 && height < 0)
546         height = -1;
547     else if (height > 0 && height < 1)
548         height = 1;
549     else
550         height = round(height);
551     cairo_device_to_user_distance(cr, &width, &height);
552     result.setWidth(narrowPrecisionToFloat(width));
553     result.setHeight(narrowPrecisionToFloat(height));
554
555     return result;
556 }
557
558 bool isAcceleratedContext(PlatformContextCairo& platformContext)
559 {
560     return cairo_surface_get_type(cairo_get_target(platformContext.cr())) == CAIRO_SURFACE_TYPE_GL;
561 }
562
563 } // namespace State
564
565 FillSource::FillSource(const GraphicsContextState& state)
566     : globalAlpha(state.alpha)
567     , fillRule(state.fillRule)
568 {
569     if (state.fillPattern) {
570         pattern.object = adoptRef(state.fillPattern->createPlatformPattern(AffineTransform()));
571
572         auto& patternImage = state.fillPattern->tileImage();
573         pattern.size = FloatSize(patternImage.width(), patternImage.height());
574         pattern.transform = state.fillPattern->patternSpaceTransform();
575         pattern.repeatX = state.fillPattern->repeatX();
576         pattern.repeatY = state.fillPattern->repeatY();
577     } else if (state.fillGradient) {
578         gradient.base = adoptRef(state.fillGradient->createPlatformGradient(1));
579         if (state.alpha != 1)
580             gradient.alphaAdjusted = adoptRef(state.fillGradient->createPlatformGradient(state.alpha));
581     } else
582         color = state.fillColor;
583 }
584
585 StrokeSource::StrokeSource(const GraphicsContextState& state)
586     : globalAlpha(state.alpha)
587 {
588     if (state.strokePattern)
589         pattern = adoptRef(state.strokePattern->createPlatformPattern(AffineTransform()));
590     else if (state.strokeGradient) {
591         gradient.base = adoptRef(state.strokeGradient->createPlatformGradient(1));
592         if (state.alpha != 1)
593             gradient.alphaAdjusted = adoptRef(state.strokeGradient->createPlatformGradient(state.alpha));
594     } else
595         color = state.strokeColor;
596 }
597
598 ShadowState::ShadowState(const GraphicsContextState& state)
599     : offset(state.shadowOffset)
600     , blur(state.shadowBlur)
601     , color(state.shadowColor)
602     , ignoreTransforms(state.shadowsIgnoreTransforms)
603     , globalAlpha(state.alpha)
604     , globalCompositeOperator(state.compositeOperator)
605 {
606 }
607
608 bool ShadowState::isVisible() const
609 {
610     return color.isVisible() && (offset.width() || offset.height() || blur);
611 }
612
613 bool ShadowState::isRequired(PlatformContextCairo& platformContext) const
614 {
615     // We can't avoid ShadowBlur if the shadow has blur.
616     if (color.isVisible() && blur)
617         return true;
618
619     // We can avoid ShadowBlur and optimize, since we're not drawing on a
620     // canvas and box shadows are affected by the transformation matrix.
621     if (!ignoreTransforms)
622         return false;
623
624     // We can avoid ShadowBlur, since there are no transformations to apply to the canvas.
625     if (State::getCTM(platformContext).isIdentity())
626         return false;
627
628     // Otherwise, no chance avoiding ShadowBlur.
629     return true;
630 }
631
632 void setLineCap(PlatformContextCairo& platformContext, LineCap lineCap)
633 {
634     cairo_line_cap_t cairoCap;
635     switch (lineCap) {
636     case ButtCap:
637         cairoCap = CAIRO_LINE_CAP_BUTT;
638         break;
639     case RoundCap:
640         cairoCap = CAIRO_LINE_CAP_ROUND;
641         break;
642     case SquareCap:
643         cairoCap = CAIRO_LINE_CAP_SQUARE;
644         break;
645     }
646     cairo_set_line_cap(platformContext.cr(), cairoCap);
647 }
648
649 void setLineDash(PlatformContextCairo& platformContext, const DashArray& dashes, float dashOffset)
650 {
651     if (std::all_of(dashes.begin(), dashes.end(), [](auto& dash) { return !dash; }))
652         cairo_set_dash(platformContext.cr(), 0, 0, 0);
653     else
654         cairo_set_dash(platformContext.cr(), dashes.data(), dashes.size(), dashOffset);
655 }
656
657 void setLineJoin(PlatformContextCairo& platformContext, LineJoin lineJoin)
658 {
659     cairo_line_join_t cairoJoin;
660     switch (lineJoin) {
661     case MiterJoin:
662         cairoJoin = CAIRO_LINE_JOIN_MITER;
663         break;
664     case RoundJoin:
665         cairoJoin = CAIRO_LINE_JOIN_ROUND;
666         break;
667     case BevelJoin:
668         cairoJoin = CAIRO_LINE_JOIN_BEVEL;
669         break;
670     }
671     cairo_set_line_join(platformContext.cr(), cairoJoin);
672 }
673
674 void setMiterLimit(PlatformContextCairo& platformContext, float miterLimit)
675 {
676     cairo_set_miter_limit(platformContext.cr(), miterLimit);
677 }
678
679 void fillRect(PlatformContextCairo& platformContext, const FloatRect& rect, const FillSource& fillSource, const ShadowState& shadowState)
680 {
681     cairo_t* cr = platformContext.cr();
682
683     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
684     drawPathShadow(platformContext, fillSource, { }, shadowState, Fill);
685     fillCurrentCairoPath(platformContext, fillSource);
686 }
687
688 void fillRect(PlatformContextCairo& platformContext, const FloatRect& rect, const Color& color, const ShadowState& shadowState)
689 {
690     if (shadowState.isVisible()) {
691         ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
692         shadow.drawRectShadow(State::getCTM(platformContext), State::getClipBounds(platformContext), FloatRoundedRect(rect),
693             [&platformContext, &shadowState](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect& sourceRect)
694             {
695                 fillShadowBuffer(platformContext, layerImage, layerOrigin, layerSize, sourceRect, shadowState);
696             });
697     }
698
699     fillRectWithColor(platformContext.cr(), rect, color);
700 }
701
702 void fillRect(PlatformContextCairo& platformContext, const FloatRect& rect, cairo_pattern_t* platformPattern)
703 {
704     cairo_t* cr = platformContext.cr();
705
706     cairo_set_source(cr, platformPattern);
707     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
708     cairo_fill(cr);
709 }
710
711 void fillRoundedRect(PlatformContextCairo& platformContext, const FloatRoundedRect& rect, const Color& color, const ShadowState& shadowState)
712 {
713     if (shadowState.isVisible()) {
714         ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
715         shadow.drawRectShadow(State::getCTM(platformContext), State::getClipBounds(platformContext), rect,
716             [&platformContext, &shadowState](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect& sourceRect)
717             {
718                 fillShadowBuffer(platformContext, layerImage, layerOrigin, layerSize, sourceRect, shadowState);
719             });
720     }
721
722     cairo_t* cr = platformContext.cr();
723     cairo_save(cr);
724
725     Path path;
726     path.addRoundedRect(rect);
727     appendWebCorePathToCairoContext(cr, path);
728     setSourceRGBAFromColor(cr, color);
729     cairo_fill(cr);
730
731     cairo_restore(cr);
732 }
733
734 void fillRectWithRoundedHole(PlatformContextCairo& platformContext, const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const FillSource& fillSource, const ShadowState& shadowState)
735 {
736     // FIXME: this should leverage the specified color.
737
738     if (shadowState.isVisible()) {
739         ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
740         shadow.drawInsetShadow(State::getCTM(platformContext), State::getClipBounds(platformContext), rect, roundedHoleRect,
741             [&platformContext, &shadowState](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect& sourceRect)
742             {
743                 fillShadowBuffer(platformContext, layerImage, layerOrigin, layerSize, sourceRect, shadowState);
744             });
745     }
746
747     Path path;
748     path.addRect(rect);
749     if (!roundedHoleRect.radii().isZero())
750         path.addRoundedRect(roundedHoleRect);
751     else
752         path.addRect(roundedHoleRect.rect());
753
754     cairo_t* cr = platformContext.cr();
755
756     cairo_save(cr);
757     setPathOnCairoContext(platformContext.cr(), path.platformPath()->context());
758     fillCurrentCairoPath(platformContext, fillSource);
759     cairo_restore(cr);
760 }
761
762 void fillPath(PlatformContextCairo& platformContext, const Path& path, const FillSource& fillSource, const ShadowState& shadowState)
763 {
764     cairo_t* cr = platformContext.cr();
765
766     setPathOnCairoContext(cr, path.platformPath()->context());
767     drawPathShadow(platformContext, fillSource, { }, shadowState, Fill);
768     fillCurrentCairoPath(platformContext, fillSource);
769 }
770
771 void strokeRect(PlatformContextCairo& platformContext, const FloatRect& rect, float lineWidth, const StrokeSource& strokeSource, const ShadowState& shadowState)
772 {
773     cairo_t* cr = platformContext.cr();
774     cairo_save(cr);
775
776     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
777     cairo_set_line_width(cr, lineWidth);
778     drawPathShadow(platformContext, { }, strokeSource, shadowState, Stroke);
779     prepareForStroking(cr, strokeSource, PreserveAlpha);
780     cairo_stroke(cr);
781
782     cairo_restore(cr);
783 }
784
785 void strokePath(PlatformContextCairo& platformContext, const Path& path, const StrokeSource& strokeSource, const ShadowState& shadowState)
786 {
787     cairo_t* cr = platformContext.cr();
788
789     setPathOnCairoContext(cr, path.platformPath()->context());
790     drawPathShadow(platformContext, { }, strokeSource, shadowState, Stroke);
791     prepareForStroking(cr, strokeSource, PreserveAlpha);
792     cairo_stroke(cr);
793 }
794
795 void clearRect(PlatformContextCairo& platformContext, const FloatRect& rect)
796 {
797     cairo_t* cr = platformContext.cr();
798
799     cairo_save(cr);
800     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
801     cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
802     cairo_fill(cr);
803     cairo_restore(cr);
804 }
805
806 void drawGlyphs(PlatformContextCairo& platformContext, const FillSource& fillSource, const StrokeSource& strokeSource, const ShadowState& shadowState, const FloatPoint& point, cairo_scaled_font_t* scaledFont, double syntheticBoldOffset, const Vector<cairo_glyph_t>& glyphs, float xOffset, TextDrawingModeFlags textDrawingMode, float strokeThickness, const FloatSize& shadowOffset, const Color& shadowColor)
807 {
808     drawGlyphsShadow(platformContext, shadowState, textDrawingMode, shadowOffset, shadowColor, point, scaledFont, syntheticBoldOffset, glyphs);
809
810     cairo_t* cr = platformContext.cr();
811     cairo_save(cr);
812
813     if (textDrawingMode & TextModeFill) {
814         prepareForFilling(cr, fillSource, AdjustPatternForGlobalAlpha);
815         drawGlyphsToContext(cr, scaledFont, syntheticBoldOffset, glyphs);
816     }
817
818     // Prevent running into a long computation within cairo. If the stroke width is
819     // twice the size of the width of the text we will not ask cairo to stroke
820     // the text as even one single stroke would cover the full wdth of the text.
821     //  See https://bugs.webkit.org/show_bug.cgi?id=33759.
822     if (textDrawingMode & TextModeStroke && strokeThickness < 2 * xOffset) {
823         prepareForStroking(cr, strokeSource, PreserveAlpha);
824         cairo_set_line_width(cr, strokeThickness);
825
826         // This may disturb the CTM, but we are going to call cairo_restore soon after.
827         cairo_set_scaled_font(cr, scaledFont);
828         cairo_glyph_path(cr, glyphs.data(), glyphs.size());
829         cairo_stroke(cr);
830     }
831
832     cairo_restore(cr);
833 }
834
835 void drawNativeImage(PlatformContextCairo& platformContext, cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator compositeOperator, BlendMode blendMode, ImageOrientation orientation, InterpolationQuality imageInterpolationQuality, float globalAlpha, const ShadowState& shadowState)
836 {
837     platformContext.save();
838
839     // Set the compositing operation.
840     if (compositeOperator == CompositeSourceOver && blendMode == BlendMode::Normal && !cairoSurfaceHasAlpha(surface))
841         Cairo::State::setCompositeOperation(platformContext, CompositeCopy, BlendMode::Normal);
842     else
843         Cairo::State::setCompositeOperation(platformContext, compositeOperator, blendMode);
844
845     FloatRect dst = destRect;
846     if (orientation != DefaultImageOrientation) {
847         // ImageOrientation expects the origin to be at (0, 0).
848         Cairo::translate(platformContext, dst.x(), dst.y());
849         dst.setLocation(FloatPoint());
850         Cairo::concatCTM(platformContext, orientation.transformFromDefault(dst.size()));
851         if (orientation.usesWidthAsHeight()) {
852             // The destination rectangle will have its width and height already reversed for the orientation of
853             // the image, as it was needed for page layout, so we need to reverse it back here.
854             dst = FloatRect(dst.x(), dst.y(), dst.height(), dst.width());
855         }
856     }
857
858     drawSurface(platformContext, surface, dst, srcRect, imageInterpolationQuality, globalAlpha, shadowState);
859     platformContext.restore();
860 }
861
862 void drawPattern(PlatformContextCairo& platformContext, cairo_surface_t* surface, const IntSize& size, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform, const FloatPoint& phase, CompositeOperator compositeOperator, BlendMode blendMode)
863 {
864     // FIXME: Investigate why the size has to be passed in as an IntRect.
865     drawPatternToCairoContext(platformContext.cr(), surface, size, tileRect, patternTransform, phase, toCairoOperator(compositeOperator, blendMode), destRect);
866 }
867
868 void drawSurface(PlatformContextCairo& platformContext, cairo_surface_t* surface, const FloatRect& destRect, const FloatRect& originalSrcRect, InterpolationQuality imageInterpolationQuality, float globalAlpha, const ShadowState& shadowState)
869 {
870     // Avoid invalid cairo matrix with small values.
871     if (std::fabs(destRect.width()) < 0.5f || std::fabs(destRect.height()) < 0.5f)
872         return;
873
874     FloatRect srcRect = originalSrcRect;
875
876     // We need to account for negative source dimensions by flipping the rectangle.
877     if (originalSrcRect.width() < 0) {
878         srcRect.setX(originalSrcRect.x() + originalSrcRect.width());
879         srcRect.setWidth(std::fabs(originalSrcRect.width()));
880     }
881     if (originalSrcRect.height() < 0) {
882         srcRect.setY(originalSrcRect.y() + originalSrcRect.height());
883         srcRect.setHeight(std::fabs(originalSrcRect.height()));
884     }
885
886     RefPtr<cairo_surface_t> patternSurface = surface;
887     float leftPadding = 0;
888     float topPadding = 0;
889     if (srcRect.x() || srcRect.y() || srcRect.size() != cairoSurfaceSize(surface)) {
890         // Cairo subsurfaces don't support floating point boundaries well, so we expand the rectangle.
891         IntRect expandedSrcRect(enclosingIntRect(srcRect));
892
893         // We use a subsurface here so that we don't end up sampling outside the originalSrcRect rectangle.
894         // See https://bugs.webkit.org/show_bug.cgi?id=58309
895         patternSurface = adoptRef(cairo_surface_create_for_rectangle(surface, expandedSrcRect.x(),
896             expandedSrcRect.y(), expandedSrcRect.width(), expandedSrcRect.height()));
897
898         leftPadding = static_cast<float>(expandedSrcRect.x()) - floorf(srcRect.x());
899         topPadding = static_cast<float>(expandedSrcRect.y()) - floorf(srcRect.y());
900     }
901
902     RefPtr<cairo_pattern_t> pattern = adoptRef(cairo_pattern_create_for_surface(patternSurface.get()));
903
904     switch (imageInterpolationQuality) {
905     case InterpolationNone:
906     case InterpolationLow:
907         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_FAST);
908         break;
909     case InterpolationMedium:
910     case InterpolationDefault:
911         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_GOOD);
912         break;
913     case InterpolationHigh:
914         cairo_pattern_set_filter(pattern.get(), CAIRO_FILTER_BEST);
915         break;
916     }
917     cairo_pattern_set_extend(pattern.get(), CAIRO_EXTEND_PAD);
918
919     // The pattern transformation properly scales the pattern for when the source rectangle is a
920     // different size than the destination rectangle. We also account for any offset we introduced
921     // by expanding floating point source rectangle sizes. It's important to take the absolute value
922     // of the scale since the original width and height might be negative.
923     float scaleX = std::fabs(srcRect.width() / destRect.width());
924     float scaleY = std::fabs(srcRect.height() / destRect.height());
925     cairo_matrix_t matrix = { scaleX, 0, 0, scaleY, leftPadding, topPadding };
926     cairo_pattern_set_matrix(pattern.get(), &matrix);
927
928     ShadowBlur shadow({ shadowState.blur, shadowState.blur }, shadowState.offset, shadowState.color, shadowState.ignoreTransforms);
929     if (shadow.type() != ShadowBlur::NoShadow) {
930         shadow.drawShadowLayer(State::getCTM(platformContext), State::getClipBounds(platformContext), destRect,
931             [&pattern, &destRect](GraphicsContext& shadowContext)
932             {
933                 drawPatternToCairoContext(shadowContext.platformContext()->cr(), pattern.get(), destRect, 1);
934             },
935             [&platformContext, &shadowState](ImageBuffer& layerImage, const FloatPoint& layerOrigin, const FloatSize& layerSize, const FloatRect&)
936             {
937                 drawShadowLayerBuffer(platformContext, layerImage, layerOrigin, layerSize, shadowState);
938             });
939     }
940
941     auto* cr = platformContext.cr();
942     cairo_save(cr);
943     drawPatternToCairoContext(cr, pattern.get(), destRect, globalAlpha);
944     cairo_restore(cr);
945 }
946
947 void drawRect(PlatformContextCairo& platformContext, const FloatRect& rect, float borderThickness, const Color& fillColor, StrokeStyle strokeStyle, const Color& strokeColor)
948 {
949     // FIXME: how should borderThickness be used?
950     UNUSED_PARAM(borderThickness);
951
952     cairo_t* cr = platformContext.cr();
953     cairo_save(cr);
954
955     fillRectWithColor(cr, rect, fillColor);
956
957     if (strokeStyle != NoStroke) {
958         setSourceRGBAFromColor(cr, strokeColor);
959         FloatRect r(rect);
960         r.inflate(-.5f);
961         cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
962         cairo_set_line_width(cr, 1.0); // borderThickness?
963         cairo_stroke(cr);
964     }
965
966     cairo_restore(cr);
967 }
968
969 void drawLine(PlatformContextCairo& platformContext, const FloatPoint& point1, const FloatPoint& point2, StrokeStyle strokeStyle, const Color& strokeColor, float strokeThickness, bool shouldAntialias)
970 {
971     bool isVerticalLine = (point1.x() + strokeThickness == point2.x());
972     float strokeWidth = isVerticalLine ? point2.y() - point1.y() : point2.x() - point1.x();
973     if (!strokeThickness || !strokeWidth)
974         return;
975
976     cairo_t* cairoContext = platformContext.cr();
977     float cornerWidth = 0;
978     bool drawsDashedLine = strokeStyle == DottedStroke || strokeStyle == DashedStroke;
979
980     if (drawsDashedLine) {
981         cairo_save(cairoContext);
982         // Figure out end points to ensure we always paint corners.
983         cornerWidth = dashedLineCornerWidthForStrokeWidth(strokeWidth, strokeStyle, strokeThickness);
984         if (isVerticalLine) {
985             fillRectWithColor(cairoContext, FloatRect(point1.x(), point1.y(), strokeThickness, cornerWidth), strokeColor);
986             fillRectWithColor(cairoContext, FloatRect(point1.x(), point2.y() - cornerWidth, strokeThickness, cornerWidth), strokeColor);
987         } else {
988             fillRectWithColor(cairoContext, FloatRect(point1.x(), point1.y(), cornerWidth, strokeThickness), strokeColor);
989             fillRectWithColor(cairoContext, FloatRect(point2.x() - cornerWidth, point1.y(), cornerWidth, strokeThickness), strokeColor);
990         }
991         strokeWidth -= 2 * cornerWidth;
992         float patternWidth = dashedLinePatternWidthForStrokeWidth(strokeWidth, strokeStyle, strokeThickness);
993         // Check if corner drawing sufficiently covers the line.
994         if (strokeWidth <= patternWidth + 1) {
995             cairo_restore(cairoContext);
996             return;
997         }
998
999         float patternOffset = dashedLinePatternOffsetForPatternAndStrokeWidth(patternWidth, strokeWidth);
1000         const double dashedLine[2] = { static_cast<double>(patternWidth), static_cast<double>(patternWidth) };
1001         cairo_set_dash(cairoContext, dashedLine, 2, patternOffset);
1002     } else {
1003         setSourceRGBAFromColor(cairoContext, strokeColor);
1004         if (strokeThickness < 1)
1005             cairo_set_line_width(cairoContext, 1);
1006     }
1007
1008     auto centeredPoints = centerLineAndCutOffCorners(isVerticalLine, cornerWidth, point1, point2);
1009     auto p1 = centeredPoints[0];
1010     auto p2 = centeredPoints[1];
1011
1012     if (shouldAntialias)
1013         cairo_set_antialias(cairoContext, CAIRO_ANTIALIAS_NONE);
1014
1015     cairo_new_path(cairoContext);
1016     cairo_move_to(cairoContext, p1.x(), p1.y());
1017     cairo_line_to(cairoContext, p2.x(), p2.y());
1018     cairo_stroke(cairoContext);
1019     if (drawsDashedLine)
1020         cairo_restore(cairoContext);
1021
1022     if (shouldAntialias)
1023         cairo_set_antialias(cairoContext, CAIRO_ANTIALIAS_DEFAULT);
1024 }
1025
1026 void drawLinesForText(PlatformContextCairo& platformContext, const FloatPoint& point, const DashArray& widths, bool printing, bool doubleUnderlines, const Color& color, float strokeThickness)
1027 {
1028     Color modifiedColor = color;
1029     FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(platformContext, point, widths.last(), printing, modifiedColor, strokeThickness);
1030
1031     Vector<FloatRect, 4> dashBounds;
1032     ASSERT(!(widths.size() % 2));
1033     dashBounds.reserveInitialCapacity(dashBounds.size() / 2);
1034     for (size_t i = 0; i < widths.size(); i += 2)
1035         dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y()), FloatSize(widths[i+1] - widths[i], bounds.height())));
1036
1037     if (doubleUnderlines) {
1038         // The space between double underlines is equal to the height of the underline
1039         for (size_t i = 0; i < widths.size(); i += 2)
1040             dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y() + 2 * bounds.height()), FloatSize(widths[i+1] - widths[i], bounds.height())));
1041     }
1042
1043     cairo_t* cr = platformContext.cr();
1044     cairo_save(cr);
1045
1046     for (auto& dash : dashBounds)
1047         fillRectWithColor(cr, dash, modifiedColor);
1048
1049     cairo_restore(cr);
1050 }
1051
1052 void drawDotsForDocumentMarker(PlatformContextCairo& platformContext, const FloatRect& rect, DocumentMarkerLineStyle style)
1053 {
1054     if (style.mode != DocumentMarkerLineStyle::Mode::Spelling
1055         && style.mode != DocumentMarkerLineStyle::Mode::Grammar)
1056         return;
1057
1058     cairo_t* cr = platformContext.cr();
1059     cairo_save(cr);
1060
1061     if (style.mode == DocumentMarkerLineStyle::Mode::Spelling)
1062         cairo_set_source_rgb(cr, 1, 0, 0);
1063     else if (style.mode == DocumentMarkerLineStyle::Mode::Grammar)
1064         cairo_set_source_rgb(cr, 0, 1, 0);
1065
1066     drawErrorUnderline(cr, rect.x(), rect.y(), rect.width(), rect.height());
1067     cairo_restore(cr);
1068 }
1069
1070 void drawEllipse(PlatformContextCairo& platformContext, const FloatRect& rect, const Color& fillColor, StrokeStyle strokeStyle, const Color& strokeColor, float strokeThickness)
1071 {
1072     cairo_t* cr = platformContext.cr();
1073
1074     cairo_save(cr);
1075     float yRadius = .5 * rect.height();
1076     float xRadius = .5 * rect.width();
1077     cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
1078     cairo_scale(cr, xRadius, yRadius);
1079     cairo_arc(cr, 0., 0., 1., 0., 2 * piFloat);
1080     cairo_restore(cr);
1081
1082     if (fillColor.isVisible()) {
1083         setSourceRGBAFromColor(cr, fillColor);
1084         cairo_fill_preserve(cr);
1085     }
1086
1087     if (strokeStyle != NoStroke) {
1088         setSourceRGBAFromColor(cr, strokeColor);
1089         cairo_set_line_width(cr, strokeThickness);
1090         cairo_stroke(cr);
1091     } else
1092         cairo_new_path(cr);
1093 }
1094
1095 void drawFocusRing(PlatformContextCairo& platformContext, const Path& path, float width, const Color& color)
1096 {
1097     // FIXME: We should draw paths that describe a rectangle with rounded corners
1098     // so as to be consistent with how we draw rectangular focus rings.
1099     Color ringColor = color;
1100     adjustFocusRingColor(ringColor);
1101     adjustFocusRingLineWidth(width);
1102
1103     cairo_t* cr = platformContext.cr();
1104     cairo_save(cr);
1105
1106     cairo_push_group(cr);
1107     appendWebCorePathToCairoContext(cr, path);
1108     setSourceRGBAFromColor(cr, ringColor);
1109     cairo_set_line_width(cr, width);
1110     Cairo::State::setStrokeStyle(platformContext, focusRingStrokeStyle());
1111     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1112     cairo_stroke_preserve(cr);
1113
1114     cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
1115     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
1116     cairo_fill(cr);
1117
1118     cairo_pop_group_to_source(cr);
1119     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1120     cairo_paint(cr);
1121
1122     cairo_restore(cr);
1123 }
1124
1125 void drawFocusRing(PlatformContextCairo& platformContext, const Vector<FloatRect>& rects, float width, const Color& color)
1126 {
1127     Path path;
1128 #if PLATFORM(GTK)
1129     for (const auto& rect : rects)
1130         path.addRect(rect);
1131 #else
1132     unsigned rectCount = rects.size();
1133     int radius = (width - 1) / 2;
1134     Path subPath;
1135     for (unsigned i = 0; i < rectCount; ++i) {
1136         if (i > 0)
1137             subPath.clear();
1138         subPath.addRoundedRect(rects[i], FloatSize(radius, radius));
1139         path.addPath(subPath, AffineTransform());
1140     }
1141 #endif
1142
1143     drawFocusRing(platformContext, path, width, color);
1144 }
1145
1146 void save(PlatformContextCairo& platformContext)
1147 {
1148     platformContext.save();
1149
1150     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1151         graphicsContextPrivate->save();
1152 }
1153
1154 void restore(PlatformContextCairo& platformContext)
1155 {
1156     platformContext.restore();
1157
1158     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1159         graphicsContextPrivate->restore();
1160 }
1161
1162 void translate(PlatformContextCairo& platformContext, float x, float y)
1163 {
1164     cairo_translate(platformContext.cr(), x, y);
1165
1166     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1167         graphicsContextPrivate->translate(x, y);
1168 }
1169
1170 void rotate(PlatformContextCairo& platformContext, float angleInRadians)
1171 {
1172     cairo_rotate(platformContext.cr(), angleInRadians);
1173
1174     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1175         graphicsContextPrivate->rotate(angleInRadians);
1176 }
1177
1178 void scale(PlatformContextCairo& platformContext, const FloatSize& size)
1179 {
1180     cairo_scale(platformContext.cr(), size.width(), size.height());
1181
1182     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1183         graphicsContextPrivate->scale(size);
1184 }
1185
1186 void concatCTM(PlatformContextCairo& platformContext, const AffineTransform& transform)
1187 {
1188     const cairo_matrix_t matrix = toCairoMatrix(transform);
1189     cairo_transform(platformContext.cr(), &matrix);
1190
1191     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1192         graphicsContextPrivate->concatCTM(transform);
1193 }
1194
1195 void beginTransparencyLayer(PlatformContextCairo& platformContext, float opacity)
1196 {
1197     cairo_push_group(platformContext.cr());
1198     platformContext.layers().append(opacity);
1199 }
1200
1201 void endTransparencyLayer(PlatformContextCairo& platformContext)
1202 {
1203     cairo_t* cr = platformContext.cr();
1204     cairo_pop_group_to_source(cr);
1205     cairo_paint_with_alpha(cr, platformContext.layers().takeLast());
1206 }
1207
1208 void clip(PlatformContextCairo& platformContext, const FloatRect& rect)
1209 {
1210     cairo_t* cr = platformContext.cr();
1211     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
1212     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1213     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
1214     // The rectangular clip function is traditionally not expected to
1215     // antialias. If we don't force antialiased clipping here,
1216     // edge fringe artifacts may occur at the layer edges
1217     // when a transformation is applied to the GraphicsContext
1218     // while drawing the transformed layer.
1219     cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr);
1220     cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1221     cairo_clip(cr);
1222     cairo_set_fill_rule(cr, savedFillRule);
1223     cairo_set_antialias(cr, savedAntialiasRule);
1224
1225     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1226         graphicsContextPrivate->clip(rect);
1227 }
1228
1229 void clipOut(PlatformContextCairo& platformContext, const FloatRect& rect)
1230 {
1231     cairo_t* cr = platformContext.cr();
1232     double x1, y1, x2, y2;
1233     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1234     cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1235     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
1236     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1237     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1238     cairo_clip(cr);
1239     cairo_set_fill_rule(cr, savedFillRule);
1240 }
1241
1242 void clipOut(PlatformContextCairo& platformContext, const Path& path)
1243 {
1244     cairo_t* cr = platformContext.cr();
1245     double x1, y1, x2, y2;
1246     cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1247     cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1248     appendWebCorePathToCairoContext(cr, path);
1249
1250     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1251     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1252     cairo_clip(cr);
1253     cairo_set_fill_rule(cr, savedFillRule);
1254 }
1255
1256 void clipPath(PlatformContextCairo& platformContext, const Path& path, WindRule clipRule)
1257 {
1258     cairo_t* cr = platformContext.cr();
1259
1260     if (!path.isNull())
1261         setPathOnCairoContext(cr, path.platformPath()->context());
1262
1263     cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1264     cairo_set_fill_rule(cr, clipRule == WindRule::EvenOdd ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
1265     cairo_clip(cr);
1266     cairo_set_fill_rule(cr, savedFillRule);
1267
1268     if (auto* graphicsContextPrivate = platformContext.graphicsContextPrivate())
1269         graphicsContextPrivate->clip(path);
1270 }
1271
1272 void clipToImageBuffer(PlatformContextCairo& platformContext, cairo_surface_t* image, const FloatRect& destRect)
1273 {
1274     platformContext.pushImageMask(image, destRect);
1275 }
1276
1277 } // namespace Cairo
1278 } // namespace WebCore
1279
1280 #endif // USE(CAIRO)