1 /*
2 * Copyright (C) 2006 Apple Computer, 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 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "config.h"
31 #include "GraphicsContext.h"
32
33 #if PLATFORM(CAIRO)
34
35 #include "AffineTransform.h"
36 #include "CairoPath.h"
37 #include "FEGaussianBlur.h"
38 #include "FloatRect.h"
39 #include "Font.h"
40 #include "ImageBuffer.h"
41 #include "ImageBufferFilter.h"
42 #include "IntRect.h"
43 #include "NotImplemented.h"
44 #include "Path.h"
45 #include "Pattern.h"
46 #include "SimpleFontData.h"
47 #include "SourceGraphic.h"
48
49 #include <cairo.h>
50 #include <math.h>
51 #include <stdio.h>
52 #include <wtf/MathExtras.h>
53
54 #if PLATFORM(GTK)
55 #include <gdk/gdk.h>
56 #include <pango/pango.h>
57 #elif PLATFORM(WIN)
58 #include <cairo-win32.h>
59 #endif
60 #include "GraphicsContextPlatformPrivateCairo.h"
61 #include "GraphicsContextPrivate.h"
62
63 #ifndef M_PI
64 #define M_PI 3.14159265358979323846
65 #endif
66
67 namespace WebCore {
68
setColor(cairo_t * cr,const Color & col)69 static inline void setColor(cairo_t* cr, const Color& col)
70 {
71 float red, green, blue, alpha;
72 col.getRGBA(red, green, blue, alpha);
73 cairo_set_source_rgba(cr, red, green, blue, alpha);
74 }
75
setPlatformFill(GraphicsContext * context,cairo_t * cr,GraphicsContextPrivate * gcp)76 static inline void setPlatformFill(GraphicsContext* context, cairo_t* cr, GraphicsContextPrivate* gcp)
77 {
78 cairo_save(cr);
79 if (gcp->state.fillPattern) {
80 AffineTransform affine;
81 cairo_set_source(cr, gcp->state.fillPattern->createPlatformPattern(affine));
82 } else if (gcp->state.fillGradient)
83 cairo_set_source(cr, gcp->state.fillGradient->platformGradient());
84 else
85 setColor(cr, context->fillColor());
86 cairo_clip_preserve(cr);
87 cairo_paint_with_alpha(cr, gcp->state.globalAlpha);
88 cairo_restore(cr);
89 }
90
setPlatformStroke(GraphicsContext * context,cairo_t * cr,GraphicsContextPrivate * gcp)91 static inline void setPlatformStroke(GraphicsContext* context, cairo_t* cr, GraphicsContextPrivate* gcp)
92 {
93 cairo_save(cr);
94 if (gcp->state.strokePattern) {
95 AffineTransform affine;
96 cairo_set_source(cr, gcp->state.strokePattern->createPlatformPattern(affine));
97 } else if (gcp->state.strokeGradient)
98 cairo_set_source(cr, gcp->state.strokeGradient->platformGradient());
99 else {
100 Color strokeColor = colorWithOverrideAlpha(context->strokeColor().rgb(), context->strokeColor().alpha() / 255.f * gcp->state.globalAlpha);
101 setColor(cr, strokeColor);
102 }
103 if (gcp->state.globalAlpha < 1.0f && (gcp->state.strokePattern || gcp->state.strokeGradient)) {
104 cairo_push_group(cr);
105 cairo_paint_with_alpha(cr, gcp->state.globalAlpha);
106 cairo_pop_group_to_source(cr);
107 }
108 cairo_stroke_preserve(cr);
109 cairo_restore(cr);
110 }
111
112 // A fillRect helper
fillRectSourceOver(cairo_t * cr,const FloatRect & rect,const Color & col)113 static inline void fillRectSourceOver(cairo_t* cr, const FloatRect& rect, const Color& col)
114 {
115 setColor(cr, col);
116 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
117 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
118 cairo_fill(cr);
119 }
120
copyContextProperties(cairo_t * srcCr,cairo_t * dstCr)121 static inline void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr)
122 {
123 cairo_set_antialias(dstCr, cairo_get_antialias(srcCr));
124
125 size_t dashCount = cairo_get_dash_count(srcCr);
126 Vector<double> dashes(dashCount);
127
128 double offset;
129 cairo_get_dash(srcCr, dashes.data(), &offset);
130 cairo_set_dash(dstCr, dashes.data(), dashCount, offset);
131 cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr));
132 cairo_set_line_join(dstCr, cairo_get_line_join(srcCr));
133 cairo_set_line_width(dstCr, cairo_get_line_width(srcCr));
134 cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr));
135 cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr));
136 }
137
calculateShadowBufferDimensions(IntSize & shadowBufferSize,FloatRect & shadowRect,float & kernelSize,const FloatRect & sourceRect,const IntSize & shadowSize,int shadowBlur)138 void GraphicsContext::calculateShadowBufferDimensions(IntSize& shadowBufferSize, FloatRect& shadowRect, float& kernelSize, const FloatRect& sourceRect, const IntSize& shadowSize, int shadowBlur)
139 {
140 #if ENABLE(FILTERS)
141 // calculate the kernel size according to the HTML5 canvas shadow specification
142 kernelSize = (shadowBlur < 8 ? shadowBlur / 2.f : sqrt(shadowBlur * 2.f));
143 int blurRadius = ceil(kernelSize);
144
145 shadowBufferSize = IntSize(sourceRect.width() + blurRadius * 2, sourceRect.height() + blurRadius * 2);
146
147 // determine dimensions of shadow rect
148 shadowRect = FloatRect(sourceRect.location(), shadowBufferSize);
149 shadowRect.move(shadowSize.width() - kernelSize, shadowSize.height() - kernelSize);
150 #endif
151 }
152
drawPathShadow(GraphicsContext * context,GraphicsContextPrivate * gcp,bool fillShadow,bool strokeShadow)153 static inline void drawPathShadow(GraphicsContext* context, GraphicsContextPrivate* gcp, bool fillShadow, bool strokeShadow)
154 {
155 #if ENABLE(FILTERS)
156 IntSize shadowSize;
157 int shadowBlur;
158 Color shadowColor;
159 if (!context->getShadow(shadowSize, shadowBlur, shadowColor))
160 return;
161
162 // Calculate filter values to create appropriate shadow.
163 cairo_t* cr = context->platformContext();
164 cairo_path_t* path = cairo_copy_path(cr);
165 double x0, x1, y0, y1;
166 if (strokeShadow)
167 cairo_stroke_extents(cr, &x0, &y0, &x1, &y1);
168 else
169 cairo_fill_extents(cr, &x0, &y0, &x1, &y1);
170 FloatRect rect(x0, y0, x1 - x0, y1 - y0);
171
172 IntSize shadowBufferSize;
173 FloatRect shadowRect;
174 float kernelSize = 0;
175 GraphicsContext::calculateShadowBufferDimensions(shadowBufferSize, shadowRect, kernelSize, rect, shadowSize, shadowBlur);
176
177 // Create suitably-sized ImageBuffer to hold the shadow.
178 OwnPtr<ImageBuffer> shadowBuffer = ImageBuffer::create(shadowBufferSize);
179
180 // Draw shadow into a new ImageBuffer.
181 cairo_t* shadowContext = shadowBuffer->context()->platformContext();
182 copyContextProperties(cr, shadowContext);
183 cairo_translate(shadowContext, -rect.x() + kernelSize, -rect.y() + kernelSize);
184 cairo_new_path(shadowContext);
185 cairo_append_path(shadowContext, path);
186
187 if (fillShadow)
188 setPlatformFill(context, shadowContext, gcp);
189 if (strokeShadow)
190 setPlatformStroke(context, shadowContext, gcp);
191
192 context->createPlatformShadow(shadowBuffer.release(), shadowColor, shadowRect, kernelSize);
193 #endif
194 }
195
GraphicsContext(PlatformGraphicsContext * cr)196 GraphicsContext::GraphicsContext(PlatformGraphicsContext* cr)
197 : m_common(createGraphicsContextPrivate())
198 , m_data(new GraphicsContextPlatformPrivate)
199 {
200 m_data->cr = cairo_reference(cr);
201 m_data->syncContext(cr);
202 setPaintingDisabled(!cr);
203 }
204
~GraphicsContext()205 GraphicsContext::~GraphicsContext()
206 {
207 destroyGraphicsContextPrivate(m_common);
208 delete m_data;
209 }
210
getCTM() const211 AffineTransform GraphicsContext::getCTM() const
212 {
213 cairo_t* cr = platformContext();
214 cairo_matrix_t m;
215 cairo_get_matrix(cr, &m);
216 return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0);
217 }
218
platformContext() const219 cairo_t* GraphicsContext::platformContext() const
220 {
221 return m_data->cr;
222 }
223
savePlatformState()224 void GraphicsContext::savePlatformState()
225 {
226 cairo_save(m_data->cr);
227 m_data->save();
228 }
229
restorePlatformState()230 void GraphicsContext::restorePlatformState()
231 {
232 cairo_restore(m_data->cr);
233 m_data->restore();
234 }
235
236 // Draws a filled rectangle with a stroked border.
drawRect(const IntRect & rect)237 void GraphicsContext::drawRect(const IntRect& rect)
238 {
239 if (paintingDisabled())
240 return;
241
242 cairo_t* cr = m_data->cr;
243 cairo_save(cr);
244
245 if (fillColor().alpha())
246 fillRectSourceOver(cr, rect, fillColor());
247
248 if (strokeStyle() != NoStroke) {
249 setColor(cr, strokeColor());
250 FloatRect r(rect);
251 r.inflate(-.5f);
252 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
253 cairo_set_line_width(cr, 1.0);
254 cairo_stroke(cr);
255 }
256
257 cairo_restore(cr);
258 }
259
260 // This is only used to draw borders.
drawLine(const IntPoint & point1,const IntPoint & point2)261 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
262 {
263 if (paintingDisabled())
264 return;
265
266 StrokeStyle style = strokeStyle();
267 if (style == NoStroke)
268 return;
269
270 cairo_t* cr = m_data->cr;
271 cairo_save(cr);
272
273 float width = strokeThickness();
274 if (width < 1)
275 width = 1;
276
277 FloatPoint p1 = point1;
278 FloatPoint p2 = point2;
279 bool isVerticalLine = (p1.x() == p2.x());
280
281 adjustLineToPixelBoundaries(p1, p2, width, style);
282 cairo_set_line_width(cr, width);
283
284 int patWidth = 0;
285 switch (style) {
286 case NoStroke:
287 case SolidStroke:
288 break;
289 case DottedStroke:
290 patWidth = static_cast<int>(width);
291 break;
292 case DashedStroke:
293 patWidth = 3*static_cast<int>(width);
294 break;
295 }
296
297 setColor(cr, strokeColor());
298
299 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
300
301 if (patWidth) {
302 // Do a rect fill of our endpoints. This ensures we always have the
303 // appearance of being a border. We then draw the actual dotted/dashed line.
304 if (isVerticalLine) {
305 fillRectSourceOver(cr, FloatRect(p1.x() - width/2, p1.y() - width, width, width), strokeColor());
306 fillRectSourceOver(cr, FloatRect(p2.x() - width/2, p2.y(), width, width), strokeColor());
307 } else {
308 fillRectSourceOver(cr, FloatRect(p1.x() - width, p1.y() - width/2, width, width), strokeColor());
309 fillRectSourceOver(cr, FloatRect(p2.x(), p2.y() - width/2, width, width), strokeColor());
310 }
311
312 // Example: 80 pixels with a width of 30 pixels.
313 // Remainder is 20. The maximum pixels of line we could paint
314 // will be 50 pixels.
315 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*static_cast<int>(width);
316 int remainder = distance%patWidth;
317 int coverage = distance-remainder;
318 int numSegments = coverage/patWidth;
319
320 float patternOffset = 0;
321 // Special case 1px dotted borders for speed.
322 if (patWidth == 1)
323 patternOffset = 1.0;
324 else {
325 bool evenNumberOfSegments = !(numSegments % 2);
326 if (remainder)
327 evenNumberOfSegments = !evenNumberOfSegments;
328 if (evenNumberOfSegments) {
329 if (remainder) {
330 patternOffset += patWidth - remainder;
331 patternOffset += remainder / 2;
332 } else
333 patternOffset = patWidth / 2;
334 } else if (!evenNumberOfSegments) {
335 if (remainder)
336 patternOffset = (patWidth - remainder) / 2;
337 }
338 }
339
340 double dash = patWidth;
341 cairo_set_dash(cr, &dash, 1, patternOffset);
342 }
343
344 cairo_move_to(cr, p1.x(), p1.y());
345 cairo_line_to(cr, p2.x(), p2.y());
346
347 cairo_stroke(cr);
348 cairo_restore(cr);
349 }
350
351 // This method is only used to draw the little circles used in lists.
drawEllipse(const IntRect & rect)352 void GraphicsContext::drawEllipse(const IntRect& rect)
353 {
354 if (paintingDisabled())
355 return;
356
357 cairo_t* cr = m_data->cr;
358 cairo_save(cr);
359 float yRadius = .5 * rect.height();
360 float xRadius = .5 * rect.width();
361 cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
362 cairo_scale(cr, xRadius, yRadius);
363 cairo_arc(cr, 0., 0., 1., 0., 2 * M_PI);
364 cairo_restore(cr);
365
366 if (fillColor().alpha()) {
367 setColor(cr, fillColor());
368 cairo_fill_preserve(cr);
369 }
370
371 if (strokeStyle() != NoStroke) {
372 setColor(cr, strokeColor());
373 cairo_set_line_width(cr, strokeThickness());
374 cairo_stroke(cr);
375 }
376
377 cairo_new_path(cr);
378 }
379
strokeArc(const IntRect & rect,int startAngle,int angleSpan)380 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
381 {
382 if (paintingDisabled() || strokeStyle() == NoStroke)
383 return;
384
385 int x = rect.x();
386 int y = rect.y();
387 float w = rect.width();
388 float h = rect.height();
389 float scaleFactor = h / w;
390 float reverseScaleFactor = w / h;
391
392 float hRadius = w / 2;
393 float vRadius = h / 2;
394 float fa = startAngle;
395 float falen = fa + angleSpan;
396
397 cairo_t* cr = m_data->cr;
398 cairo_save(cr);
399
400 if (w != h)
401 cairo_scale(cr, 1., scaleFactor);
402
403 cairo_arc_negative(cr, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, -fa * M_PI/180, -falen * M_PI/180);
404
405 if (w != h)
406 cairo_scale(cr, 1., reverseScaleFactor);
407
408 float width = strokeThickness();
409 int patWidth = 0;
410
411 switch (strokeStyle()) {
412 case DottedStroke:
413 patWidth = static_cast<int>(width / 2);
414 break;
415 case DashedStroke:
416 patWidth = 3 * static_cast<int>(width / 2);
417 break;
418 default:
419 break;
420 }
421
422 setColor(cr, strokeColor());
423
424 if (patWidth) {
425 // Example: 80 pixels with a width of 30 pixels.
426 // Remainder is 20. The maximum pixels of line we could paint
427 // will be 50 pixels.
428 int distance;
429 if (hRadius == vRadius)
430 distance = static_cast<int>((M_PI * hRadius) / 2.0);
431 else // We are elliptical and will have to estimate the distance
432 distance = static_cast<int>((M_PI * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0)) / 2.0);
433
434 int remainder = distance % patWidth;
435 int coverage = distance - remainder;
436 int numSegments = coverage / patWidth;
437
438 float patternOffset = 0.0;
439 // Special case 1px dotted borders for speed.
440 if (patWidth == 1)
441 patternOffset = 1.0;
442 else {
443 bool evenNumberOfSegments = !(numSegments % 2);
444 if (remainder)
445 evenNumberOfSegments = !evenNumberOfSegments;
446 if (evenNumberOfSegments) {
447 if (remainder) {
448 patternOffset += patWidth - remainder;
449 patternOffset += remainder / 2.0;
450 } else
451 patternOffset = patWidth / 2.0;
452 } else {
453 if (remainder)
454 patternOffset = (patWidth - remainder) / 2.0;
455 }
456 }
457
458 double dash = patWidth;
459 cairo_set_dash(cr, &dash, 1, patternOffset);
460 }
461
462 cairo_stroke(cr);
463 cairo_restore(cr);
464 }
465
drawConvexPolygon(size_t npoints,const FloatPoint * points,bool shouldAntialias)466 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
467 {
468 if (paintingDisabled())
469 return;
470
471 if (npoints <= 1)
472 return;
473
474 cairo_t* cr = m_data->cr;
475
476 cairo_save(cr);
477 cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
478 cairo_move_to(cr, points[0].x(), points[0].y());
479 for (size_t i = 1; i < npoints; i++)
480 cairo_line_to(cr, points[i].x(), points[i].y());
481 cairo_close_path(cr);
482
483 if (fillColor().alpha()) {
484 setColor(cr, fillColor());
485 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
486 cairo_fill_preserve(cr);
487 }
488
489 if (strokeStyle() != NoStroke) {
490 setColor(cr, strokeColor());
491 cairo_set_line_width(cr, strokeThickness());
492 cairo_stroke(cr);
493 }
494
495 cairo_new_path(cr);
496 cairo_restore(cr);
497 }
498
fillPath()499 void GraphicsContext::fillPath()
500 {
501 if (paintingDisabled())
502 return;
503
504 cairo_t* cr = m_data->cr;
505
506 cairo_set_fill_rule(cr, fillRule() == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
507 drawPathShadow(this, m_common, true, false);
508
509 setPlatformFill(this, cr, m_common);
510 cairo_new_path(cr);
511 }
512
strokePath()513 void GraphicsContext::strokePath()
514 {
515 if (paintingDisabled())
516 return;
517
518 cairo_t* cr = m_data->cr;
519 drawPathShadow(this, m_common, false, true);
520
521 setPlatformStroke(this, cr, m_common);
522 cairo_new_path(cr);
523
524 }
525
drawPath()526 void GraphicsContext::drawPath()
527 {
528 if (paintingDisabled())
529 return;
530
531 cairo_t* cr = m_data->cr;
532
533 cairo_set_fill_rule(cr, fillRule() == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
534 drawPathShadow(this, m_common, true, true);
535
536 setPlatformFill(this, cr, m_common);
537 setPlatformStroke(this, cr, m_common);
538 cairo_new_path(cr);
539 }
540
fillRect(const FloatRect & rect)541 void GraphicsContext::fillRect(const FloatRect& rect)
542 {
543 if (paintingDisabled())
544 return;
545
546 cairo_t* cr = m_data->cr;
547 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
548 fillPath();
549 }
550
drawBorderlessRectShadow(GraphicsContext * context,const FloatRect & rect,const Color & rectColor)551 static void drawBorderlessRectShadow(GraphicsContext* context, const FloatRect& rect, const Color& rectColor)
552 {
553 #if ENABLE(FILTERS)
554 IntSize shadowSize;
555 int shadowBlur;
556 Color shadowColor;
557
558 if (!context->getShadow(shadowSize, shadowBlur, shadowColor))
559 return;
560
561 IntSize shadowBufferSize;
562 FloatRect shadowRect;
563 float kernelSize = 0;
564 GraphicsContext::calculateShadowBufferDimensions(shadowBufferSize, shadowRect, kernelSize, rect, shadowSize, shadowBlur);
565
566 // Draw shadow into a new ImageBuffer
567 OwnPtr<ImageBuffer> shadowBuffer = ImageBuffer::create(shadowBufferSize);
568 GraphicsContext* shadowContext = shadowBuffer->context();
569 shadowContext->fillRect(FloatRect(FloatPoint(kernelSize, kernelSize), rect.size()), rectColor, DeviceColorSpace);
570
571 context->createPlatformShadow(shadowBuffer.release(), shadowColor, shadowRect, kernelSize);
572 #endif
573 }
574
fillRect(const FloatRect & rect,const Color & color,ColorSpace colorSpace)575 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
576 {
577 if (paintingDisabled())
578 return;
579
580 drawBorderlessRectShadow(this, rect, color);
581 if (color.alpha())
582 fillRectSourceOver(m_data->cr, rect, color);
583 }
584
clip(const FloatRect & rect)585 void GraphicsContext::clip(const FloatRect& rect)
586 {
587 if (paintingDisabled())
588 return;
589
590 cairo_t* cr = m_data->cr;
591 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
592 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
593 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
594 cairo_clip(cr);
595 cairo_set_fill_rule(cr, savedFillRule);
596 m_data->clip(rect);
597 }
598
clipPath(WindRule clipRule)599 void GraphicsContext::clipPath(WindRule clipRule)
600 {
601 if (paintingDisabled())
602 return;
603
604 cairo_t* cr = m_data->cr;
605 cairo_set_fill_rule(cr, clipRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING);
606 cairo_clip(cr);
607 }
608
drawFocusRing(const Vector<Path> & paths,int width,int offset,const Color & color)609 void GraphicsContext::drawFocusRing(const Vector<Path>& paths, int width, int offset, const Color& color)
610 {
611 // FIXME: implement
612 }
613
drawFocusRing(const Vector<IntRect> & rects,int width,int,const Color & color)614 void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int /* offset */, const Color& color)
615 {
616 if (paintingDisabled())
617 return;
618
619 unsigned rectCount = rects.size();
620
621 cairo_t* cr = m_data->cr;
622 cairo_save(cr);
623 cairo_push_group(cr);
624 cairo_new_path(cr);
625
626 #if PLATFORM(GTK)
627 GdkRegion* reg = gdk_region_new();
628 for (unsigned i = 0; i < rectCount; i++) {
629 GdkRectangle rect = rects[i];
630 gdk_region_union_with_rect(reg, &rect);
631 }
632 gdk_cairo_region(cr, reg);
633 gdk_region_destroy(reg);
634
635 setColor(cr, color);
636 cairo_set_line_width(cr, 2.0f);
637 setPlatformStrokeStyle(DottedStroke);
638 #else
639 int radius = (width - 1) / 2;
640 for (unsigned i = 0; i < rectCount; i++)
641 addPath(Path::createRoundedRectangle(rects[i], FloatSize(radius, radius)));
642
643 // Force the alpha to 50%. This matches what the Mac does with outline rings.
644 Color ringColor(color.red(), color.green(), color.blue(), 127);
645 setColor(cr, ringColor);
646 cairo_set_line_width(cr, width);
647 setPlatformStrokeStyle(SolidStroke);
648 #endif
649
650 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
651 cairo_stroke_preserve(cr);
652
653 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
654 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
655 cairo_fill(cr);
656
657 cairo_pop_group_to_source(cr);
658 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
659 cairo_paint(cr);
660 cairo_restore(cr);
661 }
662
drawLineForText(const IntPoint & origin,int width,bool printing)663 void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool printing)
664 {
665 if (paintingDisabled())
666 return;
667
668 // This is a workaround for http://bugs.webkit.org/show_bug.cgi?id=15659
669 StrokeStyle savedStrokeStyle = strokeStyle();
670 setStrokeStyle(SolidStroke);
671
672 IntPoint endPoint = origin + IntSize(width, 0);
673 drawLine(origin, endPoint);
674
675 setStrokeStyle(savedStrokeStyle);
676 }
677
drawLineForMisspellingOrBadGrammar(const IntPoint & origin,int width,bool grammar)678 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& origin, int width, bool grammar)
679 {
680 if (paintingDisabled())
681 return;
682
683 cairo_t* cr = m_data->cr;
684 cairo_save(cr);
685
686 // Convention is green for grammar, red for spelling
687 // These need to become configurable
688 if (grammar)
689 cairo_set_source_rgb(cr, 0, 1, 0);
690 else
691 cairo_set_source_rgb(cr, 1, 0, 0);
692
693 #if PLATFORM(GTK)
694 // We ignore most of the provided constants in favour of the platform style
695 pango_cairo_show_error_underline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness);
696 #else
697 notImplemented();
698 #endif
699
700 cairo_restore(cr);
701 }
702
roundToDevicePixels(const FloatRect & frect)703 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
704 {
705 FloatRect result;
706 double x = frect.x();
707 double y = frect.y();
708 cairo_t* cr = m_data->cr;
709 cairo_user_to_device(cr, &x, &y);
710 x = round(x);
711 y = round(y);
712 cairo_device_to_user(cr, &x, &y);
713 result.setX(static_cast<float>(x));
714 result.setY(static_cast<float>(y));
715 x = frect.width();
716 y = frect.height();
717 cairo_user_to_device_distance(cr, &x, &y);
718 x = round(x);
719 y = round(y);
720 cairo_device_to_user_distance(cr, &x, &y);
721 result.setWidth(static_cast<float>(x));
722 result.setHeight(static_cast<float>(y));
723 return result;
724 }
725
translate(float x,float y)726 void GraphicsContext::translate(float x, float y)
727 {
728 if (paintingDisabled())
729 return;
730
731 cairo_t* cr = m_data->cr;
732 cairo_translate(cr, x, y);
733 m_data->translate(x, y);
734 }
735
origin()736 IntPoint GraphicsContext::origin()
737 {
738 cairo_matrix_t matrix;
739 cairo_t* cr = m_data->cr;
740 cairo_get_matrix(cr, &matrix);
741 return IntPoint(static_cast<int>(matrix.x0), static_cast<int>(matrix.y0));
742 }
743
setPlatformFillColor(const Color & col,ColorSpace colorSpace)744 void GraphicsContext::setPlatformFillColor(const Color& col, ColorSpace colorSpace)
745 {
746 // Cairo contexts can't hold separate fill and stroke colors
747 // so we set them just before we actually fill or stroke
748 }
749
setPlatformStrokeColor(const Color & col,ColorSpace colorSpace)750 void GraphicsContext::setPlatformStrokeColor(const Color& col, ColorSpace colorSpace)
751 {
752 // Cairo contexts can't hold separate fill and stroke colors
753 // so we set them just before we actually fill or stroke
754 }
755
setPlatformStrokeThickness(float strokeThickness)756 void GraphicsContext::setPlatformStrokeThickness(float strokeThickness)
757 {
758 if (paintingDisabled())
759 return;
760
761 cairo_set_line_width(m_data->cr, strokeThickness);
762 }
763
setPlatformStrokeStyle(const StrokeStyle & strokeStyle)764 void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle)
765 {
766 static double dashPattern[] = {5.0, 5.0};
767 static double dotPattern[] = {1.0, 1.0};
768
769 if (paintingDisabled())
770 return;
771
772 switch (strokeStyle) {
773 case NoStroke:
774 // FIXME: is it the right way to emulate NoStroke?
775 cairo_set_line_width(m_data->cr, 0);
776 break;
777 case SolidStroke:
778 cairo_set_dash(m_data->cr, 0, 0, 0);
779 break;
780 case DottedStroke:
781 cairo_set_dash(m_data->cr, dotPattern, 2, 0);
782 break;
783 case DashedStroke:
784 cairo_set_dash(m_data->cr, dashPattern, 2, 0);
785 break;
786 }
787 }
788
setURLForRect(const KURL & link,const IntRect & destRect)789 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
790 {
791 notImplemented();
792 }
793
concatCTM(const AffineTransform & transform)794 void GraphicsContext::concatCTM(const AffineTransform& transform)
795 {
796 if (paintingDisabled())
797 return;
798
799 cairo_t* cr = m_data->cr;
800 const cairo_matrix_t matrix = cairo_matrix_t(transform);
801 cairo_transform(cr, &matrix);
802 m_data->concatCTM(transform);
803 }
804
addInnerRoundedRectClip(const IntRect & rect,int thickness)805 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
806 {
807 if (paintingDisabled())
808 return;
809
810 clip(rect);
811
812 Path p;
813 FloatRect r(rect);
814 // Add outer ellipse
815 p.addEllipse(r);
816 // Add inner ellipse
817 r.inflate(-thickness);
818 p.addEllipse(r);
819 addPath(p);
820
821 cairo_t* cr = m_data->cr;
822 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
823 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
824 cairo_clip(cr);
825 cairo_set_fill_rule(cr, savedFillRule);
826 }
827
clipToImageBuffer(const FloatRect & rect,const ImageBuffer * imageBuffer)828 void GraphicsContext::clipToImageBuffer(const FloatRect& rect, const ImageBuffer* imageBuffer)
829 {
830 if (paintingDisabled())
831 return;
832
833 notImplemented();
834 }
835
setPlatformShadow(IntSize const & size,int,Color const &,ColorSpace)836 void GraphicsContext::setPlatformShadow(IntSize const& size, int, Color const&, ColorSpace)
837 {
838 // Cairo doesn't support shadows natively, they are drawn manually in the draw*
839 // functions
840
841 if (m_common->state.shadowsIgnoreTransforms) {
842 // Meaning that this graphics context is associated with a CanvasRenderingContext
843 // We flip the height since CG and HTML5 Canvas have opposite Y axis
844 m_common->state.shadowSize = IntSize(size.width(), -size.height());
845 }
846 }
847
createPlatformShadow(PassOwnPtr<ImageBuffer> buffer,const Color & shadowColor,const FloatRect & shadowRect,float kernelSize)848 void GraphicsContext::createPlatformShadow(PassOwnPtr<ImageBuffer> buffer, const Color& shadowColor, const FloatRect& shadowRect, float kernelSize)
849 {
850 #if ENABLE(FILTERS)
851 cairo_t* cr = m_data->cr;
852
853 // draw the shadow without blurring, if kernelSize is zero
854 if (!kernelSize) {
855 setColor(cr, shadowColor);
856 cairo_mask_surface(cr, buffer->image()->nativeImageForCurrentFrame(), shadowRect.x(), shadowRect.y());
857 return;
858 }
859
860 // limit kernel size to 1000, this is what CG is doing.
861 kernelSize = std::min(1000.f, kernelSize);
862
863 // create filter
864 RefPtr<Filter> filter = ImageBufferFilter::create();
865 filter->setSourceImage(buffer.release());
866 RefPtr<FilterEffect> source = SourceGraphic::create();
867 source->setScaledSubRegion(FloatRect(FloatPoint(), shadowRect.size()));
868 source->setIsAlphaImage(true);
869 RefPtr<FilterEffect> blur = FEGaussianBlur::create(source.get(), kernelSize, kernelSize);
870 blur->setScaledSubRegion(FloatRect(FloatPoint(), shadowRect.size()));
871 blur->apply(filter.get());
872
873 // Mask the filter with the shadow color and draw it to the context.
874 // Masking makes it possible to just blur the alpha channel.
875 setColor(cr, shadowColor);
876 cairo_mask_surface(cr, blur->resultImage()->image()->nativeImageForCurrentFrame(), shadowRect.x(), shadowRect.y());
877 #endif
878 }
879
clearPlatformShadow()880 void GraphicsContext::clearPlatformShadow()
881 {
882 notImplemented();
883 }
884
beginTransparencyLayer(float opacity)885 void GraphicsContext::beginTransparencyLayer(float opacity)
886 {
887 if (paintingDisabled())
888 return;
889
890 cairo_t* cr = m_data->cr;
891 cairo_push_group(cr);
892 m_data->layers.append(opacity);
893 m_data->beginTransparencyLayer();
894 }
895
endTransparencyLayer()896 void GraphicsContext::endTransparencyLayer()
897 {
898 if (paintingDisabled())
899 return;
900
901 cairo_t* cr = m_data->cr;
902
903 cairo_pop_group_to_source(cr);
904 cairo_paint_with_alpha(cr, m_data->layers.last());
905 m_data->layers.removeLast();
906 m_data->endTransparencyLayer();
907 }
908
clearRect(const FloatRect & rect)909 void GraphicsContext::clearRect(const FloatRect& rect)
910 {
911 if (paintingDisabled())
912 return;
913
914 cairo_t* cr = m_data->cr;
915
916 cairo_save(cr);
917 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
918 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
919 cairo_fill(cr);
920 cairo_restore(cr);
921 }
922
strokeRect(const FloatRect & rect,float width)923 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
924 {
925 if (paintingDisabled())
926 return;
927
928 cairo_t* cr = m_data->cr;
929 cairo_save(cr);
930 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
931 cairo_set_line_width(cr, width);
932 strokePath();
933 cairo_restore(cr);
934 }
935
setLineCap(LineCap lineCap)936 void GraphicsContext::setLineCap(LineCap lineCap)
937 {
938 if (paintingDisabled())
939 return;
940
941 cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT;
942 switch (lineCap) {
943 case ButtCap:
944 // no-op
945 break;
946 case RoundCap:
947 cairoCap = CAIRO_LINE_CAP_ROUND;
948 break;
949 case SquareCap:
950 cairoCap = CAIRO_LINE_CAP_SQUARE;
951 break;
952 }
953 cairo_set_line_cap(m_data->cr, cairoCap);
954 }
955
setLineDash(const DashArray & dashes,float dashOffset)956 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
957 {
958 cairo_set_dash(m_data->cr, dashes.data(), dashes.size(), dashOffset);
959 }
960
setLineJoin(LineJoin lineJoin)961 void GraphicsContext::setLineJoin(LineJoin lineJoin)
962 {
963 if (paintingDisabled())
964 return;
965
966 cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER;
967 switch (lineJoin) {
968 case MiterJoin:
969 // no-op
970 break;
971 case RoundJoin:
972 cairoJoin = CAIRO_LINE_JOIN_ROUND;
973 break;
974 case BevelJoin:
975 cairoJoin = CAIRO_LINE_JOIN_BEVEL;
976 break;
977 }
978 cairo_set_line_join(m_data->cr, cairoJoin);
979 }
980
setMiterLimit(float miter)981 void GraphicsContext::setMiterLimit(float miter)
982 {
983 if (paintingDisabled())
984 return;
985
986 cairo_set_miter_limit(m_data->cr, miter);
987 }
988
setAlpha(float alpha)989 void GraphicsContext::setAlpha(float alpha)
990 {
991 m_common->state.globalAlpha = alpha;
992 }
993
getAlpha()994 float GraphicsContext::getAlpha()
995 {
996 return m_common->state.globalAlpha;
997 }
998
toCairoOperator(CompositeOperator op)999 static inline cairo_operator_t toCairoOperator(CompositeOperator op)
1000 {
1001 switch (op) {
1002 case CompositeClear:
1003 return CAIRO_OPERATOR_CLEAR;
1004 case CompositeCopy:
1005 return CAIRO_OPERATOR_SOURCE;
1006 case CompositeSourceOver:
1007 return CAIRO_OPERATOR_OVER;
1008 case CompositeSourceIn:
1009 return CAIRO_OPERATOR_IN;
1010 case CompositeSourceOut:
1011 return CAIRO_OPERATOR_OUT;
1012 case CompositeSourceAtop:
1013 return CAIRO_OPERATOR_ATOP;
1014 case CompositeDestinationOver:
1015 return CAIRO_OPERATOR_DEST_OVER;
1016 case CompositeDestinationIn:
1017 return CAIRO_OPERATOR_DEST_IN;
1018 case CompositeDestinationOut:
1019 return CAIRO_OPERATOR_DEST_OUT;
1020 case CompositeDestinationAtop:
1021 return CAIRO_OPERATOR_DEST_ATOP;
1022 case CompositeXOR:
1023 return CAIRO_OPERATOR_XOR;
1024 case CompositePlusDarker:
1025 return CAIRO_OPERATOR_SATURATE;
1026 case CompositeHighlight:
1027 // There is no Cairo equivalent for CompositeHighlight.
1028 return CAIRO_OPERATOR_OVER;
1029 case CompositePlusLighter:
1030 return CAIRO_OPERATOR_ADD;
1031 default:
1032 return CAIRO_OPERATOR_SOURCE;
1033 }
1034 }
1035
setCompositeOperation(CompositeOperator op)1036 void GraphicsContext::setCompositeOperation(CompositeOperator op)
1037 {
1038 if (paintingDisabled())
1039 return;
1040
1041 cairo_set_operator(m_data->cr, toCairoOperator(op));
1042 }
1043
beginPath()1044 void GraphicsContext::beginPath()
1045 {
1046 if (paintingDisabled())
1047 return;
1048
1049 cairo_t* cr = m_data->cr;
1050 cairo_new_path(cr);
1051 }
1052
addPath(const Path & path)1053 void GraphicsContext::addPath(const Path& path)
1054 {
1055 if (paintingDisabled())
1056 return;
1057
1058 cairo_t* cr = m_data->cr;
1059 cairo_path_t* p = cairo_copy_path(path.platformPath()->m_cr);
1060 cairo_append_path(cr, p);
1061 cairo_path_destroy(p);
1062 }
1063
clip(const Path & path)1064 void GraphicsContext::clip(const Path& path)
1065 {
1066 if (paintingDisabled())
1067 return;
1068
1069 cairo_t* cr = m_data->cr;
1070 cairo_path_t* p = cairo_copy_path(path.platformPath()->m_cr);
1071 cairo_append_path(cr, p);
1072 cairo_path_destroy(p);
1073 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1074 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
1075 cairo_clip(cr);
1076 cairo_set_fill_rule(cr, savedFillRule);
1077 m_data->clip(path);
1078 }
1079
canvasClip(const Path & path)1080 void GraphicsContext::canvasClip(const Path& path)
1081 {
1082 clip(path);
1083 }
1084
clipOut(const Path & path)1085 void GraphicsContext::clipOut(const Path& path)
1086 {
1087 if (paintingDisabled())
1088 return;
1089
1090 cairo_t* cr = m_data->cr;
1091 double x1, y1, x2, y2;
1092 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1093 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1094 addPath(path);
1095
1096 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1097 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1098 cairo_clip(cr);
1099 cairo_set_fill_rule(cr, savedFillRule);
1100 }
1101
rotate(float radians)1102 void GraphicsContext::rotate(float radians)
1103 {
1104 if (paintingDisabled())
1105 return;
1106
1107 cairo_rotate(m_data->cr, radians);
1108 m_data->rotate(radians);
1109 }
1110
scale(const FloatSize & size)1111 void GraphicsContext::scale(const FloatSize& size)
1112 {
1113 if (paintingDisabled())
1114 return;
1115
1116 cairo_scale(m_data->cr, size.width(), size.height());
1117 m_data->scale(size);
1118 }
1119
clipOut(const IntRect & r)1120 void GraphicsContext::clipOut(const IntRect& r)
1121 {
1122 if (paintingDisabled())
1123 return;
1124
1125 cairo_t* cr = m_data->cr;
1126 double x1, y1, x2, y2;
1127 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
1128 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1);
1129 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
1130 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr);
1131 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
1132 cairo_clip(cr);
1133 cairo_set_fill_rule(cr, savedFillRule);
1134 }
1135
clipOutEllipseInRect(const IntRect & r)1136 void GraphicsContext::clipOutEllipseInRect(const IntRect& r)
1137 {
1138 if (paintingDisabled())
1139 return;
1140
1141 Path p;
1142 p.addEllipse(r);
1143 clipOut(p);
1144 }
1145
fillRoundedRect(const IntRect & r,const IntSize & topLeft,const IntSize & topRight,const IntSize & bottomLeft,const IntSize & bottomRight,const Color & color,ColorSpace colorSpace)1146 void GraphicsContext::fillRoundedRect(const IntRect& r, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace)
1147 {
1148 if (paintingDisabled())
1149 return;
1150
1151 cairo_t* cr = m_data->cr;
1152 cairo_save(cr);
1153 beginPath();
1154 addPath(Path::createRoundedRectangle(r, topLeft, topRight, bottomLeft, bottomRight));
1155 setColor(cr, color);
1156 drawPathShadow(this, m_common, true, false);
1157 cairo_fill(cr);
1158 cairo_restore(cr);
1159 }
1160
1161 #if PLATFORM(GTK)
setGdkExposeEvent(GdkEventExpose * expose)1162 void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose)
1163 {
1164 m_data->expose = expose;
1165 }
1166
gdkExposeEvent() const1167 GdkEventExpose* GraphicsContext::gdkExposeEvent() const
1168 {
1169 return m_data->expose;
1170 }
1171
gdkDrawable() const1172 GdkDrawable* GraphicsContext::gdkDrawable() const
1173 {
1174 if (!m_data->expose)
1175 return 0;
1176
1177 return GDK_DRAWABLE(m_data->expose->window);
1178 }
1179 #endif
1180
setPlatformShouldAntialias(bool enable)1181 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1182 {
1183 if (paintingDisabled())
1184 return;
1185
1186 // When true, use the default Cairo backend antialias mode (usually this
1187 // enables standard 'grayscale' antialiasing); false to explicitly disable
1188 // antialiasing. This is the same strategy as used in drawConvexPolygon().
1189 cairo_set_antialias(m_data->cr, enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
1190 }
1191
setImageInterpolationQuality(InterpolationQuality)1192 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality)
1193 {
1194 }
1195
imageInterpolationQuality() const1196 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1197 {
1198 return InterpolationDefault;
1199 }
1200
1201 } // namespace WebCore
1202
1203 #endif // PLATFORM(CAIRO)
1204