JavaScriptCore:
[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.h>
34 #include <math.h>
35 #if WIN32
36 #include <cairo-win32.h>
37 #endif
38
39 #ifndef M_PI
40 #define M_PI 3.14159265358979323846
41 #endif
42
43 namespace WebCore {
44
45 class GraphicsContextPlatformPrivate {
46 public:
47     GraphicsContextPlatformPrivate();
48     ~GraphicsContextPlatformPrivate();
49
50     cairo_t* context;
51 };
52
53 static inline void setColor(cairo_t* cr, const Color& col)
54 {
55     float red, green, blue, alpha;
56     col.getRGBA(red, green, blue, alpha);
57     cairo_set_source_rgba(cr, red, green, blue, alpha);
58 }
59
60 // A fillRect helper
61 static inline void fillRectSourceOver(cairo_t* cr, const FloatRect& rect, const Color& col)
62 {
63     setColor(cr, col);
64     cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
65     cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
66     cairo_fill(cr);
67 }
68
69 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate()
70     :  context(0)
71 {
72 }
73
74 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
75 {
76     cairo_destroy(context);
77 }
78
79 #if WIN32
80 GraphicsContext::GraphicsContext(HDC dc)
81     : m_common(createGraphicsContextPrivate())
82     , m_data(new GraphicsContextPlatformPrivate)
83 {
84     cairo_surface_t* surface = cairo_win32_surface_create(dc);
85     m_data->context = cairo_create(surface);
86 }
87 #endif
88
89 #if PLATFORM(GDK)
90 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
91     : m_common(createGraphicsContextPrivate())
92     , m_data(new GraphicsContextPlatformPrivate)
93 {
94     m_data->context = cairo_reference(context);
95 }
96 #endif
97
98 GraphicsContext::~GraphicsContext()
99 {
100     destroyGraphicsContextPrivate(m_common);
101     delete m_data;
102 }
103
104 cairo_t* GraphicsContext::platformContext() const
105 {
106     return m_data->context;
107 }
108
109 void GraphicsContext::savePlatformState()
110 {
111     cairo_save(m_data->context);
112 }
113
114 void GraphicsContext::restorePlatformState()
115 {
116     cairo_restore(m_data->context);
117 }
118
119 // Draws a filled rectangle with a stroked border.
120 void GraphicsContext::drawRect(const IntRect& rect)
121 {
122     if (paintingDisabled())
123         return;
124     
125     cairo_t* context = m_data->context;
126     if (fillColor().alpha())
127         fillRectSourceOver(context, rect, fillColor());
128
129     if (pen().style() != Pen::NoPen) {
130         setColor(context, pen().color());
131         FloatRect r(rect);
132         r.inflate(-.5f);
133         cairo_rectangle(context, r.x(), r.y(), r.width(), r.height());
134         cairo_set_line_width(context, 1.0);
135         cairo_stroke(context);
136     }
137 }
138
139 // FIXME: Now that this is refactored, it should be shared by all contexts.
140 static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth, const Pen::PenStyle& penStyle)
141 {
142     // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
143     // works out.  For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
144     // (50+53)/2 = 103/2 = 51 when we want 51.5.  It is always true that an even width gave
145     // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
146     if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
147         if (p1.x() == p2.x()) {
148             p1.setY(p1.y() + strokeWidth);
149             p2.setY(p2.y() - strokeWidth);
150         }
151         else {
152             p1.setX(p1.x() + strokeWidth);
153             p2.setX(p2.x() - strokeWidth);
154         }
155     }
156     
157     if (((int)strokeWidth)%2) {
158         if (p1.x() == p2.x()) {
159             // We're a vertical line.  Adjust our x.
160             p1.setX(p1.x() + 0.5);
161             p2.setX(p2.x() + 0.5);
162         }
163         else {
164             // We're a horizontal line. Adjust our y.
165             p1.setY(p1.y() + 0.5);
166             p2.setY(p2.y() + 0.5);
167         }
168     }
169 }
170
171 // This is only used to draw borders.
172 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
173 {
174     if (paintingDisabled())
175         return;
176
177     cairo_t* context = m_data->context;
178     cairo_save(context);
179
180     Pen::PenStyle penStyle = pen().style();
181     if (penStyle == Pen::NoPen)
182         return;
183     float width = pen().width();
184     if (width < 1)
185         width = 1;
186
187     FloatPoint p1 = point1;
188     FloatPoint p2 = point2;
189     bool isVerticalLine = (p1.x() == p2.x());
190     
191     adjustLineToPixelBounderies(p1, p2, width, penStyle);
192     cairo_set_line_width(context, width);
193
194     int patWidth = 0;
195     switch (penStyle) {
196     case Pen::NoPen:
197     case Pen::SolidLine:
198         break;
199     case Pen::DotLine:
200         patWidth = (int)width;
201         break;
202     case Pen::DashLine:
203         patWidth = 3*(int)width;
204         break;
205     }
206
207     setColor(context, pen().color());
208     
209     cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
210     
211     if (patWidth) {
212         // Do a rect fill of our endpoints.  This ensures we always have the
213         // appearance of being a border.  We then draw the actual dotted/dashed line.
214         if (isVerticalLine) {
215             fillRectSourceOver(context, FloatRect(p1.x()-width/2, p1.y()-width, width, width), pen().color());
216             fillRectSourceOver(context, FloatRect(p2.x()-width/2, p2.y(), width, width), pen().color());
217         } else {
218             fillRectSourceOver(context, FloatRect(p1.x()-width, p1.y()-width/2, width, width), pen().color());
219             fillRectSourceOver(context, FloatRect(p2.x(), p2.y()-width/2, width, width), pen().color());
220         }
221         
222         // Example: 80 pixels with a width of 30 pixels.
223         // Remainder is 20.  The maximum pixels of line we could paint
224         // will be 50 pixels.
225         int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
226         int remainder = distance%patWidth;
227         int coverage = distance-remainder;
228         int numSegments = coverage/patWidth;
229
230         float patternOffset = 0;
231         // Special case 1px dotted borders for speed.
232         if (patWidth == 1)
233             patternOffset = 1.0;
234         else {
235             bool evenNumberOfSegments = numSegments%2 == 0;
236             if (remainder)
237                 evenNumberOfSegments = !evenNumberOfSegments;
238             if (evenNumberOfSegments) {
239                 if (remainder) {
240                     patternOffset += patWidth - remainder;
241                     patternOffset += remainder/2;
242                 }
243                 else
244                     patternOffset = patWidth/2;
245             }
246             else if (!evenNumberOfSegments) {
247                 if (remainder)
248                     patternOffset = (patWidth - remainder)/2;
249             }
250         }
251         
252         double dash = patWidth;
253         cairo_set_dash(context, &dash, 1, patternOffset);
254     }
255
256     cairo_move_to(context, p1.x(), p1.y());
257     cairo_line_to(context, p2.x(), p2.y());
258
259     cairo_stroke(context);
260     cairo_restore(context);
261 }
262
263 // This method is only used to draw the little circles used in lists.
264 void GraphicsContext::drawEllipse(const IntRect& rect)
265 {
266     if (paintingDisabled())
267         return;
268     
269     cairo_t* context = m_data->context;
270     cairo_save(context);
271     float yRadius = .5 * rect.height();
272     float xRadius = .5 * rect.width();
273     cairo_translate(context, rect.x() + xRadius, rect.y() + yRadius);
274     cairo_scale(context, xRadius, yRadius);
275     cairo_arc(context, 0., 0., 1., 0., 2 * M_PI);
276     cairo_restore(context);
277
278     if (fillColor().alpha()) {
279         setColor(context, fillColor());
280         cairo_fill(context);
281     }
282     if (pen().style() != Pen::NoPen) {
283         setColor(context, pen().color());
284         unsigned penWidth = pen().width();
285         if (penWidth == 0) 
286             penWidth++;
287         cairo_set_line_width(context, penWidth);
288         cairo_stroke(context);
289     }
290 }
291
292 // FIXME: This function needs to be adjusted to match the functionality on the Mac side.
293 void GraphicsContext::drawArc(const IntRect& rect, float thickness, int startAngle, int angleSpan)
294 {
295     if (paintingDisabled())
296         return;
297     
298     int x = rect.x();
299     int y = rect.y();
300     float w = (float)rect.width();
301     float h = (float)rect.height();
302     float scaleFactor = h / w;
303     float reverseScaleFactor = w / h;
304     
305     cairo_t* context = m_data->context;
306     if (pen().style() != Pen::NoPen) {        
307         float r = w / 2;
308         float fa = startAngle;
309         float falen =  fa + angleSpan;
310         cairo_arc(context, x + r, y + r, r, -fa * M_PI/180, -falen * M_PI/180);
311         
312         setColor(context, pen().color());
313         cairo_set_line_width(context, pen().width());
314         cairo_stroke(context);
315     }
316 }
317
318 void GraphicsContext::drawConvexPolygon(const IntPointArray& points)
319 {
320     if (paintingDisabled())
321         return;
322
323     int npoints = points.size();
324     if (npoints <= 1)
325         return;
326
327     cairo_t* context = m_data->context;
328
329     cairo_save(context);
330     cairo_set_antialias(context, CAIRO_ANTIALIAS_NONE);
331     cairo_move_to(context, points[0].x(), points[0].y());
332     for (int i = 1; i < npoints; i++)
333         cairo_line_to(context, points[i].x(), points[i].y());
334     cairo_close_path(context);
335
336     if (fillColor().alpha()) {
337         setColor(context, fillColor());
338         cairo_set_fill_rule(context, CAIRO_FILL_RULE_EVEN_ODD);
339         cairo_fill(context);
340     }
341
342     if (pen().style() != Pen::NoPen) {
343         setColor(context, pen().color());
344         cairo_set_line_width(context, pen().width());
345         cairo_stroke(context);
346     }
347     cairo_restore(context);
348 }
349
350 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
351 {
352     if (paintingDisabled())
353         return;
354
355     if (color.alpha())
356         fillRectSourceOver(m_data->context, rect, color);
357 }
358
359 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
360 {
361     if (paintingDisabled())
362         return;
363
364     if (color.alpha())
365         fillRectSourceOver(m_data->context, rect, color);
366 }
367
368 void GraphicsContext::addClip(const IntRect& rect)
369 {
370     if (paintingDisabled())
371         return;
372
373     cairo_t* context = m_data->context;
374     cairo_rectangle(context, rect.x(), rect.y(), rect.width(), rect.height());
375     cairo_clip(context);
376 }
377
378 void GraphicsContext::drawFocusRing(const Color& color)
379 {
380     if (paintingDisabled())
381         return;
382     int radius = (focusRingWidth() - 1) / 2;
383     int offset = radius + focusRingOffset();
384     
385     const Vector<IntRect>& rects = focusRingRects();
386     unsigned rectCount = rects.size();
387     IntRect finalFocusRect;
388     for (unsigned i = 0; i < rectCount; i++) {
389         IntRect focusRect = rects[i];
390         focusRect.inflate(offset);
391         finalFocusRect.unite(focusRect);
392     }
393     // FIXME: These rects should be rounded
394     cairo_rectangle(m_data->context, finalFocusRect.x(), finalFocusRect.y(), finalFocusRect.width(), finalFocusRect.height());
395     
396     // Force the alpha to 50%.  This matches what the Mac does with outline rings.
397     Color ringColor(color.red(), color.green(), color.blue(), 127);
398     setColor(m_data->context, ringColor);
399     cairo_stroke(m_data->context);
400 }
401
402 void GraphicsContext::setFocusRingClip(const IntRect&)
403 {
404 }
405
406 void GraphicsContext::clearFocusRingClip()
407 {
408 }
409
410 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset, int width, bool printing)
411 {
412     if (paintingDisabled())
413         return;
414
415     IntPoint origin = point + IntSize(0, yOffset + 1);
416     IntPoint endPoint = origin + IntSize(width, 0);
417     drawLine(origin, endPoint);
418 }
419
420 void GraphicsContext::drawLineForMisspelling(const IntPoint& point, int width)
421 {
422     // FIXME: Implement.
423 }
424
425 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
426 {
427     FloatRect result;
428     double x =frect.x();
429     double y = frect.y();
430     cairo_t* context = m_data->context;
431     cairo_user_to_device(context,&x,&y);
432     x = round(x);
433     y = round(y);
434     cairo_device_to_user(context,&x,&y);
435     result.setX((float)x);
436     result.setY((float)y);
437     x = frect.width();
438     y = frect.height();
439     cairo_user_to_device_distance(context,&x,&y);
440     x = round(x);
441     y = round(y);
442     cairo_device_to_user_distance(context,&x,&y);
443     result.setWidth((float)x);
444     result.setHeight((float)y);
445     return result; 
446 }
447
448 }