1 /* 2 * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. 3 * Copyright (C) 2007 Alp Toker <alp (at) atoker.com> 4 * Copyright (C) 2008, 2009 Dirk Schulze <krit (at) webkit.org> 5 * Copyright (C) 2008 Nuanti Ltd. 6 * Copyright (C) 2009 Brent Fulgham <bfulgham (at) webkit.org> 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 21 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 25 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 #include "config.h" 31 #include "GraphicsContext.h" 32 33 #if PLATFORM(CAIRO) 34 35 #include "AffineTransform.h" 36 #include "CairoPath.h" 37 #include "FEGaussianBlur.h" 38 #include "FloatRect.h" 39 #include "Font.h" 40 #include "ImageBuffer.h" 41 #include "ImageBufferFilter.h" 42 #include "IntRect.h" 43 #include "NotImplemented.h" 44 #include "Path.h" 45 #include "Pattern.h" 46 #include "SimpleFontData.h" 47 #include "SourceGraphic.h" 48 49 #include <cairo.h> 50 #include <math.h> 51 #include <stdio.h> 52 #include <wtf/MathExtras.h> 53 54 #if PLATFORM(GTK) 55 #include <gdk/gdk.h> 56 #include <pango/pango.h> 57 #elif PLATFORM(WIN) 58 #include <cairo-win32.h> 59 #endif 60 #include "GraphicsContextPlatformPrivateCairo.h" 61 #include "GraphicsContextPrivate.h" 62 63 #ifndef M_PI 64 #define M_PI 3.14159265358979323846 65 #endif 66 67 namespace WebCore { 68 69 static inline void setColor(cairo_t* cr, const Color& col) 70 { 71 float red, green, blue, alpha; 72 col.getRGBA(red, green, blue, alpha); 73 cairo_set_source_rgba(cr, red, green, blue, alpha); 74 } 75 76 static inline void setPlatformFill(GraphicsContext* context, cairo_t* cr, GraphicsContextPrivate* gcp) 77 { 78 cairo_save(cr); 79 if (gcp->state.fillPattern) { 80 AffineTransform affine; 81 cairo_set_source(cr, gcp->state.fillPattern->createPlatformPattern(affine)); 82 } else if (gcp->state.fillGradient) 83 cairo_set_source(cr, gcp->state.fillGradient->platformGradient()); 84 else 85 setColor(cr, context->fillColor()); 86 cairo_clip_preserve(cr); 87 cairo_paint_with_alpha(cr, gcp->state.globalAlpha); 88 cairo_restore(cr); 89 } 90 91 static inline void setPlatformStroke(GraphicsContext* context, cairo_t* cr, GraphicsContextPrivate* gcp) 92 { 93 cairo_save(cr); 94 if (gcp->state.strokePattern) { 95 AffineTransform affine; 96 cairo_set_source(cr, gcp->state.strokePattern->createPlatformPattern(affine)); 97 } else if (gcp->state.strokeGradient) 98 cairo_set_source(cr, gcp->state.strokeGradient->platformGradient()); 99 else { 100 Color strokeColor = colorWithOverrideAlpha(context->strokeColor().rgb(), context->strokeColor().alpha() / 255.f * gcp->state.globalAlpha); 101 setColor(cr, strokeColor); 102 } 103 if (gcp->state.globalAlpha < 1.0f && (gcp->state.strokePattern || gcp->state.strokeGradient)) { 104 cairo_push_group(cr); 105 cairo_paint_with_alpha(cr, gcp->state.globalAlpha); 106 cairo_pop_group_to_source(cr); 107 } 108 cairo_stroke_preserve(cr); 109 cairo_restore(cr); 110 } 111 112 // A fillRect helper 113 static inline void fillRectSourceOver(cairo_t* cr, const FloatRect& rect, const Color& col) 114 { 115 setColor(cr, col); 116 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 117 cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 118 cairo_fill(cr); 119 } 120 121 static inline void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr) 122 { 123 cairo_set_antialias(dstCr, cairo_get_antialias(srcCr)); 124 125 size_t dashCount = cairo_get_dash_count(srcCr); 126 Vector<double> dashes(dashCount); 127 128 double offset; 129 cairo_get_dash(srcCr, dashes.data(), &offset); 130 cairo_set_dash(dstCr, dashes.data(), dashCount, offset); 131 cairo_set_line_cap(dstCr, cairo_get_line_cap(srcCr)); 132 cairo_set_line_join(dstCr, cairo_get_line_join(srcCr)); 133 cairo_set_line_width(dstCr, cairo_get_line_width(srcCr)); 134 cairo_set_miter_limit(dstCr, cairo_get_miter_limit(srcCr)); 135 cairo_set_fill_rule(dstCr, cairo_get_fill_rule(srcCr)); 136 } 137 138 void GraphicsContext::calculateShadowBufferDimensions(IntSize& shadowBufferSize, FloatRect& shadowRect, float& kernelSize, const FloatRect& sourceRect, const IntSize& shadowSize, int shadowBlur) 139 { 140 #if ENABLE(FILTERS) 141 // calculate the kernel size according to the HTML5 canvas shadow specification 142 kernelSize = (shadowBlur < 8 ? shadowBlur / 2.f : sqrt(shadowBlur * 2.f)); 143 int blurRadius = ceil(kernelSize); 144 145 shadowBufferSize = IntSize(sourceRect.width() + blurRadius * 2, sourceRect.height() + blurRadius * 2); 146 147 // determine dimensions of shadow rect 148 shadowRect = FloatRect(sourceRect.location(), shadowBufferSize); 149 shadowRect.move(shadowSize.width() - kernelSize, shadowSize.height() - kernelSize); 150 #endif 151 } 152 153 static inline void drawPathShadow(GraphicsContext* context, GraphicsContextPrivate* gcp, bool fillShadow, bool strokeShadow) 154 { 155 #if ENABLE(FILTERS) 156 IntSize shadowSize; 157 int shadowBlur; 158 Color shadowColor; 159 if (!context->getShadow(shadowSize, shadowBlur, shadowColor)) 160 return; 161 162 // Calculate filter values to create appropriate shadow. 163 cairo_t* cr = context->platformContext(); 164 cairo_path_t* path = cairo_copy_path(cr); 165 double x0, x1, y0, y1; 166 if (strokeShadow) 167 cairo_stroke_extents(cr, &x0, &y0, &x1, &y1); 168 else 169 cairo_fill_extents(cr, &x0, &y0, &x1, &y1); 170 FloatRect rect(x0, y0, x1 - x0, y1 - y0); 171 172 IntSize shadowBufferSize; 173 FloatRect shadowRect; 174 float kernelSize = 0; 175 GraphicsContext::calculateShadowBufferDimensions(shadowBufferSize, shadowRect, kernelSize, rect, shadowSize, shadowBlur); 176 177 // Create suitably-sized ImageBuffer to hold the shadow. 178 OwnPtr<ImageBuffer> shadowBuffer = ImageBuffer::create(shadowBufferSize); 179 180 // Draw shadow into a new ImageBuffer. 181 cairo_t* shadowContext = shadowBuffer->context()->platformContext(); 182 copyContextProperties(cr, shadowContext); 183 cairo_translate(shadowContext, -rect.x() + kernelSize, -rect.y() + kernelSize); 184 cairo_new_path(shadowContext); 185 cairo_append_path(shadowContext, path); 186 187 if (fillShadow) 188 setPlatformFill(context, shadowContext, gcp); 189 if (strokeShadow) 190 setPlatformStroke(context, shadowContext, gcp); 191 192 context->createPlatformShadow(shadowBuffer.release(), shadowColor, shadowRect, kernelSize); 193 #endif 194 } 195 196 GraphicsContext::GraphicsContext(PlatformGraphicsContext* cr) 197 : m_common(createGraphicsContextPrivate()) 198 , m_data(new GraphicsContextPlatformPrivate) 199 { 200 m_data->cr = cairo_reference(cr); 201 m_data->syncContext(cr); 202 setPaintingDisabled(!cr); 203 } 204 205 GraphicsContext::~GraphicsContext() 206 { 207 destroyGraphicsContextPrivate(m_common); 208 delete m_data; 209 } 210 211 AffineTransform GraphicsContext::getCTM() const 212 { 213 cairo_t* cr = platformContext(); 214 cairo_matrix_t m; 215 cairo_get_matrix(cr, &m); 216 return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0); 217 } 218 219 cairo_t* GraphicsContext::platformContext() const 220 { 221 return m_data->cr; 222 } 223 224 void GraphicsContext::savePlatformState() 225 { 226 cairo_save(m_data->cr); 227 m_data->save(); 228 } 229 230 void GraphicsContext::restorePlatformState() 231 { 232 cairo_restore(m_data->cr); 233 m_data->restore(); 234 } 235 236 // Draws a filled rectangle with a stroked border. 237 void GraphicsContext::drawRect(const IntRect& rect) 238 { 239 if (paintingDisabled()) 240 return; 241 242 cairo_t* cr = m_data->cr; 243 cairo_save(cr); 244 245 if (fillColor().alpha()) 246 fillRectSourceOver(cr, rect, fillColor()); 247 248 if (strokeStyle() != NoStroke) { 249 setColor(cr, strokeColor()); 250 FloatRect r(rect); 251 r.inflate(-.5f); 252 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height()); 253 cairo_set_line_width(cr, 1.0); 254 cairo_stroke(cr); 255 } 256 257 cairo_restore(cr); 258 } 259 260 // This is only used to draw borders. 261 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) 262 { 263 if (paintingDisabled()) 264 return; 265 266 StrokeStyle style = strokeStyle(); 267 if (style == NoStroke) 268 return; 269 270 cairo_t* cr = m_data->cr; 271 cairo_save(cr); 272 273 float width = strokeThickness(); 274 if (width < 1) 275 width = 1; 276 277 FloatPoint p1 = point1; 278 FloatPoint p2 = point2; 279 bool isVerticalLine = (p1.x() == p2.x()); 280 281 adjustLineToPixelBoundaries(p1, p2, width, style); 282 cairo_set_line_width(cr, width); 283 284 int patWidth = 0; 285 switch (style) { 286 case NoStroke: 287 case SolidStroke: 288 break; 289 case DottedStroke: 290 patWidth = static_cast<int>(width); 291 break; 292 case DashedStroke: 293 patWidth = 3*static_cast<int>(width); 294 break; 295 } 296 297 setColor(cr, strokeColor()); 298 299 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); 300 301 if (patWidth) { 302 // Do a rect fill of our endpoints. This ensures we always have the 303 // appearance of being a border. We then draw the actual dotted/dashed line. 304 if (isVerticalLine) { 305 fillRectSourceOver(cr, FloatRect(p1.x() - width/2, p1.y() - width, width, width), strokeColor()); 306 fillRectSourceOver(cr, FloatRect(p2.x() - width/2, p2.y(), width, width), strokeColor()); 307 } else { 308 fillRectSourceOver(cr, FloatRect(p1.x() - width, p1.y() - width/2, width, width), strokeColor()); 309 fillRectSourceOver(cr, FloatRect(p2.x(), p2.y() - width/2, width, width), strokeColor()); 310 } 311 312 // Example: 80 pixels with a width of 30 pixels. 313 // Remainder is 20. The maximum pixels of line we could paint 314 // will be 50 pixels. 315 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*static_cast<int>(width); 316 int remainder = distance%patWidth; 317 int coverage = distance-remainder; 318 int numSegments = coverage/patWidth; 319 320 float patternOffset = 0; 321 // Special case 1px dotted borders for speed. 322 if (patWidth == 1) 323 patternOffset = 1.0; 324 else { 325 bool evenNumberOfSegments = !(numSegments % 2); 326 if (remainder) 327 evenNumberOfSegments = !evenNumberOfSegments; 328 if (evenNumberOfSegments) { 329 if (remainder) { 330 patternOffset += patWidth - remainder; 331 patternOffset += remainder / 2; 332 } else 333 patternOffset = patWidth / 2; 334 } else if (!evenNumberOfSegments) { 335 if (remainder) 336 patternOffset = (patWidth - remainder) / 2; 337 } 338 } 339 340 double dash = patWidth; 341 cairo_set_dash(cr, &dash, 1, patternOffset); 342 } 343 344 cairo_move_to(cr, p1.x(), p1.y()); 345 cairo_line_to(cr, p2.x(), p2.y()); 346 347 cairo_stroke(cr); 348 cairo_restore(cr); 349 } 350 351 // This method is only used to draw the little circles used in lists. 352 void GraphicsContext::drawEllipse(const IntRect& rect) 353 { 354 if (paintingDisabled()) 355 return; 356 357 cairo_t* cr = m_data->cr; 358 cairo_save(cr); 359 float yRadius = .5 * rect.height(); 360 float xRadius = .5 * rect.width(); 361 cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius); 362 cairo_scale(cr, xRadius, yRadius); 363 cairo_arc(cr, 0., 0., 1., 0., 2 * M_PI); 364 cairo_restore(cr); 365 366 if (fillColor().alpha()) { 367 setColor(cr, fillColor()); 368 cairo_fill_preserve(cr); 369 } 370 371 if (strokeStyle() != NoStroke) { 372 setColor(cr, strokeColor()); 373 cairo_set_line_width(cr, strokeThickness()); 374 cairo_stroke(cr); 375 } 376 377 cairo_new_path(cr); 378 } 379 380 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan) 381 { 382 if (paintingDisabled() || strokeStyle() == NoStroke) 383 return; 384 385 int x = rect.x(); 386 int y = rect.y(); 387 float w = rect.width(); 388 float h = rect.height(); 389 float scaleFactor = h / w; 390 float reverseScaleFactor = w / h; 391 392 float hRadius = w / 2; 393 float vRadius = h / 2; 394 float fa = startAngle; 395 float falen = fa + angleSpan; 396 397 cairo_t* cr = m_data->cr; 398 cairo_save(cr); 399 400 if (w != h) 401 cairo_scale(cr, 1., scaleFactor); 402 403 cairo_arc_negative(cr, x + hRadius, (y + vRadius) * reverseScaleFactor, hRadius, -fa * M_PI/180, -falen * M_PI/180); 404 405 if (w != h) 406 cairo_scale(cr, 1., reverseScaleFactor); 407 408 float width = strokeThickness(); 409 int patWidth = 0; 410 411 switch (strokeStyle()) { 412 case DottedStroke: 413 patWidth = static_cast<int>(width / 2); 414 break; 415 case DashedStroke: 416 patWidth = 3 * static_cast<int>(width / 2); 417 break; 418 default: 419 break; 420 } 421 422 setColor(cr, strokeColor()); 423 424 if (patWidth) { 425 // Example: 80 pixels with a width of 30 pixels. 426 // Remainder is 20. The maximum pixels of line we could paint 427 // will be 50 pixels. 428 int distance; 429 if (hRadius == vRadius) 430 distance = static_cast<int>((M_PI * hRadius) / 2.0); 431 else // We are elliptical and will have to estimate the distance 432 distance = static_cast<int>((M_PI * sqrtf((hRadius * hRadius + vRadius * vRadius) / 2.0)) / 2.0); 433 434 int remainder = distance % patWidth; 435 int coverage = distance - remainder; 436 int numSegments = coverage / patWidth; 437 438 float patternOffset = 0.0; 439 // Special case 1px dotted borders for speed. 440 if (patWidth == 1) 441 patternOffset = 1.0; 442 else { 443 bool evenNumberOfSegments = !(numSegments % 2); 444 if (remainder) 445 evenNumberOfSegments = !evenNumberOfSegments; 446 if (evenNumberOfSegments) { 447 if (remainder) { 448 patternOffset += patWidth - remainder; 449 patternOffset += remainder / 2.0; 450 } else 451 patternOffset = patWidth / 2.0; 452 } else { 453 if (remainder) 454 patternOffset = (patWidth - remainder) / 2.0; 455 } 456 } 457 458 double dash = patWidth; 459 cairo_set_dash(cr, &dash, 1, patternOffset); 460 } 461 462 cairo_stroke(cr); 463 cairo_restore(cr); 464 } 465 466 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias) 467 { 468 if (paintingDisabled()) 469 return; 470 471 if (npoints <= 1) 472 return; 473 474 cairo_t* cr = m_data->cr; 475 476 cairo_save(cr); 477 cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); 478 cairo_move_to(cr, points[0].x(), points[0].y()); 479 for (size_t i = 1; i < npoints; i++) 480 cairo_line_to(cr, points[i].x(), points[i].y()); 481 cairo_close_path(cr); 482 483 if (fillColor().alpha()) { 484 setColor(cr, fillColor()); 485 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 486 cairo_fill_preserve(cr); 487 } 488 489 if (strokeStyle() != NoStroke) { 490 setColor(cr, strokeColor()); 491 cairo_set_line_width(cr, strokeThickness()); 492 cairo_stroke(cr); 493 } 494 495 cairo_new_path(cr); 496 cairo_restore(cr); 497 } 498 499 void GraphicsContext::fillPath() 500 { 501 if (paintingDisabled()) 502 return; 503 504 cairo_t* cr = m_data->cr; 505 506 cairo_set_fill_rule(cr, fillRule() == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 507 drawPathShadow(this, m_common, true, false); 508 509 setPlatformFill(this, cr, m_common); 510 cairo_new_path(cr); 511 } 512 513 void GraphicsContext::strokePath() 514 { 515 if (paintingDisabled()) 516 return; 517 518 cairo_t* cr = m_data->cr; 519 drawPathShadow(this, m_common, false, true); 520 521 setPlatformStroke(this, cr, m_common); 522 cairo_new_path(cr); 523 524 } 525 526 void GraphicsContext::drawPath() 527 { 528 if (paintingDisabled()) 529 return; 530 531 cairo_t* cr = m_data->cr; 532 533 cairo_set_fill_rule(cr, fillRule() == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 534 drawPathShadow(this, m_common, true, true); 535 536 setPlatformFill(this, cr, m_common); 537 setPlatformStroke(this, cr, m_common); 538 cairo_new_path(cr); 539 } 540 541 void GraphicsContext::fillRect(const FloatRect& rect) 542 { 543 if (paintingDisabled()) 544 return; 545 546 cairo_t* cr = m_data->cr; 547 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 548 fillPath(); 549 } 550 551 static void drawBorderlessRectShadow(GraphicsContext* context, const FloatRect& rect, const Color& rectColor) 552 { 553 #if ENABLE(FILTERS) 554 IntSize shadowSize; 555 int shadowBlur; 556 Color shadowColor; 557 558 if (!context->getShadow(shadowSize, shadowBlur, shadowColor)) 559 return; 560 561 IntSize shadowBufferSize; 562 FloatRect shadowRect; 563 float kernelSize = 0; 564 GraphicsContext::calculateShadowBufferDimensions(shadowBufferSize, shadowRect, kernelSize, rect, shadowSize, shadowBlur); 565 566 // Draw shadow into a new ImageBuffer 567 OwnPtr<ImageBuffer> shadowBuffer = ImageBuffer::create(shadowBufferSize); 568 GraphicsContext* shadowContext = shadowBuffer->context(); 569 shadowContext->fillRect(FloatRect(FloatPoint(kernelSize, kernelSize), rect.size()), rectColor, DeviceColorSpace); 570 571 context->createPlatformShadow(shadowBuffer.release(), shadowColor, shadowRect, kernelSize); 572 #endif 573 } 574 575 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) 576 { 577 if (paintingDisabled()) 578 return; 579 580 drawBorderlessRectShadow(this, rect, color); 581 if (color.alpha()) 582 fillRectSourceOver(m_data->cr, rect, color); 583 } 584 585 void GraphicsContext::clip(const FloatRect& rect) 586 { 587 if (paintingDisabled()) 588 return; 589 590 cairo_t* cr = m_data->cr; 591 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 592 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 593 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 594 cairo_clip(cr); 595 cairo_set_fill_rule(cr, savedFillRule); 596 m_data->clip(rect); 597 } 598 599 void GraphicsContext::clipPath(WindRule clipRule) 600 { 601 if (paintingDisabled()) 602 return; 603 604 cairo_t* cr = m_data->cr; 605 cairo_set_fill_rule(cr, clipRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 606 cairo_clip(cr); 607 } 608 609 void GraphicsContext::drawFocusRing(const Vector<Path>& paths, int width, int offset, const Color& color) 610 { 611 // FIXME: implement 612 } 613 614 void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int /* offset */, const Color& color) 615 { 616 if (paintingDisabled()) 617 return; 618 619 unsigned rectCount = rects.size(); 620 621 cairo_t* cr = m_data->cr; 622 cairo_save(cr); 623 cairo_push_group(cr); 624 cairo_new_path(cr); 625 626 #if PLATFORM(GTK) 627 GdkRegion* reg = gdk_region_new(); 628 for (unsigned i = 0; i < rectCount; i++) { 629 GdkRectangle rect = rects[i]; 630 gdk_region_union_with_rect(reg, &rect); 631 } 632 gdk_cairo_region(cr, reg); 633 gdk_region_destroy(reg); 634 635 setColor(cr, color); 636 cairo_set_line_width(cr, 2.0f); 637 setPlatformStrokeStyle(DottedStroke); 638 #else 639 int radius = (width - 1) / 2; 640 for (unsigned i = 0; i < rectCount; i++) 641 addPath(Path::createRoundedRectangle(rects[i], FloatSize(radius, radius))); 642 643 // Force the alpha to 50%. This matches what the Mac does with outline rings. 644 Color ringColor(color.red(), color.green(), color.blue(), 127); 645 setColor(cr, ringColor); 646 cairo_set_line_width(cr, width); 647 setPlatformStrokeStyle(SolidStroke); 648 #endif 649 650 cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 651 cairo_stroke_preserve(cr); 652 653 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 654 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 655 cairo_fill(cr); 656 657 cairo_pop_group_to_source(cr); 658 cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 659 cairo_paint(cr); 660 cairo_restore(cr); 661 } 662 663 void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool printing) 664 { 665 if (paintingDisabled()) 666 return; 667 668 // This is a workaround for http://bugs.webkit.org/show_bug.cgi?id=15659 669 StrokeStyle savedStrokeStyle = strokeStyle(); 670 setStrokeStyle(SolidStroke); 671 672 IntPoint endPoint = origin + IntSize(width, 0); 673 drawLine(origin, endPoint); 674 675 setStrokeStyle(savedStrokeStyle); 676 } 677 678 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& origin, int width, bool grammar) 679 { 680 if (paintingDisabled()) 681 return; 682 683 cairo_t* cr = m_data->cr; 684 cairo_save(cr); 685 686 // Convention is green for grammar, red for spelling 687 // These need to become configurable 688 if (grammar) 689 cairo_set_source_rgb(cr, 0, 1, 0); 690 else 691 cairo_set_source_rgb(cr, 1, 0, 0); 692 693 #if PLATFORM(GTK) 694 // We ignore most of the provided constants in favour of the platform style 695 pango_cairo_show_error_underline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness); 696 #else 697 notImplemented(); 698 #endif 699 700 cairo_restore(cr); 701 } 702 703 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect) 704 { 705 FloatRect result; 706 double x = frect.x(); 707 double y = frect.y(); 708 cairo_t* cr = m_data->cr; 709 cairo_user_to_device(cr, &x, &y); 710 x = round(x); 711 y = round(y); 712 cairo_device_to_user(cr, &x, &y); 713 result.setX(static_cast<float>(x)); 714 result.setY(static_cast<float>(y)); 715 x = frect.width(); 716 y = frect.height(); 717 cairo_user_to_device_distance(cr, &x, &y); 718 x = round(x); 719 y = round(y); 720 cairo_device_to_user_distance(cr, &x, &y); 721 result.setWidth(static_cast<float>(x)); 722 result.setHeight(static_cast<float>(y)); 723 return result; 724 } 725 726 void GraphicsContext::translate(float x, float y) 727 { 728 if (paintingDisabled()) 729 return; 730 731 cairo_t* cr = m_data->cr; 732 cairo_translate(cr, x, y); 733 m_data->translate(x, y); 734 } 735 736 IntPoint GraphicsContext::origin() 737 { 738 cairo_matrix_t matrix; 739 cairo_t* cr = m_data->cr; 740 cairo_get_matrix(cr, &matrix); 741 return IntPoint(static_cast<int>(matrix.x0), static_cast<int>(matrix.y0)); 742 } 743 744 void GraphicsContext::setPlatformFillColor(const Color& col, ColorSpace colorSpace) 745 { 746 // Cairo contexts can't hold separate fill and stroke colors 747 // so we set them just before we actually fill or stroke 748 } 749 750 void GraphicsContext::setPlatformStrokeColor(const Color& col, ColorSpace colorSpace) 751 { 752 // Cairo contexts can't hold separate fill and stroke colors 753 // so we set them just before we actually fill or stroke 754 } 755 756 void GraphicsContext::setPlatformStrokeThickness(float strokeThickness) 757 { 758 if (paintingDisabled()) 759 return; 760 761 cairo_set_line_width(m_data->cr, strokeThickness); 762 } 763 764 void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle) 765 { 766 static double dashPattern[] = {5.0, 5.0}; 767 static double dotPattern[] = {1.0, 1.0}; 768 769 if (paintingDisabled()) 770 return; 771 772 switch (strokeStyle) { 773 case NoStroke: 774 // FIXME: is it the right way to emulate NoStroke? 775 cairo_set_line_width(m_data->cr, 0); 776 break; 777 case SolidStroke: 778 cairo_set_dash(m_data->cr, 0, 0, 0); 779 break; 780 case DottedStroke: 781 cairo_set_dash(m_data->cr, dotPattern, 2, 0); 782 break; 783 case DashedStroke: 784 cairo_set_dash(m_data->cr, dashPattern, 2, 0); 785 break; 786 } 787 } 788 789 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) 790 { 791 notImplemented(); 792 } 793 794 void GraphicsContext::concatCTM(const AffineTransform& transform) 795 { 796 if (paintingDisabled()) 797 return; 798 799 cairo_t* cr = m_data->cr; 800 const cairo_matrix_t matrix = cairo_matrix_t(transform); 801 cairo_transform(cr, &matrix); 802 m_data->concatCTM(transform); 803 } 804 805 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) 806 { 807 if (paintingDisabled()) 808 return; 809 810 clip(rect); 811 812 Path p; 813 FloatRect r(rect); 814 // Add outer ellipse 815 p.addEllipse(r); 816 // Add inner ellipse 817 r.inflate(-thickness); 818 p.addEllipse(r); 819 addPath(p); 820 821 cairo_t* cr = m_data->cr; 822 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 823 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 824 cairo_clip(cr); 825 cairo_set_fill_rule(cr, savedFillRule); 826 } 827 828 void GraphicsContext::clipToImageBuffer(const FloatRect& rect, const ImageBuffer* imageBuffer) 829 { 830 if (paintingDisabled()) 831 return; 832 833 notImplemented(); 834 } 835 836 void GraphicsContext::setPlatformShadow(IntSize const& size, int, Color const&, ColorSpace) 837 { 838 // Cairo doesn't support shadows natively, they are drawn manually in the draw* 839 // functions 840 841 if (m_common->state.shadowsIgnoreTransforms) { 842 // Meaning that this graphics context is associated with a CanvasRenderingContext 843 // We flip the height since CG and HTML5 Canvas have opposite Y axis 844 m_common->state.shadowSize = IntSize(size.width(), -size.height()); 845 } 846 } 847 848 void GraphicsContext::createPlatformShadow(PassOwnPtr<ImageBuffer> buffer, const Color& shadowColor, const FloatRect& shadowRect, float kernelSize) 849 { 850 #if ENABLE(FILTERS) 851 cairo_t* cr = m_data->cr; 852 853 // draw the shadow without blurring, if kernelSize is zero 854 if (!kernelSize) { 855 setColor(cr, shadowColor); 856 cairo_mask_surface(cr, buffer->image()->nativeImageForCurrentFrame(), shadowRect.x(), shadowRect.y()); 857 return; 858 } 859 860 // limit kernel size to 1000, this is what CG is doing. 861 kernelSize = std::min(1000.f, kernelSize); 862 863 // create filter 864 RefPtr<Filter> filter = ImageBufferFilter::create(); 865 filter->setSourceImage(buffer.release()); 866 RefPtr<FilterEffect> source = SourceGraphic::create(); 867 source->setScaledSubRegion(FloatRect(FloatPoint(), shadowRect.size())); 868 source->setIsAlphaImage(true); 869 RefPtr<FilterEffect> blur = FEGaussianBlur::create(source.get(), kernelSize, kernelSize); 870 blur->setScaledSubRegion(FloatRect(FloatPoint(), shadowRect.size())); 871 blur->apply(filter.get()); 872 873 // Mask the filter with the shadow color and draw it to the context. 874 // Masking makes it possible to just blur the alpha channel. 875 setColor(cr, shadowColor); 876 cairo_mask_surface(cr, blur->resultImage()->image()->nativeImageForCurrentFrame(), shadowRect.x(), shadowRect.y()); 877 #endif 878 } 879 880 void GraphicsContext::clearPlatformShadow() 881 { 882 notImplemented(); 883 } 884 885 void GraphicsContext::beginTransparencyLayer(float opacity) 886 { 887 if (paintingDisabled()) 888 return; 889 890 cairo_t* cr = m_data->cr; 891 cairo_push_group(cr); 892 m_data->layers.append(opacity); 893 m_data->beginTransparencyLayer(); 894 } 895 896 void GraphicsContext::endTransparencyLayer() 897 { 898 if (paintingDisabled()) 899 return; 900 901 cairo_t* cr = m_data->cr; 902 903 cairo_pop_group_to_source(cr); 904 cairo_paint_with_alpha(cr, m_data->layers.last()); 905 m_data->layers.removeLast(); 906 m_data->endTransparencyLayer(); 907 } 908 909 void GraphicsContext::clearRect(const FloatRect& rect) 910 { 911 if (paintingDisabled()) 912 return; 913 914 cairo_t* cr = m_data->cr; 915 916 cairo_save(cr); 917 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 918 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 919 cairo_fill(cr); 920 cairo_restore(cr); 921 } 922 923 void GraphicsContext::strokeRect(const FloatRect& rect, float width) 924 { 925 if (paintingDisabled()) 926 return; 927 928 cairo_t* cr = m_data->cr; 929 cairo_save(cr); 930 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 931 cairo_set_line_width(cr, width); 932 strokePath(); 933 cairo_restore(cr); 934 } 935 936 void GraphicsContext::setLineCap(LineCap lineCap) 937 { 938 if (paintingDisabled()) 939 return; 940 941 cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT; 942 switch (lineCap) { 943 case ButtCap: 944 // no-op 945 break; 946 case RoundCap: 947 cairoCap = CAIRO_LINE_CAP_ROUND; 948 break; 949 case SquareCap: 950 cairoCap = CAIRO_LINE_CAP_SQUARE; 951 break; 952 } 953 cairo_set_line_cap(m_data->cr, cairoCap); 954 } 955 956 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 957 { 958 cairo_set_dash(m_data->cr, dashes.data(), dashes.size(), dashOffset); 959 } 960 961 void GraphicsContext::setLineJoin(LineJoin lineJoin) 962 { 963 if (paintingDisabled()) 964 return; 965 966 cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER; 967 switch (lineJoin) { 968 case MiterJoin: 969 // no-op 970 break; 971 case RoundJoin: 972 cairoJoin = CAIRO_LINE_JOIN_ROUND; 973 break; 974 case BevelJoin: 975 cairoJoin = CAIRO_LINE_JOIN_BEVEL; 976 break; 977 } 978 cairo_set_line_join(m_data->cr, cairoJoin); 979 } 980 981 void GraphicsContext::setMiterLimit(float miter) 982 { 983 if (paintingDisabled()) 984 return; 985 986 cairo_set_miter_limit(m_data->cr, miter); 987 } 988 989 void GraphicsContext::setAlpha(float alpha) 990 { 991 m_common->state.globalAlpha = alpha; 992 } 993 994 float GraphicsContext::getAlpha() 995 { 996 return m_common->state.globalAlpha; 997 } 998 999 static inline cairo_operator_t toCairoOperator(CompositeOperator op) 1000 { 1001 switch (op) { 1002 case CompositeClear: 1003 return CAIRO_OPERATOR_CLEAR; 1004 case CompositeCopy: 1005 return CAIRO_OPERATOR_SOURCE; 1006 case CompositeSourceOver: 1007 return CAIRO_OPERATOR_OVER; 1008 case CompositeSourceIn: 1009 return CAIRO_OPERATOR_IN; 1010 case CompositeSourceOut: 1011 return CAIRO_OPERATOR_OUT; 1012 case CompositeSourceAtop: 1013 return CAIRO_OPERATOR_ATOP; 1014 case CompositeDestinationOver: 1015 return CAIRO_OPERATOR_DEST_OVER; 1016 case CompositeDestinationIn: 1017 return CAIRO_OPERATOR_DEST_IN; 1018 case CompositeDestinationOut: 1019 return CAIRO_OPERATOR_DEST_OUT; 1020 case CompositeDestinationAtop: 1021 return CAIRO_OPERATOR_DEST_ATOP; 1022 case CompositeXOR: 1023 return CAIRO_OPERATOR_XOR; 1024 case CompositePlusDarker: 1025 return CAIRO_OPERATOR_SATURATE; 1026 case CompositeHighlight: 1027 // There is no Cairo equivalent for CompositeHighlight. 1028 return CAIRO_OPERATOR_OVER; 1029 case CompositePlusLighter: 1030 return CAIRO_OPERATOR_ADD; 1031 default: 1032 return CAIRO_OPERATOR_SOURCE; 1033 } 1034 } 1035 1036 void GraphicsContext::setCompositeOperation(CompositeOperator op) 1037 { 1038 if (paintingDisabled()) 1039 return; 1040 1041 cairo_set_operator(m_data->cr, toCairoOperator(op)); 1042 } 1043 1044 void GraphicsContext::beginPath() 1045 { 1046 if (paintingDisabled()) 1047 return; 1048 1049 cairo_t* cr = m_data->cr; 1050 cairo_new_path(cr); 1051 } 1052 1053 void GraphicsContext::addPath(const Path& path) 1054 { 1055 if (paintingDisabled()) 1056 return; 1057 1058 cairo_t* cr = m_data->cr; 1059 cairo_path_t* p = cairo_copy_path(path.platformPath()->m_cr); 1060 cairo_append_path(cr, p); 1061 cairo_path_destroy(p); 1062 } 1063 1064 void GraphicsContext::clip(const Path& path) 1065 { 1066 if (paintingDisabled()) 1067 return; 1068 1069 cairo_t* cr = m_data->cr; 1070 cairo_path_t* p = cairo_copy_path(path.platformPath()->m_cr); 1071 cairo_append_path(cr, p); 1072 cairo_path_destroy(p); 1073 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 1074 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 1075 cairo_clip(cr); 1076 cairo_set_fill_rule(cr, savedFillRule); 1077 m_data->clip(path); 1078 } 1079 1080 void GraphicsContext::canvasClip(const Path& path) 1081 { 1082 clip(path); 1083 } 1084 1085 void GraphicsContext::clipOut(const Path& path) 1086 { 1087 if (paintingDisabled()) 1088 return; 1089 1090 cairo_t* cr = m_data->cr; 1091 double x1, y1, x2, y2; 1092 cairo_clip_extents(cr, &x1, &y1, &x2, &y2); 1093 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); 1094 addPath(path); 1095 1096 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 1097 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 1098 cairo_clip(cr); 1099 cairo_set_fill_rule(cr, savedFillRule); 1100 } 1101 1102 void GraphicsContext::rotate(float radians) 1103 { 1104 if (paintingDisabled()) 1105 return; 1106 1107 cairo_rotate(m_data->cr, radians); 1108 m_data->rotate(radians); 1109 } 1110 1111 void GraphicsContext::scale(const FloatSize& size) 1112 { 1113 if (paintingDisabled()) 1114 return; 1115 1116 cairo_scale(m_data->cr, size.width(), size.height()); 1117 m_data->scale(size); 1118 } 1119 1120 void GraphicsContext::clipOut(const IntRect& r) 1121 { 1122 if (paintingDisabled()) 1123 return; 1124 1125 cairo_t* cr = m_data->cr; 1126 double x1, y1, x2, y2; 1127 cairo_clip_extents(cr, &x1, &y1, &x2, &y2); 1128 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); 1129 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height()); 1130 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 1131 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 1132 cairo_clip(cr); 1133 cairo_set_fill_rule(cr, savedFillRule); 1134 } 1135 1136 void GraphicsContext::clipOutEllipseInRect(const IntRect& r) 1137 { 1138 if (paintingDisabled()) 1139 return; 1140 1141 Path p; 1142 p.addEllipse(r); 1143 clipOut(p); 1144 } 1145 1146 void GraphicsContext::fillRoundedRect(const IntRect& r, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace) 1147 { 1148 if (paintingDisabled()) 1149 return; 1150 1151 cairo_t* cr = m_data->cr; 1152 cairo_save(cr); 1153 beginPath(); 1154 addPath(Path::createRoundedRectangle(r, topLeft, topRight, bottomLeft, bottomRight)); 1155 setColor(cr, color); 1156 drawPathShadow(this, m_common, true, false); 1157 cairo_fill(cr); 1158 cairo_restore(cr); 1159 } 1160 1161 #if PLATFORM(GTK) 1162 void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose) 1163 { 1164 m_data->expose = expose; 1165 } 1166 1167 GdkEventExpose* GraphicsContext::gdkExposeEvent() const 1168 { 1169 return m_data->expose; 1170 } 1171 1172 GdkDrawable* GraphicsContext::gdkDrawable() const 1173 { 1174 if (!m_data->expose) 1175 return 0; 1176 1177 return GDK_DRAWABLE(m_data->expose->window); 1178 } 1179 #endif 1180 1181 void GraphicsContext::setPlatformShouldAntialias(bool enable) 1182 { 1183 if (paintingDisabled()) 1184 return; 1185 1186 // When true, use the default Cairo backend antialias mode (usually this 1187 // enables standard 'grayscale' antialiasing); false to explicitly disable 1188 // antialiasing. This is the same strategy as used in drawConvexPolygon(). 1189 cairo_set_antialias(m_data->cr, enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); 1190 } 1191 1192 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality) 1193 { 1194 } 1195 1196 InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1197 { 1198 return InterpolationDefault; 1199 } 1200 1201 } // namespace WebCore 1202 1203 #endif // PLATFORM(CAIRO) 1204