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
backgroundRectAdjustedForBleedAvoidance(GraphicsContext * context,const IntRect & borderRect,BackgroundBleedAvoidance bleedAvoidance)574 static IntRect backgroundRectAdjustedForBleedAvoidance(GraphicsContext* context, const IntRect& borderRect, BackgroundBleedAvoidance bleedAvoidance)
575 {
576 if (bleedAvoidance != BackgroundBleedShrinkBackground)
577 return borderRect;
578
579 IntRect adjustedRect = borderRect;
580 // We need to shrink the border by one device pixel on each side.
581 AffineTransform ctm = context->getCTM();
582 FloatSize contextScale(static_cast<float>(ctm.xScale()), static_cast<float>(ctm.yScale()));
583 adjustedRect.inflateX(-ceilf(1 / contextScale.width()));
584 adjustedRect.inflateY(-ceilf(1 / contextScale.height()));
585 return adjustedRect;
586 }
587
paintFillLayerExtended(const PaintInfo & paintInfo,const Color & color,const FillLayer * bgLayer,int tx,int ty,int w,int h,BackgroundBleedAvoidance bleedAvoidance,InlineFlowBox * box,int inlineBoxWidth,int inlineBoxHeight,CompositeOperator op,RenderObject * backgroundObject)588 void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, const Color& color, const FillLayer* bgLayer, int tx, int ty, int w, int h,
589 BackgroundBleedAvoidance bleedAvoidance, InlineFlowBox* box, int inlineBoxWidth, int inlineBoxHeight, CompositeOperator op, RenderObject* backgroundObject)
590 {
591 GraphicsContext* context = paintInfo.context;
592 if (context->paintingDisabled())
593 return;
594
595 IntRect borderRect(tx, ty, w, h);
596 if (borderRect.isEmpty())
597 return;
598
599 bool includeLeftEdge = box ? box->includeLogicalLeftEdge() : true;
600 bool includeRightEdge = box ? box->includeLogicalRightEdge() : true;
601
602 bool hasRoundedBorder = style()->hasBorderRadius() && (includeLeftEdge || includeRightEdge);
603 bool clippedWithLocalScrolling = hasOverflowClip() && bgLayer->attachment() == LocalBackgroundAttachment;
604 bool isBorderFill = bgLayer->clip() == BorderFillBox;
605 bool isRoot = this->isRoot();
606
607 Color bgColor = color;
608 StyleImage* bgImage = bgLayer->image();
609 bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(style()->effectiveZoom());
610
611 // When this style flag is set, change existing background colors and images to a solid white background.
612 // If there's no bg color or image, leave it untouched to avoid affecting transparency.
613 // We don't try to avoid loading the background images, because this style flag is only set
614 // when printing, and at that point we've already loaded the background images anyway. (To avoid
615 // loading the background images we'd have to do this check when applying styles rather than
616 // while rendering.)
617 if (style()->forceBackgroundsToWhite()) {
618 // Note that we can't reuse this variable below because the bgColor might be changed
619 bool shouldPaintBackgroundColor = !bgLayer->next() && bgColor.isValid() && bgColor.alpha() > 0;
620 if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
621 bgColor = Color::white;
622 shouldPaintBackgroundImage = false;
623 }
624 }
625
626 bool colorVisible = bgColor.isValid() && bgColor.alpha() > 0;
627
628 // Fast path for drawing simple color backgrounds.
629 if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill) {
630 if (!colorVisible)
631 return;
632
633 if (hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer) {
634 RoundedIntRect border = getBackgroundRoundedRect(backgroundRectAdjustedForBleedAvoidance(context, borderRect, bleedAvoidance), box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
635 context->fillRoundedRect(border, bgColor, style()->colorSpace());
636 } else
637 context->fillRect(borderRect, bgColor, style()->colorSpace());
638
639 return;
640 }
641
642 bool clipToBorderRadius = hasRoundedBorder && bleedAvoidance != BackgroundBleedUseTransparencyLayer;
643 GraphicsContextStateSaver clipToBorderStateSaver(*context, clipToBorderRadius);
644 if (clipToBorderRadius) {
645 RoundedIntRect border = getBackgroundRoundedRect(backgroundRectAdjustedForBleedAvoidance(context, borderRect, bleedAvoidance), box, inlineBoxWidth, inlineBoxHeight, includeLeftEdge, includeRightEdge);
646 context->addRoundedRectClip(border);
647 }
648
649 int bLeft = includeLeftEdge ? borderLeft() : 0;
650 int bRight = includeRightEdge ? borderRight() : 0;
651 int pLeft = includeLeftEdge ? paddingLeft() : 0;
652 int pRight = includeRightEdge ? paddingRight() : 0;
653
654 GraphicsContextStateSaver clipWithScrollingStateSaver(*context, clippedWithLocalScrolling);
655 if (clippedWithLocalScrolling) {
656 // Clip to the overflow area.
657 context->clip(toRenderBox(this)->overflowClipRect(tx, ty));
658
659 // Now adjust our tx, ty, w, h to reflect a scrolled content box with borders at the ends.
660 IntSize offset = layer()->scrolledContentOffset();
661 tx -= offset.width();
662 ty -= offset.height();
663 w = bLeft + layer()->scrollWidth() + bRight;
664 h = borderTop() + layer()->scrollHeight() + borderBottom();
665 }
666
667 GraphicsContextStateSaver backgroundClipStateSaver(*context, false);
668 if (bgLayer->clip() == PaddingFillBox || bgLayer->clip() == ContentFillBox) {
669 // Clip to the padding or content boxes as necessary.
670 bool includePadding = bgLayer->clip() == ContentFillBox;
671 int x = tx + bLeft + (includePadding ? pLeft : 0);
672 int y = ty + borderTop() + (includePadding ? paddingTop() : 0);
673 int width = w - bLeft - bRight - (includePadding ? pLeft + pRight : 0);
674 int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0);
675 backgroundClipStateSaver.save();
676 context->clip(IntRect(x, y, width, height));
677 } else if (bgLayer->clip() == TextFillBox) {
678 // We have to draw our text into a mask that can then be used to clip background drawing.
679 // First figure out how big the mask has to be. It should be no bigger than what we need
680 // to actually render, so we should intersect the dirty rect with the border box of the background.
681 IntRect maskRect(tx, ty, w, h);
682 maskRect.intersect(paintInfo.rect);
683
684 // Now create the mask.
685 OwnPtr<ImageBuffer> maskImage = ImageBuffer::create(maskRect.size());
686 if (!maskImage)
687 return;
688
689 GraphicsContext* maskImageContext = maskImage->context();
690 maskImageContext->translate(-maskRect.x(), -maskRect.y());
691
692 // Now add the text to the clip. We do this by painting using a special paint phase that signals to
693 // InlineTextBoxes that they should just add their contents to the clip.
694 PaintInfo info(maskImageContext, maskRect, PaintPhaseTextClip, true, 0, 0);
695 if (box) {
696 RootInlineBox* root = box->root();
697 box->paint(info, tx - box->x(), ty - box->y(), root->lineTop(), root->lineBottom());
698 } else {
699 int x = isBox() ? toRenderBox(this)->x() : 0;
700 int y = isBox() ? toRenderBox(this)->y() : 0;
701 paint(info, tx - x, ty - y);
702 }
703
704 // The mask has been created. Now we just need to clip to it.
705 backgroundClipStateSaver.save();
706 context->clipToImageBuffer(maskImage.get(), maskRect);
707 }
708
709 // Only fill with a base color (e.g., white) if we're the root document, since iframes/frames with
710 // no background in the child document should show the parent's background.
711 bool isOpaqueRoot = false;
712 if (isRoot) {
713 isOpaqueRoot = true;
714 if (!bgLayer->next() && !(bgColor.isValid() && bgColor.alpha() == 255) && view()->frameView()) {
715 Element* ownerElement = document()->ownerElement();
716 if (ownerElement) {
717 if (!ownerElement->hasTagName(frameTag)) {
718 // Locate the <body> element using the DOM. This is easier than trying
719 // to crawl around a render tree with potential :before/:after content and
720 // anonymous blocks created by inline <body> tags etc. We can locate the <body>
721 // render object very easily via the DOM.
722 HTMLElement* body = document()->body();
723 if (body) {
724 // Can't scroll a frameset document anyway.
725 isOpaqueRoot = body->hasLocalName(framesetTag);
726 }
727 #if ENABLE(SVG)
728 else {
729 // SVG documents and XML documents with SVG root nodes are transparent.
730 isOpaqueRoot = !document()->hasSVGRootNode();
731 }
732 #endif
733 }
734 } else
735 isOpaqueRoot = !view()->frameView()->isTransparent();
736 }
737 view()->frameView()->setContentIsOpaque(isOpaqueRoot);
738 }
739
740 // Paint the color first underneath all images.
741 if (!bgLayer->next()) {
742 IntRect rect(tx, ty, w, h);
743 rect.intersect(paintInfo.rect);
744 // If we have an alpha and we are painting the root element, go ahead and blend with the base background color.
745 if (isOpaqueRoot) {
746 Color baseColor = view()->frameView()->baseBackgroundColor();
747 if (baseColor.alpha() > 0) {
748 CompositeOperator previousOperator = context->compositeOperation();
749 context->setCompositeOperation(CompositeCopy);
750 context->fillRect(rect, baseColor, style()->colorSpace());
751 context->setCompositeOperation(previousOperator);
752 } else
753 context->clearRect(rect);
754 }
755
756 if (bgColor.isValid() && bgColor.alpha() > 0)
757 context->fillRect(rect, bgColor, style()->colorSpace());
758 }
759
760 // no progressive loading of the background image
761 if (shouldPaintBackgroundImage) {
762 IntRect destRect;
763 IntPoint phase;
764 IntSize tileSize;
765
766 calculateBackgroundImageGeometry(bgLayer, tx, ty, w, h, destRect, phase, tileSize);
767 IntPoint destOrigin = destRect.location();
768 destRect.intersect(paintInfo.rect);
769 if (!destRect.isEmpty()) {
770 phase += destRect.location() - destOrigin;
771 CompositeOperator compositeOp = op == CompositeSourceOver ? bgLayer->composite() : op;
772 RenderObject* clientForBackgroundImage = backgroundObject ? backgroundObject : this;
773 RefPtr<Image> image = bgImage->image(clientForBackgroundImage, tileSize);
774 bool useLowQualityScaling = shouldPaintAtLowQuality(context, image.get(), bgLayer, tileSize);
775 context->drawTiledImage(image.get(), style()->colorSpace(), destRect, phase, tileSize, compositeOp, useLowQualityScaling);
776 }
777 }
778 }
779
calculateFillTileSize(const FillLayer * fillLayer,IntSize positioningAreaSize) const780 IntSize RenderBoxModelObject::calculateFillTileSize(const FillLayer* fillLayer, IntSize positioningAreaSize) const
781 {
782 StyleImage* image = fillLayer->image();
783 image->setImageContainerSize(positioningAreaSize); // Use the box established by background-origin.
784
785 EFillSizeType type = fillLayer->size().type;
786
787 switch (type) {
788 case SizeLength: {
789 int w = positioningAreaSize.width();
790 int h = positioningAreaSize.height();
791
792 Length layerWidth = fillLayer->size().size.width();
793 Length layerHeight = fillLayer->size().size.height();
794
795 if (layerWidth.isFixed())
796 w = layerWidth.value();
797 else if (layerWidth.isPercent())
798 w = layerWidth.calcValue(positioningAreaSize.width());
799
800 if (layerHeight.isFixed())
801 h = layerHeight.value();
802 else if (layerHeight.isPercent())
803 h = layerHeight.calcValue(positioningAreaSize.height());
804
805 // If one of the values is auto we have to use the appropriate
806 // scale to maintain our aspect ratio.
807 if (layerWidth.isAuto() && !layerHeight.isAuto()) {
808 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
809 if (imageIntrinsicSize.height())
810 w = imageIntrinsicSize.width() * h / imageIntrinsicSize.height();
811 } else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
812 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
813 if (imageIntrinsicSize.width())
814 h = imageIntrinsicSize.height() * w / imageIntrinsicSize.width();
815 } else if (layerWidth.isAuto() && layerHeight.isAuto()) {
816 // If both width and height are auto, use the image's intrinsic size.
817 IntSize imageIntrinsicSize = image->imageSize(this, style()->effectiveZoom());
818 w = imageIntrinsicSize.width();
819 h = imageIntrinsicSize.height();
820 }
821
822 return IntSize(max(1, w), max(1, h));
823 }
824 case Contain:
825 case Cover: {
826 IntSize imageIntrinsicSize = image->imageSize(this, 1);
827 float horizontalScaleFactor = imageIntrinsicSize.width()
828 ? static_cast<float>(positioningAreaSize.width()) / imageIntrinsicSize.width() : 1;
829 float verticalScaleFactor = imageIntrinsicSize.height()
830 ? static_cast<float>(positioningAreaSize.height()) / imageIntrinsicSize.height() : 1;
831 float scaleFactor = type == Contain ? min(horizontalScaleFactor, verticalScaleFactor) : max(horizontalScaleFactor, verticalScaleFactor);
832 return IntSize(max<int>(1, imageIntrinsicSize.width() * scaleFactor), max<int>(1, imageIntrinsicSize.height() * scaleFactor));
833 }
834 case SizeNone:
835 break;
836 }
837
838 return image->imageSize(this, style()->effectiveZoom());
839 }
840
calculateBackgroundImageGeometry(const FillLayer * fillLayer,int tx,int ty,int w,int h,IntRect & destRect,IntPoint & phase,IntSize & tileSize)841 void RenderBoxModelObject::calculateBackgroundImageGeometry(const FillLayer* fillLayer, int tx, int ty, int w, int h,
842 IntRect& destRect, IntPoint& phase, IntSize& tileSize)
843 {
844 int left = 0;
845 int top = 0;
846 IntSize positioningAreaSize;
847
848 // Determine the background positioning area and set destRect to the background painting area.
849 // destRect will be adjusted later if the background is non-repeating.
850 bool fixedAttachment = fillLayer->attachment() == FixedBackgroundAttachment;
851
852 #if ENABLE(FAST_MOBILE_SCROLLING)
853 if (view()->frameView() && view()->frameView()->canBlitOnScroll()) {
854 // As a side effect of an optimization to blit on scroll, we do not honor the CSS
855 // property "background-attachment: fixed" because it may result in rendering
856 // artifacts. Note, these artifacts only appear if we are blitting on scroll of
857 // a page that has fixed background images.
858 fixedAttachment = false;
859 }
860 #endif
861
862 if (!fixedAttachment) {
863 destRect = IntRect(tx, ty, w, h);
864
865 int right = 0;
866 int bottom = 0;
867 // Scroll and Local.
868 if (fillLayer->origin() != BorderFillBox) {
869 left = borderLeft();
870 right = borderRight();
871 top = borderTop();
872 bottom = borderBottom();
873 if (fillLayer->origin() == ContentFillBox) {
874 left += paddingLeft();
875 right += paddingRight();
876 top += paddingTop();
877 bottom += paddingBottom();
878 }
879 }
880
881 // The background of the box generated by the root element covers the entire canvas including
882 // its margins. Since those were added in already, we have to factor them out when computing
883 // the background positioning area.
884 if (isRoot()) {
885 positioningAreaSize = IntSize(toRenderBox(this)->width() - left - right, toRenderBox(this)->height() - top - bottom);
886 left += marginLeft();
887 top += marginTop();
888 } else
889 positioningAreaSize = IntSize(w - left - right, h - top - bottom);
890 } else {
891 destRect = viewRect();
892 positioningAreaSize = destRect.size();
893 }
894
895 tileSize = calculateFillTileSize(fillLayer, positioningAreaSize);
896
897 EFillRepeat backgroundRepeatX = fillLayer->repeatX();
898 EFillRepeat backgroundRepeatY = fillLayer->repeatY();
899
900 int xPosition = fillLayer->xPosition().calcMinValue(positioningAreaSize.width() - tileSize.width(), true);
901 if (backgroundRepeatX == RepeatFill)
902 phase.setX(tileSize.width() ? tileSize.width() - (xPosition + left) % tileSize.width() : 0);
903 else {
904 destRect.move(max(xPosition + left, 0), 0);
905 phase.setX(-min(xPosition + left, 0));
906 destRect.setWidth(tileSize.width() + min(xPosition + left, 0));
907 }
908
909 int yPosition = fillLayer->yPosition().calcMinValue(positioningAreaSize.height() - tileSize.height(), true);
910 if (backgroundRepeatY == RepeatFill)
911 phase.setY(tileSize.height() ? tileSize.height() - (yPosition + top) % tileSize.height() : 0);
912 else {
913 destRect.move(0, max(yPosition + top, 0));
914 phase.setY(-min(yPosition + top, 0));
915 destRect.setHeight(tileSize.height() + min(yPosition + top, 0));
916 }
917
918 if (fixedAttachment)
919 phase.move(max(tx - destRect.x(), 0), max(ty - destRect.y(), 0));
920
921 destRect.intersect(IntRect(tx, ty, w, h));
922 }
923
paintNinePieceImage(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,const NinePieceImage & ninePieceImage,CompositeOperator op)924 bool RenderBoxModelObject::paintNinePieceImage(GraphicsContext* graphicsContext, int tx, int ty, int w, int h, const RenderStyle* style,
925 const NinePieceImage& ninePieceImage, CompositeOperator op)
926 {
927 StyleImage* styleImage = ninePieceImage.image();
928 if (!styleImage)
929 return false;
930
931 if (!styleImage->isLoaded())
932 return true; // Never paint a nine-piece image incrementally, but don't paint the fallback borders either.
933
934 if (!styleImage->canRender(style->effectiveZoom()))
935 return false;
936
937 // FIXME: border-image is broken with full page zooming when tiling has to happen, since the tiling function
938 // doesn't have any understanding of the zoom that is in effect on the tile.
939 styleImage->setImageContainerSize(IntSize(w, h));
940 IntSize imageSize = styleImage->imageSize(this, 1.0f);
941 int imageWidth = imageSize.width();
942 int imageHeight = imageSize.height();
943
944 int topSlice = min(imageHeight, ninePieceImage.slices().top().calcValue(imageHeight));
945 int bottomSlice = min(imageHeight, ninePieceImage.slices().bottom().calcValue(imageHeight));
946 int leftSlice = min(imageWidth, ninePieceImage.slices().left().calcValue(imageWidth));
947 int rightSlice = min(imageWidth, ninePieceImage.slices().right().calcValue(imageWidth));
948
949 ENinePieceImageRule hRule = ninePieceImage.horizontalRule();
950 ENinePieceImageRule vRule = ninePieceImage.verticalRule();
951
952 bool fitToBorder = style->borderImage() == ninePieceImage;
953
954 int leftWidth = fitToBorder ? style->borderLeftWidth() : leftSlice;
955 int topWidth = fitToBorder ? style->borderTopWidth() : topSlice;
956 int rightWidth = fitToBorder ? style->borderRightWidth() : rightSlice;
957 int bottomWidth = fitToBorder ? style->borderBottomWidth() : bottomSlice;
958
959 bool drawLeft = leftSlice > 0 && leftWidth > 0;
960 bool drawTop = topSlice > 0 && topWidth > 0;
961 bool drawRight = rightSlice > 0 && rightWidth > 0;
962 bool drawBottom = bottomSlice > 0 && bottomWidth > 0;
963 bool drawMiddle = (imageWidth - leftSlice - rightSlice) > 0 && (w - leftWidth - rightWidth) > 0 &&
964 (imageHeight - topSlice - bottomSlice) > 0 && (h - topWidth - bottomWidth) > 0;
965
966 RefPtr<Image> image = styleImage->image(this, imageSize);
967 ColorSpace colorSpace = style->colorSpace();
968
969 if (drawLeft) {
970 // Paint the top and bottom left corners.
971
972 // The top left corner rect is (tx, ty, leftWidth, topWidth)
973 // The rect to use from within the image is obtained from our slice, and is (0, 0, leftSlice, topSlice)
974 if (drawTop)
975 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty, leftWidth, topWidth),
976 IntRect(0, 0, leftSlice, topSlice), op);
977
978 // The bottom left corner rect is (tx, ty + h - bottomWidth, leftWidth, bottomWidth)
979 // The rect to use from within the image is (0, imageHeight - bottomSlice, leftSlice, botomSlice)
980 if (drawBottom)
981 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx, ty + h - bottomWidth, leftWidth, bottomWidth),
982 IntRect(0, imageHeight - bottomSlice, leftSlice, bottomSlice), op);
983
984 // Paint the left edge.
985 // Have to scale and tile into the border rect.
986 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx, ty + topWidth, leftWidth,
987 h - topWidth - bottomWidth),
988 IntRect(0, topSlice, leftSlice, imageHeight - topSlice - bottomSlice),
989 Image::StretchTile, (Image::TileRule)vRule, op);
990 }
991
992 if (drawRight) {
993 // Paint the top and bottom right corners
994 // The top right corner rect is (tx + w - rightWidth, ty, rightWidth, topWidth)
995 // The rect to use from within the image is obtained from our slice, and is (imageWidth - rightSlice, 0, rightSlice, topSlice)
996 if (drawTop)
997 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty, rightWidth, topWidth),
998 IntRect(imageWidth - rightSlice, 0, rightSlice, topSlice), op);
999
1000 // The bottom right corner rect is (tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth)
1001 // The rect to use from within the image is (imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice)
1002 if (drawBottom)
1003 graphicsContext->drawImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + h - bottomWidth, rightWidth, bottomWidth),
1004 IntRect(imageWidth - rightSlice, imageHeight - bottomSlice, rightSlice, bottomSlice), op);
1005
1006 // Paint the right edge.
1007 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + w - rightWidth, ty + topWidth, rightWidth,
1008 h - topWidth - bottomWidth),
1009 IntRect(imageWidth - rightSlice, topSlice, rightSlice, imageHeight - topSlice - bottomSlice),
1010 Image::StretchTile, (Image::TileRule)vRule, op);
1011 }
1012
1013 // Paint the top edge.
1014 if (drawTop)
1015 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty, w - leftWidth - rightWidth, topWidth),
1016 IntRect(leftSlice, 0, imageWidth - rightSlice - leftSlice, topSlice),
1017 (Image::TileRule)hRule, Image::StretchTile, op);
1018
1019 // Paint the bottom edge.
1020 if (drawBottom)
1021 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + h - bottomWidth,
1022 w - leftWidth - rightWidth, bottomWidth),
1023 IntRect(leftSlice, imageHeight - bottomSlice, imageWidth - rightSlice - leftSlice, bottomSlice),
1024 (Image::TileRule)hRule, Image::StretchTile, op);
1025
1026 // Paint the middle.
1027 if (drawMiddle)
1028 graphicsContext->drawTiledImage(image.get(), colorSpace, IntRect(tx + leftWidth, ty + topWidth, w - leftWidth - rightWidth,
1029 h - topWidth - bottomWidth),
1030 IntRect(leftSlice, topSlice, imageWidth - rightSlice - leftSlice, imageHeight - topSlice - bottomSlice),
1031 (Image::TileRule)hRule, (Image::TileRule)vRule, op);
1032
1033 return true;
1034 }
1035
1036 class BorderEdge {
1037 public:
BorderEdge(int edgeWidth,const Color & edgeColor,EBorderStyle edgeStyle,bool edgeIsTransparent,bool edgeIsPresent=true)1038 BorderEdge(int edgeWidth, const Color& edgeColor, EBorderStyle edgeStyle, bool edgeIsTransparent, bool edgeIsPresent = true)
1039 : width(edgeWidth)
1040 , color(edgeColor)
1041 , style(edgeStyle)
1042 , isTransparent(edgeIsTransparent)
1043 , isPresent(edgeIsPresent)
1044 {
1045 if (style == DOUBLE && edgeWidth < 3)
1046 style = SOLID;
1047 }
1048
BorderEdge()1049 BorderEdge()
1050 : width(0)
1051 , style(BHIDDEN)
1052 , isTransparent(false)
1053 , isPresent(false)
1054 {
1055 }
1056
hasVisibleColorAndStyle() const1057 bool hasVisibleColorAndStyle() const { return style > BHIDDEN && !isTransparent; }
shouldRender() const1058 bool shouldRender() const { return isPresent && hasVisibleColorAndStyle(); }
presentButInvisible() const1059 bool presentButInvisible() const { return usedWidth() && !hasVisibleColorAndStyle(); }
obscuresBackgroundEdge(float scale) const1060 bool obscuresBackgroundEdge(float scale) const
1061 {
1062 if (!isPresent || isTransparent || width < (2 * scale) || color.hasAlpha() || style == BHIDDEN)
1063 return false;
1064
1065 if (style == DOTTED || style == DASHED)
1066 return false;
1067
1068 if (style == DOUBLE)
1069 return width >= 5 * scale; // The outer band needs to be >= 2px wide at unit scale.
1070
1071 return true;
1072 }
1073
usedWidth() const1074 int usedWidth() const { return isPresent ? width : 0; }
1075
getDoubleBorderStripeWidths(int & outerWidth,int & innerWidth) const1076 void getDoubleBorderStripeWidths(int& outerWidth, int& innerWidth) const
1077 {
1078 int fullWidth = usedWidth();
1079 outerWidth = fullWidth / 3;
1080 innerWidth = fullWidth * 2 / 3;
1081
1082 // We need certain integer rounding results
1083 if (fullWidth % 3 == 2)
1084 outerWidth += 1;
1085
1086 if (fullWidth % 3 == 1)
1087 innerWidth += 1;
1088 }
1089
1090 int width;
1091 Color color;
1092 EBorderStyle style;
1093 bool isTransparent;
1094 bool isPresent;
1095 };
1096
1097 #if HAVE(PATH_BASED_BORDER_RADIUS_DRAWING)
borderWillArcInnerEdge(const IntSize & firstRadius,const IntSize & secondRadius)1098 static bool borderWillArcInnerEdge(const IntSize& firstRadius, const IntSize& secondRadius)
1099 {
1100 return !firstRadius.isZero() || !secondRadius.isZero();
1101 }
1102
1103 enum BorderEdgeFlag {
1104 TopBorderEdge = 1 << BSTop,
1105 RightBorderEdge = 1 << BSRight,
1106 BottomBorderEdge = 1 << BSBottom,
1107 LeftBorderEdge = 1 << BSLeft,
1108 AllBorderEdges = TopBorderEdge | BottomBorderEdge | LeftBorderEdge | RightBorderEdge
1109 };
1110
edgeFlagForSide(BoxSide side)1111 static inline BorderEdgeFlag edgeFlagForSide(BoxSide side)
1112 {
1113 return static_cast<BorderEdgeFlag>(1 << side);
1114 }
1115
includesEdge(BorderEdgeFlags flags,BoxSide side)1116 static inline bool includesEdge(BorderEdgeFlags flags, BoxSide side)
1117 {
1118 return flags & edgeFlagForSide(side);
1119 }
1120
edgesShareColor(const BorderEdge & firstEdge,const BorderEdge & secondEdge)1121 inline bool edgesShareColor(const BorderEdge& firstEdge, const BorderEdge& secondEdge)
1122 {
1123 return firstEdge.color == secondEdge.color;
1124 }
1125
styleRequiresClipPolygon(EBorderStyle style)1126 inline bool styleRequiresClipPolygon(EBorderStyle style)
1127 {
1128 return style == DOTTED || style == DASHED; // These are drawn with a stroke, so we have to clip to get corner miters.
1129 }
1130
borderStyleFillsBorderArea(EBorderStyle style)1131 static bool borderStyleFillsBorderArea(EBorderStyle style)
1132 {
1133 return !(style == DOTTED || style == DASHED || style == DOUBLE);
1134 }
1135
borderStyleHasInnerDetail(EBorderStyle style)1136 static bool borderStyleHasInnerDetail(EBorderStyle style)
1137 {
1138 return style == GROOVE || style == RIDGE || style == DOUBLE;
1139 }
1140
borderStyleIsDottedOrDashed(EBorderStyle style)1141 static bool borderStyleIsDottedOrDashed(EBorderStyle style)
1142 {
1143 return style == DOTTED || style == DASHED;
1144 }
1145
1146 // OUTSET darkens the bottom and right (and maybe lightens the top and left)
1147 // INSET darkens the top and left (and maybe lightens the bottom and right)
borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style,BoxSide side,BoxSide adjacentSide)1148 static inline bool borderStyleHasUnmatchedColorsAtCorner(EBorderStyle style, BoxSide side, BoxSide adjacentSide)
1149 {
1150 // These styles match at the top/left and bottom/right.
1151 if (style == INSET || style == GROOVE || style == RIDGE || style == OUTSET) {
1152 const BorderEdgeFlags topRightFlags = edgeFlagForSide(BSTop) | edgeFlagForSide(BSRight);
1153 const BorderEdgeFlags bottomLeftFlags = edgeFlagForSide(BSBottom) | edgeFlagForSide(BSLeft);
1154
1155 BorderEdgeFlags flags = edgeFlagForSide(side) | edgeFlagForSide(adjacentSide);
1156 return flags == topRightFlags || flags == bottomLeftFlags;
1157 }
1158 return false;
1159 }
1160
colorsMatchAtCorner(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1161 static inline bool colorsMatchAtCorner(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1162 {
1163 if (edges[side].shouldRender() != edges[adjacentSide].shouldRender())
1164 return false;
1165
1166 if (!edgesShareColor(edges[side], edges[adjacentSide]))
1167 return false;
1168
1169 return !borderStyleHasUnmatchedColorsAtCorner(edges[side].style, side, adjacentSide);
1170 }
1171
1172 // This assumes that we draw in order: top, bottom, left, right.
willBeOverdrawn(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[])1173 static inline bool willBeOverdrawn(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[])
1174 {
1175 switch (side) {
1176 case BSTop:
1177 case BSBottom:
1178 if (edges[adjacentSide].presentButInvisible())
1179 return false;
1180
1181 if (!edgesShareColor(edges[side], edges[adjacentSide]) && edges[adjacentSide].color.hasAlpha())
1182 return false;
1183
1184 if (!borderStyleFillsBorderArea(edges[adjacentSide].style))
1185 return false;
1186
1187 return true;
1188
1189 case BSLeft:
1190 case BSRight:
1191 // These draw last, so are never overdrawn.
1192 return false;
1193 }
1194 return false;
1195 }
1196
borderStylesRequireMitre(BoxSide side,BoxSide adjacentSide,EBorderStyle style,EBorderStyle adjacentStyle)1197 static inline bool borderStylesRequireMitre(BoxSide side, BoxSide adjacentSide, EBorderStyle style, EBorderStyle adjacentStyle)
1198 {
1199 if (style == DOUBLE || adjacentStyle == DOUBLE || adjacentStyle == GROOVE || adjacentStyle == RIDGE)
1200 return true;
1201
1202 if (borderStyleIsDottedOrDashed(style) != borderStyleIsDottedOrDashed(adjacentStyle))
1203 return true;
1204
1205 if (style != adjacentStyle)
1206 return true;
1207
1208 return borderStyleHasUnmatchedColorsAtCorner(style, side, adjacentSide);
1209 }
1210
joinRequiresMitre(BoxSide side,BoxSide adjacentSide,const BorderEdge edges[],bool allowOverdraw)1211 static bool joinRequiresMitre(BoxSide side, BoxSide adjacentSide, const BorderEdge edges[], bool allowOverdraw)
1212 {
1213 if ((edges[side].isTransparent && edges[adjacentSide].isTransparent) || !edges[adjacentSide].isPresent)
1214 return false;
1215
1216 if (allowOverdraw && willBeOverdrawn(side, adjacentSide, edges))
1217 return false;
1218
1219 if (!edgesShareColor(edges[side], edges[adjacentSide]))
1220 return true;
1221
1222 if (borderStylesRequireMitre(side, adjacentSide, edges[side].style, edges[adjacentSide].style))
1223 return true;
1224
1225 return false;
1226 }
1227
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,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1228 void RenderBoxModelObject::paintOneBorderSide(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1229 const IntRect& sideRect, BoxSide side, BoxSide adjacentSide1, BoxSide adjacentSide2, const BorderEdge edges[], const Path* path,
1230 BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1231 {
1232 const BorderEdge& edgeToRender = edges[side];
1233 const BorderEdge& adjacentEdge1 = edges[adjacentSide1];
1234 const BorderEdge& adjacentEdge2 = edges[adjacentSide2];
1235
1236 bool mitreAdjacentSide1 = joinRequiresMitre(side, adjacentSide1, edges, !antialias);
1237 bool mitreAdjacentSide2 = joinRequiresMitre(side, adjacentSide2, edges, !antialias);
1238
1239 bool adjacentSide1StylesMatch = colorsMatchAtCorner(side, adjacentSide1, edges);
1240 bool adjacentSide2StylesMatch = colorsMatchAtCorner(side, adjacentSide2, edges);
1241
1242 const Color& colorToPaint = overrideColor ? *overrideColor : edgeToRender.color;
1243
1244 if (path) {
1245 GraphicsContextStateSaver stateSaver(*graphicsContext);
1246 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, adjacentSide1StylesMatch, adjacentSide2StylesMatch);
1247 float thickness = max(max(edgeToRender.width, adjacentEdge1.width), adjacentEdge2.width);
1248 drawBoxSideFromPath(graphicsContext, outerBorder.rect(), *path, edges, edgeToRender.width, thickness, side, style,
1249 colorToPaint, edgeToRender.style, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1250 } else {
1251 bool shouldClip = styleRequiresClipPolygon(edgeToRender.style) && (mitreAdjacentSide1 || mitreAdjacentSide2);
1252
1253 GraphicsContextStateSaver clipStateSaver(*graphicsContext, shouldClip);
1254 if (shouldClip) {
1255 clipBorderSidePolygon(graphicsContext, outerBorder, innerBorder, side, !mitreAdjacentSide1, !mitreAdjacentSide2);
1256 // Since we clipped, no need to draw with a mitre.
1257 mitreAdjacentSide1 = false;
1258 mitreAdjacentSide2 = false;
1259 }
1260
1261 drawLineForBoxSide(graphicsContext, sideRect.x(), sideRect.y(), sideRect.maxX(), sideRect.maxY(), side, colorToPaint, edgeToRender.style,
1262 mitreAdjacentSide1 ? adjacentEdge1.width : 0, mitreAdjacentSide2 ? adjacentEdge2.width : 0, antialias);
1263 }
1264 }
1265
paintBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],BorderEdgeFlags edgeSet,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias,const Color * overrideColor)1266 void RenderBoxModelObject::paintBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1267 const BorderEdge edges[], BorderEdgeFlags edgeSet, BackgroundBleedAvoidance bleedAvoidance,
1268 bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias, const Color* overrideColor)
1269 {
1270 bool renderRadii = outerBorder.isRounded();
1271
1272 Path roundedPath;
1273 if (renderRadii)
1274 roundedPath.addRoundedRect(outerBorder);
1275
1276 if (edges[BSTop].shouldRender() && includesEdge(edgeSet, BSTop)) {
1277 IntRect sideRect = outerBorder.rect();
1278 sideRect.setHeight(edges[BSTop].width);
1279
1280 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSTop].style) || borderWillArcInnerEdge(innerBorder.radii().topLeft(), innerBorder.radii().topRight()));
1281 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSTop, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1282 }
1283
1284 if (edges[BSBottom].shouldRender() && includesEdge(edgeSet, BSBottom)) {
1285 IntRect sideRect = outerBorder.rect();
1286 sideRect.shiftYEdgeTo(sideRect.maxY() - edges[BSBottom].width);
1287
1288 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSBottom].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().bottomRight()));
1289 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSBottom, BSLeft, BSRight, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1290 }
1291
1292 if (edges[BSLeft].shouldRender() && includesEdge(edgeSet, BSLeft)) {
1293 IntRect sideRect = outerBorder.rect();
1294 sideRect.setWidth(edges[BSLeft].width);
1295
1296 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSLeft].style) || borderWillArcInnerEdge(innerBorder.radii().bottomLeft(), innerBorder.radii().topLeft()));
1297 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSLeft, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1298 }
1299
1300 if (edges[BSRight].shouldRender() && includesEdge(edgeSet, BSRight)) {
1301 IntRect sideRect = outerBorder.rect();
1302 sideRect.shiftXEdgeTo(sideRect.maxX() - edges[BSRight].width);
1303
1304 bool usePath = renderRadii && (borderStyleHasInnerDetail(edges[BSRight].style) || borderWillArcInnerEdge(innerBorder.radii().bottomRight(), innerBorder.radii().topRight()));
1305 paintOneBorderSide(graphicsContext, style, outerBorder, innerBorder, sideRect, BSRight, BSTop, BSBottom, edges, usePath ? &roundedPath : 0, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, overrideColor);
1306 }
1307 }
1308
paintTranslucentBorderSides(GraphicsContext * graphicsContext,const RenderStyle * style,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,const BorderEdge edges[],BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge,bool antialias)1309 void RenderBoxModelObject::paintTranslucentBorderSides(GraphicsContext* graphicsContext, const RenderStyle* style, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1310 const BorderEdge edges[], BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge, bool antialias)
1311 {
1312 BorderEdgeFlags edgesToDraw = AllBorderEdges;
1313 while (edgesToDraw) {
1314 // Find undrawn edges sharing a color.
1315 Color commonColor;
1316
1317 BorderEdgeFlags commonColorEdgeSet = 0;
1318 for (int i = BSTop; i <= BSLeft; ++i) {
1319 BoxSide currSide = static_cast<BoxSide>(i);
1320 if (!includesEdge(edgesToDraw, currSide))
1321 continue;
1322
1323 bool includeEdge;
1324 if (!commonColorEdgeSet) {
1325 commonColor = edges[currSide].color;
1326 includeEdge = true;
1327 } else
1328 includeEdge = edges[currSide].color == commonColor;
1329
1330 if (includeEdge)
1331 commonColorEdgeSet |= edgeFlagForSide(currSide);
1332 }
1333
1334 bool useTransparencyLayer = commonColor.hasAlpha();
1335 if (useTransparencyLayer) {
1336 graphicsContext->beginTransparencyLayer(static_cast<float>(commonColor.alpha()) / 255);
1337 commonColor = Color(commonColor.red(), commonColor.green(), commonColor.blue());
1338 }
1339
1340 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, commonColorEdgeSet, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias, &commonColor);
1341
1342 if (useTransparencyLayer)
1343 graphicsContext->endTransparencyLayer();
1344
1345 edgesToDraw &= ~commonColorEdgeSet;
1346 }
1347 }
1348
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1349 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1350 const RenderStyle* style, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1351 {
1352 // border-image is not affected by border-radius.
1353 if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1354 return;
1355
1356 if (graphicsContext->paintingDisabled())
1357 return;
1358
1359 BorderEdge edges[4];
1360 getBorderEdgeInfo(edges, includeLogicalLeftEdge, includeLogicalRightEdge);
1361
1362 IntRect borderRect(tx, ty, w, h);
1363 RoundedIntRect outerBorder = style->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1364 RoundedIntRect innerBorder = style->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
1365
1366 const AffineTransform& currentCTM = graphicsContext->getCTM();
1367 // 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.
1368 bool antialias = !currentCTM.isIdentityOrTranslationOrFlipped();
1369
1370 bool haveAlphaColor = false;
1371 bool haveAllSolidEdges = true;
1372 bool allEdgesVisible = true;
1373 bool allEdgesShareColor = true;
1374 int firstVisibleEdge = -1;
1375
1376 for (int i = BSTop; i <= BSLeft; ++i) {
1377 const BorderEdge& currEdge = edges[i];
1378 if (currEdge.presentButInvisible()) {
1379 allEdgesVisible = false;
1380 continue;
1381 }
1382
1383 if (!currEdge.width)
1384 continue;
1385
1386 if (firstVisibleEdge == -1)
1387 firstVisibleEdge = i;
1388 else if (currEdge.color != edges[firstVisibleEdge].color)
1389 allEdgesShareColor = false;
1390
1391 if (currEdge.color.hasAlpha())
1392 haveAlphaColor = true;
1393
1394 if (currEdge.style != SOLID)
1395 haveAllSolidEdges = false;
1396 }
1397
1398 // isRenderable() check avoids issue described in https://bugs.webkit.org/show_bug.cgi?id=38787
1399 if (haveAllSolidEdges && allEdgesVisible && allEdgesShareColor && innerBorder.isRenderable()) {
1400 // Fast path for drawing all solid edges.
1401 if (outerBorder.isRounded() || haveAlphaColor) {
1402 Path path;
1403
1404 if (outerBorder.isRounded() && bleedAvoidance != BackgroundBleedUseTransparencyLayer)
1405 path.addRoundedRect(outerBorder);
1406 else
1407 path.addRect(outerBorder.rect());
1408
1409 if (innerBorder.isRounded())
1410 path.addRoundedRect(innerBorder);
1411 else
1412 path.addRect(innerBorder.rect());
1413
1414 graphicsContext->setFillRule(RULE_EVENODD);
1415 graphicsContext->setFillColor(edges[firstVisibleEdge].color, style->colorSpace());
1416 graphicsContext->fillPath(path);
1417 } else
1418 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1419
1420 return;
1421 }
1422
1423 bool clipToOuterBorder = outerBorder.isRounded();
1424 GraphicsContextStateSaver stateSaver(*graphicsContext, clipToOuterBorder);
1425 if (clipToOuterBorder) {
1426 // Clip to the inner and outer radii rects.
1427 if (bleedAvoidance != BackgroundBleedUseTransparencyLayer)
1428 graphicsContext->addRoundedRectClip(outerBorder);
1429 graphicsContext->clipOutRoundedRect(innerBorder);
1430 }
1431
1432 if (haveAlphaColor)
1433 paintTranslucentBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1434 else
1435 paintBorderSides(graphicsContext, style, outerBorder, innerBorder, edges, AllBorderEdges, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge, antialias);
1436 }
1437
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,BackgroundBleedAvoidance bleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1438 void RenderBoxModelObject::drawBoxSideFromPath(GraphicsContext* graphicsContext, const IntRect& borderRect, const Path& borderPath, const BorderEdge edges[],
1439 float thickness, float drawThickness, BoxSide side, const RenderStyle* style,
1440 Color color, EBorderStyle borderStyle, BackgroundBleedAvoidance bleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1441 {
1442 if (thickness <= 0)
1443 return;
1444
1445 if (borderStyle == DOUBLE && thickness < 3)
1446 borderStyle = SOLID;
1447
1448 switch (borderStyle) {
1449 case BNONE:
1450 case BHIDDEN:
1451 return;
1452 case DOTTED:
1453 case DASHED: {
1454 graphicsContext->setStrokeColor(color, style->colorSpace());
1455
1456 // The stroke is doubled here because the provided path is the
1457 // outside edge of the border so half the stroke is clipped off.
1458 // The extra multiplier is so that the clipping mask can antialias
1459 // the edges to prevent jaggies.
1460 graphicsContext->setStrokeThickness(drawThickness * 2 * 1.1f);
1461 graphicsContext->setStrokeStyle(borderStyle == DASHED ? DashedStroke : DottedStroke);
1462
1463 // If the number of dashes that fit in the path is odd and non-integral then we
1464 // will have an awkwardly-sized dash at the end of the path. To try to avoid that
1465 // here, we simply make the whitespace dashes ever so slightly bigger.
1466 // FIXME: This could be even better if we tried to manipulate the dash offset
1467 // and possibly the gapLength to get the corners dash-symmetrical.
1468 float dashLength = thickness * ((borderStyle == DASHED) ? 3.0f : 1.0f);
1469 float gapLength = dashLength;
1470 float numberOfDashes = borderPath.length() / dashLength;
1471 // Don't try to show dashes if we have less than 2 dashes + 2 gaps.
1472 // FIXME: should do this test per side.
1473 if (numberOfDashes >= 4) {
1474 bool evenNumberOfFullDashes = !((int)numberOfDashes % 2);
1475 bool integralNumberOfDashes = !(numberOfDashes - (int)numberOfDashes);
1476 if (!evenNumberOfFullDashes && !integralNumberOfDashes) {
1477 float numberOfGaps = numberOfDashes / 2;
1478 gapLength += (dashLength / numberOfGaps);
1479 }
1480
1481 DashArray lineDash;
1482 lineDash.append(dashLength);
1483 lineDash.append(gapLength);
1484 graphicsContext->setLineDash(lineDash, dashLength);
1485 }
1486
1487 // FIXME: stroking the border path causes issues with tight corners:
1488 // https://bugs.webkit.org/show_bug.cgi?id=58711
1489 // Also, to get the best appearance we should stroke a path between the two borders.
1490 graphicsContext->strokePath(borderPath);
1491 return;
1492 }
1493 case DOUBLE: {
1494 // Get the inner border rects for both the outer border line and the inner border line
1495 int outerBorderTopWidth;
1496 int innerBorderTopWidth;
1497 edges[BSTop].getDoubleBorderStripeWidths(outerBorderTopWidth, innerBorderTopWidth);
1498
1499 int outerBorderRightWidth;
1500 int innerBorderRightWidth;
1501 edges[BSRight].getDoubleBorderStripeWidths(outerBorderRightWidth, innerBorderRightWidth);
1502
1503 int outerBorderBottomWidth;
1504 int innerBorderBottomWidth;
1505 edges[BSBottom].getDoubleBorderStripeWidths(outerBorderBottomWidth, innerBorderBottomWidth);
1506
1507 int outerBorderLeftWidth;
1508 int innerBorderLeftWidth;
1509 edges[BSLeft].getDoubleBorderStripeWidths(outerBorderLeftWidth, innerBorderLeftWidth);
1510
1511 // Draw inner border line
1512 {
1513 GraphicsContextStateSaver stateSaver(*graphicsContext);
1514 RoundedIntRect innerClip = style->getRoundedInnerBorderFor(borderRect,
1515 innerBorderTopWidth, innerBorderBottomWidth, innerBorderLeftWidth, innerBorderRightWidth,
1516 includeLogicalLeftEdge, includeLogicalRightEdge);
1517
1518 graphicsContext->addRoundedRectClip(innerClip);
1519 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1520 }
1521
1522 // Draw outer border line
1523 {
1524 GraphicsContextStateSaver stateSaver(*graphicsContext);
1525 IntRect outerRect = borderRect;
1526 if (bleedAvoidance == BackgroundBleedUseTransparencyLayer) {
1527 outerRect.inflate(1);
1528 ++outerBorderTopWidth;
1529 ++outerBorderBottomWidth;
1530 ++outerBorderLeftWidth;
1531 ++outerBorderRightWidth;
1532 }
1533
1534 RoundedIntRect outerClip = style->getRoundedInnerBorderFor(outerRect,
1535 outerBorderTopWidth, outerBorderBottomWidth, outerBorderLeftWidth, outerBorderRightWidth,
1536 includeLogicalLeftEdge, includeLogicalRightEdge);
1537 graphicsContext->clipOutRoundedRect(outerClip);
1538 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, SOLID, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1539 }
1540 return;
1541 }
1542 case RIDGE:
1543 case GROOVE:
1544 {
1545 EBorderStyle s1;
1546 EBorderStyle s2;
1547 if (borderStyle == GROOVE) {
1548 s1 = INSET;
1549 s2 = OUTSET;
1550 } else {
1551 s1 = OUTSET;
1552 s2 = INSET;
1553 }
1554
1555 // Paint full border
1556 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s1, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1557
1558 // Paint inner only
1559 GraphicsContextStateSaver stateSaver(*graphicsContext);
1560 int topWidth = edges[BSTop].usedWidth() / 2;
1561 int bottomWidth = edges[BSBottom].usedWidth() / 2;
1562 int leftWidth = edges[BSLeft].usedWidth() / 2;
1563 int rightWidth = edges[BSRight].usedWidth() / 2;
1564
1565 RoundedIntRect clipRect = style->getRoundedInnerBorderFor(borderRect,
1566 topWidth, bottomWidth, leftWidth, rightWidth,
1567 includeLogicalLeftEdge, includeLogicalRightEdge);
1568
1569 graphicsContext->addRoundedRectClip(clipRect);
1570 drawBoxSideFromPath(graphicsContext, borderRect, borderPath, edges, thickness, drawThickness, side, style, color, s2, bleedAvoidance, includeLogicalLeftEdge, includeLogicalRightEdge);
1571 return;
1572 }
1573 case INSET:
1574 if (side == BSTop || side == BSLeft)
1575 color = color.dark();
1576 break;
1577 case OUTSET:
1578 if (side == BSBottom || side == BSRight)
1579 color = color.dark();
1580 break;
1581 default:
1582 break;
1583 }
1584
1585 graphicsContext->setStrokeStyle(NoStroke);
1586 graphicsContext->setFillColor(color, style->colorSpace());
1587 graphicsContext->drawRect(borderRect);
1588 }
1589 #else
paintBorder(GraphicsContext * graphicsContext,int tx,int ty,int w,int h,const RenderStyle * style,BackgroundBleedAvoidance,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)1590 void RenderBoxModelObject::paintBorder(GraphicsContext* graphicsContext, int tx, int ty, int w, int h,
1591 const RenderStyle* style, BackgroundBleedAvoidance, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
1592 {
1593 // FIXME: This old version of paintBorder should be removed when all ports implement
1594 // GraphicsContext::clipConvexPolygon()!! This should happen soon.
1595 if (paintNinePieceImage(graphicsContext, tx, ty, w, h, style, style->borderImage()))
1596 return;
1597
1598 const Color& topColor = style->visitedDependentColor(CSSPropertyBorderTopColor);
1599 const Color& bottomColor = style->visitedDependentColor(CSSPropertyBorderBottomColor);
1600 const Color& leftColor = style->visitedDependentColor(CSSPropertyBorderLeftColor);
1601 const Color& rightColor = style->visitedDependentColor(CSSPropertyBorderRightColor);
1602
1603 bool topTransparent = style->borderTopIsTransparent();
1604 bool bottomTransparent = style->borderBottomIsTransparent();
1605 bool rightTransparent = style->borderRightIsTransparent();
1606 bool leftTransparent = style->borderLeftIsTransparent();
1607
1608 EBorderStyle topStyle = style->borderTopStyle();
1609 EBorderStyle bottomStyle = style->borderBottomStyle();
1610 EBorderStyle leftStyle = style->borderLeftStyle();
1611 EBorderStyle rightStyle = style->borderRightStyle();
1612
1613 bool horizontal = style->isHorizontalWritingMode();
1614 bool renderTop = topStyle > BHIDDEN && !topTransparent && (horizontal || includeLogicalLeftEdge);
1615 bool renderLeft = leftStyle > BHIDDEN && !leftTransparent && (!horizontal || includeLogicalLeftEdge);
1616 bool renderRight = rightStyle > BHIDDEN && !rightTransparent && (!horizontal || includeLogicalRightEdge);
1617 bool renderBottom = bottomStyle > BHIDDEN && !bottomTransparent && (horizontal || includeLogicalRightEdge);
1618
1619
1620 RoundedIntRect border(tx, ty, w, h);
1621
1622 GraphicsContextStateSaver stateSaver(*graphicsContext, false);
1623 if (style->hasBorderRadius()) {
1624 border.includeLogicalEdges(style->getRoundedBorderFor(border.rect()).radii(),
1625 horizontal, includeLogicalLeftEdge, includeLogicalRightEdge);
1626 if (border.isRounded()) {
1627 stateSaver.save();
1628 graphicsContext->addRoundedRectClip(border);
1629 }
1630 }
1631
1632 int firstAngleStart, secondAngleStart, firstAngleSpan, secondAngleSpan;
1633 float thickness;
1634 bool renderRadii = border.isRounded();
1635 bool upperLeftBorderStylesMatch = renderLeft && (topStyle == leftStyle) && (topColor == leftColor);
1636 bool upperRightBorderStylesMatch = renderRight && (topStyle == rightStyle) && (topColor == rightColor) && (topStyle != OUTSET) && (topStyle != RIDGE) && (topStyle != INSET) && (topStyle != GROOVE);
1637 bool lowerLeftBorderStylesMatch = renderLeft && (bottomStyle == leftStyle) && (bottomColor == leftColor) && (bottomStyle != OUTSET) && (bottomStyle != RIDGE) && (bottomStyle != INSET) && (bottomStyle != GROOVE);
1638 bool lowerRightBorderStylesMatch = renderRight && (bottomStyle == rightStyle) && (bottomColor == rightColor);
1639
1640 if (renderTop) {
1641 bool ignoreLeft = (renderRadii && border.radii().topLeft().width() > 0)
1642 || (topColor == leftColor && topTransparent == leftTransparent && topStyle >= OUTSET
1643 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1644
1645 bool ignoreRight = (renderRadii && border.radii().topRight().width() > 0)
1646 || (topColor == rightColor && topTransparent == rightTransparent && topStyle >= OUTSET
1647 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1648
1649 int x = tx;
1650 int x2 = tx + w;
1651 if (renderRadii) {
1652 x += border.radii().topLeft().width();
1653 x2 -= border.radii().topRight().width();
1654 }
1655
1656 drawLineForBoxSide(graphicsContext, x, ty, x2, ty + style->borderTopWidth(), BSTop, topColor, topStyle,
1657 ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1658
1659 if (renderRadii) {
1660 int leftY = ty;
1661
1662 // We make the arc double thick and let the clip rect take care of clipping the extra off.
1663 // We're doing this because it doesn't seem possible to match the curve of the clip exactly
1664 // with the arc-drawing function.
1665 thickness = style->borderTopWidth() * 2;
1666
1667 if (border.radii().topLeft().width()) {
1668 int leftX = tx;
1669 // The inner clip clips inside the arc. This is especially important for 1px borders.
1670 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1671 && (style->borderTopWidth() < border.radii().topLeft().height())
1672 && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1673
1674 GraphicsContextStateSaver stateSaver(*graphicsContext, applyLeftInnerClip);
1675 if (applyLeftInnerClip)
1676 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1677 style->borderTopWidth());
1678
1679 firstAngleStart = 90;
1680 firstAngleSpan = upperLeftBorderStylesMatch ? 90 : 45;
1681
1682 // Draw upper left arc
1683 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1684 BSTop, topColor, topStyle, true);
1685 }
1686
1687 if (border.radii().topRight().width()) {
1688 int rightX = tx + w - border.radii().topRight().width() * 2;
1689 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1690 && (style->borderTopWidth() < border.radii().topRight().height())
1691 && (topStyle != DOUBLE || style->borderTopWidth() > 6);
1692
1693 GraphicsContextStateSaver stateSaver(*graphicsContext, applyRightInnerClip);
1694 if (applyRightInnerClip)
1695 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, leftY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1696 style->borderTopWidth());
1697
1698 if (upperRightBorderStylesMatch) {
1699 secondAngleStart = 0;
1700 secondAngleSpan = 90;
1701 } else {
1702 secondAngleStart = 45;
1703 secondAngleSpan = 45;
1704 }
1705
1706 // Draw upper right arc
1707 drawArcForBoxSide(graphicsContext, rightX, leftY, thickness, border.radii().topRight(), secondAngleStart, secondAngleSpan,
1708 BSTop, topColor, topStyle, false);
1709 }
1710 }
1711 }
1712
1713 if (renderBottom) {
1714 bool ignoreLeft = (renderRadii && border.radii().bottomLeft().width() > 0)
1715 || (bottomColor == leftColor && bottomTransparent == leftTransparent && bottomStyle >= OUTSET
1716 && (leftStyle == DOTTED || leftStyle == DASHED || leftStyle == SOLID || leftStyle == OUTSET));
1717
1718 bool ignoreRight = (renderRadii && border.radii().bottomRight().width() > 0)
1719 || (bottomColor == rightColor && bottomTransparent == rightTransparent && bottomStyle >= OUTSET
1720 && (rightStyle == DOTTED || rightStyle == DASHED || rightStyle == SOLID || rightStyle == INSET));
1721
1722 int x = tx;
1723 int x2 = tx + w;
1724 if (renderRadii) {
1725 x += border.radii().bottomLeft().width();
1726 x2 -= border.radii().bottomRight().width();
1727 }
1728
1729 drawLineForBoxSide(graphicsContext, x, ty + h - style->borderBottomWidth(), x2, ty + h, BSBottom, bottomColor, bottomStyle,
1730 ignoreLeft ? 0 : style->borderLeftWidth(), ignoreRight ? 0 : style->borderRightWidth());
1731
1732 if (renderRadii) {
1733 thickness = style->borderBottomWidth() * 2;
1734
1735 if (border.radii().bottomLeft().width()) {
1736 int leftX = tx;
1737 int leftY = ty + h - border.radii().bottomLeft().height() * 2;
1738 bool applyLeftInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1739 && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1740 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1741
1742 GraphicsContextStateSaver stateSaver(*graphicsContext, applyLeftInnerClip);
1743 if (applyLeftInnerClip)
1744 graphicsContext->addInnerRoundedRectClip(IntRect(leftX, leftY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1745 style->borderBottomWidth());
1746
1747 if (lowerLeftBorderStylesMatch) {
1748 firstAngleStart = 180;
1749 firstAngleSpan = 90;
1750 } else {
1751 firstAngleStart = 225;
1752 firstAngleSpan = 45;
1753 }
1754
1755 // Draw lower left arc
1756 drawArcForBoxSide(graphicsContext, leftX, leftY, thickness, border.radii().bottomLeft(), firstAngleStart, firstAngleSpan,
1757 BSBottom, bottomColor, bottomStyle, true);
1758 }
1759
1760 if (border.radii().bottomRight().width()) {
1761 int rightY = ty + h - border.radii().bottomRight().height() * 2;
1762 int rightX = tx + w - border.radii().bottomRight().width() * 2;
1763 bool applyRightInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1764 && (style->borderBottomWidth() < border.radii().bottomRight().height())
1765 && (bottomStyle != DOUBLE || style->borderBottomWidth() > 6);
1766
1767 GraphicsContextStateSaver stateSaver(*graphicsContext, applyRightInnerClip);
1768 if (applyRightInnerClip)
1769 graphicsContext->addInnerRoundedRectClip(IntRect(rightX, rightY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1770 style->borderBottomWidth());
1771
1772 secondAngleStart = 270;
1773 secondAngleSpan = lowerRightBorderStylesMatch ? 90 : 45;
1774
1775 // Draw lower right arc
1776 drawArcForBoxSide(graphicsContext, rightX, rightY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1777 BSBottom, bottomColor, bottomStyle, false);
1778 }
1779 }
1780 }
1781
1782 if (renderLeft) {
1783 bool ignoreTop = (renderRadii && border.radii().topLeft().height() > 0)
1784 || (topColor == leftColor && topTransparent == leftTransparent && leftStyle >= OUTSET
1785 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1786
1787 bool ignoreBottom = (renderRadii && border.radii().bottomLeft().height() > 0)
1788 || (bottomColor == leftColor && bottomTransparent == leftTransparent && leftStyle >= OUTSET
1789 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1790
1791 int y = ty;
1792 int y2 = ty + h;
1793 if (renderRadii) {
1794 y += border.radii().topLeft().height();
1795 y2 -= border.radii().bottomLeft().height();
1796 }
1797
1798 drawLineForBoxSide(graphicsContext, tx, y, tx + style->borderLeftWidth(), y2, BSLeft, leftColor, leftStyle,
1799 ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1800
1801 if (renderRadii && (!upperLeftBorderStylesMatch || !lowerLeftBorderStylesMatch)) {
1802 int topX = tx;
1803 thickness = style->borderLeftWidth() * 2;
1804
1805 if (!upperLeftBorderStylesMatch && border.radii().topLeft().width()) {
1806 int topY = ty;
1807 bool applyTopInnerClip = (style->borderLeftWidth() < border.radii().topLeft().width())
1808 && (style->borderTopWidth() < border.radii().topLeft().height())
1809 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1810
1811 GraphicsContextStateSaver stateSaver(*graphicsContext, applyTopInnerClip);
1812 if (applyTopInnerClip)
1813 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topLeft().width() * 2, border.radii().topLeft().height() * 2),
1814 style->borderLeftWidth());
1815
1816 firstAngleStart = 135;
1817 firstAngleSpan = 45;
1818
1819 // Draw top left arc
1820 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topLeft(), firstAngleStart, firstAngleSpan,
1821 BSLeft, leftColor, leftStyle, true);
1822 }
1823
1824 if (!lowerLeftBorderStylesMatch && border.radii().bottomLeft().width()) {
1825 int bottomY = ty + h - border.radii().bottomLeft().height() * 2;
1826 bool applyBottomInnerClip = (style->borderLeftWidth() < border.radii().bottomLeft().width())
1827 && (style->borderBottomWidth() < border.radii().bottomLeft().height())
1828 && (leftStyle != DOUBLE || style->borderLeftWidth() > 6);
1829
1830 GraphicsContextStateSaver stateSaver(*graphicsContext, applyBottomInnerClip);
1831 if (applyBottomInnerClip)
1832 graphicsContext->addInnerRoundedRectClip(IntRect(topX, bottomY, border.radii().bottomLeft().width() * 2, border.radii().bottomLeft().height() * 2),
1833 style->borderLeftWidth());
1834
1835 secondAngleStart = 180;
1836 secondAngleSpan = 45;
1837
1838 // Draw bottom left arc
1839 drawArcForBoxSide(graphicsContext, topX, bottomY, thickness, border.radii().bottomLeft(), secondAngleStart, secondAngleSpan,
1840 BSLeft, leftColor, leftStyle, false);
1841 }
1842 }
1843 }
1844
1845 if (renderRight) {
1846 bool ignoreTop = (renderRadii && border.radii().topRight().height() > 0)
1847 || ((topColor == rightColor) && (topTransparent == rightTransparent)
1848 && (rightStyle >= DOTTED || rightStyle == INSET)
1849 && (topStyle == DOTTED || topStyle == DASHED || topStyle == SOLID || topStyle == OUTSET));
1850
1851 bool ignoreBottom = (renderRadii && border.radii().bottomRight().height() > 0)
1852 || ((bottomColor == rightColor) && (bottomTransparent == rightTransparent)
1853 && (rightStyle >= DOTTED || rightStyle == INSET)
1854 && (bottomStyle == DOTTED || bottomStyle == DASHED || bottomStyle == SOLID || bottomStyle == INSET));
1855
1856 int y = ty;
1857 int y2 = ty + h;
1858 if (renderRadii) {
1859 y += border.radii().topRight().height();
1860 y2 -= border.radii().bottomRight().height();
1861 }
1862
1863 drawLineForBoxSide(graphicsContext, tx + w - style->borderRightWidth(), y, tx + w, y2, BSRight, rightColor, rightStyle,
1864 ignoreTop ? 0 : style->borderTopWidth(), ignoreBottom ? 0 : style->borderBottomWidth());
1865
1866 if (renderRadii && (!upperRightBorderStylesMatch || !lowerRightBorderStylesMatch)) {
1867 thickness = style->borderRightWidth() * 2;
1868
1869 if (!upperRightBorderStylesMatch && border.radii().topRight().width()) {
1870 int topX = tx + w - border.radii().topRight().width() * 2;
1871 int topY = ty;
1872 bool applyTopInnerClip = (style->borderRightWidth() < border.radii().topRight().width())
1873 && (style->borderTopWidth() < border.radii().topRight().height())
1874 && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1875
1876 GraphicsContextStateSaver stateSaver(*graphicsContext, applyTopInnerClip);
1877 if (applyTopInnerClip)
1878 graphicsContext->addInnerRoundedRectClip(IntRect(topX, topY, border.radii().topRight().width() * 2, border.radii().topRight().height() * 2),
1879 style->borderRightWidth());
1880
1881 firstAngleStart = 0;
1882 firstAngleSpan = 45;
1883
1884 // Draw top right arc
1885 drawArcForBoxSide(graphicsContext, topX, topY, thickness, border.radii().topRight(), firstAngleStart, firstAngleSpan,
1886 BSRight, rightColor, rightStyle, true);
1887 }
1888
1889 if (!lowerRightBorderStylesMatch && border.radii().bottomRight().width()) {
1890 int bottomX = tx + w - border.radii().bottomRight().width() * 2;
1891 int bottomY = ty + h - border.radii().bottomRight().height() * 2;
1892 bool applyBottomInnerClip = (style->borderRightWidth() < border.radii().bottomRight().width())
1893 && (style->borderBottomWidth() < border.radii().bottomRight().height())
1894 && (rightStyle != DOUBLE || style->borderRightWidth() > 6);
1895
1896 GraphicsContextStateSaver stateSaver(*graphicsContext, applyBottomInnerClip);
1897 if (applyBottomInnerClip)
1898 graphicsContext->addInnerRoundedRectClip(IntRect(bottomX, bottomY, border.radii().bottomRight().width() * 2, border.radii().bottomRight().height() * 2),
1899 style->borderRightWidth());
1900
1901 secondAngleStart = 315;
1902 secondAngleSpan = 45;
1903
1904 // Draw bottom right arc
1905 drawArcForBoxSide(graphicsContext, bottomX, bottomY, thickness, border.radii().bottomRight(), secondAngleStart, secondAngleSpan,
1906 BSRight, rightColor, rightStyle, false);
1907 }
1908 }
1909 }
1910 }
1911 #endif
1912
findInnerVertex(const FloatPoint & outerCorner,const FloatPoint & innerCorner,const FloatPoint & centerPoint,FloatPoint & result)1913 static void findInnerVertex(const FloatPoint& outerCorner, const FloatPoint& innerCorner, const FloatPoint& centerPoint, FloatPoint& result)
1914 {
1915 // If the line between outer and inner corner is towards the horizontal, intersect with a vertical line through the center,
1916 // otherwise with a horizontal line through the center. The points that form this line are arbitrary (we use 0, 100).
1917 // Note that if findIntersection fails, it will leave result untouched.
1918 if (fabs(outerCorner.x() - innerCorner.x()) > fabs(outerCorner.y() - innerCorner.y()))
1919 findIntersection(outerCorner, innerCorner, FloatPoint(centerPoint.x(), 0), FloatPoint(centerPoint.x(), 100), result);
1920 else
1921 findIntersection(outerCorner, innerCorner, FloatPoint(0, centerPoint.y()), FloatPoint(100, centerPoint.y()), result);
1922 }
1923
clipBorderSidePolygon(GraphicsContext * graphicsContext,const RoundedIntRect & outerBorder,const RoundedIntRect & innerBorder,BoxSide side,bool firstEdgeMatches,bool secondEdgeMatches)1924 void RenderBoxModelObject::clipBorderSidePolygon(GraphicsContext* graphicsContext, const RoundedIntRect& outerBorder, const RoundedIntRect& innerBorder,
1925 BoxSide side, bool firstEdgeMatches, bool secondEdgeMatches)
1926 {
1927 FloatPoint quad[4];
1928
1929 const IntRect& outerRect = outerBorder.rect();
1930 const IntRect& innerRect = innerBorder.rect();
1931
1932 FloatPoint centerPoint(innerRect.location().x() + static_cast<float>(innerRect.width()) / 2, innerRect.location().y() + static_cast<float>(innerRect.height()) / 2);
1933
1934 // For each side, create a quad that encompasses all parts of that side that may draw,
1935 // including areas inside the innerBorder.
1936 //
1937 // 0----------------3
1938 // 0 \ / 0
1939 // |\ 1----------- 2 /|
1940 // | 1 1 |
1941 // | | | |
1942 // | | | |
1943 // | 2 2 |
1944 // |/ 1------------2 \|
1945 // 3 / \ 3
1946 // 0----------------3
1947 //
1948 switch (side) {
1949 case BSTop:
1950 quad[0] = outerRect.minXMinYCorner();
1951 quad[1] = innerRect.minXMinYCorner();
1952 quad[2] = innerRect.maxXMinYCorner();
1953 quad[3] = outerRect.maxXMinYCorner();
1954
1955 if (!innerBorder.radii().topLeft().isZero())
1956 findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1957
1958 if (!innerBorder.radii().topRight().isZero())
1959 findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[2]);
1960 break;
1961
1962 case BSLeft:
1963 quad[0] = outerRect.minXMinYCorner();
1964 quad[1] = innerRect.minXMinYCorner();
1965 quad[2] = innerRect.minXMaxYCorner();
1966 quad[3] = outerRect.minXMaxYCorner();
1967
1968 if (!innerBorder.radii().topLeft().isZero())
1969 findInnerVertex(outerRect.minXMinYCorner(), innerRect.minXMinYCorner(), centerPoint, quad[1]);
1970
1971 if (!innerBorder.radii().bottomLeft().isZero())
1972 findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[2]);
1973 break;
1974
1975 case BSBottom:
1976 quad[0] = outerRect.minXMaxYCorner();
1977 quad[1] = innerRect.minXMaxYCorner();
1978 quad[2] = innerRect.maxXMaxYCorner();
1979 quad[3] = outerRect.maxXMaxYCorner();
1980
1981 if (!innerBorder.radii().bottomLeft().isZero())
1982 findInnerVertex(outerRect.minXMaxYCorner(), innerRect.minXMaxYCorner(), centerPoint, quad[1]);
1983
1984 if (!innerBorder.radii().bottomRight().isZero())
1985 findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
1986 break;
1987
1988 case BSRight:
1989 quad[0] = outerRect.maxXMinYCorner();
1990 quad[1] = innerRect.maxXMinYCorner();
1991 quad[2] = innerRect.maxXMaxYCorner();
1992 quad[3] = outerRect.maxXMaxYCorner();
1993
1994 if (!innerBorder.radii().topRight().isZero())
1995 findInnerVertex(outerRect.maxXMinYCorner(), innerRect.maxXMinYCorner(), centerPoint, quad[1]);
1996
1997 if (!innerBorder.radii().bottomRight().isZero())
1998 findInnerVertex(outerRect.maxXMaxYCorner(), innerRect.maxXMaxYCorner(), centerPoint, quad[2]);
1999 break;
2000 }
2001
2002 // If the border matches both of its adjacent sides, don't anti-alias the clip, and
2003 // if neither side matches, anti-alias the clip.
2004 if (firstEdgeMatches == secondEdgeMatches) {
2005 graphicsContext->clipConvexPolygon(4, quad, !firstEdgeMatches);
2006 return;
2007 }
2008
2009 // Square off the end which shouldn't be affected by antialiasing, and clip.
2010 FloatPoint firstQuad[4];
2011 firstQuad[0] = quad[0];
2012 firstQuad[1] = quad[1];
2013 firstQuad[2] = side == BSTop || side == BSBottom ? FloatPoint(quad[3].x(), quad[2].y())
2014 : FloatPoint(quad[2].x(), quad[3].y());
2015 firstQuad[3] = quad[3];
2016 graphicsContext->clipConvexPolygon(4, firstQuad, !firstEdgeMatches);
2017
2018 FloatPoint secondQuad[4];
2019 secondQuad[0] = quad[0];
2020 secondQuad[1] = side == BSTop || side == BSBottom ? FloatPoint(quad[0].x(), quad[1].y())
2021 : FloatPoint(quad[1].x(), quad[0].y());
2022 secondQuad[2] = quad[2];
2023 secondQuad[3] = quad[3];
2024 // Antialiasing affects the second side.
2025 graphicsContext->clipConvexPolygon(4, secondQuad, !secondEdgeMatches);
2026 }
2027
getBorderEdgeInfo(BorderEdge edges[],bool includeLogicalLeftEdge,bool includeLogicalRightEdge) const2028 void RenderBoxModelObject::getBorderEdgeInfo(BorderEdge edges[], bool includeLogicalLeftEdge, bool includeLogicalRightEdge) const
2029 {
2030 const RenderStyle* style = this->style();
2031 bool horizontal = style->isHorizontalWritingMode();
2032
2033 edges[BSTop] = BorderEdge(style->borderTopWidth(),
2034 style->visitedDependentColor(CSSPropertyBorderTopColor),
2035 style->borderTopStyle(),
2036 style->borderTopIsTransparent(),
2037 horizontal || includeLogicalLeftEdge);
2038
2039 edges[BSRight] = BorderEdge(style->borderRightWidth(),
2040 style->visitedDependentColor(CSSPropertyBorderRightColor),
2041 style->borderRightStyle(),
2042 style->borderRightIsTransparent(),
2043 !horizontal || includeLogicalRightEdge);
2044
2045 edges[BSBottom] = BorderEdge(style->borderBottomWidth(),
2046 style->visitedDependentColor(CSSPropertyBorderBottomColor),
2047 style->borderBottomStyle(),
2048 style->borderBottomIsTransparent(),
2049 horizontal || includeLogicalRightEdge);
2050
2051 edges[BSLeft] = BorderEdge(style->borderLeftWidth(),
2052 style->visitedDependentColor(CSSPropertyBorderLeftColor),
2053 style->borderLeftStyle(),
2054 style->borderLeftIsTransparent(),
2055 !horizontal || includeLogicalLeftEdge);
2056 }
2057
borderObscuresBackgroundEdge(const FloatSize & contextScale) const2058 bool RenderBoxModelObject::borderObscuresBackgroundEdge(const FloatSize& contextScale) const
2059 {
2060 BorderEdge edges[4];
2061 getBorderEdgeInfo(edges);
2062
2063 for (int i = BSTop; i <= BSLeft; ++i) {
2064 const BorderEdge& currEdge = edges[i];
2065 // FIXME: for vertical text
2066 float axisScale = (i == BSTop || i == BSBottom) ? contextScale.height() : contextScale.width();
2067 if (!currEdge.obscuresBackgroundEdge(axisScale))
2068 return false;
2069 }
2070
2071 return true;
2072 }
2073
areaCastingShadowInHole(const IntRect & holeRect,int shadowBlur,int shadowSpread,const IntSize & shadowOffset)2074 static inline IntRect areaCastingShadowInHole(const IntRect& holeRect, int shadowBlur, int shadowSpread, const IntSize& shadowOffset)
2075 {
2076 IntRect bounds(holeRect);
2077
2078 bounds.inflate(shadowBlur);
2079
2080 if (shadowSpread < 0)
2081 bounds.inflate(-shadowSpread);
2082
2083 IntRect offsetBounds = bounds;
2084 offsetBounds.move(-shadowOffset);
2085 return unionRect(bounds, offsetBounds);
2086 }
2087
paintBoxShadow(GraphicsContext * context,int tx,int ty,int w,int h,const RenderStyle * s,ShadowStyle shadowStyle,bool includeLogicalLeftEdge,bool includeLogicalRightEdge)2088 void RenderBoxModelObject::paintBoxShadow(GraphicsContext* context, int tx, int ty, int w, int h, const RenderStyle* s, ShadowStyle shadowStyle, bool includeLogicalLeftEdge, bool includeLogicalRightEdge)
2089 {
2090 // FIXME: Deal with border-image. Would be great to use border-image as a mask.
2091
2092 if (context->paintingDisabled() || !s->boxShadow())
2093 return;
2094
2095 IntRect borderRect(tx, ty, w, h);
2096 RoundedIntRect border = (shadowStyle == Inset) ? s->getRoundedInnerBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge)
2097 : s->getRoundedBorderFor(borderRect, includeLogicalLeftEdge, includeLogicalRightEdge);
2098
2099 bool hasBorderRadius = s->hasBorderRadius();
2100 bool isHorizontal = s->isHorizontalWritingMode();
2101
2102 bool hasOpaqueBackground = s->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && s->visitedDependentColor(CSSPropertyBackgroundColor).alpha() == 255;
2103 for (const ShadowData* shadow = s->boxShadow(); shadow; shadow = shadow->next()) {
2104 if (shadow->style() != shadowStyle)
2105 continue;
2106
2107 IntSize shadowOffset(shadow->x(), shadow->y());
2108 int shadowBlur = shadow->blur();
2109 int shadowSpread = shadow->spread();
2110
2111 if (shadowOffset.isZero() && !shadowBlur && !shadowSpread)
2112 continue;
2113
2114 const Color& shadowColor = shadow->color();
2115
2116 if (shadow->style() == Normal) {
2117 RoundedIntRect fillRect = border;
2118 fillRect.inflate(shadowSpread);
2119 if (fillRect.isEmpty())
2120 continue;
2121
2122 IntRect shadowRect(border.rect());
2123 shadowRect.inflate(shadowBlur + shadowSpread);
2124 shadowRect.move(shadowOffset);
2125
2126 GraphicsContextStateSaver stateSaver(*context);
2127 context->clip(shadowRect);
2128
2129 // Move the fill just outside the clip, adding 1 pixel separation so that the fill does not
2130 // bleed in (due to antialiasing) if the context is transformed.
2131 IntSize extraOffset(w + max(0, shadowOffset.width()) + shadowBlur + 2 * shadowSpread + 1, 0);
2132 shadowOffset -= extraOffset;
2133 fillRect.move(extraOffset);
2134
2135 if (shadow->isWebkitBoxShadow())
2136 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2137 else
2138 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2139
2140 if (hasBorderRadius) {
2141 RoundedIntRect rectToClipOut = border;
2142
2143 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2144 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2145 // corners. Those are avoided by insetting the clipping path by one pixel.
2146 if (hasOpaqueBackground) {
2147 rectToClipOut.inflateWithRadii(-1);
2148 }
2149
2150 if (!rectToClipOut.isEmpty())
2151 context->clipOutRoundedRect(rectToClipOut);
2152
2153 fillRect.expandRadii(shadowSpread);
2154 context->fillRoundedRect(fillRect, Color::black, s->colorSpace());
2155 } else {
2156 IntRect rectToClipOut = border.rect();
2157
2158 // If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
2159 // when painting the shadow. On the other hand, it introduces subpixel gaps along the
2160 // edges if they are not pixel-aligned. Those are avoided by insetting the clipping path
2161 // by one pixel.
2162 if (hasOpaqueBackground) {
2163 AffineTransform currentTransformation = context->getCTM();
2164 if (currentTransformation.a() != 1 || (currentTransformation.d() != 1 && currentTransformation.d() != -1)
2165 || currentTransformation.b() || currentTransformation.c())
2166 rectToClipOut.inflate(-1);
2167 }
2168
2169 if (!rectToClipOut.isEmpty())
2170 context->clipOut(rectToClipOut);
2171 context->fillRect(fillRect.rect(), Color::black, s->colorSpace());
2172 }
2173 } else {
2174 // Inset shadow.
2175 IntRect holeRect(border.rect());
2176 holeRect.inflate(-shadowSpread);
2177
2178 if (holeRect.isEmpty()) {
2179 if (hasBorderRadius)
2180 context->fillRoundedRect(border, shadowColor, s->colorSpace());
2181 else
2182 context->fillRect(border.rect(), shadowColor, s->colorSpace());
2183 continue;
2184 }
2185
2186 if (!includeLogicalLeftEdge) {
2187 if (isHorizontal) {
2188 holeRect.move(-max(shadowOffset.width(), 0) - shadowBlur, 0);
2189 holeRect.setWidth(holeRect.width() + max(shadowOffset.width(), 0) + shadowBlur);
2190 } else {
2191 holeRect.move(0, -max(shadowOffset.height(), 0) - shadowBlur);
2192 holeRect.setHeight(holeRect.height() + max(shadowOffset.height(), 0) + shadowBlur);
2193 }
2194 }
2195 if (!includeLogicalRightEdge) {
2196 if (isHorizontal)
2197 holeRect.setWidth(holeRect.width() - min(shadowOffset.width(), 0) + shadowBlur);
2198 else
2199 holeRect.setHeight(holeRect.height() - min(shadowOffset.height(), 0) + shadowBlur);
2200 }
2201
2202 Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255);
2203
2204 IntRect outerRect = areaCastingShadowInHole(border.rect(), shadowBlur, shadowSpread, shadowOffset);
2205 RoundedIntRect roundedHole(holeRect, border.radii());
2206
2207 GraphicsContextStateSaver stateSaver(*context);
2208 if (hasBorderRadius) {
2209 Path path;
2210 path.addRoundedRect(border);
2211 context->clip(path);
2212 roundedHole.shrinkRadii(shadowSpread);
2213 } else
2214 context->clip(border.rect());
2215
2216 IntSize extraOffset(2 * w + max(0, shadowOffset.width()) + shadowBlur - 2 * shadowSpread + 1, 0);
2217 context->translate(extraOffset.width(), extraOffset.height());
2218 shadowOffset -= extraOffset;
2219
2220 if (shadow->isWebkitBoxShadow())
2221 context->setLegacyShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2222 else
2223 context->setShadow(shadowOffset, shadowBlur, shadowColor, s->colorSpace());
2224
2225 context->fillRectWithRoundedHole(outerRect, roundedHole, fillColor, s->colorSpace());
2226 }
2227 }
2228 }
2229
containingBlockLogicalWidthForContent() const2230 int RenderBoxModelObject::containingBlockLogicalWidthForContent() const
2231 {
2232 return containingBlock()->availableLogicalWidth();
2233 }
2234
continuation() const2235 RenderBoxModelObject* RenderBoxModelObject::continuation() const
2236 {
2237 if (!continuationMap)
2238 return 0;
2239 return continuationMap->get(this);
2240 }
2241
setContinuation(RenderBoxModelObject * continuation)2242 void RenderBoxModelObject::setContinuation(RenderBoxModelObject* continuation)
2243 {
2244 if (continuation) {
2245 if (!continuationMap)
2246 continuationMap = new ContinuationMap;
2247 continuationMap->set(this, continuation);
2248 } else {
2249 if (continuationMap)
2250 continuationMap->remove(this);
2251 }
2252 }
2253
2254 } // namespace WebCore
2255