2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved.
3 * Copyright (C) 2007 Alp Toker <alp@atoker.com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 #include "GraphicsContext.h"
32 #include "CairoPath.h"
33 #include "FloatRect.h"
37 #include "NotImplemented.h"
42 #include <wtf/MathExtras.h>
45 #include <cairo-win32.h>
50 #include <pango/pango.h>
55 #define M_PI 3.14159265358979323846
60 class GraphicsContextPlatformPrivate {
62 GraphicsContextPlatformPrivate();
63 ~GraphicsContextPlatformPrivate();
69 GdkEventExpose* expose;
73 static inline void setColor(cairo_t* cr, const Color& col)
75 float red, green, blue, alpha;
76 col.getRGBA(red, green, blue, alpha);
77 cairo_set_source_rgba(cr, red, green, blue, alpha);
81 static inline void fillRectSourceOver(cairo_t* cr, const FloatRect& rect, const Color& col)
84 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
85 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
89 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate()
97 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
103 GraphicsContext::GraphicsContext(HDC dc)
104 : m_common(createGraphicsContextPrivate())
105 , m_data(new GraphicsContextPlatformPrivate)
108 cairo_surface_t* surface = cairo_win32_surface_create(dc);
109 m_data->cr = cairo_create(surface);
111 setPaintingDisabled(true);
117 GraphicsContext::GraphicsContext(PlatformGraphicsContext* cr)
118 : m_common(createGraphicsContextPrivate())
119 , m_data(new GraphicsContextPlatformPrivate)
121 m_data->cr = cairo_reference(cr);
122 setPaintingDisabled(!cr);
125 GraphicsContext::~GraphicsContext()
127 destroyGraphicsContextPrivate(m_common);
131 cairo_t* GraphicsContext::platformContext() const
136 void GraphicsContext::savePlatformState()
138 cairo_save(m_data->cr);
141 void GraphicsContext::restorePlatformState()
143 cairo_restore(m_data->cr);
146 // Draws a filled rectangle with a stroked border.
147 void GraphicsContext::drawRect(const IntRect& rect)
149 if (paintingDisabled())
152 cairo_t* cr = m_data->cr;
155 if (fillColor().alpha())
156 fillRectSourceOver(cr, rect, fillColor());
158 if (strokeStyle() != NoStroke) {
159 setColor(cr, strokeColor());
162 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height());
163 cairo_set_line_width(cr, 1.0);
170 // FIXME: Now that this is refactored, it should be shared by all contexts.
171 static void adjustLineToPixelBoundaries(FloatPoint& p1, FloatPoint& p2, float strokeWidth, StrokeStyle style)
173 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
174 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
175 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
176 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
177 if (style == DottedStroke || style == DashedStroke) {
178 if (p1.x() == p2.x()) {
179 p1.setY(p1.y() + strokeWidth);
180 p2.setY(p2.y() - strokeWidth);
183 p1.setX(p1.x() + strokeWidth);
184 p2.setX(p2.x() - strokeWidth);
188 if (static_cast<int>(strokeWidth) % 2) {
189 if (p1.x() == p2.x()) {
190 // We're a vertical line. Adjust our x.
191 p1.setX(p1.x() + 0.5);
192 p2.setX(p2.x() + 0.5);
195 // We're a horizontal line. Adjust our y.
196 p1.setY(p1.y() + 0.5);
197 p2.setY(p2.y() + 0.5);
202 // This is only used to draw borders.
203 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
205 if (paintingDisabled())
208 StrokeStyle style = strokeStyle();
209 if (style == NoStroke)
212 cairo_t* cr = m_data->cr;
215 float width = strokeThickness();
219 FloatPoint p1 = point1;
220 FloatPoint p2 = point2;
221 bool isVerticalLine = (p1.x() == p2.x());
223 adjustLineToPixelBoundaries(p1, p2, width, style);
224 cairo_set_line_width(cr, width);
232 patWidth = static_cast<int>(width);
235 patWidth = 3*static_cast<int>(width);
239 setColor(cr, strokeColor());
241 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
244 // Do a rect fill of our endpoints. This ensures we always have the
245 // appearance of being a border. We then draw the actual dotted/dashed line.
246 if (isVerticalLine) {
247 fillRectSourceOver(cr, FloatRect(p1.x() - width/2, p1.y() - width, width, width), strokeColor());
248 fillRectSourceOver(cr, FloatRect(p2.x() - width/2, p2.y(), width, width), strokeColor());
250 fillRectSourceOver(cr, FloatRect(p1.x() - width, p1.y() - width/2, width, width), strokeColor());
251 fillRectSourceOver(cr, FloatRect(p2.x(), p2.y() - width/2, width, width), strokeColor());
254 // Example: 80 pixels with a width of 30 pixels.
255 // Remainder is 20. The maximum pixels of line we could paint
256 // will be 50 pixels.
257 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*static_cast<int>(width);
258 int remainder = distance%patWidth;
259 int coverage = distance-remainder;
260 int numSegments = coverage/patWidth;
262 float patternOffset = 0;
263 // Special case 1px dotted borders for speed.
267 bool evenNumberOfSegments = numSegments%2 == 0;
269 evenNumberOfSegments = !evenNumberOfSegments;
270 if (evenNumberOfSegments) {
272 patternOffset += patWidth - remainder;
273 patternOffset += remainder/2;
276 patternOffset = patWidth/2;
278 else if (!evenNumberOfSegments) {
280 patternOffset = (patWidth - remainder)/2;
284 double dash = patWidth;
285 cairo_set_dash(cr, &dash, 1, patternOffset);
288 cairo_move_to(cr, p1.x(), p1.y());
289 cairo_line_to(cr, p2.x(), p2.y());
295 // This method is only used to draw the little circles used in lists.
296 void GraphicsContext::drawEllipse(const IntRect& rect)
298 if (paintingDisabled())
301 cairo_t* cr = m_data->cr;
303 float yRadius = .5 * rect.height();
304 float xRadius = .5 * rect.width();
305 cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius);
306 cairo_scale(cr, xRadius, yRadius);
307 cairo_arc(cr, 0., 0., 1., 0., 2 * M_PI);
310 if (fillColor().alpha()) {
311 setColor(cr, fillColor());
312 cairo_fill_preserve(cr);
315 if (strokeStyle() != NoStroke) {
316 setColor(cr, strokeColor());
317 cairo_set_line_width(cr, strokeThickness());
324 // FIXME: This function needs to be adjusted to match the functionality on the Mac side.
325 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
327 if (paintingDisabled())
330 if (strokeStyle() == NoStroke)
335 float w = rect.width();
336 #if 0 // FIXME: unused so far
337 float h = rect.height();
338 float scaleFactor = h / w;
339 float reverseScaleFactor = w / h;
342 float fa = startAngle;
343 float falen = fa + angleSpan;
345 cairo_t* cr = m_data->cr;
347 cairo_arc_negative(cr, x + r, y + r, r, -fa * M_PI/180, -falen * M_PI/180);
348 setColor(cr, strokeColor());
349 cairo_set_line_width(cr, strokeThickness());
354 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
356 if (paintingDisabled())
362 cairo_t* cr = m_data->cr;
365 cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE);
366 cairo_move_to(cr, points[0].x(), points[0].y());
367 for (size_t i = 1; i < npoints; i++)
368 cairo_line_to(cr, points[i].x(), points[i].y());
369 cairo_close_path(cr);
371 if (fillColor().alpha()) {
372 setColor(cr, fillColor());
373 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
374 cairo_fill_preserve(cr);
377 if (strokeStyle() != NoStroke) {
378 setColor(cr, strokeColor());
379 cairo_set_line_width(cr, strokeThickness());
387 void GraphicsContext::fillRect(const IntRect& rect, const Color& color)
389 if (paintingDisabled())
393 fillRectSourceOver(m_data->cr, rect, color);
396 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color)
398 if (paintingDisabled())
402 fillRectSourceOver(m_data->cr, rect, color);
405 void GraphicsContext::clip(const IntRect& rect)
407 if (paintingDisabled())
410 cairo_t* cr = m_data->cr;
411 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
415 void GraphicsContext::drawFocusRing(const Color& color)
417 if (paintingDisabled())
420 int radius = (focusRingWidth() - 1) / 2;
421 int offset = radius + focusRingOffset();
423 const Vector<IntRect>& rects = focusRingRects();
424 unsigned rectCount = rects.size();
425 IntRect finalFocusRect;
426 for (unsigned i = 0; i < rectCount; i++) {
427 IntRect focusRect = rects[i];
428 focusRect.inflate(offset);
429 finalFocusRect.unite(focusRect);
432 cairo_t* cr = m_data->cr;
434 // FIXME: These rects should be rounded
435 cairo_rectangle(cr, finalFocusRect.x(), finalFocusRect.y(), finalFocusRect.width(), finalFocusRect.height());
437 // Force the alpha to 50%. This matches what the Mac does with outline rings.
438 Color ringColor(color.red(), color.green(), color.blue(), 127);
439 setColor(cr, ringColor);
444 void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool printing)
446 if (paintingDisabled())
449 // This is a workaround for http://bugs.webkit.org/show_bug.cgi?id=15659
450 StrokeStyle savedStrokeStyle = strokeStyle();
451 setStrokeStyle(SolidStroke);
453 IntPoint endPoint = origin + IntSize(width, 0);
454 drawLine(origin, endPoint);
456 setStrokeStyle(savedStrokeStyle);
459 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& origin, int width, bool grammar)
461 if (paintingDisabled())
465 cairo_t* cr = m_data->cr;
468 // Convention is green for grammar, red for spelling
469 // These need to become configurable
471 cairo_set_source_rgb(cr, 0, 1, 0);
473 cairo_set_source_rgb(cr, 1, 0, 0);
475 // We ignore most of the provided constants in favour of the platform style
476 pango_cairo_show_error_underline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness);
484 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
487 double x = frect.x();
488 double y = frect.y();
489 cairo_t* cr = m_data->cr;
490 cairo_user_to_device(cr, &x, &y);
493 cairo_device_to_user(cr, &x, &y);
494 result.setX(static_cast<float>(x));
495 result.setY(static_cast<float>(y));
498 cairo_user_to_device_distance(cr, &x, &y);
501 cairo_device_to_user_distance(cr, &x, &y);
502 result.setWidth(static_cast<float>(x));
503 result.setHeight(static_cast<float>(y));
507 void GraphicsContext::translate(float x, float y)
509 if (paintingDisabled())
512 cairo_t* cr = m_data->cr;
513 cairo_translate(cr, x, y);
516 IntPoint GraphicsContext::origin()
518 cairo_matrix_t matrix;
519 cairo_t* cr = m_data->cr;
520 cairo_get_matrix(cr, &matrix);
521 return IntPoint(static_cast<int>(matrix.x0), static_cast<int>(matrix.y0));
524 void GraphicsContext::setPlatformFillColor(const Color& col)
526 // FIXME: this is probably a no-op but I'm not sure
527 // notImplemented(); // commented-out because it's chatty and clutters output
530 void GraphicsContext::setPlatformStrokeColor(const Color& col)
532 // FIXME: this is probably a no-op but I'm not sure
533 //notImplemented(); // commented-out because it's chatty and clutters output
536 void GraphicsContext::setPlatformStrokeThickness(float strokeThickness)
538 if (paintingDisabled())
541 cairo_set_line_width(m_data->cr, strokeThickness);
544 void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle)
546 static double dashPattern[] = {5.0, 5.0};
547 static double dotPattern[] = {1.0, 1.0};
549 if (paintingDisabled())
552 switch (strokeStyle) {
554 // FIXME: is it the right way to emulate NoStroke?
555 cairo_set_line_width(m_data->cr, 0);
558 cairo_set_dash(m_data->cr, 0, 0, 0);
561 cairo_set_dash(m_data->cr, dotPattern, 2, 0);
564 cairo_set_dash(m_data->cr, dashPattern, 2, 0);
572 void GraphicsContext::setPlatformFont(const Font& font)
574 if (paintingDisabled())
578 // FIXME: is it the right thing to do? Also, doesn't work on Win because
579 // there FontData doesn't have ::setFont()
580 const FontData *fontData = font.primaryFont();
581 fontData->setFont(m_data->cr);
585 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
590 void GraphicsContext::concatCTM(const AffineTransform& transform)
592 if (paintingDisabled())
595 cairo_t* cr = m_data->cr;
596 const cairo_matrix_t* matrix = reinterpret_cast<const cairo_matrix_t*>(&transform);
597 cairo_transform(cr, matrix);
600 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
602 if (paintingDisabled())
608 path.addEllipse(rect);
611 inner.inflate(-thickness);
612 path.addEllipse(inner);
614 cairo_t* cr = m_data->cr;
615 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
620 void GraphicsContext::setShadow(IntSize const&, int, Color const&)
625 void GraphicsContext::clearShadow()
630 void GraphicsContext::beginTransparencyLayer(float opacity)
632 if (paintingDisabled())
635 cairo_t* cr = m_data->cr;
636 cairo_push_group(cr);
637 m_data->layers.append(opacity);
640 void GraphicsContext::endTransparencyLayer()
642 if (paintingDisabled())
645 cairo_t* cr = m_data->cr;
647 cairo_pop_group_to_source(cr);
648 cairo_paint_with_alpha(cr, m_data->layers.last());
649 m_data->layers.removeLast();
652 void GraphicsContext::clearRect(const FloatRect& rect)
654 if (paintingDisabled())
657 cairo_t* cr = m_data->cr;
660 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
661 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
666 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
668 if (paintingDisabled())
671 cairo_t* cr = m_data->cr;
673 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height());
674 setColor(cr, strokeColor());
675 cairo_set_line_width(cr, width);
680 void GraphicsContext::setLineCap(LineCap lineCap)
682 if (paintingDisabled())
685 cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT;
691 cairoCap = CAIRO_LINE_CAP_ROUND;
694 cairoCap = CAIRO_LINE_CAP_SQUARE;
697 cairo_set_line_cap(m_data->cr, cairoCap);
700 void GraphicsContext::setLineJoin(LineJoin lineJoin)
702 if (paintingDisabled())
705 cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER;
711 cairoJoin = CAIRO_LINE_JOIN_ROUND;
714 cairoJoin = CAIRO_LINE_JOIN_BEVEL;
717 cairo_set_line_join(m_data->cr, cairoJoin);
720 void GraphicsContext::setMiterLimit(float miter)
722 if (paintingDisabled())
725 cairo_set_miter_limit(m_data->cr, miter);
728 void GraphicsContext::setAlpha(float)
733 static inline cairo_operator_t toCairoOperator(CompositeOperator op)
737 return CAIRO_OPERATOR_CLEAR;
739 return CAIRO_OPERATOR_SOURCE;
740 case CompositeSourceOver:
741 return CAIRO_OPERATOR_OVER;
742 case CompositeSourceIn:
743 return CAIRO_OPERATOR_IN;
744 case CompositeSourceOut:
745 return CAIRO_OPERATOR_OUT;
746 case CompositeSourceAtop:
747 return CAIRO_OPERATOR_ATOP;
748 case CompositeDestinationOver:
749 return CAIRO_OPERATOR_DEST_OVER;
750 case CompositeDestinationIn:
751 return CAIRO_OPERATOR_DEST_IN;
752 case CompositeDestinationOut:
753 return CAIRO_OPERATOR_DEST_OUT;
754 case CompositeDestinationAtop:
755 return CAIRO_OPERATOR_DEST_ATOP;
757 return CAIRO_OPERATOR_XOR;
758 case CompositePlusDarker:
759 return CAIRO_OPERATOR_OVER;
760 case CompositeHighlight:
761 return CAIRO_OPERATOR_OVER;
762 case CompositePlusLighter:
763 return CAIRO_OPERATOR_OVER;
766 return CAIRO_OPERATOR_OVER;
769 void GraphicsContext::setCompositeOperation(CompositeOperator op)
771 if (paintingDisabled())
774 cairo_set_operator(m_data->cr, toCairoOperator(op));
777 void GraphicsContext::beginPath()
779 if (paintingDisabled())
782 cairo_t* cr = m_data->cr;
786 void GraphicsContext::addPath(const Path& path)
788 if (paintingDisabled())
791 cairo_t* cr = m_data->cr;
792 cairo_path_t *p = cairo_copy_path(path.platformPath()->m_cr);
793 cairo_append_path(cr, p);
794 cairo_path_destroy(p);
797 void GraphicsContext::clip(const Path& path)
799 if (paintingDisabled())
802 cairo_t* cr = m_data->cr;
803 cairo_path_t *p = cairo_copy_path(path.platformPath()->m_cr);
804 cairo_append_path(cr, p);
805 cairo_path_destroy(p);
809 void GraphicsContext::clipOut(const Path&)
814 void GraphicsContext::rotate(float radians)
816 if (paintingDisabled())
819 cairo_rotate(m_data->cr, radians);
822 void GraphicsContext::scale(const FloatSize& size)
824 if (paintingDisabled())
827 cairo_scale(m_data->cr, size.width(), size.height());
830 void GraphicsContext::clipOut(const IntRect&)
835 void GraphicsContext::clipOutEllipseInRect(const IntRect&)
840 void GraphicsContext::fillRoundedRect(const IntRect&, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color&)
846 void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose)
848 m_data->expose = expose;
851 GdkEventExpose* GraphicsContext::gdkExposeEvent() const
853 return m_data->expose;
856 GdkDrawable* GraphicsContext::gdkDrawable() const
861 return GDK_DRAWABLE(m_data->expose->window);
864 IntPoint GraphicsContext::translatePoint(const IntPoint& point) const
867 cairo_get_matrix(m_data->cr, &tm);
868 double x = point.x();
869 double y = point.y();
871 cairo_matrix_transform_point(&tm, &x, &y);
872 return IntPoint(x, y);
876 } // namespace WebCore
878 #endif // PLATFORM(CAIRO)