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