1 /*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
5 * (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
6 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
7 * Copyright (C) 2010 Google Inc. All rights reserved.
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 *
24 */
25
26 #include "config.h"
27 #include "RenderBoxModelObject.h"
28
29 #include "GraphicsContext.h"
30 #include "HTMLFrameOwnerElement.h"
31 #include "HTMLNames.h"
32 #include "ImageBuffer.h"
33 #include "Path.h"
34 #include "RenderBlock.h"
35 #include "RenderInline.h"
36 #include "RenderLayer.h"
37 #include "RenderView.h"
38 #include <wtf/CurrentTime.h>
39
40 using namespace std;
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 bool RenderBoxModelObject::s_wasFloating = false;
47 bool RenderBoxModelObject::s_hadLayer = false;
48 bool RenderBoxModelObject::s_layerWasSelfPainting = false;
49
50 static const double cInterpolationCutoff = 800. * 800.;
51 static const double cLowQualityTimeThreshold = 0.500; // 500 ms
52
53 typedef HashMap<const void*, IntSize> LayerSizeMap;
54 typedef HashMap<RenderBoxModelObject*, LayerSizeMap> ObjectLayerSizeMap;
55
56 // The HashMap for storing continuation pointers.
57 // An inline can be split with blocks occuring in between the inline content.
58 // When this occurs we need a pointer to the next object. We can basically be
59 // split into a sequence of inlines and blocks. The continuation will either be
60 // an anonymous block (that houses other blocks) or it will be an inline flow.
61 // <b><i><p>Hello</p></i></b>. In this example the <i> will have a block as
62 // its continuation but the <b> will just have an inline as its continuation.
63 typedef HashMap<const RenderBoxModelObject*, RenderBoxModelObject*> ContinuationMap;
64 static ContinuationMap* continuationMap = 0;
65
66 class ImageQualityController {
67 WTF_MAKE_NONCOPYABLE(ImageQualityController); WTF_MAKE_FAST_ALLOCATED;
68 public:
69 ImageQualityController();
70 bool shouldPaintAtLowQuality(GraphicsContext*, RenderBoxModelObject*, Image*, const void* layer, const IntSize&);
71 void removeLayer(RenderBoxModelObject*, LayerSizeMap* innerMap, const void* layer);
72 void set(RenderBoxModelObject*, LayerSizeMap* innerMap, const void* layer, const IntSize&);
73 void objectDestroyed(RenderBoxModelObject*);
isEmpty()74 bool isEmpty() { return m_objectLayerSizeMap.isEmpty(); }
75
76 private:
77 void highQualityRepaintTimerFired(Timer<ImageQualityController>*);
78 void restartTimer();
79
80 ObjectLayerSizeMap m_objectLayerSizeMap;
81 Timer<ImageQualityController> m_timer;
82 bool m_animatedResizeIsActive;
83 };
84
ImageQualityController()85 ImageQualityController::ImageQualityController()
86 : m_timer(this, &ImageQualityController::highQualityRepaintTimerFired)
87 , m_animatedResizeIsActive(false)
88 {
89 }
90
removeLayer(RenderBoxModelObject * object,LayerSizeMap * innerMap,const void * layer)91 void ImageQualityController::removeLayer(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer)
92 {
93 if (innerMap) {
94 innerMap->remove(layer);
95 if (innerMap->isEmpty())
96 objectDestroyed(object);
97 }
98 }
99
set(RenderBoxModelObject * object,LayerSizeMap * innerMap,const void * layer,const IntSize & size)100 void ImageQualityController::set(RenderBoxModelObject* object, LayerSizeMap* innerMap, const void* layer, const IntSize& size)
101 {
102 if (innerMap)
103 innerMap->set(layer, size);
104 else {
105 LayerSizeMap newInnerMap;
106 newInnerMap.set(layer, size);
107 m_objectLayerSizeMap.set(object, newInnerMap);
108 }
109 }
110
objectDestroyed(RenderBoxModelObject * object)111 void ImageQualityController::objectDestroyed(RenderBoxModelObject* object)
112 {
113 m_objectLayerSizeMap.remove(object);
114 if (m_objectLayerSizeMap.isEmpty()) {
115 m_animatedResizeIsActive = false;
116 m_timer.stop();
117 }
118 }
119
highQualityRepaintTimerFired(Timer<ImageQualityController> *)120 void ImageQualityController::highQualityRepaintTimerFired(Timer<ImageQualityController>*)
121 {
122 if (m_animatedResizeIsActive) {
123 m_animatedResizeIsActive = false;
124 for (ObjectLayerSizeMap::iterator it = m_objectLayerSizeMap.begin(); it != m_objectLayerSizeMap.end(); ++it)
125 it->first->repaint();
126 }
127 }
128
restartTimer()129 void ImageQualityController::restartTimer()
130 {
131 m_timer.startOneShot(cLowQualityTimeThreshold);
132 }
133
shouldPaintAtLowQuality(GraphicsContext * context,RenderBoxModelObject * object,Image * image,const void * layer,const IntSize & size)134 bool ImageQualityController::shouldPaintAtLowQuality(GraphicsContext* context, RenderBoxModelObject* object, Image* image, const void *layer, const IntSize& size)
135 {
136 // If the image is not a bitmap image, then none of this is relevant and we just paint at high
137 // quality.
138 if (!image || !image->isBitmapImage() || context->paintingDisabled())
139 return false;
140
141 // Make sure to use the unzoomed image size, since if a full page zoom is in effect, the image
142 // is actually being scaled.
143 IntSize imageSize(image->width(), image->height());
144
145 // Look ourselves up in the hashtables.
146 ObjectLayerSizeMap::iterator i = m_objectLayerSizeMap.find(object);
147 LayerSizeMap* innerMap = i != m_objectLayerSizeMap.end() ? &i->second : 0;
148 IntSize oldSize;
149 bool isFirstResize = true;
150 if (innerMap) {
151 LayerSizeMap::iterator j = innerMap->find(layer);
152 if (j != innerMap->end()) {
153 isFirstResize = false;
154 oldSize = j->second;
155 }
156 }
157
158 const AffineTransform& currentTransform = context->getCTM();
159 bool contextIsScaled = !currentTransform.isIdentityOrTranslationOrFlipped();
160 if (!contextIsScaled && imageSize == size) {
161 // There is no scale in effect. If we had a scale in effect before, we can just remove this object from the list.
162 removeLayer(object, innerMap, layer);
163 return false;
164 }
165
166 // There is no need to hash scaled images that always use low quality mode when the page demands it. This is the iChat case.
167 if (object->document()->page()->inLowQualityImageInterpolationMode()) {
168 double totalPixels = static_cast<double>(image->width()) * static_cast<double>(image->height());
169 if (totalPixels > cInterpolationCutoff)
170 return true;
171 }
172
173 // If an animated resize is active, paint in low quality and kick the timer ahead.
174 if (m_animatedResizeIsActive) {
175 set(object, innerMap, layer, size);
176 restartTimer();
177 return true;
178 }
179 // If this is the first time resizing this image, or its size is the
180 // same as the last resize, draw at high res, but record the paint
181 // size and set the timer.
182 if (isFirstResize || oldSize == size) {
183 restartTimer();
184 set(object, innerMap, layer, size);
185 return false;
186 }
187 // If the timer is no longer active, draw at high quality and don't
188 // set the timer.
189 if (!m_timer.isActive()) {
190 removeLayer(object, innerMap, layer);
191 return false;
192 }
193 // This object has been resized to two different sizes while the timer
194 // is active, so draw at low quality, set the flag for animated resizes and
195 // the object to the list for high quality redraw.
196 set(object, innerMap, layer, size);
197 m_animatedResizeIsActive = true;
198 restartTimer();
199 return true;
200 }
201
202 static ImageQualityController* gImageQualityController = 0;
203
imageQualityController()204 static ImageQualityController* imageQualityController()
205 {
206 if (!gImageQualityController)
207 gImageQualityController = new ImageQualityController;
208
209 return gImageQualityController;
210 }
211
setSelectionState(SelectionState s)212 void RenderBoxModelObject::setSelectionState(SelectionState s)
213 {
214 if (selectionState() == s)
215 return;
216
217 if (s == SelectionInside && selectionState() != SelectionNone)
218 return;
219
220 if ((s == SelectionStart && selectionState() == SelectionEnd)
221 || (s == SelectionEnd && selectionState() == SelectionStart))
222 RenderObject::setSelectionState(SelectionBoth);
223 else
224 RenderObject::setSelectionState(s);
225
226 // FIXME:
227 // We should consider whether it is OK propagating to ancestor RenderInlines.
228 // This is a workaround for http://webkit.org/b/32123
229 RenderBlock* cb = containingBlock();
230 if (cb && !cb->isRenderView())
231 cb->setSelectionState(s);
232 }
233
shouldPaintAtLowQuality(GraphicsContext * context,Image * image,const void * layer,const IntSize & size)234 bool RenderBoxModelObject::shouldPaintAtLowQuality(GraphicsContext* context, Image* image, const void* layer, const IntSize& size)
235 {
236 return imageQualityController()->shouldPaintAtLowQuality(context, this, image, layer, size);
237 }
238
RenderBoxModelObject(Node * node)239 RenderBoxModelObject::RenderBoxModelObject(Node* node)
240 : RenderObject(node)
241 , m_layer(0)
242 {
243 }
244
~RenderBoxModelObject()245 RenderBoxModelObject::~RenderBoxModelObject()
246 {
247 // Our layer should have been destroyed and cleared by now
248 ASSERT(!hasLayer());
249 ASSERT(!m_layer);
250 if (gImageQualityController) {
251 gImageQualityController->objectDestroyed(this);
252 if (gImageQualityController->isEmpty()) {
253 delete gImageQualityController;
254 gImageQualityController = 0;
255 }
256 }
257 }
258
destroyLayer()259 void RenderBoxModelObject::destroyLayer()
260 {
261 ASSERT(!hasLayer()); // Callers should have already called setHasLayer(false)
262 ASSERT(m_layer);
263 m_layer->destroy(renderArena());
264 m_layer = 0;
265 }
266
destroy()267 void RenderBoxModelObject::destroy()
268 {
269 // This must be done before we destroy the RenderObject.
270 if (m_layer)
271 m_layer->clearClipRects();
272
273 // A continuation of this RenderObject should be destroyed at subclasses.
274 ASSERT(!continuation());
275
276 // RenderObject::destroy calls back to destroyLayer() for layer destruction
277 RenderObject::destroy();
278 }
279
hasSelfPaintingLayer() const280 bool RenderBoxModelObject::hasSelfPaintingLayer() const
281 {
282 return m_layer && m_layer->isSelfPaintingLayer();
283 }
284
styleWillChange(StyleDifference diff,const RenderStyle * newStyle)285 void RenderBoxModelObject::styleWillChange(StyleDifference diff, const RenderStyle* newStyle)
286 {
287 s_wasFloating = isFloating();
288 s_hadLayer = hasLayer();
289 if (s_hadLayer)
290 s_layerWasSelfPainting = layer()->isSelfPaintingLayer();
291
292 // If our z-index changes value or our visibility changes,
293 // we need to dirty our stacking context's z-order list.
294 if (style() && newStyle) {
295 if (parent()) {
296 // Do a repaint with the old style first, e.g., for example if we go from
297 // having an outline to not having an outline.
298 if (diff == StyleDifferenceRepaintLayer) {
299 layer()->repaintIncludingDescendants();
300 if (!(style()->clip() == newStyle->clip()))
301 layer()->clearClipRectsIncludingDescendants();
302 } else if (diff == StyleDifferenceRepaint || newStyle->outlineSize() < style()->outlineSize())
303 repaint();
304 }
305
306 if (diff == StyleDifferenceLayout || diff == StyleDifferenceSimplifiedLayout) {
307 // When a layout hint happens, we go ahead and do a repaint of the layer, since the layer could
308 // end up being destroyed.
309 if (hasLayer()) {
310 if (style()->position() != newStyle->position() ||
311 style()->zIndex() != newStyle->zIndex() ||
312 style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
313 !(style()->clip() == newStyle->clip()) ||
314 style()->hasClip() != newStyle->hasClip() ||
315 style()->opacity() != newStyle->opacity() ||
316 style()->transform() != newStyle->transform())
317 layer()->repaintIncludingDescendants();
318 } else if (newStyle->hasTransform() || newStyle->opacity() < 1) {
319 // If we don't have a layer yet, but we are going to get one because of transform or opacity,
320 // then we need to repaint the old position of the object.
321 repaint();
322 }
323 }
324
325 if (hasLayer() && (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() ||
326 style()->zIndex() != newStyle->zIndex() ||
327 style()->visibility() != newStyle->visibility())) {
328 layer()->dirtyStackingContextZOrderLists();
329 if (style()->hasAutoZIndex() != newStyle->hasAutoZIndex() || style()->visibility() != newStyle->visibility())
330 layer()->dirtyZOrderLists();
331 }
332 }
333
334 RenderObject::styleWillChange(diff, newStyle);
335 }
336
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)337 void RenderBoxModelObject::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
338 {
339 RenderObject::styleDidChange(diff, oldStyle);
340 updateBoxModelInfoFromStyle();
341
342 if (requiresLayer()) {
343 if (!layer()) {
344 if (s_wasFloating && isFloating())
345 setChildNeedsLayout(true);
346 m_layer = new (renderArena()) RenderLayer(this);
347 setHasLayer(true);
348 m_layer->insertOnlyThisLayer();
349 if (parent() && !needsLayout() && containingBlock()) {
350 m_layer->setNeedsFullRepaint();
351 m_layer->updateLayerPositions();
352 }
353 }
354 } else if (layer() && layer()->parent()) {
355 setHasTransform(false); // Either a transform wasn't specified or the object doesn't support transforms, so just null out the bit.
356 setHasReflection(false);
357 m_layer->removeOnlyThisLayer(); // calls destroyLayer() which clears m_layer
358 if (s_wasFloating && isFloating())
359 setChildNeedsLayout(true);
360 }
361
362 if (layer()) {
363 layer()->styleChanged(diff, oldStyle);
364 if (s_hadLayer && layer()->isSelfPaintingLayer() != s_layerWasSelfPainting)
365 setChildNeedsLayout(true);
366 }
367 }
368
updateBoxModelInfoFromStyle()369 void RenderBoxModelObject::updateBoxModelInfoFromStyle()
370 {
371 // Set the appropriate bits for a box model object. Since all bits are cleared in styleWillChange,
372 // we only check for bits that could possibly be set to true.
373 setHasBoxDecorations(hasBackground() || style()->hasBorder() || style()->hasAppearance() || style()->boxShadow());
374 setInline(style()->isDisplayInlineType());
375 setRelPositioned(style()->position() == RelativePosition);
376 setHorizontalWritingMode(style()->isHorizontalWritingMode());
377 }
378
relativePositionOffsetX() const379 int RenderBoxModelObject::relativePositionOffsetX() const
380 {
381 // Objects that shrink to avoid floats normally use available line width when computing containing block width. However
382 // in the case of relative positioning using percentages, we can't do this. The offset should always be resolved using the
383 // available width of the containing block. Therefore we don't use containingBlockLogicalWidthForContent() here, but instead explicitly
384 // call availableWidth on our containing block.
385 if (!style()->left().isAuto()) {
386 RenderBlock* cb = containingBlock();
387 if (!style()->right().isAuto() && !cb->style()->isLeftToRightDirection())
388 return -style()->right().calcValue(cb->availableWidth());
389 return style()->left().calcValue(cb->availableWidth());
390 }
391 if (!style()->right().isAuto()) {
392 RenderBlock* cb = containingBlock();
393 return -style()->right().calcValue(cb->availableWidth());
394 }
395 return 0;
396 }
397
relativePositionOffsetY() const398 int RenderBoxModelObject::relativePositionOffsetY() const
399 {
400 RenderBlock* containingBlock = this->containingBlock();
401
402 // If the containing block of a relatively positioned element does not
403 // specify a height, a percentage top or bottom offset should be resolved as
404 // auto. An exception to this is if the containing block has the WinIE quirk
405 // where <html> and <body> assume the size of the viewport. In this case,
406 // calculate the percent offset based on this height.
407 // See <https://bugs.webkit.org/show_bug.cgi?id=26396>.
408 if (!style()->top().isAuto()
409 && (!containingBlock->style()->height().isAuto()
410 || !style()->top().isPercent()
411 || containingBlock->stretchesToViewport()))
412 return style()->top().calcValue(containingBlock->availableHeight());
413
414 if (!style()->bottom().isAuto()
415 && (!containingBlock->style()->height().isAuto()
416 || !style()->bottom().isPercent()
417 || containingBlock->stretchesToViewport()))
418 return -style()->bottom().calcValue(containingBlock->availableHeight());
419
420 return 0;
421 }
422
offsetLeft() const423 int RenderBoxModelObject::offsetLeft() const
424 {
425 // If the element is the HTML body element or does not have an associated box
426 // return 0 and stop this algorithm.
427 if (isBody())
428 return 0;
429
430 RenderBoxModelObject* offsetPar = offsetParent();
431 int xPos = (isBox() ? toRenderBox(this)->x() : 0);
432
433 // If the offsetParent of the element is null, or is the HTML body element,
434 // return the distance between the canvas origin and the left border edge
435 // of the element and stop this algorithm.
436 if (offsetPar) {
437 if (offsetPar->isBox() && !offsetPar->isBody())
438 xPos -= toRenderBox(offsetPar)->borderLeft();
439 if (!isPositioned()) {
440 if (isRelPositioned())
441 xPos += relativePositionOffsetX();
442 RenderObject* curr = parent();
443 while (curr && curr != offsetPar) {
444 // FIXME: What are we supposed to do inside SVG content?
445 if (curr->isBox() && !curr->isTableRow())
446 xPos += toRenderBox(curr)->x();
447 curr = curr->parent();
448 }
449 if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
450 xPos += toRenderBox(offsetPar)->x();
451 }
452 }
453
454 return xPos;
455 }
456
offsetTop() const457 int RenderBoxModelObject::offsetTop() const
458 {
459 // If the element is the HTML body element or does not have an associated box
460 // return 0 and stop this algorithm.
461 if (isBody())
462 return 0;
463
464 RenderBoxModelObject* offsetPar = offsetParent();
465 int yPos = (isBox() ? toRenderBox(this)->y() : 0);
466
467 // If the offsetParent of the element is null, or is the HTML body element,
468 // return the distance between the canvas origin and the top border edge
469 // of the element and stop this algorithm.
470 if (offsetPar) {
471 if (offsetPar->isBox() && !offsetPar->isBody())
472 yPos -= toRenderBox(offsetPar)->borderTop();
473 if (!isPositioned()) {
474 if (isRelPositioned())
475 yPos += relativePositionOffsetY();
476 RenderObject* curr = parent();
477 while (curr && curr != offsetPar) {
478 // FIXME: What are we supposed to do inside SVG content?
479 if (curr->isBox() && !curr->isTableRow())
480 yPos += toRenderBox(curr)->y();
481 curr = curr->parent();
482 }
483 if (offsetPar->isBox() && offsetPar->isBody() && !offsetPar->isRelPositioned() && !offsetPar->isPositioned())
484 yPos += toRenderBox(offsetPar)->y();
485 }
486 }
487 return yPos;
488 }
489
paddingTop(bool) const490 int RenderBoxModelObject::paddingTop(bool) const
491 {
492 int w = 0;
493 Length padding = style()->paddingTop();
494 if (padding.isPercent())
495 w = containingBlock()->availableLogicalWidth();
496 return padding.calcMinValue(w);
497 }
498
paddingBottom(bool) const499 int RenderBoxModelObject::paddingBottom(bool) const
500 {
501 int w = 0;
502 Length padding = style()->paddingBottom();
503 if (padding.isPercent())
504 w = containingBlock()->availableLogicalWidth();
505 return padding.calcMinValue(w);
506 }
507
paddingLeft(bool) const508 int RenderBoxModelObject::paddingLeft(bool) const
509 {
510 int w = 0;
511 Length padding = style()->paddingLeft();
512 if (padding.isPercent())
513 w = containingBlock()->availableLogicalWidth();
514 return padding.calcMinValue(w);
515 }
516
paddingRight(bool) const517 int RenderBoxModelObject::paddingRight(bool) const
518 {
519 int w = 0;
520 Length padding = style()->paddingRight();
521 if (padding.isPercent())
522 w = containingBlock()->availableLogicalWidth();
523 return padding.calcMinValue(w);
524 }
525
paddingBefore(bool) const526 int RenderBoxModelObject::paddingBefore(bool) const
527 {
528 int w = 0;
529 Length padding = style()->paddingBefore();
530 if (padding.isPercent())
531 w = containingBlock()->availableLogicalWidth();
532 return padding.calcMinValue(w);
533 }
534
paddingAfter(bool) const535 int RenderBoxModelObject::paddingAfter(bool) const
536 {
537 int w = 0;
538 Length padding = style()->paddingAfter();
539 if (padding.isPercent())
540 w = containingBlock()->availableLogicalWidth();
541 return padding.calcMinValue(w);
542 }
543
paddingStart(bool) const544 int RenderBoxModelObject::paddingStart(bool) const
545 {
546 int w = 0;
547 Length padding = style()->paddingStart();
548 if (padding.isPercent())
549 w = containingBlock()->availableLogicalWidth();
550 return padding.calcMinValue(w);
551 }
552
paddingEnd(bool) const553 int RenderBoxModelObject::paddingEnd(bool) const
554 {
555 int w = 0;
556 Length padding = style()->paddingEnd();
557 if (padding.isPercent())
558 w = containingBlock()->availableLogicalWidth();
559 return padding.calcMinValue(w);
560 }
561
getBackgroundRoundedRect(const IntRect & borderRect,InlineFlowBox * box,int inlineBoxWidth,int inlineBoxHeight,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)562 RoundedIntRect RenderBoxModelObject::getBackgroundRoundedRect(const IntRect& borderRect, InlineFlowBox* box, int inlineBoxWidth, int inlineBoxHeight,
563 bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
564 {
565 RoundedIntRect border = style()->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
566 if (box && (box->nextLineBox() || box->prevLineBox())) {
567 RoundedIntRect segmentBorder = style()->getRoundedBorderFor(IntRect(0, 0, inlineBoxWidth, inlineBoxHeight), includeLogicalLeftEdge, includeLogicalRightEdge);
568 border.setRadii(segmentBorder.radii());
569 }
570
571 return border;
572 }
573
paintFillLayerExtended(const PaintInfo & paintInfo,const Color & color,const FillLayer * bgLayer,int tx,int ty,int w,int h,InlineFlowBox * box,int inlineBoxWidth,int inlineBoxHeight,CompositeOperator op,RenderObject * backgroundObject)574 void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer* bgLayer, int tx, int ty, int w, int h,
575 InlineFlowBox* box, int inlineBoxWidth, int inlineBoxHeight, CompositeOperator op, RenderObject* backgroundObject)
576 {
577 GraphicsContext* context = paintInfo.context;
578 if (context->paintingDisabled())
579 return;
580
581 bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
582 bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
583
584 bool hasRoundedBorder = style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge);
585 bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment;
586 bool isBorderFill = bgLayer->clip() == BorderFillBox;
587 bool isRoot = this->isRoot();
588
589 Color bgColor = color;
590 StyleImage* bgImage = bgLayer->image();
591 bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(style()->effectiveZoom());
592
593 // When this style flag is set, change existing background colors and images to a solid white background.
594 // If there's no bg color or image, leave it untouched to avoid affecting transparency.
595 // We don't try to avoid loading the background images, because this style flag is only set
596 // when printing, and at that point we've already loaded the background images anyway. (To avoid
597 // loading the background images we'd have to do this check when applying styles rather than
598 // while rendering.)
599 if (style()->forceBackgroundsToWhite()) {
600 // Note that we can't reuse this variable below because the bgColor might be changed
601 bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0;
602 if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
603 bgColor = Color::white;
604 shouldPaintBackgroundImage = false;
605 }
606 }
607
608 bool colorVisible = bgColor.isValid() && bgColor.alpha() > 0;
609
610 // Fast path for drawing simple color backgrounds.
611 if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill) {
612 if (!colorVisible)
613 return;
614
615 IntRect borderRect(tx, ty, w, h);
616 if (borderRect.isEmpty())
617 return;
618
619 if (hasRoundedBorder) {
620 RoundedIntRect border = getBackgroundRoundedRect(borderRect, box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
621 context->fillRoundedRect(border, bgColor, style()->colorSpace());
622 } else
623 context->fillRect(borderRect, bgColor, style()->colorSpace());
624
625 return;
626 }
627
628 bool clippedToBorderRadius = false;
629 if (hasRoundedBorder) {
630 IntRect borderRect(tx, ty, w, h);
631
632 if (borderRect.isEmpty())
633 return;
634
635 context->save();
636
637 RoundedIntRect border = getBackgroundRoundedRect(borderRect, box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
638 context->addRoundedRectClip(border);
639 clippedToBorderRadius = true;
640 }
641
642 int bLeft = includeLeftEdge ? borderLeft() : 0;
643 int bRight = includeRightEdge ? borderRight() : 0;
644 int pLeft = includeLeftEdge ? paddingLeft() : 0;
645 int pRight = includeRightEdge ? paddingRight() : 0;
646
647 if (clippedWithLocalScrolling) {
648 // Clip to the overflow area.
649 context->save();
650 context->clip(toRenderBox(this)->overflowClipRect(tx, ty));
651
652 // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends.
653 IntSize offset = layer()->scrolledContentOffset();
654 tx -= offset.width();
655 ty -= offset.height();
656 w = bLeft + layer()->scrollWidth() + bRight;
657 h = borderTop() + layer()->scrollHeight() + borderBottom();
658 }
659
660 if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) {
661 // Clip to the padding or content boxes as necessary.
662 bool includePadding = bgLayer->clip() == ContentFillBox;
663 int x = tx + bLeft + (includePadding ? pLeft : 0);
664 int y = ty + borderTop() + (includePadding ? paddingTop() : 0);
665 int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0);
666 int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0);
667 context->save();
668 context->clip(IntRect(x, y, width, height));
669 } else if (bgLayer->clip() == TextFillBox) {
670 // We have to draw our text into a mask that can then be used to clip background drawing.
671 // First figure out how big the mask has to be. It should be no bigger than what we need
672 // to actually render, so we should intersect the dirty rect with the border box of the background.
673 IntRect maskRect(tx, ty, w, h);
674 maskRect.intersect(paintInfo.rect);
675
676 // Now create the mask.
677 OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size());
678 if (!maskImage)
679 return;
680
681 GraphicsContext* maskImageContext = maskImage->context();
682 maskImageContext->translate(-maskRect.x(), -maskRect.y());
683
684 // Now add the text to the clip. We do this by painting using a special paint phase that signals to
685 // InlineTextBoxes that they should just add their contents to the clip.
686 PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0);
687 if (box) {
688 RootInlineBox* root = box->root();
689 box->paint(info, tx - box->x(), ty - box->y(), root->lineTop(), root->lineBottom());
690 } else {
691 int x = isBox() ? toRenderBox(this)->x() : 0;
692 int y = isBox() ? toRenderBox(this)->y() : 0;
693 paint(info, tx - x, ty - y);
694 }
695
696 // The mask has been created. Now we just need to clip to it.
697 context->save();
698 context->clipToImageBuffer(maskImage.get(), maskRect);
699 }
700
701 // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with
702 // no background in the child document should show the parent's background.
703 bool isOpaqueRoot = false;
704 if (isRoot) {
705 isOpaqueRoot = true;
706 if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) {
707 Element* ownerElement = document()->ownerElement();
708 if (ownerElement) {
709 if (!ownerElement->hasTagName(frameTag)) {
710 // Locate the <body> element using the DOM. This is easier than trying
711 // to crawl around a render tree with potential :before/:after content and
712 // anonymous blocks created by inline <body> tags etc. We can locate the <body>
713 // render object very easily via the DOM.
714 HTMLElement* body = document()->body();
715 if (body) {
716 // Can't scroll a frameset document anyway.
717 isOpaqueRoot = body->hasLocalName(framesetTag);
718 }
719 #if ENABLE(SVG)
720 else {
721 // SVG documents and XML documents with SVG root nodes are transparent.
722 isOpaqueRoot = !document()->hasSVGRootNode();
723 }
724 #endif
725 }
726 } else
727 isOpaqueRoot = !view()->frameView()->isTransparent();
728 }
729 view()->frameView()->setContentIsOpaque(isOpaqueRoot);
730 }
731
732 // Paint the color first underneath all images.
733 if (!bgLayer->next()) {
734 IntRect rect(tx, ty, w, h);
735 rect.intersect(paintInfo.rect);
736 // If we have an alpha and we are painting the root element, go ahead and blend with the base background color.
737 if (isOpaqueRoot) {
738 Color baseColor = view()->frameView()->baseBackgroundColor();
739 if (baseColor.alpha() > 0) {
740 CompositeOperator previousOperator = context->compositeOperation();
741 context->setCompositeOperation(CompositeCopy);
742 context->fillRect(rect, baseColor, style()->colorSpace());
743 context->setCompositeOperation(previousOperator);
744 } else
745 context->clearRect(rect);
746 }
747
748 if (bgColor.isValid() && bgColor.alpha() > 0)
749 context->fillRect(rect, bgColor, style()->colorSpace());
750 }
751
752 // no progressive loading of the background image
753 if (shouldPaintBackgroundImage) {
754 IntRect destRect;
755 IntPoint phase;
756 IntSize tileSize;
757
758 calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize);
759 IntPoint destOrigin = destRect.location();
760 destRect.intersect(paintInfo.rect);
761 if (!destRect.isEmpty()) {
762 phase += destRect.location() - destOrigin;
763 CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op;
764 RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this;
765 RefPtr<Image> image = bgImage->image(clientForBackgroundImage, tileSize);
766 bool useLowQualityScaling = shouldPaintAtLowQuality(context, image.get(), bgLayer, tileSize);
767 context->drawTiledImage(image.get(), style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling);
768 }
769 }
770
771 if (!isBorderFill) // Undo the background clip
772 context->restore();
773
774 if (clippedToBorderRadius) // Undo the border radius clip
775 context->restore();
776
777 if (clippedWithLocalScrolling) // Undo the clip for local background attachments.
778 context->restore();
779 }
780
calculateFillTileSize(const FillLayer * fillLayer,IntSize positioningAreaSize) const781 IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const
782 {
783 StyleImage* image = fillLayer->image();
784 image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin.
785
786 EFillSizeType type = fillLayer->size().type;
787
788 switch (type) {
789 case SizeLength: {
790 int w = positioningAreaSize.width();
791 int h = positioningAreaSize.height();
792
793 Length layerWidth = fillLayer->size().size.width();
794 Length layerHeight = fillLayer->size().size.height();
795
796 if (layerWidth.isFixed())
797 w = layerWidth.value();
798 else if (layerWidth.isPercent())
799 w = layerWidth.calcValue(positioningAreaSize.width());
800
801 if (layerHeight.isFixed())
802 h = layerHeight.value();
803 else if (layerHeight.isPercent())
804 h = layerHeight.calcValue(positioningAreaSize.height());
805
806 // If one of the values is auto we have to use the appropriate
807 // scale to maintain our aspect ratio.
808 if (layerWidth.isAuto() && !layerHeight.isAuto()) {
809 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
810 if (imageIntrinsicSize.height())
811 w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height();
812 } else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
813 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
814 if (imageIntrinsicSize.width())
815 h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width();
816 } else if (layerWidth.isAuto() && layerHeight.isAuto()) {
817 // If both width and height are auto, use the image's intrinsic size.
818 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
819 w = imageIntrinsicSize.width();
820 h = imageIntrinsicSize.height();
821 }
822
823 return IntSize(max(1, w), max(1, h));
824 }
825 case Contain:
826 case Cover: {
827 IntSize imageIntrinsicSize = image->imageSize(this, 1);
828 float horizontalScaleFactor = imageIntrinsicSize.width()
829 ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1;
830 float verticalScaleFactor = imageIntrinsicSize.height()
831 ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1;
832 float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor);
833 return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor));
834 }
835 case SizeNone:
836 break;
837 }
838
839 return image->imageSize(this, style()->effectiveZoom());
840 }
841
calculateBackgroundImageGeometry(const FillLayer * fillLayer,int tx,int ty,int w,int h,IntRect & destRect,IntPoint & phase,IntSize & tileSize)842 void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h,
843 IntRect& destRect, IntPoint& phase, IntSize& tileSize)
844 {
845 int left = 0;
846 int top = 0;
847 IntSize positioningAreaSize;
848
849 // Determine the background positioning area and set destRect to the background painting area.
850 // destRect will be adjusted later if the background is non-repeating.
851 bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment;
852
853 #if ENABLE(FAST_MOBILE_SCROLLING)
854 if (view()->frameView() && view()->frameView()->canBlitOnScroll()) {
855 // As a side effect of an optimization to blit on scroll, we do not honor the CSS
856 // property "background-attachment: fixed" because it may result in rendering
857 // artifacts. Note, these artifacts only appear if we are blitting on scroll of
858 // a page that has fixed background images.
859 fixedAttachment = false;
860 }
861 #endif
862
863 if (!fixedAttachment) {
864 destRect = IntRect(tx, ty, w, h);
865
866 int right = 0;
867 int bottom = 0;
868 // Scroll and Local.
869 if (fillLayer->origin() != BorderFillBox) {
870 left = borderLeft();
871 right = borderRight();
872 top = borderTop();
873 bottom = borderBottom();
874 if (fillLayer->origin() == ContentFillBox) {
875 left += paddingLeft();
876 right += paddingRight();
877 top += paddingTop();
878 bottom += paddingBottom();
879 }
880 }
881
882 // The background of the box generated by the root element covers the entire canvas including
883 // its margins. Since those were added in already, we have to factor them out when computing
884 // the background positioning area.
885 if (isRoot()) {
886 positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom);
887 left += marginLeft();
888 top += marginTop();
889 } else
890 positioningAreaSize = IntSize(w - left - right, h - top - bottom);
891 } else {
892 destRect = viewRect();
893 positioningAreaSize = destRect.size();
894 }
895
896 tileSize = calculateFillTileSize(fillLayer, positioningAreaSize);
897
898 EFillRepeat backgroundRepeatX = fillLayer->repeatX();
899 EFillRepeat backgroundRepeatY = fillLayer->repeatY();
900
901 int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true);
902 if (backgroundRepeatX == RepeatFill)
903 phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0);
904 else {
905 destRect.move(max(xPosition + left, 0), 0);
906 phase.setX(-min(xPosition + left, 0));
907 destRect.setWidth(tileSize.width() + min(xPosition + left, 0));
908 }
909
910 int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true);
911 if (backgroundRepeatY == RepeatFill)
912 phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0);
913 else {
914 destRect.move(0, max(yPosition + top, 0));
915 phase.setY(-min(yPosition + top, 0));
916 destRect.setHeight(tileSize.height() + min(yPosition + top, 0));
917 }
918
919 if (fixedAttachment)
920 phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0));
921
922 destRect.intersect(IntRect(tx, ty, w, h));
923 }
924
paintNinePieceImage(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,const NinePieceImage & ninePieceImage,CompositeOperator op)925 bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style,
926 const NinePieceImage& ninePieceImage, CompositeOperator op)
927 {
928 StyleImage* styleImage = ninePieceImage.image();
929 if (!styleImage)
930 return false;
931
932 if (!styleImage->isLoaded())
933 return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either.
934
935 if (!styleImage->canRender(style->effectiveZoom()))
936 return false;
937
938 // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function
939 // doesn't have any understanding of the zoom that is in effect on the tile.
940 styleImage->setImageContainerSize(IntSize(w, h));
941 IntSize imageSize = styleImage->imageSize(this, 1.0f);
942 int imageWidth = imageSize.width();
943 int imageHeight = imageSize.height();
944
945 int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight));
946 int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight));
947 int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth));
948 int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth));
949
950 ENinePieceImageRule hRule = ninePieceImage.horizontalRule();
951 ENinePieceImageRule vRule = ninePieceImage.verticalRule();
952
953 bool fitToBorder = style->borderImage() == ninePieceImage;
954
955 int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice;
956 int topWidth = fitToBorder ? style->borderTopWidth() : topSlice;
957 int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice;
958 int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice;
959
960 bool drawLeft = leftSlice > 0 && leftWidth > 0;
961 bool drawTop = topSlice > 0 && topWidth > 0;
962 bool drawRight = rightSlice > 0 && rightWidth > 0;
963 bool drawBottom = bottomSlice > 0 && bottomWidth > 0;
964 bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 &&
965 (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0;
966
967 RefPtr<Image> image = styleImage->image(this, imageSize);
968 ColorSpace colorSpace = style->colorSpace();
969
970 if (drawLeft) {
971 // Paint the top and bottom left corners.
972
973 // The top left corner rect is (tx, ty, leftWidth, topWidth)
974 // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice)
975 if (drawTop)
976 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty, leftWidth, topWidth),
977 IntRect(0, 0, leftSlice, topSlice), op);
978
979 // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth)
980 // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice)
981 if (drawBottom)
982 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth),
983 IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op);
984
985 // Paint the left edge.
986 // Have to scale and tile into the border rect.
987 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx, ty + topWidth, leftWidth,
988 h - topWidth - bottomWidth),
989 IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice),
990 Image::StretchTile, (Image::TileRule)vRule, op);
991 }
992
993 if (drawRight) {
994 // Paint the top and bottom right corners
995 // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth)
996 // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice)
997 if (drawTop)
998 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth),
999 IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op);
1000
1001 // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth)
1002 // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice)
1003 if (drawBottom)
1004 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth),
1005 IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op);
1006
1007 // Paint the right edge.
1008 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth,
1009 h - topWidth - bottomWidth),
1010 IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice),
1011 Image::StretchTile, (Image::TileRule)vRule, op);
1012 }
1013
1014 // Paint the top edge.
1015 if (drawTop)
1016 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth),
1017 IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice),
1018 (Image::TileRule)hRule, Image::StretchTile, op);
1019
1020 // Paint the bottom edge.
1021 if (drawBottom)
1022 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth,
1023 w - leftWidth - rightWidth, bottomWidth),
1024 IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice),
1025 (Image::TileRule)hRule, Image::StretchTile, op);
1026
1027 // Paint the middle.
1028 if (drawMiddle)
1029 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth,
1030 h - topWidth - bottomWidth),
1031 IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice),
1032 (Image::TileRule)hRule, (Image::TileRule)vRule, op);
1033
1034 return true;
1035 }
1036
1037 #if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING)
borderWillArcInnerEdge(const IntSize & firstRadius,const IntSize & secondRadius)1038 static bool borderWillArcInnerEdge(const IntSize& firstRadius, const IntSize& secondRadius)
1039 {
1040 return !firstRadius.isZero() || !secondRadius.isZero();
1041 }
1042
1043 enum BorderEdgeFlag {
1044 TopBorderEdge = 1 << BSTop,
1045 RightBorderEdge = 1 << BSRight,
1046 BottomBorderEdge = 1 << BSBottom,
1047 LeftBorderEdge = 1 << BSLeft,
1048 AllBorderEdges = TopBorderEdge | BottomBorderEdge | LeftBorderEdge | RightBorderEdge
1049 };
1050
edgeFlagForSide(BoxSide side)1051 static inline BorderEdgeFlag edgeFlagForSide(BoxSide side)
1052 {
1053 return static_cast<BorderEdgeFlag>(1 << side);
1054 }
1055
includesEdge(BorderEdgeFlags flags,BoxSide side)1056 static inline bool includesEdge(BorderEdgeFlags flags, BoxSide side)
1057 {
1058 return flags & edgeFlagForSide(side);
1059 }
1060
1061 class BorderEdge {
1062 public:
BorderEdge(int edgeWidth,const Color & edgeColor,EBorderStyle edgeStyle,bool edgeIsTransparent,bool edgeIsPresent)1063 BorderEdge(int edgeWidth, const Color& edgeColor, EBorderStyle edgeStyle, bool edgeIsTransparent, bool edgeIsPresent)
1064 : width(edgeWidth)
1065 , color(edgeColor)
1066 , style(edgeStyle)
1067 , isTransparent(edgeIsTransparent)
1068 , isPresent(edgeIsPresent)
1069 {
1070 if (style == DOUBLE && edgeWidth < 3)
1071 style = SOLID;
1072 }
1073
hasVisibleColorAndStyle() const1074 bool hasVisibleColorAndStyle() const { return style > BHIDDEN && !isTransparent; }
shouldRender() const1075 bool shouldRender() const { return isPresent && hasVisibleColorAndStyle(); }
presentButInvisible() const1076 bool presentButInvisible() const { return usedWidth() && !hasVisibleColorAndStyle(); }
1077
usedWidth() const1078 int usedWidth() const { return isPresent ? width : 0; }
1079
getDoubleBorderStripeWidths(int & outerWidth,int & innerWidth) const1080 void getDoubleBorderStripeWidths(int& outerWidth, int& innerWidth) const
1081 {
1082 int fullWidth = usedWidth();
1083 outerWidth = fullWidth / 3;
1084 innerWidth = fullWidth * 2 / 3;
1085
1086 // We need certain integer rounding results
1087 if (fullWidth % 3 == 2)
1088 outerWidth += 1;
1089
1090 if (fullWidth % 3 == 1)
1091 innerWidth += 1;
1092 }
1093
1094 int width;
1095 Color color;
1096 EBorderStyle style;
1097 bool isTransparent;
1098 bool isPresent;
1099 };
1100
edgesShareColor(const BorderEdge & firstEdge,const BorderEdge & secondEdge)1101 inline bool edgesShareColor(const BorderEdge& firstEdge, const BorderEdge& secondEdge)
1102 {
1103 return firstEdge.color == secondEdge.color;
1104 }
1105
styleRequiresClipPolygon(EBorderStyle style)1106 inline bool styleRequiresClipPolygon(EBorderStyle style)
1107 {
1108 return style == DOTTED || style == DASHED; // These are drawn with a stroke, so we have to clip to get corner miters.
1109 }
1110
borderStyleFillsBorderArea(EBorderStyle style)1111 static bool borderStyleFillsBorderArea(EBorderStyle style)
1112 {
1113 return !(style == DOTTED || style == DASHED || style == DOUBLE);
1114 }
1115
borderStyleHasInnerDetail(EBorderStyle style)1116 static bool borderStyleHasInnerDetail(EBorderStyle style)
1117 {
1118 return style == GROOVE || style == RIDGE || style == DOUBLE;
1119 }
1120
borderStyleIsDottedOrDashed(EBorderStyle style)1121 static bool borderStyleIsDottedOrDashed(EBorderStyle style)
1122 {
1123 return style == DOTTED || style == DASHED;
1124 }
1125
1126 // OUTSET darkens the bottom and right (and maybe lightens the top and left)
1127 // INSET darkens the top and left (and maybe lightens the bottom and right)
borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style,BoxSide side,BoxSide adjacentSide)1128 static inline bool borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style, BoxSide side, BoxSide adjacentSide)
1129 {
1130 // These styles match at the top/left and bottom/right.
1131 if (style == INSET || style == GROOVE || style == RIDGE || style == OUTSET) {
1132 const BorderEdgeFlags topRightFlags = edgeFlagForSide(BSTop) | edgeFlagForSide(BSRight);
1133 const BorderEdgeFlags bottomLeftFlags = edgeFlagForSide(BSBottom) | edgeFlagForSide(BSLeft);
1134
1135 BorderEdgeFlags flags = edgeFlagForSide(side) | edgeFlagForSide(adjacentSide);
1136 return flags == topRightFlags || flags == bottomLeftFlags;
1137 }
1138 return false;
1139 }
1140
colorsMatchAtCorner(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1141 static inline bool colorsMatchAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1142 {
1143 if (edges[side].shouldRender() != edges[adjacentSide].shouldRender())
1144 return false;
1145
1146 if (!edgesShareColor(edges[side], edges[adjacentSide]))
1147 return false;
1148
1149 return !borderStyleHasUnmatchedColorsAtCorner(edges[side].style, side, adjacentSide);
1150 }
1151
1152 // This assumes that we draw in order: top, bottom, left, right.
willBeOverdrawn(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1153 static inline bool willBeOverdrawn(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1154 {
1155 switch (side) {
1156 case BSTop:
1157 case BSBottom:
1158 if (edges[adjacentSide].presentButInvisible())
1159 return false;
1160
1161 if (!edgesShareColor(edges[side], edges[adjacentSide]) && edges[adjacentSide].color.hasAlpha())
1162 return false;
1163
1164 if (!borderStyleFillsBorderArea(edges[adjacentSide].style))
1165 return false;
1166
1167 return true;
1168
1169 case BSLeft:
1170 case BSRight:
1171 // These draw last, so are never overdrawn.
1172 return false;
1173 }
1174 return false;
1175 }
1176
borderStylesRequireMitre(BoxSide side,BoxSide adjacentSide,EBorderStyle style,EBorderStyle adjacentStyle)1177 static inline bool borderStylesRequireMitre(BoxSide side, BoxSide adjacentSide, EBorderStyle style, EBorderStyle adjacentStyle)
1178 {
1179 if (style == DOUBLE || adjacentStyle == DOUBLE || adjacentStyle == GROOVE || adjacentStyle == RIDGE)
1180 return true;
1181
1182 if (borderStyleIsDottedOrDashed(style) != borderStyleIsDottedOrDashed(adjacentStyle))
1183 return true;
1184
1185 if (style != adjacentStyle)
1186 return true;
1187
1188 return borderStyleHasUnmatchedColorsAtCorner(style, side, adjacentSide);
1189 }
1190
joinRequiresMitre(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[],bool allowOverdraw)1191 static bool joinRequiresMitre(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[], bool allowOverdraw)
1192 {
1193 if ((edges[side].isTransparent && edges[adjacentSide].isTransparent) || !edges[adjacentSide].isPresent)
1194 return false;
1195
1196 if (allowOverdraw && willBeOverdrawn(side, adjacentSide, edges))
1197 return false;
1198
1199 if (!edgesShareColor(edges[side], edges[adjacentSide]))
1200 return true;
1201
1202 if (borderStylesRequireMitre(side, adjacentSide, edges[side].style, edges[adjacentSide].style))
1203 return true;
1204
1205 return false;
1206 }
1207
paintOneBorderSide(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const IntRect & sideRect,BoxSide side,BoxSide adjacentSide1,BoxSide adjacentSide2,const BorderEdge edges[],const Path * path,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1208 void RenderBoxModelObject::paintOneBorderSide(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1209 const IntRect& sideRect, BoxSide side, BoxSide adjacentSide1, BoxSide adjacentSide2, const BorderEdge edges[], const Path* path,
1210 bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1211 {
1212 const BorderEdge& edgeToRender = edges[side];
1213 const BorderEdge& adjacentEdge1 = edges[adjacentSide1];
1214 const BorderEdge& adjacentEdge2 = edges[adjacentSide2];
1215
1216 bool mitreAdjacentSide1 = joinRequiresMitre(side, adjacentSide1, edges, !antialias);
1217 bool mitreAdjacentSide2 = joinRequiresMitre(side, adjacentSide2, edges, !antialias);
1218
1219 bool adjacentSide1StylesMatch = colorsMatchAtCorner(side, adjacentSide1, edges);
1220 bool adjacentSide2StylesMatch = colorsMatchAtCorner(side, adjacentSide2, edges);
1221
1222 const Color& colorToPaint = overrideColor ? *overrideColor : edgeToRender.color;
1223
1224 if (path) {
1225 graphicsContext->save();
1226 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, adjacentSide1StylesMatch, adjacentSide2StylesMatch);
1227 float thickness = max(max(edgeToRender.width, adjacentEdge1.width), adjacentEdge2.width);
1228 drawBoxSideFromPath(graphicsContext, outerBorder.rect(), *path, edges, edgeToRender.width, thickness, side, style, colorToPaint, edgeToRender.style, includeLogicalLeftEdge, includeLogicalRightEdge);
1229 graphicsContext->restore();
1230 } else {
1231 bool didClip = false;
1232
1233 if (styleRequiresClipPolygon(edgeToRender.style) && (mitreAdjacentSide1 || mitreAdjacentSide2)) {
1234 graphicsContext->save();
1235 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, !mitreAdjacentSide1, !mitreAdjacentSide2);
1236 didClip = true;
1237 // Since we clipped, no need to draw with a mitre.
1238 mitreAdjacentSide1 = false;
1239 mitreAdjacentSide2 = false;
1240 }
1241
1242 drawLineForBoxSide(graphicsContext, sideRect.x(), sideRect.y(), sideRect.maxX(), sideRect.maxY(), side, colorToPaint, edgeToRender.style,
1243 mitreAdjacentSide1 ? adjacentEdge1.width : 0, mitreAdjacentSide2 ? adjacentEdge2.width : 0, antialias);
1244
1245 if (didClip)
1246 graphicsContext->restore();
1247 }
1248 }
1249
paintBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],BorderEdgeFlags edgeSet,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1250 void RenderBoxModelObject::paintBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1251 const BorderEdge edges[], BorderEdgeFlags edgeSet, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1252 {
1253 bool renderRadii = outerBorder.isRounded();
1254
1255 Path roundedPath;
1256 if (renderRadii)
1257 roundedPath.addRoundedRect(outerBorder);
1258
1259 if (edges[BSTop].shouldRender() && includesEdge(edgeSet, BSTop)) {
1260 IntRect sideRect = outerBorder.rect();
1261 sideRect.setHeight(edges[BSTop].width);
1262
1263 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSTop].style) || borderWillArcInnerEdge(innerBorder.radii().topLeft(), innerBorder.radii().topRight()));
1264 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSTop, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1265 }
1266
1267 if (edges[BSBottom].shouldRender() && includesEdge(edgeSet, BSBottom)) {
1268 IntRect sideRect = outerBorder.rect();
1269 sideRect.shiftYEdgeTo(sideRect.maxY() - edges[BSBottom].width);
1270
1271 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSBottom].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().bottomRight()));
1272 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSBottom, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1273 }
1274
1275 if (edges[BSLeft].shouldRender() && includesEdge(edgeSet, BSLeft)) {
1276 IntRect sideRect = outerBorder.rect();
1277 sideRect.setWidth(edges[BSLeft].width);
1278
1279 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSLeft].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().topLeft()));
1280 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSLeft, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1281 }
1282
1283 if (edges[BSRight].shouldRender() && includesEdge(edgeSet, BSRight)) {
1284 IntRect sideRect = outerBorder.rect();
1285 sideRect.shiftXEdgeTo(sideRect.maxX() - edges[BSRight].width);
1286
1287 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSRight].style) || borderWillArcInnerEdge(innerBorder.radii().bottomRight(), innerBorder.radii().topRight()));
1288 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSRight, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1289 }
1290 }
1291
paintTranslucentBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias)1292 void RenderBoxModelObject::paintTranslucentBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1293 const BorderEdge edges[], bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias)
1294 {
1295 BorderEdgeFlags edgesToDraw = AllBorderEdges;
1296 while (edgesToDraw) {
1297 // Find undrawn edges sharing a color.
1298 Color commonColor;
1299
1300 BorderEdgeFlags commonColorEdgeSet = 0;
1301 for (int i = BSTop; i <= BSLeft; ++i) {
1302 BoxSide currSide = static_cast<BoxSide>(i);
1303 if (!includesEdge(edgesToDraw, currSide))
1304 continue;
1305
1306 bool includeEdge;
1307 if (!commonColorEdgeSet) {
1308 commonColor = edges[currSide].color;
1309 includeEdge = true;
1310 } else
1311 includeEdge = edges[currSide].color == commonColor;
1312
1313 if (includeEdge)
1314 commonColorEdgeSet |= edgeFlagForSide(currSide);
1315 }
1316
1317 bool useTransparencyLayer = commonColor.hasAlpha();
1318 if (useTransparencyLayer) {
1319 graphicsContext->beginTransparencyLayer(static_cast<float>(commonColor.alpha()) / 255);
1320 commonColor = Color(commonColor.red(), commonColor.green(), commonColor.blue());
1321 }
1322
1323 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, commonColorEdgeSet, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, &commonColor);
1324
1325 if (useTransparencyLayer)
1326 graphicsContext->endTransparencyLayer();
1327
1328 edgesToDraw &= ~commonColorEdgeSet;
1329 }
1330 }
1331
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1332 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1333 const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1334 {
1335 if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1336 return;
1337
1338 if (graphicsContext->paintingDisabled())
1339 return;
1340
1341 bool horizontal = style->isHorizontalWritingMode();
1342
1343 BorderEdge edges[4] = {
1344 // BSTop
1345 BorderEdge(style->borderTopWidth(),
1346 style->visitedDependentColor(CSSPropertyBorderTopColor),
1347 style->borderTopStyle(),
1348 style->borderTopIsTransparent(),
1349 horizontal || includeLogicalLeftEdge),
1350 // BSRight
1351 BorderEdge(style->borderRightWidth(),
1352 style->visitedDependentColor(CSSPropertyBorderRightColor),
1353 style->borderRightStyle(),
1354 style->borderRightIsTransparent(),
1355 !horizontal || includeLogicalRightEdge),
1356 // BSBottom
1357 BorderEdge(style->borderBottomWidth(),
1358 style->visitedDependentColor(CSSPropertyBorderBottomColor),
1359 style->borderBottomStyle(),
1360 style->borderBottomIsTransparent(),
1361 horizontal || includeLogicalRightEdge),
1362 // BSLeft
1363 BorderEdge(style->borderLeftWidth(),
1364 style->visitedDependentColor(CSSPropertyBorderLeftColor),
1365 style->borderLeftStyle(),
1366 style->borderLeftIsTransparent(),
1367 !horizontal || includeLogicalLeftEdge)
1368 };
1369
1370 IntRect borderRect(tx, ty, w, h);
1371 RoundedIntRect outerBorder = style->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1372 RoundedIntRect innerBorder = style->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1373
1374 const AffineTransform& currentCTM = graphicsContext->getCTM();
1375 // FIXME: this isn't quite correct. We may want to antialias when scaled by a non-integral value, or when the translation is non-integral.
1376 bool antialias = !currentCTM.isIdentityOrTranslationOrFlipped();
1377
1378 bool haveAlphaColor = false;
1379 bool haveAllSolidEdges = true;
1380 bool allEdgesVisible = true;
1381 bool allEdgesShareColor = true;
1382 int firstVisibleEdge = -1;
1383
1384 for (int i = BSTop; i <= BSLeft; ++i) {
1385 const BorderEdge& currEdge = edges[i];
1386 if (currEdge.presentButInvisible()) {
1387 allEdgesVisible = false;
1388 continue;
1389 }
1390
1391 if (!currEdge.width)
1392 continue;
1393
1394 if (firstVisibleEdge == -1)
1395 firstVisibleEdge = i;
1396 else if (currEdge.color != edges[firstVisibleEdge].color)
1397 allEdgesShareColor = false;
1398
1399 if (currEdge.color.hasAlpha())
1400 haveAlphaColor = true;
1401
1402 if (currEdge.style != SOLID)
1403 haveAllSolidEdges = false;
1404 }
1405
1406 // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787
1407 if (haveAllSolidEdges && allEdgesVisible && allEdgesShareColor && innerBorder.isRenderable()) {
1408 // Fast path for drawing all solid edges.
1409 if (outerBorder.isRounded() || haveAlphaColor) {
1410 Path path;
1411
1412 // FIXME: Path should take a RoundedIntRect directly.
1413 if (outerBorder.isRounded())
1414 path.addRoundedRect(outerBorder);
1415 else
1416 path.addRect(outerBorder.rect());
1417
1418 if (innerBorder.isRounded())
1419 path.addRoundedRect(innerBorder);
1420 else
1421 path.addRect(innerBorder.rect());
1422
1423 graphicsContext->setFillRule(RULE_EVENODD);
1424 graphicsContext->setFillColor(edges[firstVisibleEdge].color, style->colorSpace());
1425 graphicsContext->fillPath(path);
1426 } else
1427 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1428
1429 return;
1430 }
1431
1432 if (outerBorder.isRounded()) {
1433 // Clip to the inner and outer radii rects.
1434 graphicsContext->save();
1435 graphicsContext->addRoundedRectClip(outerBorder);
1436 graphicsContext->clipOutRoundedRect(innerBorder);
1437 }
1438
1439 if (haveAlphaColor)
1440 paintTranslucentBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1441 else
1442 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1443
1444 if (outerBorder.isRounded())
1445 graphicsContext->restore();
1446 }
1447
drawBoxSideFromPath(GraphicsContext * graphicsContext,const IntRect & borderRect,const Path & borderPath,const BorderEdge edges[],float thickness,float drawThickness,BoxSide side,const RenderStyle * style,Color color,EBorderStyle borderStyle,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1448 void RenderBoxModelObject::drawBoxSideFromPath(GraphicsContext* graphicsContext, const IntRect& borderRect, const Path& borderPath, const BorderEdge edges[],
1449 float thickness, float drawThickness, BoxSide side, const RenderStyle* style,
1450 Color color, EBorderStyle borderStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1451 {
1452 if (thickness <= 0)
1453 return;
1454
1455 if (borderStyle == DOUBLE && thickness < 3)
1456 borderStyle = SOLID;
1457
1458 switch (borderStyle) {
1459 case BNONE:
1460 case BHIDDEN:
1461 return;
1462 case DOTTED:
1463 case DASHED: {
1464 graphicsContext->setStrokeColor(color, style->colorSpace());
1465
1466 // The stroke is doubled here because the provided path is the
1467 // outside edge of the border so half the stroke is clipped off.
1468 // The extra multiplier is so that the clipping mask can antialias
1469 // the edges to prevent jaggies.
1470 graphicsContext->setStrokeThickness(drawThickness * 2 * 1.1f);
1471 graphicsContext->setStrokeStyle(borderStyle == DASHED ? DashedStroke : DottedStroke);
1472
1473 // If the number of dashes that fit in the path is odd and non-integral then we
1474 // will have an awkwardly-sized dash at the end of the path. To try to avoid that
1475 // here, we simply make the whitespace dashes ever so slightly bigger.
1476 // FIXME: This could be even better if we tried to manipulate the dash offset
1477 // and possibly the gapLength to get the corners dash-symmetrical.
1478 float dashLength = thickness * ((borderStyle == DASHED) ? 3.0f : 1.0f);
1479 float gapLength = dashLength;
1480 float numberOfDashes = borderPath.length() / dashLength;
1481 // Don't try to show dashes if we have less than 2 dashes + 2 gaps.
1482 // FIXME: should do this test per side.
1483 if (numberOfDashes >= 4) {
1484 bool evenNumberOfFullDashes = !((int)numberOfDashes % 2);
1485 bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes);
1486 if (!evenNumberOfFullDashes && !integralNumberOfDashes) {
1487 float numberOfGaps = numberOfDashes / 2;
1488 gapLength += (dashLength / numberOfGaps);
1489 }
1490
1491 DashArray lineDash;
1492 lineDash.append(dashLength);
1493 lineDash.append(gapLength);
1494 graphicsContext->setLineDash(lineDash, dashLength);
1495 }
1496
1497 // FIXME: stroking the border path causes issues with tight corners:
1498 // https://bugs.webkit.org/show_bug.cgi?id=58711
1499 // Also, to get the best appearance we should stroke a path between the two borders.
1500 graphicsContext->strokePath(borderPath);
1501 return;
1502 }
1503 case DOUBLE: {
1504 // Get the inner border rects for both the outer border line and the inner border line
1505 int outerBorderTopWidth;
1506 int innerBorderTopWidth;
1507 edges[BSTop].getDoubleBorderStripeWidths(outerBorderTopWidth, innerBorderTopWidth);
1508
1509 int outerBorderRightWidth;
1510 int innerBorderRightWidth;
1511 edges[BSRight].getDoubleBorderStripeWidths(outerBorderRightWidth, innerBorderRightWidth);
1512
1513 int outerBorderBottomWidth;
1514 int innerBorderBottomWidth;
1515 edges[BSBottom].getDoubleBorderStripeWidths(outerBorderBottomWidth, innerBorderBottomWidth);
1516
1517 int outerBorderLeftWidth;
1518 int innerBorderLeftWidth;
1519 edges[BSLeft].getDoubleBorderStripeWidths(outerBorderLeftWidth, innerBorderLeftWidth);
1520
1521 // Draw inner border line
1522 graphicsContext->save();
1523
1524 RoundedIntRect innerClip = style->getRoundedInnerBorderFor(borderRect,
1525 innerBorderTopWidth, innerBorderBottomWidth, innerBorderLeftWidth, innerBorderRightWidth,
1526 includeLogicalLeftEdge, includeLogicalRightEdge);
1527
1528 graphicsContext->addRoundedRectClip(innerClip);
1529 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, includeLogicalLeftEdge, includeLogicalRightEdge);
1530 graphicsContext->restore();
1531
1532 // Draw outer border line
1533 graphicsContext->save();
1534
1535 RoundedIntRect outerClip = style->getRoundedInnerBorderFor(borderRect,
1536 outerBorderTopWidth, outerBorderBottomWidth, outerBorderLeftWidth, outerBorderRightWidth,
1537 includeLogicalLeftEdge, includeLogicalRightEdge);
1538
1539 graphicsContext->clipOutRoundedRect(outerClip);
1540 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, includeLogicalLeftEdge, includeLogicalRightEdge);
1541 graphicsContext->restore();
1542 return;
1543 }
1544 case RIDGE:
1545 case GROOVE:
1546 {
1547 EBorderStyle s1;
1548 EBorderStyle s2;
1549 if (borderStyle == GROOVE) {
1550 s1 = INSET;
1551 s2 = OUTSET;
1552 } else {
1553 s1 = OUTSET;
1554 s2 = INSET;
1555 }
1556
1557 // Paint full border
1558 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s1, includeLogicalLeftEdge, includeLogicalRightEdge);
1559
1560 // Paint inner only
1561 graphicsContext->save();
1562
1563 int topWidth = edges[BSTop].usedWidth() / 2;
1564 int bottomWidth = edges[BSBottom].usedWidth() / 2;
1565 int leftWidth = edges[BSLeft].usedWidth() / 2;
1566 int rightWidth = edges[BSRight].usedWidth() / 2;
1567
1568 RoundedIntRect clipRect = style->getRoundedInnerBorderFor(borderRect,
1569 topWidth, bottomWidth, leftWidth, rightWidth,
1570 includeLogicalLeftEdge, includeLogicalRightEdge);
1571
1572 graphicsContext->addRoundedRectClip(clipRect);
1573 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s2, includeLogicalLeftEdge, includeLogicalRightEdge);
1574 graphicsContext->restore();
1575 return;
1576 }
1577 case INSET:
1578 if (side == BSTop || side == BSLeft)
1579 color = color.dark();
1580 break;
1581 case OUTSET:
1582 if (side == BSBottom || side == BSRight)
1583 color = color.dark();
1584 break;
1585 default:
1586 break;
1587 }
1588
1589 graphicsContext->setStrokeStyle(NoStroke);
1590 graphicsContext->setFillColor(color, style->colorSpace());
1591 graphicsContext->drawRect(borderRect);
1592 }
1593 #else
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1594 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1595 const RenderStyle* style, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1596 {
1597 // FIXME: This old version of paintBorder should be removed when all ports implement
1598 // GraphicsContext::clipConvexPolygon()!! This should happen soon.
1599 if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1600 return;
1601
1602 const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor);
1603 const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor);
1604 const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor);
1605 const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor);
1606
1607 bool topTransparent = style->borderTopIsTransparent();
1608 bool bottomTransparent = style->borderBottomIsTransparent();
1609 bool rightTransparent = style->borderRightIsTransparent();
1610 bool leftTransparent = style->borderLeftIsTransparent();
1611
1612 EBorderStyle topStyle = style->borderTopStyle();
1613 EBorderStyle bottomStyle = style->borderBottomStyle();
1614 EBorderStyle leftStyle = style->borderLeftStyle();
1615 EBorderStyle rightStyle = style->borderRightStyle();
1616
1617 bool horizontal = style->isHorizontalWritingMode();
1618 bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge);
1619 bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge);
1620 bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge);
1621 bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge);
1622
1623
1624 RoundedIntRect border(tx, ty, w, h);
1625 if (style->hasBorderRadius()) {
1626 border.includeLogicalEdges(style->getRoundedBorderFor(border.rect()).radii(),
1627 horizontal, includeLogicalLeftEdge, includeLogicalRightEdge);
1628 if (border.isRounded()) {
1629 graphicsContext->save();
1630 graphicsContext->addRoundedRectClip(border);
1631 }
1632 }
1633
1634 int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan;
1635 float thickness;
1636 bool renderRadii = border.isRounded();
1637 bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor);
1638 bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE);
1639 bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE);
1640 bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor);
1641
1642 if (renderTop) {
1643 bool ignoreLeft = (renderRadii && border.radii().topLeft().width() > 0)
1644 || (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET
1645 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1646
1647 bool ignoreRight = (renderRadii && border.radii().topRight().width() > 0)
1648 || (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET
1649 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1650
1651 int x = tx;
1652 int x2 = tx + w;
1653 if (renderRadii) {
1654 x += border.radii().topLeft().width();
1655 x2 -= border.radii().topRight().width();
1656 }
1657
1658 drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle,
1659 ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1660
1661 if (renderRadii) {
1662 int leftY = ty;
1663
1664 // We make the arc double thick and let the clip rect take care of clipping the extra off.
1665 // We're doing this because it doesn't seem possible to match the curve of the clip exactly
1666 // with the arc-drawing function.
1667 thickness = style->borderTopWidth() * 2;
1668
1669 if (border.radii().topLeft().width()) {
1670 int leftX = tx;
1671 // The inner clip clips inside the arc. This is especially important for 1px borders.
1672 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1673 && (style->borderTopWidth() < border.radii().topLeft().height())
1674 && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1675 if (applyLeftInnerClip) {
1676 graphicsContext->save();
1677 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1678 style->borderTopWidth());
1679 }
1680
1681 firstAngleStart = 90;
1682 firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45;
1683
1684 // Draw upper left arc
1685 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1686 BSTop, topColor, topStyle, true);
1687 if (applyLeftInnerClip)
1688 graphicsContext->restore();
1689 }
1690
1691 if (border.radii().topRight().width()) {
1692 int rightX = tx + w - border.radii().topRight().width() * 2;
1693 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1694 && (style->borderTopWidth() < border.radii().topRight().height())
1695 && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1696 if (applyRightInnerClip) {
1697 graphicsContext->save();
1698 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1699 style->borderTopWidth());
1700 }
1701
1702 if (upperRightBorderStylesMatch) {
1703 secondAngleStart = 0;
1704 secondAngleSpan = 90;
1705 } else {
1706 secondAngleStart = 45;
1707 secondAngleSpan = 45;
1708 }
1709
1710 // Draw upper right arc
1711 drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, border.radii().topRight(), secondAngleStart, secondAngleSpan,
1712 BSTop, topColor, topStyle, false);
1713 if (applyRightInnerClip)
1714 graphicsContext->restore();
1715 }
1716 }
1717 }
1718
1719 if (renderBottom) {
1720 bool ignoreLeft = (renderRadii && border.radii().bottomLeft().width() > 0)
1721 || (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET
1722 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1723
1724 bool ignoreRight = (renderRadii && border.radii().bottomRight().width() > 0)
1725 || (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET
1726 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1727
1728 int x = tx;
1729 int x2 = tx + w;
1730 if (renderRadii) {
1731 x += border.radii().bottomLeft().width();
1732 x2 -= border.radii().bottomRight().width();
1733 }
1734
1735 drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, bottomStyle,
1736 ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1737
1738 if (renderRadii) {
1739 thickness = style->borderBottomWidth() * 2;
1740
1741 if (border.radii().bottomLeft().width()) {
1742 int leftX = tx;
1743 int leftY = ty + h - border.radii().bottomLeft().height() * 2;
1744 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1745 && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1746 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1747 if (applyLeftInnerClip) {
1748 graphicsContext->save();
1749 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1750 style->borderBottomWidth());
1751 }
1752
1753 if (lowerLeftBorderStylesMatch) {
1754 firstAngleStart = 180;
1755 firstAngleSpan = 90;
1756 } else {
1757 firstAngleStart = 225;
1758 firstAngleSpan = 45;
1759 }
1760
1761 // Draw lower left arc
1762 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().bottomLeft(), firstAngleStart, firstAngleSpan,
1763 BSBottom, bottomColor, bottomStyle, true);
1764 if (applyLeftInnerClip)
1765 graphicsContext->restore();
1766 }
1767
1768 if (border.radii().bottomRight().width()) {
1769 int rightY = ty + h - border.radii().bottomRight().height() * 2;
1770 int rightX = tx + w - border.radii().bottomRight().width() * 2;
1771 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1772 && (style->borderBottomWidth() < border.radii().bottomRight().height())
1773 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1774 if (applyRightInnerClip) {
1775 graphicsContext->save();
1776 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1777 style->borderBottomWidth());
1778 }
1779
1780 secondAngleStart = 270;
1781 secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45;
1782
1783 // Draw lower right arc
1784 drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1785 BSBottom, bottomColor, bottomStyle, false);
1786 if (applyRightInnerClip)
1787 graphicsContext->restore();
1788 }
1789 }
1790 }
1791
1792 if (renderLeft) {
1793 bool ignoreTop = (renderRadii && border.radii().topLeft().height() > 0)
1794 || (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET
1795 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1796
1797 bool ignoreBottom = (renderRadii && border.radii().bottomLeft().height() > 0)
1798 || (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET
1799 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1800
1801 int y = ty;
1802 int y2 = ty + h;
1803 if (renderRadii) {
1804 y += border.radii().topLeft().height();
1805 y2 -= border.radii().bottomLeft().height();
1806 }
1807
1808 drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, leftStyle,
1809 ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1810
1811 if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) {
1812 int topX = tx;
1813 thickness = style->borderLeftWidth() * 2;
1814
1815 if (!upperLeftBorderStylesMatch && border.radii().topLeft().width()) {
1816 int topY = ty;
1817 bool applyTopInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1818 && (style->borderTopWidth() < border.radii().topLeft().height())
1819 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1820 if (applyTopInnerClip) {
1821 graphicsContext->save();
1822 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1823 style->borderLeftWidth());
1824 }
1825
1826 firstAngleStart = 135;
1827 firstAngleSpan = 45;
1828
1829 // Draw top left arc
1830 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1831 BSLeft, leftColor, leftStyle, true);
1832 if (applyTopInnerClip)
1833 graphicsContext->restore();
1834 }
1835
1836 if (!lowerLeftBorderStylesMatch && border.radii().bottomLeft().width()) {
1837 int bottomY = ty + h - border.radii().bottomLeft().height() * 2;
1838 bool applyBottomInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1839 && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1840 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1841 if (applyBottomInnerClip) {
1842 graphicsContext->save();
1843 graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1844 style->borderLeftWidth());
1845 }
1846
1847 secondAngleStart = 180;
1848 secondAngleSpan = 45;
1849
1850 // Draw bottom left arc
1851 drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, border.radii().bottomLeft(), secondAngleStart, secondAngleSpan,
1852 BSLeft, leftColor, leftStyle, false);
1853 if (applyBottomInnerClip)
1854 graphicsContext->restore();
1855 }
1856 }
1857 }
1858
1859 if (renderRight) {
1860 bool ignoreTop = (renderRadii && border.radii().topRight().height() > 0)
1861 || ((topColor == rightColor) && (topTransparent == rightTransparent)
1862 && (rightStyle >= DOTTED || rightStyle == INSET)
1863 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1864
1865 bool ignoreBottom = (renderRadii && border.radii().bottomRight().height() > 0)
1866 || ((bottomColor == rightColor) && (bottomTransparent == rightTransparent)
1867 && (rightStyle >= DOTTED || rightStyle == INSET)
1868 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1869
1870 int y = ty;
1871 int y2 = ty + h;
1872 if (renderRadii) {
1873 y += border.radii().topRight().height();
1874 y2 -= border.radii().bottomRight().height();
1875 }
1876
1877 drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, rightStyle,
1878 ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1879
1880 if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) {
1881 thickness = style->borderRightWidth() * 2;
1882
1883 if (!upperRightBorderStylesMatch && border.radii().topRight().width()) {
1884 int topX = tx + w - border.radii().topRight().width() * 2;
1885 int topY = ty;
1886 bool applyTopInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1887 && (style->borderTopWidth() < border.radii().topRight().height())
1888 && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1889 if (applyTopInnerClip) {
1890 graphicsContext->save();
1891 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1892 style->borderRightWidth());
1893 }
1894
1895 firstAngleStart = 0;
1896 firstAngleSpan = 45;
1897
1898 // Draw top right arc
1899 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topRight(), firstAngleStart, firstAngleSpan,
1900 BSRight, rightColor, rightStyle, true);
1901 if (applyTopInnerClip)
1902 graphicsContext->restore();
1903 }
1904
1905 if (!lowerRightBorderStylesMatch && border.radii().bottomRight().width()) {
1906 int bottomX = tx + w - border.radii().bottomRight().width() * 2;
1907 int bottomY = ty + h - border.radii().bottomRight().height() * 2;
1908 bool applyBottomInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1909 && (style->borderBottomWidth() < border.radii().bottomRight().height())
1910 && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1911 if (applyBottomInnerClip) {
1912 graphicsContext->save();
1913 graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1914 style->borderRightWidth());
1915 }
1916
1917 secondAngleStart = 315;
1918 secondAngleSpan = 45;
1919
1920 // Draw bottom right arc
1921 drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1922 BSRight, rightColor, rightStyle, false);
1923 if (applyBottomInnerClip)
1924 graphicsContext->restore();
1925 }
1926 }
1927 }
1928
1929 if (renderRadii)
1930 graphicsContext->restore();
1931 }
1932 #endif
1933
findInnerVertex(const FloatPoint & outerCorner,const FloatPoint & innerCorner,const FloatPoint & centerPoint,FloatPoint & result)1934 static void findInnerVertex(const FloatPoint& outerCorner, const FloatPoint& innerCorner, const FloatPoint& centerPoint, FloatPoint& result)
1935 {
1936 // If the line between outer and inner corner is towards the horizontal, intersect with a vertical line through the center,
1937 // otherwise with a horizontal line through the center. The points that form this line are arbitrary (we use 0, 100).
1938 // Note that if findIntersection fails, it will leave result untouched.
1939 if (fabs(outerCorner.x() - innerCorner.x()) > fabs(outerCorner.y() - innerCorner.y()))
1940 findIntersection(outerCorner, innerCorner, FloatPoint(centerPoint.x(), 0), FloatPoint(centerPoint.x(), 100), result);
1941 else
1942 findIntersection(outerCorner, innerCorner, FloatPoint(0, centerPoint.y()), FloatPoint(100, centerPoint.y()), result);
1943 }
1944
clipBorderSidePolygon(GraphicsContext * graphicsContext,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,BoxSide side,bool firstEdgeMatches,bool secondEdgeMatches)1945 void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1946 BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches)
1947 {
1948 FloatPoint quad[4];
1949
1950 const IntRect& outerRect = outerBorder.rect();
1951 const IntRect& innerRect = innerBorder.rect();
1952
1953 FloatPoint centerPoint(innerRect.location().x() + static_cast<float>(innerRect.width()) / 2, innerRect.location().y() + static_cast<float>(innerRect.height()) / 2);
1954
1955 // For each side, create a quad that encompasses all parts of that side that may draw,
1956 // including areas inside the innerBorder.
1957 //
1958 // 0----------------3
1959 // 0 \ / 0
1960 // |\ 1----------- 2 /|
1961 // | 1 1 |
1962 // | | | |
1963 // | | | |
1964 // | 2 2 |
1965 // |/ 1------------2 \|
1966 // 3 / \ 3
1967 // 0----------------3
1968 //
1969 switch (side) {
1970 case BSTop:
1971 quad[0] = outerRect.minXMinYCorner();
1972 quad[1] = innerRect.minXMinYCorner();
1973 quad[2] = innerRect.maxXMinYCorner();
1974 quad[3] = outerRect.maxXMinYCorner();
1975
1976 if (!innerBorder.radii().topLeft().isZero())
1977 findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1978
1979 if (!innerBorder.radii().topRight().isZero())
1980 findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[2]);
1981 break;
1982
1983 case BSLeft:
1984 quad[0] = outerRect.minXMinYCorner();
1985 quad[1] = innerRect.minXMinYCorner();
1986 quad[2] = innerRect.minXMaxYCorner();
1987 quad[3] = outerRect.minXMaxYCorner();
1988
1989 if (!innerBorder.radii().topLeft().isZero())
1990 findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1991
1992 if (!innerBorder.radii().bottomLeft().isZero())
1993 findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[2]);
1994 break;
1995
1996 case BSBottom:
1997 quad[0] = outerRect.minXMaxYCorner();
1998 quad[1] = innerRect.minXMaxYCorner();
1999 quad[2] = innerRect.maxXMaxYCorner();
2000 quad[3] = outerRect.maxXMaxYCorner();
2001
2002 if (!innerBorder.radii().bottomLeft().isZero())
2003 findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[1]);
2004
2005 if (!innerBorder.radii().bottomRight().isZero())
2006 findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
2007 break;
2008
2009 case BSRight:
2010 quad[0] = outerRect.maxXMinYCorner();
2011 quad[1] = innerRect.maxXMinYCorner();
2012 quad[2] = innerRect.maxXMaxYCorner();
2013 quad[3] = outerRect.maxXMaxYCorner();
2014
2015 if (!innerBorder.radii().topRight().isZero())
2016 findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[1]);
2017
2018 if (!innerBorder.radii().bottomRight().isZero())
2019 findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
2020 break;
2021 }
2022
2023 // If the border matches both of its adjacent sides, don't anti-alias the clip, and
2024 // if neither side matches, anti-alias the clip.
2025 if (firstEdgeMatches == secondEdgeMatches) {
2026 graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches);
2027 return;
2028 }
2029
2030 // Square off the end which shouldn't be affected by antialiasing, and clip.
2031 FloatPoint firstQuad[4];
2032 firstQuad[0] = quad[0];
2033 firstQuad[1] = quad[1];
2034 firstQuad[2] = side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y())
2035 : FloatPoint(quad[2].x(), quad[3].y());
2036 firstQuad[3] = quad[3];
2037 graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches);
2038
2039 FloatPoint secondQuad[4];
2040 secondQuad[0] = quad[0];
2041 secondQuad[1] = side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y())
2042 : FloatPoint(quad[1].x(), quad[0].y());
2043 secondQuad[2] = quad[2];
2044 secondQuad[3] = quad[3];
2045 // Antialiasing affects the second side.
2046 graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches);
2047 }
2048
areaCastingShadowInHole(const IntRect & holeRect,int shadowBlur,int shadowSpread,const IntSize & shadowOffset)2049 static inline IntRect areaCastingShadowInHole(const IntRect& holeRect, int shadowBlur, int shadowSpread, const IntSize& shadowOffset)
2050 {
2051 IntRect bounds(holeRect);
2052
2053 bounds.inflate(shadowBlur);
2054
2055 if (shadowSpread < 0)
2056 bounds.inflate(-shadowSpread);
2057
2058 IntRect offsetBounds = bounds;
2059 offsetBounds.move(-shadowOffset);
2060 return unionRect(bounds, offsetBounds);
2061 }
2062
paintBoxShadow(GraphicsContext * context,int tx,int ty,int w,int h,const RenderStyle * s,ShadowStyle shadowStyle,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)2063 void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
2064 {
2065 // FIXME: Deal with border-image. Would be great to use border-image as a mask.
2066
2067 if (context->paintingDisabled() || !s->boxShadow())
2068 return;
2069
2070 IntRect borderRect(tx, ty, w, h);
2071 RoundedIntRect border = (shadowStyle == Inset) ? s->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge)
2072 : s->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
2073
2074 bool hasBorderRadius = s->hasBorderRadius();
2075 bool isHorizontal = s->isHorizontalWritingMode();
2076
2077 bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255;
2078 for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) {
2079 if (shadow->style() != shadowStyle)
2080 continue;
2081
2082 IntSize shadowOffset(shadow->x(), shadow->y());
2083 int shadowBlur = shadow->blur();
2084 int shadowSpread = shadow->spread();
2085 const Color& shadowColor = shadow->color();
2086
2087 if (shadow->style() == Normal) {
2088 RoundedIntRect fillRect = border;
2089 fillRect.inflate(shadowSpread);
2090 if (fillRect.isEmpty())
2091 continue;
2092
2093 IntRect shadowRect(border.rect());
2094 shadowRect.inflate(shadowBlur + shadowSpread);
2095 shadowRect.move(shadowOffset);
2096
2097 context->save();
2098 context->clip(shadowRect);
2099
2100 // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not
2101 // bleed in (due to antialiasing) if the context is transformed.
2102 IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0);
2103 shadowOffset -= extraOffset;
2104 fillRect.move(extraOffset);
2105
2106 if (shadow->isWebkitBoxShadow())
2107 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2108 else
2109 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2110
2111 if (hasBorderRadius) {
2112 RoundedIntRect rectToClipOut = border;
2113
2114 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2115 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2116 // corners. Those are avoided by insetting the clipping path by one pixel.
2117 if (hasOpaqueBackground) {
2118 rectToClipOut.inflateWithRadii(-1);
2119 }
2120
2121 if (!rectToClipOut.isEmpty())
2122 context->clipOutRoundedRect(rectToClipOut);
2123
2124 fillRect.expandRadii(shadowSpread);
2125 context->fillRoundedRect(fillRect, Color::black, s->colorSpace());
2126 } else {
2127 IntRect rectToClipOut = border.rect();
2128
2129 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2130 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2131 // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path
2132 // by one pixel.
2133 if (hasOpaqueBackground) {
2134 AffineTransform currentTransformation = context->getCTM();
2135 if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1)
2136 || currentTransformation.b() || currentTransformation.c())
2137 rectToClipOut.inflate(-1);
2138 }
2139
2140 if (!rectToClipOut.isEmpty())
2141 context->clipOut(rectToClipOut);
2142 context->fillRect(fillRect.rect(), Color::black, s->colorSpace());
2143 }
2144
2145 context->restore();
2146 } else {
2147 // Inset shadow.
2148 IntRect holeRect(border.rect());
2149 holeRect.inflate(-shadowSpread);
2150
2151 if (holeRect.isEmpty()) {
2152 if (hasBorderRadius)
2153 context->fillRoundedRect(border, shadowColor, s->colorSpace());
2154 else
2155 context->fillRect(border.rect(), shadowColor, s->colorSpace());
2156 continue;
2157 }
2158
2159 if (!includeLogicalLeftEdge) {
2160 if (isHorizontal) {
2161 holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0);
2162 holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur);
2163 } else {
2164 holeRect.move(0, -max(shadowOffset.height(), 0) - shadowBlur);
2165 holeRect.setHeight(holeRect.height() + max(shadowOffset.height(), 0) + shadowBlur);
2166 }
2167 }
2168 if (!includeLogicalRightEdge) {
2169 if (isHorizontal)
2170 holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur);
2171 else
2172 holeRect.setHeight(holeRect.height() - min(shadowOffset.height(), 0) + shadowBlur);
2173 }
2174
2175 Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255);
2176
2177 IntRect outerRect = areaCastingShadowInHole(border.rect(), shadowBlur, shadowSpread, shadowOffset);
2178 RoundedIntRect roundedHole(holeRect, border.radii());
2179
2180 context->save();
2181
2182 if (hasBorderRadius) {
2183 Path path;
2184 path.addRoundedRect(border);
2185 context->clip(path);
2186 roundedHole.shrinkRadii(shadowSpread);
2187 } else
2188 context->clip(border.rect());
2189
2190 IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0);
2191 context->translate(extraOffset.width(), extraOffset.height());
2192 shadowOffset -= extraOffset;
2193
2194 if (shadow->isWebkitBoxShadow())
2195 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2196 else
2197 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2198
2199 context->fillRectWithRoundedHole(outerRect, roundedHole, fillColor, s->colorSpace());
2200
2201 context->restore();
2202 }
2203 }
2204 }
2205
containingBlockLogicalWidthForContent() const2206 int RenderBoxModelObject::containingBlockLogicalWidthForContent() const
2207 {
2208 return containingBlock()->availableLogicalWidth();
2209 }
2210
continuation() const2211 RenderBoxModelObject* RenderBoxModelObject::continuation() const
2212 {
2213 if (!continuationMap)
2214 return 0;
2215 return continuationMap->get(this);
2216 }
2217
setContinuation(RenderBoxModelObject * continuation)2218 void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation)
2219 {
2220 if (continuation) {
2221 if (!continuationMap)
2222 continuationMap = new ContinuationMap;
2223 continuationMap->set(this, continuation);
2224 } else {
2225 if (continuationMap)
2226 continuationMap->remove(this);
2227 }
2228 }
2229
2230 } // namespace WebCore
2231