This patch makes drawHighlightForText a completely cross-platform method
[WebKit-https.git] / WebCore / platform / cairo / GraphicsContextCairo.cpp
1 /*
2  * Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #include "config.h"
27 #include "GraphicsContext.h"
28
29 #include "FloatRect.h"
30 #include "Font.h"
31 #include "IntPointArray.h"
32 #include "IntRect.h"
33 #include <cairo-win32.h>
34
35 #ifndef M_PI
36 #define M_PI 3.14159265358979323846
37 #endif
38
39 namespace WebCore {
40
41 class GraphicsContextPlatformPrivate {
42 public:
43     GraphicsContextPlatformPrivate();
44     ~GraphicsContextPlatformPrivate();
45
46     cairo_t* context;
47 };
48
49 static inline void setColor(cairo_t* cr, const Color& col)
50 {
51     float red, green, blue, alpha;
52     col.getRGBA(red, green, blue, alpha);
53     cairo_set_source_rgba(cr, red, green, blue, alpha);
54 }
55
56 // A fillRect helper
57 static inline void fillRectSourceOver(cairo_t* cr, const FloatRect& rect, const Color& col)
58 {
59     setColor(cr, col);
60     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
61     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
62     cairo_fill(cr);
63 }
64
65 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate()
66     :  context(0)
67 {
68 }
69
70 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
71 {
72     cairo_destroy(context);
73 }
74
75 GraphicsContext::GraphicsContext(HDC dc)
76     : m_common(createGraphicsContextPrivate())
77     , m_data(new GraphicsContextPlatformPrivate)
78 {
79     cairo_surface_t* surface = cairo_win32_surface_create(dc);
80     m_data->context = cairo_create(surface);
81 }
82
83 GraphicsContext::GraphicsContext(cairo_t* context)
84     : m_common(createGraphicsContextPrivate())
85     , m_data(new GraphicsContextPlatformPrivate)
86 {
87     m_data->context = cairo_reference(context);
88 }
89
90 GraphicsContext::~GraphicsContext()
91 {
92     destroyGraphicsContextPrivate(m_common);
93     delete m_data;
94 }
95
96 cairo_t* GraphicsContext::platformContext() const
97 {
98     return m_data->context;
99 }
100
101 void GraphicsContext::savePlatformState()
102 {
103     cairo_save(m_data->context);
104 }
105
106 void GraphicsContext::restorePlatformState()
107 {
108     cairo_restore(m_data->context);
109 }
110
111 // Draws a filled rectangle with a stroked border.
112 void GraphicsContext::drawRect(const IntRect& rect)
113 {
114     if (paintingDisabled())
115         return;
116     
117     cairo_t* context = m_data->context;
118     if (fillColor().alpha())
119         fillRectSourceOver(context, rect, fillColor());
120
121     if (pen().style() != Pen::NoPen) {
122         setColor(context, pen().color());
123         FloatRect r(rect);
124         r.inflate(-.5f);
125         cairo_rectangle(context, r.x(), r.y(), r.width(), r.height());
126         cairo_set_line_width(context, 1.0);
127         cairo_stroke(context);
128     }
129 }
130
131 // FIXME: Now that this is refactored, it should be shared by all contexts.
132 static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth, const Pen::PenStyle& penStyle)
133 {
134     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
135     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
136     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
137     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
138     if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
139         if (p1.x() == p2.x()) {
140             p1.setY(p1.y() + strokeWidth);
141             p2.setY(p2.y() - strokeWidth);
142         }
143         else {
144             p1.setX(p1.x() + strokeWidth);
145             p2.setX(p2.x() - strokeWidth);
146         }
147     }
148     
149     if (((int)strokeWidth)%2) {
150         if (p1.x() == p2.x()) {
151             // We're a vertical line.  Adjust our x.
152             p1.setX(p1.x() + 0.5);
153             p2.setX(p2.x() + 0.5);
154         }
155         else {
156             // We're a horizontal line. Adjust our y.
157             p1.setY(p1.y() + 0.5);
158             p2.setY(p2.y() + 0.5);
159         }
160     }
161 }
162
163 // This is only used to draw borders.
164 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
165 {
166     if (paintingDisabled())
167         return;
168
169     cairo_t* context = m_data->context;
170     cairo_save(context);
171
172     Pen::PenStyle penStyle = pen().style();
173     if (penStyle == Pen::NoPen)
174         return;
175     float width = pen().width();
176     if (width < 1)
177         width = 1;
178
179     FloatPoint p1 = point1;
180     FloatPoint p2 = point2;
181     bool isVerticalLine = (p1.x() == p2.x());
182     
183     adjustLineToPixelBounderies(p1, p2, width, penStyle);
184     cairo_set_line_width(context, width);
185
186     int patWidth = 0;
187     switch (penStyle) {
188     case Pen::NoPen:
189     case Pen::SolidLine:
190         break;
191     case Pen::DotLine:
192         patWidth = (int)width;
193         break;
194     case Pen::DashLine:
195         patWidth = 3*(int)width;
196         break;
197     }
198
199     setColor(context, pen().color());
200     
201     cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
202     
203     if (patWidth) {
204         // Do a rect fill of our endpoints.  This ensures we always have the
205         // appearance of being a border.  We then draw the actual dotted/dashed line.
206         if (isVerticalLine) {
207             fillRectSourceOver(context, FloatRect(p1.x()-width/2, p1.y()-width, width, width), pen().color());
208             fillRectSourceOver(context, FloatRect(p2.x()-width/2, p2.y(), width, width), pen().color());
209         } else {
210             fillRectSourceOver(context, FloatRect(p1.x()-width, p1.y()-width/2, width, width), pen().color());
211             fillRectSourceOver(context, FloatRect(p2.x(), p2.y()-width/2, width, width), pen().color());
212         }
213         
214         // Example: 80 pixels with a width of 30 pixels.
215         // Remainder is 20.  The maximum pixels of line we could paint
216         // will be 50 pixels.
217         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
218         int remainder = distance%patWidth;
219         int coverage = distance-remainder;
220         int numSegments = coverage/patWidth;
221
222         float patternOffset = 0;
223         // Special case 1px dotted borders for speed.
224         if (patWidth == 1)
225             patternOffset = 1.0;
226         else {
227             bool evenNumberOfSegments = numSegments%2 == 0;
228             if (remainder)
229                 evenNumberOfSegments = !evenNumberOfSegments;
230             if (evenNumberOfSegments) {
231                 if (remainder) {
232                     patternOffset += patWidth - remainder;
233                     patternOffset += remainder/2;
234                 }
235                 else
236                     patternOffset = patWidth/2;
237             }
238             else if (!evenNumberOfSegments) {
239                 if (remainder)
240                     patternOffset = (patWidth - remainder)/2;
241             }
242         }
243         
244         double dash = patWidth;
245         cairo_set_dash(context, &dash, 1, patternOffset);
246     }
247
248     cairo_move_to(context, p1.x(), p1.y());
249     cairo_line_to(context, p2.x(), p2.y());
250
251     cairo_stroke(context);
252     cairo_restore(context);
253 }
254
255 // This method is only used to draw the little circles used in lists.
256 void GraphicsContext::drawEllipse(const IntRect& rect)
257 {
258     if (paintingDisabled())
259         return;
260     
261     cairo_t* context = m_data->context;
262     cairo_save(context);
263     float yRadius = .5 * rect.height();
264     float xRadius = .5 * rect.width();
265     cairo_translate(context, rect.x() + xRadius, rect.y() + yRadius);
266     cairo_scale(context, xRadius, yRadius);
267     cairo_arc(context, 0., 0., 1., 0., 2 * M_PI);
268     cairo_restore(context);
269
270     if (fillColor().alpha()) {
271         setColor(context, fillColor());
272         cairo_fill(context);
273     }
274     if (pen().style() != Pen::NoPen) {
275         setColor(context, pen().color());
276         unsigned penWidth = pen().width();
277         if (penWidth == 0) 
278             penWidth++;
279         cairo_set_line_width(context, penWidth);
280         cairo_stroke(context);
281     }
282 }
283
284 void GraphicsContext::drawArc(int x, int y, int w, int h, int a, int alen)
285 {
286     // Only supports arc on circles.  That's all khtml needs.
287     ASSERT(w == h);
288
289     if (paintingDisabled())
290         return;
291     
292     cairo_t* context = m_data->context;
293     if (pen().style() != Pen::NoPen) {        
294         float r = (float)w / 2;
295         float fa = (float)a / 16;
296         float falen =  fa + (float)alen / 16;
297         cairo_arc(context, x + r, y + r, r, -fa * M_PI/180, -falen * M_PI/180);
298         
299         setColor(context, pen().color());
300         cairo_set_line_width(context, pen().width());
301         cairo_stroke(context);
302     }
303 }
304
305 void GraphicsContext::drawConvexPolygon(const IntPointArray& points)
306 {
307     if (paintingDisabled())
308         return;
309
310     int npoints = points.size();
311     if (npoints <= 1)
312         return;
313
314     cairo_t* context = m_data->context;
315
316     cairo_save(context);
317     cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
318     cairo_move_to(context, points[0].x(), points[0].y());
319     for (int i = 1; i < npoints; i++)
320         cairo_line_to(context, points[i].x(), points[i].y());
321     cairo_close_path(context);
322
323     if (fillColor().alpha()) {
324         setColor(context, fillColor());
325         cairo_set_fill_rule(context, CAIRO_FILL_RULE_EVEN_ODD);
326         cairo_fill(context);
327     }
328
329     if (pen().style() != Pen::NoPen) {
330         setColor(context, pen().color());
331         cairo_set_line_width(context, pen().width());
332         cairo_stroke(context);
333     }
334     cairo_restore(context);
335 }
336
337 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
338 {
339     if (paintingDisabled())
340         return;
341
342     if (color.alpha())
343         fillRectSourceOver(m_data->context, rect, color);
344 }
345
346 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
347 {
348     if (paintingDisabled())
349         return;
350
351     if (color.alpha())
352         fillRectSourceOver(m_data->context, rect, color);
353 }
354
355 void GraphicsContext::addClip(const IntRect& rect)
356 {
357     if (paintingDisabled())
358         return;
359
360     cairo_t* context = m_data->context;
361     cairo_rectangle(context, rect.x(), rect.y(), rect.width(), rect.height());
362     cairo_clip(context);
363 }
364
365 void GraphicsContext::drawFocusRing(const Color& color)
366 {
367     if (paintingDisabled())
368         return;
369     int radius = (focusRingWidth() - 1) / 2;
370     int offset = radius + focusRingOffset();
371     
372     const Vector<IntRect>& rects = focusRingRects();
373     unsigned rectCount = rects.size();
374     IntRect finalFocusRect;
375     for (unsigned i = 0; i < rectCount; i++) {
376         IntRect focusRect = rects[i];
377         focusRect.inflate(offset);
378         finalFocusRect.unite(focusRect);
379     }
380     // FIXME: These rects should be rounded
381     cairo_rectangle(m_data->context, finalFocusRect.x(), finalFocusRect.y(), finalFocusRect.width(), finalFocusRect.height());
382     
383     // Force the alpha to 50%.  This matches what the Mac does with outline rings.
384     Color ringColor(color.red(), color.green(), color.blue(), 127);
385     setColor(m_data->context, ringColor);
386     cairo_stroke(m_data->context);
387 }
388
389 void GraphicsContext::setFocusRingClip(const IntRect&)
390 {
391 }
392
393 void GraphicsContext::clearFocusRingClip()
394 {
395 }
396
397 }