1/* 2 * Copyright (C) 2006 Apple 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 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 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 "FloatRoundedRect.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 fillCurrentCairoPath(GraphicsContext* context) 154{ 155 cairo_t* cr = context->platformContext()->cr(); 156 cairo_save(cr); 157 158 context->platformContext()->prepareForFilling(context->state(), PlatformContextCairo::AdjustPatternForGlobalAlpha); 159 cairo_fill(cr); 160 161 cairo_restore(cr); 162} 163 164static inline void shadowAndFillCurrentCairoPath(GraphicsContext* context) 165{ 166 drawPathShadow(context, Fill); 167 fillCurrentCairoPath(context); 168} 169 170static inline void shadowAndStrokeCurrentCairoPath(GraphicsContext* context) 171{ 172 drawPathShadow(context, Stroke); 173 context->platformContext()->prepareForStroking(context->state()); 174 cairo_stroke(context->platformContext()->cr()); 175} 176 177GraphicsContext::GraphicsContext(cairo_t* cr) 178 : m_updatingControlTints(false) 179 , m_transparencyCount(0) 180{ 181 m_data = new GraphicsContextPlatformPrivateToplevel(new PlatformContextCairo(cr)); 182} 183 184void GraphicsContext::platformInit(PlatformContextCairo* platformContext, bool) 185{ 186 m_data = new GraphicsContextPlatformPrivate(platformContext); 187 if (platformContext) 188 m_data->syncContext(platformContext->cr()); 189 else 190 setPaintingDisabled(true); 191} 192 193void GraphicsContext::platformDestroy() 194{ 195 delete m_data; 196} 197 198AffineTransform GraphicsContext::getCTM(IncludeDeviceScale) const 199{ 200 if (paintingDisabled()) 201 return AffineTransform(); 202 203 cairo_t* cr = platformContext()->cr(); 204 cairo_matrix_t m; 205 cairo_get_matrix(cr, &m); 206 return AffineTransform(m.xx, m.yx, m.xy, m.yy, m.x0, m.y0); 207} 208 209PlatformContextCairo* GraphicsContext::platformContext() const 210{ 211 return m_data->platformContext; 212} 213 214void GraphicsContext::savePlatformState() 215{ 216 platformContext()->save(); 217 m_data->save(); 218} 219 220void GraphicsContext::restorePlatformState() 221{ 222 platformContext()->restore(); 223 m_data->restore(); 224 225 platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur), 226 m_state.shadowOffset, 227 m_state.shadowColor, 228 m_state.shadowColorSpace, 229 m_state.shadowsIgnoreTransforms); 230} 231 232// Draws a filled rectangle with a stroked border. 233void GraphicsContext::drawRect(const FloatRect& rect, float) 234{ 235 if (paintingDisabled()) 236 return; 237 238 ASSERT(!rect.isEmpty()); 239 240 cairo_t* cr = platformContext()->cr(); 241 cairo_save(cr); 242 243 fillRectWithColor(cr, rect, fillColor()); 244 245 if (strokeStyle() != NoStroke) { 246 setSourceRGBAFromColor(cr, strokeColor()); 247 FloatRect r(rect); 248 r.inflate(-.5f); 249 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height()); 250 cairo_set_line_width(cr, 1.0); 251 cairo_stroke(cr); 252 } 253 254 cairo_restore(cr); 255} 256 257static double calculateStrokePatternOffset(int distance, int patternWidth) 258{ 259 // Example: 80 pixels with a width of 30 pixels. Remainder is 20. 260 // The maximum pixels of line we could paint will be 50 pixels. 261 int remainder = distance % patternWidth; 262 int numSegments = (distance - remainder) / patternWidth; 263 264 // Special case 1px dotted borders for speed. 265 if (patternWidth == 1) 266 return 1; 267 268 bool evenNumberOfSegments = !(numSegments % 2); 269 if (remainder) 270 evenNumberOfSegments = !evenNumberOfSegments; 271 272 if (evenNumberOfSegments) { 273 if (remainder) 274 return (patternWidth - remainder) + (remainder / 2); 275 return patternWidth / 2; 276 } 277 278 // Odd number of segments. 279 if (remainder) 280 return (patternWidth - remainder) / 2.f; 281 return 0; 282} 283 284static void drawLineOnCairoContext(GraphicsContext* graphicsContext, cairo_t* context, const FloatPoint& point1, const FloatPoint& point2) 285{ 286 StrokeStyle style = graphicsContext->strokeStyle(); 287 if (style == NoStroke) 288 return; 289 290 const Color& strokeColor = graphicsContext->strokeColor(); 291 int strokeThickness = floorf(graphicsContext->strokeThickness()); 292 if (graphicsContext->strokeThickness() < 1) 293 strokeThickness = 1; 294 295 int patternWidth = 0; 296 if (style == DottedStroke) 297 patternWidth = strokeThickness; 298 else if (style == DashedStroke) 299 patternWidth = 3 * strokeThickness; 300 301 bool isVerticalLine = point1.x() == point2.x(); 302 FloatPoint point1OnPixelBoundaries = point1; 303 FloatPoint point2OnPixelBoundaries = point2; 304 GraphicsContext::adjustLineToPixelBoundaries(point1OnPixelBoundaries, point2OnPixelBoundaries, strokeThickness, style); 305 306 if (patternWidth) { 307 // Do a rect fill of our endpoints. This ensures we always have the 308 // appearance of being a border. We then draw the actual dotted/dashed line. 309 FloatRect firstRect(point1OnPixelBoundaries, FloatSize(strokeThickness, strokeThickness)); 310 FloatRect secondRect(point2OnPixelBoundaries, FloatSize(strokeThickness, strokeThickness)); 311 if (isVerticalLine) { 312 firstRect.move(-strokeThickness / 2, -strokeThickness); 313 secondRect.move(-strokeThickness / 2, 0); 314 } else { 315 firstRect.move(-strokeThickness, -strokeThickness / 2); 316 secondRect.move(0, -strokeThickness / 2); 317 } 318 fillRectWithColor(context, firstRect, strokeColor); 319 fillRectWithColor(context, secondRect, strokeColor); 320 321 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2 * strokeThickness; 322 double patternOffset = calculateStrokePatternOffset(distance, patternWidth); 323 double patternWidthAsDouble = patternWidth; 324 cairo_set_dash(context, &patternWidthAsDouble, 1, patternOffset); 325 } 326 327 setSourceRGBAFromColor(context, strokeColor); 328 cairo_set_line_width(context, strokeThickness); 329 cairo_move_to(context, point1OnPixelBoundaries.x(), point1OnPixelBoundaries.y()); 330 cairo_line_to(context, point2OnPixelBoundaries.x(), point2OnPixelBoundaries.y()); 331 cairo_stroke(context); 332} 333 334// This is only used to draw borders, so we should not draw shadows. 335void GraphicsContext::drawLine(const FloatPoint& point1, const FloatPoint& point2) 336{ 337 if (paintingDisabled()) 338 return; 339 340 cairo_t* cairoContext = platformContext()->cr(); 341 cairo_save(cairoContext); 342 drawLineOnCairoContext(this, cairoContext, point1, point2); 343 cairo_restore(cairoContext); 344} 345 346// This method is only used to draw the little circles used in lists. 347void GraphicsContext::drawEllipse(const IntRect& rect) 348{ 349 if (paintingDisabled()) 350 return; 351 352 cairo_t* cr = platformContext()->cr(); 353 cairo_save(cr); 354 float yRadius = .5 * rect.height(); 355 float xRadius = .5 * rect.width(); 356 cairo_translate(cr, rect.x() + xRadius, rect.y() + yRadius); 357 cairo_scale(cr, xRadius, yRadius); 358 cairo_arc(cr, 0., 0., 1., 0., 2 * piFloat); 359 cairo_restore(cr); 360 361 if (fillColor().alpha()) { 362 setSourceRGBAFromColor(cr, fillColor()); 363 cairo_fill_preserve(cr); 364 } 365 366 if (strokeStyle() != NoStroke) { 367 setSourceRGBAFromColor(cr, strokeColor()); 368 cairo_set_line_width(cr, strokeThickness()); 369 cairo_stroke(cr); 370 } else 371 cairo_new_path(cr); 372} 373 374void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias) 375{ 376 if (paintingDisabled()) 377 return; 378 379 if (npoints <= 1) 380 return; 381 382 cairo_t* cr = platformContext()->cr(); 383 384 cairo_save(cr); 385 cairo_set_antialias(cr, shouldAntialias ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); 386 addConvexPolygonToContext(cr, npoints, points); 387 388 if (fillColor().alpha()) { 389 setSourceRGBAFromColor(cr, fillColor()); 390 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 391 cairo_fill_preserve(cr); 392 } 393 394 if (strokeStyle() != NoStroke) { 395 setSourceRGBAFromColor(cr, strokeColor()); 396 cairo_set_line_width(cr, strokeThickness()); 397 cairo_stroke(cr); 398 } else 399 cairo_new_path(cr); 400 401 cairo_restore(cr); 402} 403 404void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased) 405{ 406 if (paintingDisabled()) 407 return; 408 409 if (numPoints <= 1) 410 return; 411 412 cairo_t* cr = platformContext()->cr(); 413 414 cairo_new_path(cr); 415 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 416 cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr); 417 418 cairo_set_antialias(cr, antialiased ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); 419 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 420 addConvexPolygonToContext(cr, numPoints, points); 421 cairo_clip(cr); 422 423 cairo_set_antialias(cr, savedAntialiasRule); 424 cairo_set_fill_rule(cr, savedFillRule); 425} 426 427void GraphicsContext::fillPath(const Path& path) 428{ 429 if (paintingDisabled() || path.isEmpty()) 430 return; 431 432 cairo_t* cr = platformContext()->cr(); 433 setPathOnCairoContext(cr, path.platformPath()->context()); 434 shadowAndFillCurrentCairoPath(this); 435} 436 437void GraphicsContext::strokePath(const Path& path) 438{ 439 if (paintingDisabled() || path.isEmpty()) 440 return; 441 442 cairo_t* cr = platformContext()->cr(); 443 setPathOnCairoContext(cr, path.platformPath()->context()); 444 shadowAndStrokeCurrentCairoPath(this); 445} 446 447void GraphicsContext::fillRect(const FloatRect& rect) 448{ 449 if (paintingDisabled()) 450 return; 451 452 cairo_t* cr = platformContext()->cr(); 453 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 454 shadowAndFillCurrentCairoPath(this); 455} 456 457void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace) 458{ 459 if (paintingDisabled()) 460 return; 461 462 if (hasShadow()) 463 platformContext()->shadowBlur().drawRectShadow(this, FloatRoundedRect(rect)); 464 465 fillRectWithColor(platformContext()->cr(), rect, color); 466} 467 468void GraphicsContext::clip(const FloatRect& rect) 469{ 470 if (paintingDisabled()) 471 return; 472 473 cairo_t* cr = platformContext()->cr(); 474 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 475 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 476 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 477 // The rectangular clip function is traditionally not expected to 478 // antialias. If we don't force antialiased clipping here, 479 // edge fringe artifacts may occur at the layer edges 480 // when a transformation is applied to the GraphicsContext 481 // while drawing the transformed layer. 482 cairo_antialias_t savedAntialiasRule = cairo_get_antialias(cr); 483 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE); 484 cairo_clip(cr); 485 cairo_set_fill_rule(cr, savedFillRule); 486 cairo_set_antialias(cr, savedAntialiasRule); 487 m_data->clip(rect); 488} 489 490void GraphicsContext::clipPath(const Path& path, WindRule clipRule) 491{ 492 if (paintingDisabled()) 493 return; 494 495 cairo_t* cr = platformContext()->cr(); 496 if (!path.isNull()) 497 setPathOnCairoContext(cr, path.platformPath()->context()); 498 cairo_set_fill_rule(cr, clipRule == RULE_EVENODD ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); 499 cairo_clip(cr); 500} 501 502IntRect GraphicsContext::clipBounds() const 503{ 504 double x1, x2, y1, y2; 505 cairo_clip_extents(platformContext()->cr(), &x1, &y1, &x2, &y2); 506 return enclosingIntRect(FloatRect(x1, y1, x2 - x1, y2 - y1)); 507} 508 509static inline void adjustFocusRingColor(Color& color) 510{ 511#if !PLATFORM(GTK) 512 // Force the alpha to 50%. This matches what the Mac does with outline rings. 513 color.setRGB(makeRGBA(color.red(), color.green(), color.blue(), 127)); 514#else 515 UNUSED_PARAM(color); 516#endif 517} 518 519static inline void adjustFocusRingLineWidth(int& width) 520{ 521#if PLATFORM(GTK) 522 width = 2; 523#else 524 UNUSED_PARAM(width); 525#endif 526} 527 528static inline StrokeStyle focusRingStrokeStyle() 529{ 530#if PLATFORM(GTK) 531 return DottedStroke; 532#else 533 return SolidStroke; 534#endif 535} 536 537void GraphicsContext::drawFocusRing(const Path& path, int width, int /* offset */, const Color& color) 538{ 539 // FIXME: We should draw paths that describe a rectangle with rounded corners 540 // so as to be consistent with how we draw rectangular focus rings. 541 Color ringColor = color; 542 adjustFocusRingColor(ringColor); 543 adjustFocusRingLineWidth(width); 544 545 cairo_t* cr = platformContext()->cr(); 546 cairo_save(cr); 547 appendWebCorePathToCairoContext(cr, path); 548 setSourceRGBAFromColor(cr, ringColor); 549 cairo_set_line_width(cr, width); 550 setPlatformStrokeStyle(focusRingStrokeStyle()); 551 cairo_stroke(cr); 552 cairo_restore(cr); 553} 554 555void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int /* offset */, const Color& color) 556{ 557 if (paintingDisabled()) 558 return; 559 560 unsigned rectCount = rects.size(); 561 562 cairo_t* cr = platformContext()->cr(); 563 cairo_save(cr); 564 cairo_push_group(cr); 565 cairo_new_path(cr); 566 567#if PLATFORM(GTK) 568#ifdef GTK_API_VERSION_2 569 GdkRegion* reg = gdk_region_new(); 570#else 571 cairo_region_t* reg = cairo_region_create(); 572#endif 573 574 for (unsigned i = 0; i < rectCount; i++) { 575#ifdef GTK_API_VERSION_2 576 GdkRectangle rect = rects[i]; 577 gdk_region_union_with_rect(reg, &rect); 578#else 579 cairo_rectangle_int_t rect = rects[i]; 580 cairo_region_union_rectangle(reg, &rect); 581#endif 582 } 583 gdk_cairo_region(cr, reg); 584#ifdef GTK_API_VERSION_2 585 gdk_region_destroy(reg); 586#else 587 cairo_region_destroy(reg); 588#endif 589#else 590 int radius = (width - 1) / 2; 591 Path path; 592 for (unsigned i = 0; i < rectCount; ++i) { 593 if (i > 0) 594 path.clear(); 595 path.addRoundedRect(rects[i], FloatSize(radius, radius)); 596 appendWebCorePathToCairoContext(cr, path); 597 } 598#endif 599 Color ringColor = color; 600 adjustFocusRingColor(ringColor); 601 adjustFocusRingLineWidth(width); 602 setSourceRGBAFromColor(cr, ringColor); 603 cairo_set_line_width(cr, width); 604 setPlatformStrokeStyle(focusRingStrokeStyle()); 605 606 cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 607 cairo_stroke_preserve(cr); 608 609 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 610 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 611 cairo_fill(cr); 612 613 cairo_pop_group_to_source(cr); 614 cairo_set_operator(cr, CAIRO_OPERATOR_OVER); 615 cairo_paint(cr); 616 cairo_restore(cr); 617} 618 619FloatRect GraphicsContext::computeLineBoundsForText(const FloatPoint& origin, float width, bool printing) 620{ 621 bool dummyBool; 622 Color dummyColor; 623 return computeLineBoundsAndAntialiasingModeForText(origin, width, printing, dummyBool, dummyColor); 624} 625 626void GraphicsContext::drawLineForText(const FloatPoint& origin, float width, bool printing, bool doubleUnderlines) 627{ 628 DashArray widths; 629 widths.append(width); 630 widths.append(0); 631 drawLinesForText(origin, widths, printing, doubleUnderlines); 632} 633 634void GraphicsContext::drawLinesForText(const FloatPoint& point, const DashArray& widths, bool printing, bool doubleUnderlines) 635{ 636 if (paintingDisabled()) 637 return; 638 639 if (widths.size() <= 0) 640 return; 641 642 Color localStrokeColor(strokeColor()); 643 644 bool shouldAntialiasLine; 645 FloatRect bounds = computeLineBoundsAndAntialiasingModeForText(point, widths.last(), printing, shouldAntialiasLine, localStrokeColor); 646 647 Vector<FloatRect, 4> dashBounds; 648 ASSERT(!(widths.size() % 2)); 649 dashBounds.reserveInitialCapacity(dashBounds.size() / 2); 650 for (size_t i = 0; i < widths.size(); i += 2) 651 dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y()), FloatSize(widths[i+1] - widths[i], bounds.height()))); 652 653 if (doubleUnderlines) { 654 // The space between double underlines is equal to the height of the underline 655 for (size_t i = 0; i < widths.size(); i += 2) 656 dashBounds.append(FloatRect(FloatPoint(bounds.x() + widths[i], bounds.y() + 2 * bounds.height()), FloatSize(widths[i+1] - widths[i], bounds.height()))); 657 } 658 659 cairo_t* cr = platformContext()->cr(); 660 cairo_save(cr); 661 662 for (auto& dash : dashBounds) 663 fillRectWithColor(cr, dash, localStrokeColor); 664 665 cairo_restore(cr); 666} 667 668void GraphicsContext::updateDocumentMarkerResources() 669{ 670 // Unnecessary, since our document markers don't use resources. 671} 672 673void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& origin, float width, DocumentMarkerLineStyle style) 674{ 675 if (paintingDisabled()) 676 return; 677 678 cairo_t* cr = platformContext()->cr(); 679 cairo_save(cr); 680 681 switch (style) { 682 case DocumentMarkerSpellingLineStyle: 683 cairo_set_source_rgb(cr, 1, 0, 0); 684 break; 685 case DocumentMarkerGrammarLineStyle: 686 cairo_set_source_rgb(cr, 0, 1, 0); 687 break; 688 default: 689 cairo_restore(cr); 690 return; 691 } 692 693 drawErrorUnderline(cr, origin.x(), origin.y(), width, cMisspellingLineThickness); 694 695 cairo_restore(cr); 696} 697 698FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect, RoundingMode) 699{ 700 FloatRect result; 701 double x = frect.x(); 702 double y = frect.y(); 703 cairo_t* cr = platformContext()->cr(); 704 cairo_user_to_device(cr, &x, &y); 705 x = round(x); 706 y = round(y); 707 cairo_device_to_user(cr, &x, &y); 708 result.setX(narrowPrecisionToFloat(x)); 709 result.setY(narrowPrecisionToFloat(y)); 710 711 // We must ensure width and height are at least 1 (or -1) when 712 // we're given float values in the range between 0 and 1 (or -1 and 0). 713 double width = frect.width(); 714 double height = frect.height(); 715 cairo_user_to_device_distance(cr, &width, &height); 716 if (width > -1 && width < 0) 717 width = -1; 718 else if (width > 0 && width < 1) 719 width = 1; 720 else 721 width = round(width); 722 if (height > -1 && width < 0) 723 height = -1; 724 else if (height > 0 && height < 1) 725 height = 1; 726 else 727 height = round(height); 728 cairo_device_to_user_distance(cr, &width, &height); 729 result.setWidth(narrowPrecisionToFloat(width)); 730 result.setHeight(narrowPrecisionToFloat(height)); 731 732 return result; 733} 734 735void GraphicsContext::translate(float x, float y) 736{ 737 if (paintingDisabled()) 738 return; 739 740 cairo_t* cr = platformContext()->cr(); 741 cairo_translate(cr, x, y); 742 m_data->translate(x, y); 743} 744 745void GraphicsContext::setPlatformFillColor(const Color&, ColorSpace) 746{ 747 // Cairo contexts can't hold separate fill and stroke colors 748 // so we set them just before we actually fill or stroke 749} 750 751void GraphicsContext::setPlatformStrokeColor(const Color&, ColorSpace) 752{ 753 // Cairo contexts can't hold separate fill and stroke colors 754 // so we set them just before we actually fill or stroke 755} 756 757void GraphicsContext::setPlatformStrokeThickness(float strokeThickness) 758{ 759 if (paintingDisabled()) 760 return; 761 762 cairo_set_line_width(platformContext()->cr(), strokeThickness); 763} 764 765void GraphicsContext::setPlatformStrokeStyle(StrokeStyle strokeStyle) 766{ 767 static double dashPattern[] = {5.0, 5.0}; 768 static double dotPattern[] = {1.0, 1.0}; 769 770 if (paintingDisabled()) 771 return; 772 773 switch (strokeStyle) { 774 case NoStroke: 775 // FIXME: is it the right way to emulate NoStroke? 776 cairo_set_line_width(platformContext()->cr(), 0); 777 break; 778 case SolidStroke: 779 case DoubleStroke: 780 case WavyStroke: // FIXME: https://bugs.webkit.org/show_bug.cgi?id=94110 - Needs platform support. 781 cairo_set_dash(platformContext()->cr(), 0, 0, 0); 782 break; 783 case DottedStroke: 784 cairo_set_dash(platformContext()->cr(), dotPattern, 2, 0); 785 break; 786 case DashedStroke: 787 cairo_set_dash(platformContext()->cr(), dashPattern, 2, 0); 788 break; 789 } 790} 791 792void GraphicsContext::setURLForRect(const URL&, const IntRect&) 793{ 794 notImplemented(); 795} 796 797void GraphicsContext::concatCTM(const AffineTransform& transform) 798{ 799 if (paintingDisabled()) 800 return; 801 802 cairo_t* cr = platformContext()->cr(); 803 const cairo_matrix_t matrix = cairo_matrix_t(transform); 804 cairo_transform(cr, &matrix); 805 m_data->concatCTM(transform); 806} 807 808void GraphicsContext::setCTM(const AffineTransform& transform) 809{ 810 if (paintingDisabled()) 811 return; 812 813 cairo_t* cr = platformContext()->cr(); 814 const cairo_matrix_t matrix = cairo_matrix_t(transform); 815 cairo_set_matrix(cr, &matrix); 816 m_data->setCTM(transform); 817} 818 819void GraphicsContext::setPlatformShadow(FloatSize const& size, float, Color const&, ColorSpace) 820{ 821 if (paintingDisabled()) 822 return; 823 824 if (m_state.shadowsIgnoreTransforms) { 825 // Meaning that this graphics context is associated with a CanvasRenderingContext 826 // We flip the height since CG and HTML5 Canvas have opposite Y axis 827 m_state.shadowOffset = FloatSize(size.width(), -size.height()); 828 } 829 830 // Cairo doesn't support shadows natively, they are drawn manually in the draw* functions using ShadowBlur. 831 platformContext()->shadowBlur().setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur), 832 m_state.shadowOffset, 833 m_state.shadowColor, 834 m_state.shadowColorSpace, 835 m_state.shadowsIgnoreTransforms); 836} 837 838void GraphicsContext::clearPlatformShadow() 839{ 840 if (paintingDisabled()) 841 return; 842 843 platformContext()->shadowBlur().clear(); 844} 845 846void GraphicsContext::beginPlatformTransparencyLayer(float opacity) 847{ 848 if (paintingDisabled()) 849 return; 850 851 cairo_t* cr = platformContext()->cr(); 852 cairo_push_group(cr); 853 m_data->layers.append(opacity); 854} 855 856void GraphicsContext::endPlatformTransparencyLayer() 857{ 858 if (paintingDisabled()) 859 return; 860 861 cairo_t* cr = platformContext()->cr(); 862 863 cairo_pop_group_to_source(cr); 864 cairo_paint_with_alpha(cr, m_data->layers.last()); 865 m_data->layers.removeLast(); 866} 867 868bool GraphicsContext::supportsTransparencyLayers() 869{ 870 return true; 871} 872 873void GraphicsContext::clearRect(const FloatRect& rect) 874{ 875 if (paintingDisabled()) 876 return; 877 878 cairo_t* cr = platformContext()->cr(); 879 880 cairo_save(cr); 881 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 882 cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); 883 cairo_fill(cr); 884 cairo_restore(cr); 885} 886 887void GraphicsContext::strokeRect(const FloatRect& rect, float width) 888{ 889 if (paintingDisabled()) 890 return; 891 892 cairo_t* cr = platformContext()->cr(); 893 cairo_save(cr); 894 cairo_rectangle(cr, rect.x(), rect.y(), rect.width(), rect.height()); 895 cairo_set_line_width(cr, width); 896 shadowAndStrokeCurrentCairoPath(this); 897 cairo_restore(cr); 898} 899 900void GraphicsContext::setLineCap(LineCap lineCap) 901{ 902 if (paintingDisabled()) 903 return; 904 905 cairo_line_cap_t cairoCap = CAIRO_LINE_CAP_BUTT; 906 switch (lineCap) { 907 case ButtCap: 908 // no-op 909 break; 910 case RoundCap: 911 cairoCap = CAIRO_LINE_CAP_ROUND; 912 break; 913 case SquareCap: 914 cairoCap = CAIRO_LINE_CAP_SQUARE; 915 break; 916 } 917 cairo_set_line_cap(platformContext()->cr(), cairoCap); 918} 919 920void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) 921{ 922 cairo_set_dash(platformContext()->cr(), dashes.data(), dashes.size(), dashOffset); 923} 924 925void GraphicsContext::setLineJoin(LineJoin lineJoin) 926{ 927 if (paintingDisabled()) 928 return; 929 930 cairo_line_join_t cairoJoin = CAIRO_LINE_JOIN_MITER; 931 switch (lineJoin) { 932 case MiterJoin: 933 // no-op 934 break; 935 case RoundJoin: 936 cairoJoin = CAIRO_LINE_JOIN_ROUND; 937 break; 938 case BevelJoin: 939 cairoJoin = CAIRO_LINE_JOIN_BEVEL; 940 break; 941 } 942 cairo_set_line_join(platformContext()->cr(), cairoJoin); 943} 944 945void GraphicsContext::setMiterLimit(float miter) 946{ 947 if (paintingDisabled()) 948 return; 949 950 cairo_set_miter_limit(platformContext()->cr(), miter); 951} 952 953void GraphicsContext::setAlpha(float alpha) 954{ 955 platformContext()->setGlobalAlpha(alpha); 956} 957 958void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op, BlendMode blendOp) 959{ 960 if (paintingDisabled()) 961 return; 962 963 cairo_operator_t cairo_op; 964 if (blendOp == BlendModeNormal) 965 cairo_op = toCairoOperator(op); 966 else 967 cairo_op = toCairoOperator(blendOp); 968 969 cairo_set_operator(platformContext()->cr(), cairo_op); 970} 971 972void GraphicsContext::clip(const Path& path, WindRule windRule) 973{ 974 if (paintingDisabled()) 975 return; 976 977 cairo_t* cr = platformContext()->cr(); 978 OwnPtr<cairo_path_t> pathCopy; 979 if (!path.isNull()) { 980 pathCopy = adoptPtr(cairo_copy_path(path.platformPath()->context())); 981 cairo_append_path(cr, pathCopy.get()); 982 } 983 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 984 if (windRule == RULE_NONZERO) 985 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING); 986 else 987 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 988 cairo_clip(cr); 989 cairo_set_fill_rule(cr, savedFillRule); 990 m_data->clip(path); 991} 992 993void GraphicsContext::canvasClip(const Path& path, WindRule windRule) 994{ 995 clip(path, windRule); 996} 997 998void GraphicsContext::clipOut(const Path& path) 999{ 1000 if (paintingDisabled()) 1001 return; 1002 1003 cairo_t* cr = platformContext()->cr(); 1004 double x1, y1, x2, y2; 1005 cairo_clip_extents(cr, &x1, &y1, &x2, &y2); 1006 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); 1007 appendWebCorePathToCairoContext(cr, path); 1008 1009 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 1010 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 1011 cairo_clip(cr); 1012 cairo_set_fill_rule(cr, savedFillRule); 1013} 1014 1015void GraphicsContext::rotate(float radians) 1016{ 1017 if (paintingDisabled()) 1018 return; 1019 1020 cairo_rotate(platformContext()->cr(), radians); 1021 m_data->rotate(radians); 1022} 1023 1024void GraphicsContext::scale(const FloatSize& size) 1025{ 1026 if (paintingDisabled()) 1027 return; 1028 1029 cairo_scale(platformContext()->cr(), size.width(), size.height()); 1030 m_data->scale(size); 1031} 1032 1033void GraphicsContext::clipOut(const FloatRect& r) 1034{ 1035 if (paintingDisabled()) 1036 return; 1037 1038 cairo_t* cr = platformContext()->cr(); 1039 double x1, y1, x2, y2; 1040 cairo_clip_extents(cr, &x1, &y1, &x2, &y2); 1041 cairo_rectangle(cr, x1, y1, x2 - x1, y2 - y1); 1042 cairo_rectangle(cr, r.x(), r.y(), r.width(), r.height()); 1043 cairo_fill_rule_t savedFillRule = cairo_get_fill_rule(cr); 1044 cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); 1045 cairo_clip(cr); 1046 cairo_set_fill_rule(cr, savedFillRule); 1047} 1048 1049void GraphicsContext::platformFillRoundedRect(const FloatRoundedRect& rect, const Color& color, ColorSpace) 1050{ 1051 if (paintingDisabled()) 1052 return; 1053 1054 if (hasShadow()) 1055 platformContext()->shadowBlur().drawRectShadow(this, rect); 1056 1057 cairo_t* cr = platformContext()->cr(); 1058 cairo_save(cr); 1059 Path path; 1060 path.addRoundedRect(rect); 1061 appendWebCorePathToCairoContext(cr, path); 1062 setSourceRGBAFromColor(cr, color); 1063 cairo_fill(cr); 1064 cairo_restore(cr); 1065} 1066 1067void GraphicsContext::fillRectWithRoundedHole(const FloatRect& rect, const FloatRoundedRect& roundedHoleRect, const Color& color, ColorSpace) 1068{ 1069 if (paintingDisabled() || !color.isValid()) 1070 return; 1071 1072 if (this->mustUseShadowBlur()) 1073 platformContext()->shadowBlur().drawInsetShadow(this, rect, roundedHoleRect); 1074 1075 Path path; 1076 path.addRect(rect); 1077 if (!roundedHoleRect.radii().isZero()) 1078 path.addRoundedRect(roundedHoleRect); 1079 else 1080 path.addRect(roundedHoleRect.rect()); 1081 1082 cairo_t* cr = platformContext()->cr(); 1083 cairo_save(cr); 1084 setPathOnCairoContext(platformContext()->cr(), path.platformPath()->context()); 1085 fillCurrentCairoPath(this); 1086 cairo_restore(cr); 1087} 1088 1089#if PLATFORM(GTK) 1090void GraphicsContext::setGdkExposeEvent(GdkEventExpose* expose) 1091{ 1092 m_data->expose = expose; 1093} 1094 1095GdkEventExpose* GraphicsContext::gdkExposeEvent() const 1096{ 1097 return m_data->expose; 1098} 1099 1100GdkWindow* GraphicsContext::gdkWindow() const 1101{ 1102 if (!m_data->expose) 1103 return 0; 1104 1105 return m_data->expose->window; 1106} 1107#endif 1108 1109void GraphicsContext::setPlatformShouldAntialias(bool enable) 1110{ 1111 if (paintingDisabled()) 1112 return; 1113 1114 // When true, use the default Cairo backend antialias mode (usually this 1115 // enables standard 'grayscale' antialiasing); false to explicitly disable 1116 // antialiasing. This is the same strategy as used in drawConvexPolygon(). 1117 cairo_set_antialias(platformContext()->cr(), enable ? CAIRO_ANTIALIAS_DEFAULT : CAIRO_ANTIALIAS_NONE); 1118} 1119 1120void GraphicsContext::setImageInterpolationQuality(InterpolationQuality quality) 1121{ 1122 platformContext()->setImageInterpolationQuality(quality); 1123} 1124 1125InterpolationQuality GraphicsContext::imageInterpolationQuality() const 1126{ 1127 return platformContext()->imageInterpolationQuality(); 1128} 1129 1130bool GraphicsContext::isAcceleratedContext() const 1131{ 1132 return cairo_surface_get_type(cairo_get_target(platformContext()->cr())) == CAIRO_SURFACE_TYPE_GL; 1133} 1134 1135#if ENABLE(3D_RENDERING) && USE(TEXTURE_MAPPER) 1136TransformationMatrix GraphicsContext::get3DTransform() const 1137{ 1138 // FIXME: Can we approximate the transformation better than this? 1139 return getCTM().toTransformationMatrix(); 1140} 1141 1142void GraphicsContext::concat3DTransform(const TransformationMatrix& transform) 1143{ 1144 concatCTM(transform.toAffineTransform()); 1145} 1146 1147void GraphicsContext::set3DTransform(const TransformationMatrix& transform) 1148{ 1149 setCTM(transform.toAffineTransform()); 1150} 1151#endif 1152 1153} // namespace WebCore 1154 1155#endif // USE(CAIRO) 1156