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 65using namespace std; 66 67#ifndef M_PI 68#define M_PI 3.14159265358979323846 69#endif 70 71namespace WebCore { 72 73static 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 94static 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 122static 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 130static 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 138enum PathDrawingStyle { 139 Fill = 1, 140 Stroke = 2, 141 FillAndStroke = Fill + Stroke 142}; 143 144static 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 187static 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 196static void strokeCurrentCairoPath(GraphicsContext* context, cairo_t* cairoContext) 197{ 198 drawPathShadow(context, Stroke); 199 setPlatformStroke(context, cairoContext); 200 cairo_new_path(cairoContext); 201} 202 203GraphicsContext::GraphicsContext(cairo_t* cr) 204 : m_updatingControlTints(false) 205{ 206 m_data = new GraphicsContextPlatformPrivateToplevel(new PlatformContextCairo(cr)); 207} 208 209void 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 218void GraphicsContext::platformDestroy() 219{ 220 delete m_data; 221} 222 223AffineTransform 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 231PlatformContextCairo* GraphicsContext::platformContext() const 232{ 233 return m_data->platformContext; 234} 235 236void GraphicsContext::savePlatformState() 237{ 238 platformContext()->save(); 239 m_data->save(); 240 m_data->shadowStack.append(m_data->shadow); 241} 242 243void 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. 257void 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. 281void 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. 372void 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 399void 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 485void 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 515void 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 538void 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 548void 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 558void 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 570void 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 582void 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 596void 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 607static 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 615static inline void adjustFocusRingLineWidth(int& width) 616{ 617#if PLATFORM(GTK) 618 width = 2; 619#endif 620} 621 622static inline StrokeStyle focusRingStrokeStyle() 623{ 624#if PLATFORM(GTK) 625 return DottedStroke; 626#else 627 return SolidStroke; 628#endif 629} 630 631void 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 649void 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 713void 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 728void 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 758FloatRect 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 795void 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 805void 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 811void 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 817void GraphicsContext::setPlatformStrokeThickness(float strokeThickness) 818{ 819 if (paintingDisabled()) 820 return; 821 822 cairo_set_line_width(platformContext()->cr(), strokeThickness); 823} 824 825void 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 850void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) 851{ 852 notImplemented(); 853} 854 855void 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 866void 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 877void 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 900void 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 914ContextShadow* GraphicsContext::contextShadow() 915{ 916 return &m_data->shadow; 917} 918 919void GraphicsContext::clearPlatformShadow() 920{ 921 m_data->shadow.clear(); 922} 923 924void 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 935void 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 948void 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 962void 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 975void 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 995void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 996{ 997 cairo_set_dash(platformContext()->cr(), dashes.data(), dashes.size(), dashOffset); 998} 999 1000void 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 1020void GraphicsContext::setMiterLimit(float miter) 1021{ 1022 if (paintingDisabled()) 1023 return; 1024 1025 cairo_set_miter_limit(platformContext()->cr(), miter); 1026} 1027 1028void GraphicsContext::setAlpha(float alpha) 1029{ 1030 m_state.globalAlpha = alpha; 1031} 1032 1033float GraphicsContext::getAlpha() 1034{ 1035 return m_state.globalAlpha; 1036} 1037 1038void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op) 1039{ 1040 if (paintingDisabled()) 1041 return; 1042 1043 cairo_set_operator(platformContext()->cr(), toCairoOperator(op)); 1044} 1045 1046void 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 1061void GraphicsContext::canvasClip(const Path& path) 1062{ 1063 clip(path); 1064} 1065 1066void 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 1083void GraphicsContext::rotate(float radians) 1084{ 1085 if (paintingDisabled()) 1086 return; 1087 1088 cairo_rotate(platformContext()->cr(), radians); 1089 m_data->rotate(radians); 1090} 1091 1092void 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 1101void 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 1117static 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 1125void 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) 1144void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose) 1145{ 1146 m_data->expose = expose; 1147} 1148 1149GdkEventExpose* GraphicsContext::gdkExposeEvent() const 1150{ 1151 return m_data->expose; 1152} 1153 1154GdkWindow* GraphicsContext::gdkWindow() const 1155{ 1156 if (!m_data->expose) 1157 return 0; 1158 1159 return m_data->expose->window; 1160} 1161#endif 1162 1163void 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 1174void GraphicsContext::setImageInterpolationQuality(InterpolationQuality) 1175{ 1176} 1177 1178InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1179{ 1180 return InterpolationDefault; 1181} 1182 1183} // namespace WebCore 1184 1185#endif // USE(CAIRO) 1186