1/*
2 * Copyright (C) 2010 Igalia S.L.
3 * Copyright (C) 2011 ProFUSION embedded systems
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "CairoUtilities.h"
29
30#if USE(CAIRO)
31
32#include "AffineTransform.h"
33#include "Color.h"
34#include "FloatPoint.h"
35#include "FloatRect.h"
36#include "IntRect.h"
37#include "Path.h"
38#include "PlatformPathCairo.h"
39#include "RefPtrCairo.h"
40#include "Region.h"
41#include <wtf/Assertions.h>
42#include <wtf/NeverDestroyed.h>
43#include <wtf/UniqueArray.h>
44#include <wtf/Vector.h>
45
46#if ENABLE(ACCELERATED_2D_CANVAS)
47#if USE(EGL) && USE(LIBEPOXY)
48#include "EpoxyEGL.h"
49#endif
50#include <cairo-gl.h>
51#endif
52
53#if OS(WINDOWS)
54#include <cairo-win32.h>
55#endif
56
57namespace WebCore {
58
59#if USE(FREETYPE) && !PLATFORM(GTK)
60const cairo_font_options_t* getDefaultCairoFontOptions()
61{
62 static NeverDestroyed<cairo_font_options_t*> options = cairo_font_options_create();
63 return options;
64}
65#endif
66
67void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr)
68{
69 cairo_set_antialias(dstCr, cairo_get_antialias(srcCr));
70
71 size_t dashCount = cairo_get_dash_count(srcCr);
72 Vector<double> dashes(dashCount);
73
74 double offset;
75 cairo_get_dash(srcCr, dashes.data(), &offset);
76 cairo_set_dash(dstCr, dashes.data(), dashCount, offset);
77 cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr));
78 cairo_set_line_join(dstCr, cairo_get_line_join(srcCr));
79 cairo_set_line_width(dstCr, cairo_get_line_width(srcCr));
80 cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr));
81 cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr));
82}
83
84void setSourceRGBAFromColor(cairo_t* context, const Color& color)
85{
86 if (color.isExtended())
87 cairo_set_source_rgba(context, color.asExtended().red(), color.asExtended().green(), color.asExtended().blue(), color.asExtended().alpha());
88 else {
89 float red, green, blue, alpha;
90 color.getRGBA(red, green, blue, alpha);
91 cairo_set_source_rgba(context, red, green, blue, alpha);
92 }
93}
94
95void appendPathToCairoContext(cairo_t* to, cairo_t* from)
96{
97 auto cairoPath = cairo_copy_path(from);
98 cairo_append_path(to, cairoPath);
99 cairo_path_destroy(cairoPath);
100}
101
102void setPathOnCairoContext(cairo_t* to, cairo_t* from)
103{
104 cairo_new_path(to);
105 appendPathToCairoContext(to, from);
106}
107
108void appendWebCorePathToCairoContext(cairo_t* context, const Path& path)
109{
110 if (path.isEmpty())
111 return;
112 appendPathToCairoContext(context, path.platformPath()->context());
113}
114
115void appendRegionToCairoContext(cairo_t* to, const cairo_region_t* region)
116{
117 if (!region)
118 return;
119
120 const int rectCount = cairo_region_num_rectangles(region);
121 for (int i = 0; i < rectCount; ++i) {
122 cairo_rectangle_int_t rect;
123 cairo_region_get_rectangle(region, i, &rect);
124 cairo_rectangle(to, rect.x, rect.y, rect.width, rect.height);
125 }
126}
127
128static cairo_operator_t toCairoCompositeOperator(CompositeOperator op)
129{
130 switch (op) {
131 case CompositeClear:
132 return CAIRO_OPERATOR_CLEAR;
133 case CompositeCopy:
134 return CAIRO_OPERATOR_SOURCE;
135 case CompositeSourceOver:
136 return CAIRO_OPERATOR_OVER;
137 case CompositeSourceIn:
138 return CAIRO_OPERATOR_IN;
139 case CompositeSourceOut:
140 return CAIRO_OPERATOR_OUT;
141 case CompositeSourceAtop:
142 return CAIRO_OPERATOR_ATOP;
143 case CompositeDestinationOver:
144 return CAIRO_OPERATOR_DEST_OVER;
145 case CompositeDestinationIn:
146 return CAIRO_OPERATOR_DEST_IN;
147 case CompositeDestinationOut:
148 return CAIRO_OPERATOR_DEST_OUT;
149 case CompositeDestinationAtop:
150 return CAIRO_OPERATOR_DEST_ATOP;
151 case CompositeXOR:
152 return CAIRO_OPERATOR_XOR;
153 case CompositePlusDarker:
154 return CAIRO_OPERATOR_DARKEN;
155 case CompositePlusLighter:
156 return CAIRO_OPERATOR_ADD;
157 case CompositeDifference:
158 return CAIRO_OPERATOR_DIFFERENCE;
159 default:
160 return CAIRO_OPERATOR_SOURCE;
161 }
162}
163
164cairo_operator_t toCairoOperator(CompositeOperator op, BlendMode blendOp)
165{
166 switch (blendOp) {
167 case BlendMode::Normal:
168 return toCairoCompositeOperator(op);
169 case BlendMode::Multiply:
170 return CAIRO_OPERATOR_MULTIPLY;
171 case BlendMode::Screen:
172 return CAIRO_OPERATOR_SCREEN;
173 case BlendMode::Overlay:
174 return CAIRO_OPERATOR_OVERLAY;
175 case BlendMode::Darken:
176 return CAIRO_OPERATOR_DARKEN;
177 case BlendMode::Lighten:
178 return CAIRO_OPERATOR_LIGHTEN;
179 case BlendMode::ColorDodge:
180 return CAIRO_OPERATOR_COLOR_DODGE;
181 case BlendMode::ColorBurn:
182 return CAIRO_OPERATOR_COLOR_BURN;
183 case BlendMode::HardLight:
184 return CAIRO_OPERATOR_HARD_LIGHT;
185 case BlendMode::SoftLight:
186 return CAIRO_OPERATOR_SOFT_LIGHT;
187 case BlendMode::Difference:
188 return CAIRO_OPERATOR_DIFFERENCE;
189 case BlendMode::Exclusion:
190 return CAIRO_OPERATOR_EXCLUSION;
191 case BlendMode::Hue:
192 return CAIRO_OPERATOR_HSL_HUE;
193 case BlendMode::Saturation:
194 return CAIRO_OPERATOR_HSL_SATURATION;
195 case BlendMode::Color:
196 return CAIRO_OPERATOR_HSL_COLOR;
197 case BlendMode::Luminosity:
198 return CAIRO_OPERATOR_HSL_LUMINOSITY;
199 default:
200 return CAIRO_OPERATOR_OVER;
201 }
202}
203
204void drawPatternToCairoContext(cairo_t* cr, cairo_surface_t* image, const IntSize& imageSize, const FloatRect& tileRect,
205 const AffineTransform& patternTransform, const FloatPoint& phase, cairo_operator_t op, const FloatRect& destRect)
206{
207 // Avoid NaN
208 if (!std::isfinite(phase.x()) || !std::isfinite(phase.y()))
209 return;
210
211 cairo_save(cr);
212
213 RefPtr<cairo_surface_t> clippedImageSurface = 0;
214 if (tileRect.size() != imageSize) {
215 IntRect imageRect = enclosingIntRect(tileRect);
216 clippedImageSurface = adoptRef(cairo_image_surface_create(CAIRO_FORMAT_ARGB32, imageRect.width(), imageRect.height()));
217 RefPtr<cairo_t> clippedImageContext = adoptRef(cairo_create(clippedImageSurface.get()));
218 cairo_set_source_surface(clippedImageContext.get(), image, -tileRect.x(), -tileRect.y());
219 cairo_paint(clippedImageContext.get());
220 image = clippedImageSurface.get();
221 }
222
223 cairo_pattern_t* pattern = cairo_pattern_create_for_surface(image);
224 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
225
226 // Due to a limitation in pixman, cairo cannot handle transformation matrices with values bigger than 32768. If the value is
227 // bigger, cairo is not able to paint anything, and this is the reason for the missing backgrounds reported in
228 // https://bugs.webkit.org/show_bug.cgi?id=154283.
229
230 // When drawing a pattern there are 2 matrices that can overflow this limitation, and they are the current transformation
231 // matrix (which translates user space coordinates to coordinates of the output device) and the pattern matrix (which translates
232 // user space coordinates to pattern coordinates). The overflow happens only in the translation components of the matrices.
233
234 // To avoid the problem in the transformation matrix what we do is remove the translation components of the transformation matrix
235 // and perform the translation by moving the destination rectangle instead. For this, we calculate such a translation amount (dx, dy)
236 // that its opposite translate (-dx, -dy) will zero the translation components of the transformation matrix. We move the current
237 // transformation matrix by (-dx, -dy) and move the destination rectangle by (dx, dy). We also need to apply the same translation to
238 // the pattern matrix, so we get the same pattern coordinates for the new destination rectangle. (dx, dy) is caclucated by transforming
239 // the current translation components by the inverse matrix of the current transformation matrix.
240
241 cairo_matrix_t ctm;
242 cairo_get_matrix(cr, &ctm);
243 double dx = 0, dy = 0;
244 cairo_matrix_transform_point(&ctm, &dx, &dy);
245 cairo_matrix_t inv = ctm;
246 if (cairo_matrix_invert(&inv) == CAIRO_STATUS_SUCCESS)
247 cairo_matrix_transform_distance(&inv, &dx, &dy);
248
249 cairo_translate(cr, -dx, -dy);
250 FloatRect adjustedDestRect(destRect);
251 adjustedDestRect.move(dx, dy);
252
253 // Regarding the pattern matrix, what we do is reduce the translation component of the matrix taking advantage of the fact that we
254 // are drawing a repeated pattern. This means that, assuming that (w, h) is the size of the pattern, samplig it at (x, y) is the same
255 // than sampling it at (x mod w, y mod h), so we transform the translation component of the pattern matrix in that way.
256
257 cairo_matrix_t patternMatrix = toCairoMatrix(patternTransform);
258 // dx and dy are added here as well to compensate the previous translation of the destination rectangle.
259 double phaseOffsetX = phase.x() + tileRect.x() * patternTransform.a() + dx;
260 double phaseOffsetY = phase.y() + tileRect.y() * patternTransform.d() + dy;
261 // this is where we perform the (x mod w, y mod h) metioned above, but with floats instead of integers.
262 phaseOffsetX -= std::trunc(phaseOffsetX / (tileRect.width() * patternTransform.a())) * tileRect.width() * patternTransform.a();
263 phaseOffsetY -= std::trunc(phaseOffsetY / (tileRect.height() * patternTransform.d())) * tileRect.height() * patternTransform.d();
264 cairo_matrix_t phaseMatrix = {1, 0, 0, 1, phaseOffsetX, phaseOffsetY};
265 cairo_matrix_t combined;
266 cairo_matrix_multiply(&combined, &patternMatrix, &phaseMatrix);
267 cairo_matrix_invert(&combined);
268 cairo_pattern_set_matrix(pattern, &combined);
269
270 cairo_set_operator(cr, op);
271 cairo_set_source(cr, pattern);
272 cairo_pattern_destroy(pattern);
273 cairo_rectangle(cr, adjustedDestRect.x(), adjustedDestRect.y(), adjustedDestRect.width(), adjustedDestRect.height());
274 cairo_fill(cr);
275
276 cairo_restore(cr);
277}
278
279RefPtr<cairo_surface_t> copyCairoImageSurface(cairo_surface_t* originalSurface)
280{
281 // Cairo doesn't provide a way to copy a cairo_surface_t.
282 // See http://lists.cairographics.org/archives/cairo/2007-June/010877.html
283 // Once cairo provides the way, use the function instead of this.
284 IntSize size = cairoSurfaceSize(originalSurface);
285 RefPtr<cairo_surface_t> newSurface = adoptRef(cairo_surface_create_similar(originalSurface,
286 cairo_surface_get_content(originalSurface), size.width(), size.height()));
287
288 RefPtr<cairo_t> cr = adoptRef(cairo_create(newSurface.get()));
289 cairo_set_source_surface(cr.get(), originalSurface, 0, 0);
290 cairo_set_operator(cr.get(), CAIRO_OPERATOR_SOURCE);
291 cairo_paint(cr.get());
292 return newSurface;
293}
294
295void copyRectFromCairoSurfaceToContext(cairo_surface_t* from, cairo_t* to, const IntSize& offset, const IntRect& rect)
296{
297 cairo_set_source_surface(to, from, offset.width(), offset.height());
298 cairo_rectangle(to, rect.x(), rect.y(), rect.width(), rect.height());
299 cairo_fill(to);
300}
301
302void copyRectFromOneSurfaceToAnother(cairo_surface_t* from, cairo_surface_t* to, const IntSize& sourceOffset, const IntRect& rect, const IntSize& destOffset, cairo_operator_t cairoOperator)
303{
304 RefPtr<cairo_t> context = adoptRef(cairo_create(to));
305 cairo_translate(context.get(), destOffset.width(), destOffset.height());
306 cairo_set_operator(context.get(), cairoOperator);
307 copyRectFromCairoSurfaceToContext(from, context.get(), sourceOffset, rect);
308}
309
310IntSize cairoSurfaceSize(cairo_surface_t* surface)
311{
312 switch (cairo_surface_get_type(surface)) {
313 case CAIRO_SURFACE_TYPE_IMAGE:
314 return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface));
315#if ENABLE(ACCELERATED_2D_CANVAS)
316 case CAIRO_SURFACE_TYPE_GL:
317 return IntSize(cairo_gl_surface_get_width(surface), cairo_gl_surface_get_height(surface));
318#endif
319#if OS(WINDOWS)
320 case CAIRO_SURFACE_TYPE_WIN32:
321 surface = cairo_win32_surface_get_image(surface);
322 ASSERT(surface);
323 ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
324 return IntSize(cairo_image_surface_get_width(surface), cairo_image_surface_get_height(surface));
325#endif
326 default:
327 ASSERT_NOT_REACHED();
328 return IntSize();
329 }
330}
331
332void flipImageSurfaceVertically(cairo_surface_t* surface)
333{
334 ASSERT(cairo_surface_get_type(surface) == CAIRO_SURFACE_TYPE_IMAGE);
335
336 IntSize size = cairoSurfaceSize(surface);
337 ASSERT(!size.isEmpty());
338
339 int stride = cairo_image_surface_get_stride(surface);
340 int halfHeight = size.height() / 2;
341
342 uint8_t* source = static_cast<uint8_t*>(cairo_image_surface_get_data(surface));
343 auto tmp = makeUniqueArray<uint8_t>(stride);
344
345 for (int i = 0; i < halfHeight; ++i) {
346 uint8_t* top = source + (i * stride);
347 uint8_t* bottom = source + ((size.height()-i-1) * stride);
348
349 memcpy(tmp.get(), top, stride);
350 memcpy(top, bottom, stride);
351 memcpy(bottom, tmp.get(), stride);
352 }
353}
354
355void cairoSurfaceSetDeviceScale(cairo_surface_t* surface, double xScale, double yScale)
356{
357 // This function was added pretty much simultaneous to when 1.13 was branched.
358#if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE)
359 cairo_surface_set_device_scale(surface, xScale, yScale);
360#else
361 UNUSED_PARAM(surface);
362 ASSERT_UNUSED(xScale, 1 == xScale);
363 ASSERT_UNUSED(yScale, 1 == yScale);
364#endif
365}
366
367void cairoSurfaceGetDeviceScale(cairo_surface_t* surface, double& xScale, double& yScale)
368{
369#if HAVE(CAIRO_SURFACE_SET_DEVICE_SCALE)
370 cairo_surface_get_device_scale(surface, &xScale, &yScale);
371#else
372 UNUSED_PARAM(surface);
373 xScale = 1;
374 yScale = 1;
375#endif
376}
377
378RefPtr<cairo_region_t> toCairoRegion(const Region& region)
379{
380 RefPtr<cairo_region_t> cairoRegion = adoptRef(cairo_region_create());
381 for (const auto& rect : region.rects()) {
382 cairo_rectangle_int_t cairoRect = rect;
383 cairo_region_union_rectangle(cairoRegion.get(), &cairoRect);
384 }
385 return cairoRegion;
386}
387
388cairo_matrix_t toCairoMatrix(const AffineTransform& transform)
389{
390 return cairo_matrix_t { transform.a(), transform.b(), transform.c(), transform.d(), transform.e(), transform.f() };
391}
392
393} // namespace WebCore
394
395#endif // USE(CAIRO)
396