1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "config.h"
6#include "core/paint/ObjectPainter.h"
7
8#include "core/rendering/PaintInfo.h"
9#include "core/rendering/RenderObject.h"
10#include "core/rendering/RenderTheme.h"
11#include "core/rendering/style/RenderStyle.h"
12#include "platform/geometry/LayoutPoint.h"
13#include "platform/graphics/GraphicsContextStateSaver.h"
14
15namespace blink {
16
17void ObjectPainter::paintFocusRing(PaintInfo& paintInfo, const LayoutPoint& paintOffset, RenderStyle* style)
18{
19    Vector<LayoutRect> focusRingRects;
20    m_renderObject.addFocusRingRects(focusRingRects, paintOffset, paintInfo.paintContainer());
21    ASSERT(style->outlineStyleIsAuto());
22    Vector<IntRect> focusRingIntRects;
23    for (size_t i = 0; i < focusRingRects.size(); ++i)
24        focusRingIntRects.append(pixelSnappedIntRect(focusRingRects[i]));
25    paintInfo.context->drawFocusRing(focusRingIntRects, style->outlineWidth(), style->outlineOffset(), m_renderObject.resolveColor(style, CSSPropertyOutlineColor));
26}
27
28void ObjectPainter::paintOutline(PaintInfo& paintInfo, const LayoutRect& paintRect)
29{
30    RenderStyle* styleToUse = m_renderObject.style();
31    if (!styleToUse->hasOutline())
32        return;
33
34    LayoutUnit outlineWidth = styleToUse->outlineWidth();
35
36    int outlineOffset = styleToUse->outlineOffset();
37
38    if (styleToUse->outlineStyleIsAuto()) {
39        if (RenderTheme::theme().shouldDrawDefaultFocusRing(&m_renderObject)) {
40            // Only paint the focus ring by hand if the theme isn't able to draw the focus ring.
41            paintFocusRing(paintInfo, paintRect.location(), styleToUse);
42        }
43        return;
44    }
45
46    if (styleToUse->outlineStyle() == BNONE)
47        return;
48
49    IntRect inner = pixelSnappedIntRect(paintRect);
50    inner.inflate(outlineOffset);
51
52    IntRect outer = pixelSnappedIntRect(inner);
53    outer.inflate(outlineWidth);
54
55    // FIXME: This prevents outlines from painting inside the object. See bug 12042
56    if (outer.isEmpty())
57        return;
58
59    EBorderStyle outlineStyle = styleToUse->outlineStyle();
60    Color outlineColor = m_renderObject.resolveColor(styleToUse, CSSPropertyOutlineColor);
61
62    GraphicsContext* graphicsContext = paintInfo.context;
63    bool useTransparencyLayer = outlineColor.hasAlpha();
64    if (useTransparencyLayer) {
65        if (outlineStyle == SOLID) {
66            Path path;
67            path.addRect(outer);
68            path.addRect(inner);
69            graphicsContext->setFillRule(RULE_EVENODD);
70            graphicsContext->setFillColor(outlineColor);
71            graphicsContext->fillPath(path);
72            return;
73        }
74        graphicsContext->beginTransparencyLayer(static_cast<float>(outlineColor.alpha()) / 255);
75        outlineColor = Color(outlineColor.red(), outlineColor.green(), outlineColor.blue());
76    }
77
78    int leftOuter = outer.x();
79    int leftInner = inner.x();
80    int rightOuter = outer.maxX();
81    int rightInner = inner.maxX();
82    int topOuter = outer.y();
83    int topInner = inner.y();
84    int bottomOuter = outer.maxY();
85    int bottomInner = inner.maxY();
86
87    drawLineForBoxSide(graphicsContext, leftOuter, topOuter, leftInner, bottomOuter, BSLeft, outlineColor, outlineStyle, outlineWidth, outlineWidth);
88    drawLineForBoxSide(graphicsContext, leftOuter, topOuter, rightOuter, topInner, BSTop, outlineColor, outlineStyle, outlineWidth, outlineWidth);
89    drawLineForBoxSide(graphicsContext, rightInner, topOuter, rightOuter, bottomOuter, BSRight, outlineColor, outlineStyle, outlineWidth, outlineWidth);
90    drawLineForBoxSide(graphicsContext, leftOuter, bottomInner, rightOuter, bottomOuter, BSBottom, outlineColor, outlineStyle, outlineWidth, outlineWidth);
91
92    if (useTransparencyLayer)
93        graphicsContext->endLayer();
94}
95
96void ObjectPainter::drawLineForBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
97    BoxSide side, Color color, EBorderStyle style,
98    int adjacentWidth1, int adjacentWidth2, bool antialias)
99{
100    int thickness;
101    int length;
102    if (side == BSTop || side == BSBottom) {
103        thickness = y2 - y1;
104        length = x2 - x1;
105    } else {
106        thickness = x2 - x1;
107        length = y2 - y1;
108    }
109
110    // FIXME: We really would like this check to be an ASSERT as we don't want to draw empty borders. However
111    // nothing guarantees that the following recursive calls to drawLineForBoxSide will have non-null dimensions.
112    if (!thickness || !length)
113        return;
114
115    if (style == DOUBLE && thickness < 3)
116        style = SOLID;
117
118    switch (style) {
119    case BNONE:
120    case BHIDDEN:
121        return;
122    case DOTTED:
123    case DASHED:
124        drawDashedOrDottedBoxSide(graphicsContext, x1, y1, x2, y2, side,
125            color, thickness, style, antialias);
126        break;
127    case DOUBLE:
128        drawDoubleBoxSide(graphicsContext, x1, y1, x2, y2, length, side, color,
129            thickness, adjacentWidth1, adjacentWidth2, antialias);
130        break;
131    case RIDGE:
132    case GROOVE:
133        drawRidgeOrGrooveBoxSide(graphicsContext, x1, y1, x2, y2, side, color,
134            style, adjacentWidth1, adjacentWidth2, antialias);
135        break;
136    case INSET:
137        // FIXME: Maybe we should lighten the colors on one side like Firefox.
138        // https://bugs.webkit.org/show_bug.cgi?id=58608
139        if (side == BSTop || side == BSLeft)
140            color = color.dark();
141        // fall through
142    case OUTSET:
143        if (style == OUTSET && (side == BSBottom || side == BSRight))
144            color = color.dark();
145        // fall through
146    case SOLID:
147        drawSolidBoxSide(graphicsContext, x1, y1, x2, y2, side, color, adjacentWidth1, adjacentWidth2, antialias);
148        break;
149    }
150}
151
152void ObjectPainter::drawDashedOrDottedBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
153    BoxSide side, Color color, int thickness, EBorderStyle style, bool antialias)
154{
155    if (thickness <= 0)
156        return;
157
158    bool wasAntialiased = graphicsContext->shouldAntialias();
159    StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
160    graphicsContext->setShouldAntialias(antialias);
161    graphicsContext->setStrokeColor(color);
162    graphicsContext->setStrokeThickness(thickness);
163    graphicsContext->setStrokeStyle(style == DASHED ? DashedStroke : DottedStroke);
164
165    switch (side) {
166    case BSBottom:
167    case BSTop:
168        graphicsContext->drawLine(IntPoint(x1, (y1 + y2) / 2), IntPoint(x2, (y1 + y2) / 2));
169        break;
170    case BSRight:
171    case BSLeft:
172        graphicsContext->drawLine(IntPoint((x1 + x2) / 2, y1), IntPoint((x1 + x2) / 2, y2));
173        break;
174    }
175    graphicsContext->setShouldAntialias(wasAntialiased);
176    graphicsContext->setStrokeStyle(oldStrokeStyle);
177}
178
179void ObjectPainter::drawDoubleBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
180    int length, BoxSide side, Color color, int thickness, int adjacentWidth1, int adjacentWidth2, bool antialias)
181{
182    int thirdOfThickness = (thickness + 1) / 3;
183    ASSERT(thirdOfThickness);
184
185    if (!adjacentWidth1 && !adjacentWidth2) {
186        StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
187        graphicsContext->setStrokeStyle(NoStroke);
188        graphicsContext->setFillColor(color);
189
190        bool wasAntialiased = graphicsContext->shouldAntialias();
191        graphicsContext->setShouldAntialias(antialias);
192
193        switch (side) {
194        case BSTop:
195        case BSBottom:
196            graphicsContext->drawRect(IntRect(x1, y1, length, thirdOfThickness));
197            graphicsContext->drawRect(IntRect(x1, y2 - thirdOfThickness, length, thirdOfThickness));
198            break;
199        case BSLeft:
200        case BSRight:
201            // FIXME: Why do we offset the border by 1 in this case but not the other one?
202            if (length > 1) {
203                graphicsContext->drawRect(IntRect(x1, y1 + 1, thirdOfThickness, length - 1));
204                graphicsContext->drawRect(IntRect(x2 - thirdOfThickness, y1 + 1, thirdOfThickness, length - 1));
205            }
206            break;
207        }
208
209        graphicsContext->setShouldAntialias(wasAntialiased);
210        graphicsContext->setStrokeStyle(oldStrokeStyle);
211        return;
212    }
213
214    int adjacent1BigThird = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 3;
215    int adjacent2BigThird = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 3;
216
217    switch (side) {
218    case BSTop:
219        drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
220            y1, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness,
221            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
222        drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
223            y2 - thirdOfThickness, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y2,
224            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
225        break;
226    case BSLeft:
227        drawLineForBoxSide(graphicsContext, x1, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
228            x1 + thirdOfThickness, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0),
229            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
230        drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
231            x2, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0),
232            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
233        break;
234    case BSBottom:
235        drawLineForBoxSide(graphicsContext, x1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
236            y1, x2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0), y1 + thirdOfThickness,
237            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
238        drawLineForBoxSide(graphicsContext, x1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
239            y2 - thirdOfThickness, x2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0), y2,
240            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
241        break;
242    case BSRight:
243        drawLineForBoxSide(graphicsContext, x1, y1 + std::max((adjacentWidth1 * 2 + 1) / 3, 0),
244            x1 + thirdOfThickness, y2 - std::max((adjacentWidth2 * 2 + 1) / 3, 0),
245            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
246        drawLineForBoxSide(graphicsContext, x2 - thirdOfThickness, y1 + std::max((-adjacentWidth1 * 2 + 1) / 3, 0),
247            x2, y2 - std::max((-adjacentWidth2 * 2 + 1) / 3, 0),
248            side, color, SOLID, adjacent1BigThird, adjacent2BigThird, antialias);
249        break;
250    default:
251        break;
252    }
253}
254
255void ObjectPainter::drawRidgeOrGrooveBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
256    BoxSide side, Color color, EBorderStyle style, int adjacentWidth1, int adjacentWidth2, bool antialias)
257{
258    EBorderStyle s1;
259    EBorderStyle s2;
260    if (style == GROOVE) {
261        s1 = INSET;
262        s2 = OUTSET;
263    } else {
264        s1 = OUTSET;
265        s2 = INSET;
266    }
267
268    int adjacent1BigHalf = ((adjacentWidth1 > 0) ? adjacentWidth1 + 1 : adjacentWidth1 - 1) / 2;
269    int adjacent2BigHalf = ((adjacentWidth2 > 0) ? adjacentWidth2 + 1 : adjacentWidth2 - 1) / 2;
270
271    switch (side) {
272    case BSTop:
273        drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1, 0) / 2, y1, x2 - std::max(-adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2,
274            side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias);
275        drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(adjacentWidth2 + 1, 0) / 2, y2,
276            side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
277        break;
278    case BSLeft:
279        drawLineForBoxSide(graphicsContext, x1, y1 + std::max(-adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(-adjacentWidth2, 0) / 2,
280            side, color, s1, adjacent1BigHalf, adjacent2BigHalf, antialias);
281        drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(adjacentWidth2 + 1, 0) / 2,
282            side, color, s2, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
283        break;
284    case BSBottom:
285        drawLineForBoxSide(graphicsContext, x1 + std::max(adjacentWidth1, 0) / 2, y1, x2 - std::max(adjacentWidth2, 0) / 2, (y1 + y2 + 1) / 2,
286            side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias);
287        drawLineForBoxSide(graphicsContext, x1 + std::max(-adjacentWidth1 + 1, 0) / 2, (y1 + y2 + 1) / 2, x2 - std::max(-adjacentWidth2 + 1, 0) / 2, y2,
288            side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
289        break;
290    case BSRight:
291        drawLineForBoxSide(graphicsContext, x1, y1 + std::max(adjacentWidth1, 0) / 2, (x1 + x2 + 1) / 2, y2 - std::max(adjacentWidth2, 0) / 2,
292            side, color, s2, adjacent1BigHalf, adjacent2BigHalf, antialias);
293        drawLineForBoxSide(graphicsContext, (x1 + x2 + 1) / 2, y1 + std::max(-adjacentWidth1 + 1, 0) / 2, x2, y2 - std::max(-adjacentWidth2 + 1, 0) / 2,
294            side, color, s1, adjacentWidth1 / 2, adjacentWidth2 / 2, antialias);
295        break;
296    }
297}
298
299void ObjectPainter::drawSolidBoxSide(GraphicsContext* graphicsContext, int x1, int y1, int x2, int y2,
300    BoxSide side, Color color, int adjacentWidth1, int adjacentWidth2, bool antialias)
301{
302    StrokeStyle oldStrokeStyle = graphicsContext->strokeStyle();
303    graphicsContext->setStrokeStyle(NoStroke);
304    graphicsContext->setFillColor(color);
305    ASSERT(x2 >= x1);
306    ASSERT(y2 >= y1);
307    if (!adjacentWidth1 && !adjacentWidth2) {
308        // Turn off antialiasing to match the behavior of drawConvexPolygon();
309        // this matters for rects in transformed contexts.
310        bool wasAntialiased = graphicsContext->shouldAntialias();
311        graphicsContext->setShouldAntialias(antialias);
312        graphicsContext->drawRect(IntRect(x1, y1, x2 - x1, y2 - y1));
313        graphicsContext->setShouldAntialias(wasAntialiased);
314        graphicsContext->setStrokeStyle(oldStrokeStyle);
315        return;
316    }
317    FloatPoint quad[4];
318    switch (side) {
319    case BSTop:
320        quad[0] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y1);
321        quad[1] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y2);
322        quad[2] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y2);
323        quad[3] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y1);
324        break;
325    case BSBottom:
326        quad[0] = FloatPoint(x1 + std::max(adjacentWidth1, 0), y1);
327        quad[1] = FloatPoint(x1 + std::max(-adjacentWidth1, 0), y2);
328        quad[2] = FloatPoint(x2 - std::max(-adjacentWidth2, 0), y2);
329        quad[3] = FloatPoint(x2 - std::max(adjacentWidth2, 0), y1);
330        break;
331    case BSLeft:
332        quad[0] = FloatPoint(x1, y1 + std::max(-adjacentWidth1, 0));
333        quad[1] = FloatPoint(x1, y2 - std::max(-adjacentWidth2, 0));
334        quad[2] = FloatPoint(x2, y2 - std::max(adjacentWidth2, 0));
335        quad[3] = FloatPoint(x2, y1 + std::max(adjacentWidth1, 0));
336        break;
337    case BSRight:
338        quad[0] = FloatPoint(x1, y1 + std::max(adjacentWidth1, 0));
339        quad[1] = FloatPoint(x1, y2 - std::max(adjacentWidth2, 0));
340        quad[2] = FloatPoint(x2, y2 - std::max(-adjacentWidth2, 0));
341        quad[3] = FloatPoint(x2, y1 + std::max(-adjacentWidth1, 0));
342        break;
343    }
344
345    graphicsContext->drawConvexPolygon(4, quad, antialias);
346    graphicsContext->setStrokeStyle(oldStrokeStyle);
347}
348
349} // namespace blink
350