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