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