1 /* 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * Copyright (C) 2000 Dirk Mueller (mueller (at) kde.org) 4 * Copyright (C) 2004, 2006, 2007 Apple Inc. All rights reserved. 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Library General Public 8 * License as published by the Free Software Foundation; either 9 * version 2 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Library General Public License for more details. 15 * 16 * You should have received a copy of the GNU Library General Public License 17 * along with this library; see the file COPYING.LIB. If not, write to 18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 * Boston, MA 02110-1301, USA. 20 * 21 */ 22 23 #include "config.h" 24 #include "RenderReplaced.h" 25 26 #include "GraphicsContext.h" 27 #include "RenderBlock.h" 28 #include "RenderLayer.h" 29 #include "RenderTheme.h" 30 #include "RenderView.h" 31 #include "VisiblePosition.h" 32 33 using namespace std; 34 35 namespace WebCore { 36 37 const int cDefaultWidth = 300; 38 const int cDefaultHeight = 150; 39 40 RenderReplaced::RenderReplaced(Node* node) 41 : RenderBox(node) 42 , m_intrinsicSize(cDefaultWidth, cDefaultHeight) 43 , m_hasIntrinsicSize(false) 44 { 45 setReplaced(true); 46 } 47 48 RenderReplaced::RenderReplaced(Node* node, const IntSize& intrinsicSize) 49 : RenderBox(node) 50 , m_intrinsicSize(intrinsicSize) 51 , m_hasIntrinsicSize(true) 52 { 53 setReplaced(true); 54 } 55 56 RenderReplaced::~RenderReplaced() 57 { 58 } 59 60 void RenderReplaced::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) 61 { 62 RenderBox::styleDidChange(diff, oldStyle); 63 64 bool hadStyle = (oldStyle != 0); 65 float oldZoom = hadStyle ? oldStyle->effectiveZoom() : RenderStyle::initialZoom(); 66 if (style() && style()->effectiveZoom() != oldZoom) 67 intrinsicSizeChanged(); 68 } 69 70 void RenderReplaced::layout() 71 { 72 ASSERT(needsLayout()); 73 74 LayoutRepainter repainter(*this, checkForRepaintDuringLayout()); 75 76 setHeight(minimumReplacedHeight()); 77 78 computeLogicalWidth(); 79 computeLogicalHeight(); 80 81 m_overflow.clear(); 82 addShadowOverflow(); 83 updateLayerTransform(); 84 85 repainter.repaintAfterLayout(); 86 setNeedsLayout(false); 87 } 88 89 void RenderReplaced::intrinsicSizeChanged() 90 { 91 int scaledWidth = static_cast<int>(cDefaultWidth * style()->effectiveZoom()); 92 int scaledHeight = static_cast<int>(cDefaultHeight * style()->effectiveZoom()); 93 m_intrinsicSize = IntSize(scaledWidth, scaledHeight); 94 setNeedsLayoutAndPrefWidthsRecalc(); 95 } 96 97 void RenderReplaced::paint(PaintInfo& paintInfo, int tx, int ty) 98 { 99 if (!shouldPaint(paintInfo, tx, ty)) 100 return; 101 102 tx += x(); 103 ty += y(); 104 105 if (hasBoxDecorations() && (paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection)) 106 paintBoxDecorations(paintInfo, tx, ty); 107 108 if (paintInfo.phase == PaintPhaseMask) { 109 paintMask(paintInfo, tx, ty); 110 return; 111 } 112 113 if ((paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) && style()->outlineWidth()) 114 paintOutline(paintInfo.context, tx, ty, width(), height()); 115 116 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseSelection) 117 return; 118 119 if (!paintInfo.shouldPaintWithinRoot(this)) 120 return; 121 122 bool drawSelectionTint = selectionState() != SelectionNone && !document()->printing(); 123 if (paintInfo.phase == PaintPhaseSelection) { 124 if (selectionState() == SelectionNone) 125 return; 126 drawSelectionTint = false; 127 } 128 129 bool completelyClippedOut = false; 130 if (style()->hasBorderRadius()) { 131 IntRect borderRect = IntRect(tx, ty, width(), height()); 132 133 if (borderRect.isEmpty()) 134 completelyClippedOut = true; 135 else { 136 // Push a clip if we have a border radius, since we want to round the foreground content that gets painted. 137 paintInfo.context->save(); 138 paintInfo.context->addRoundedRectClip(style()->getRoundedBorderFor(borderRect)); 139 } 140 } 141 142 if (!completelyClippedOut) { 143 paintReplaced(paintInfo, tx, ty); 144 145 if (style()->hasBorderRadius()) 146 paintInfo.context->restore(); 147 } 148 149 // The selection tint never gets clipped by border-radius rounding, since we want it to run right up to the edges of 150 // surrounding content. 151 if (drawSelectionTint) { 152 IntRect selectionPaintingRect = localSelectionRect(); 153 selectionPaintingRect.move(tx, ty); 154 paintInfo.context->fillRect(selectionPaintingRect, selectionBackgroundColor(), style()->colorSpace()); 155 } 156 } 157 158 bool RenderReplaced::shouldPaint(PaintInfo& paintInfo, int& tx, int& ty) 159 { 160 if (paintInfo.phase != PaintPhaseForeground && paintInfo.phase != PaintPhaseOutline && paintInfo.phase != PaintPhaseSelfOutline 161 && paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseMask) 162 return false; 163 164 if (!paintInfo.shouldPaintWithinRoot(this)) 165 return false; 166 167 // if we're invisible or haven't received a layout yet, then just bail. 168 if (style()->visibility() != VISIBLE) 169 return false; 170 171 int currentTX = tx + x(); 172 int currentTY = ty + y(); 173 174 // Early exit if the element touches the edges. 175 int top = currentTY + minYVisualOverflow(); 176 int bottom = currentTY + maxYVisualOverflow(); 177 if (isSelected() && m_inlineBoxWrapper) { 178 int selTop = ty + m_inlineBoxWrapper->root()->selectionTop(); 179 int selBottom = ty + selTop + m_inlineBoxWrapper->root()->selectionHeight(); 180 top = min(selTop, top); 181 bottom = max(selBottom, bottom); 182 } 183 184 int os = 2 * maximalOutlineSize(paintInfo.phase); 185 if (currentTX + minXVisualOverflow() >= paintInfo.rect.maxX() + os || currentTX + maxXVisualOverflow() <= paintInfo.rect.x() - os) 186 return false; 187 if (top >= paintInfo.rect.maxY() + os || bottom <= paintInfo.rect.y() - os) 188 return false; 189 190 return true; 191 } 192 193 static inline bool lengthIsSpecified(Length length) 194 { 195 LengthType lengthType = length.type(); 196 return lengthType == Fixed || lengthType == Percent; 197 } 198 199 int RenderReplaced::computeReplacedLogicalWidth(bool includeMaxWidth) const 200 { 201 int logicalWidth; 202 if (lengthIsSpecified(style()->width())) 203 logicalWidth = computeReplacedLogicalWidthUsing(style()->logicalWidth()); 204 else if (m_hasIntrinsicSize) 205 logicalWidth = calcAspectRatioLogicalWidth(); 206 else 207 logicalWidth = intrinsicLogicalWidth(); 208 209 int minLogicalWidth = computeReplacedLogicalWidthUsing(style()->logicalMinWidth()); 210 int maxLogicalWidth = !includeMaxWidth || style()->logicalMaxWidth().isUndefined() ? logicalWidth : computeReplacedLogicalWidthUsing(style()->logicalMaxWidth()); 211 212 return max(minLogicalWidth, min(logicalWidth, maxLogicalWidth)); 213 } 214 215 int RenderReplaced::computeReplacedLogicalHeight() const 216 { 217 int logicalHeight; 218 if (lengthIsSpecified(style()->logicalHeight())) 219 logicalHeight = computeReplacedLogicalHeightUsing(style()->logicalHeight()); 220 else if (m_hasIntrinsicSize) 221 logicalHeight = calcAspectRatioLogicalHeight(); 222 else 223 logicalHeight = intrinsicLogicalHeight(); 224 225 int minLogicalHeight = computeReplacedLogicalHeightUsing(style()->logicalMinHeight()); 226 int maxLogicalHeight = style()->logicalMaxHeight().isUndefined() ? logicalHeight : computeReplacedLogicalHeightUsing(style()->logicalMaxHeight()); 227 228 return max(minLogicalHeight, min(logicalHeight, maxLogicalHeight)); 229 } 230 231 int RenderReplaced::calcAspectRatioLogicalWidth() const 232 { 233 int intrinsicWidth = intrinsicLogicalWidth(); 234 int intrinsicHeight = intrinsicLogicalHeight(); 235 if (!intrinsicHeight) 236 return 0; 237 return RenderBox::computeReplacedLogicalHeight() * intrinsicWidth / intrinsicHeight; 238 } 239 240 int RenderReplaced::calcAspectRatioLogicalHeight() const 241 { 242 int intrinsicWidth = intrinsicLogicalWidth(); 243 int intrinsicHeight = intrinsicLogicalHeight(); 244 if (!intrinsicWidth) 245 return 0; 246 return RenderBox::computeReplacedLogicalWidth() * intrinsicHeight / intrinsicWidth; 247 } 248 249 void RenderReplaced::computePreferredLogicalWidths() 250 { 251 ASSERT(preferredLogicalWidthsDirty()); 252 253 int borderAndPadding = borderAndPaddingWidth(); 254 m_maxPreferredLogicalWidth = computeReplacedLogicalWidth(false) + borderAndPadding; 255 256 if (style()->maxWidth().isFixed() && style()->maxWidth().value() != undefinedLength) 257 m_maxPreferredLogicalWidth = min(m_maxPreferredLogicalWidth, style()->maxWidth().value() + (style()->boxSizing() == CONTENT_BOX ? borderAndPadding : 0)); 258 259 if (style()->width().isPercent() || style()->height().isPercent() 260 || style()->maxWidth().isPercent() || style()->maxHeight().isPercent() 261 || style()->minWidth().isPercent() || style()->minHeight().isPercent()) 262 m_minPreferredLogicalWidth = 0; 263 else 264 m_minPreferredLogicalWidth = m_maxPreferredLogicalWidth; 265 266 setPreferredLogicalWidthsDirty(false); 267 } 268 269 unsigned RenderReplaced::caretMaxRenderedOffset() const 270 { 271 return 1; 272 } 273 274 VisiblePosition RenderReplaced::positionForPoint(const IntPoint& point) 275 { 276 InlineBox* box = inlineBoxWrapper(); 277 if (!box) 278 return createVisiblePosition(0, DOWNSTREAM); 279 280 // FIXME: This code is buggy if the replaced element is relative positioned. 281 282 RootInlineBox* root = box->root(); 283 284 int top = root->selectionTop(); 285 int bottom = root->selectionBottom(); 286 287 int blockDirectionPosition = box->isHorizontal() ? point.y() + y() : point.x() + x(); 288 int lineDirectionPosition = box->isHorizontal() ? point.x() + x() : point.y() + y(); 289 290 if (blockDirectionPosition < top) 291 return createVisiblePosition(caretMinOffset(), DOWNSTREAM); // coordinates are above 292 293 if (blockDirectionPosition >= bottom) 294 return createVisiblePosition(caretMaxOffset(), DOWNSTREAM); // coordinates are below 295 296 if (node()) { 297 if (lineDirectionPosition <= box->logicalLeft() + (box->logicalWidth() / 2)) 298 return createVisiblePosition(0, DOWNSTREAM); 299 return createVisiblePosition(1, DOWNSTREAM); 300 } 301 302 return RenderBox::positionForPoint(point); 303 } 304 305 IntRect RenderReplaced::selectionRectForRepaint(RenderBoxModelObject* repaintContainer, bool clipToVisibleContent) 306 { 307 ASSERT(!needsLayout()); 308 309 if (!isSelected()) 310 return IntRect(); 311 312 IntRect rect = localSelectionRect(); 313 if (clipToVisibleContent) 314 computeRectForRepaint(repaintContainer, rect); 315 else 316 rect = localToContainerQuad(FloatRect(rect), repaintContainer).enclosingBoundingBox(); 317 318 return rect; 319 } 320 321 IntRect RenderReplaced::localSelectionRect(bool checkWhetherSelected) const 322 { 323 if (checkWhetherSelected && !isSelected()) 324 return IntRect(); 325 326 if (!m_inlineBoxWrapper) 327 // We're a block-level replaced element. Just return our own dimensions. 328 return IntRect(0, 0, width(), height()); 329 330 RootInlineBox* root = m_inlineBoxWrapper->root(); 331 int newLogicalTop = root->block()->style()->isFlippedBlocksWritingMode() ? m_inlineBoxWrapper->logicalBottom() - root->selectionBottom() : root->selectionTop() - m_inlineBoxWrapper->logicalTop(); 332 if (root->block()->style()->isHorizontalWritingMode()) 333 return IntRect(0, newLogicalTop, width(), root->selectionHeight()); 334 return IntRect(newLogicalTop, 0, root->selectionHeight(), height()); 335 } 336 337 void RenderReplaced::setSelectionState(SelectionState s) 338 { 339 RenderBox::setSelectionState(s); // The selection state for our containing block hierarchy is updated by the base class call. 340 if (m_inlineBoxWrapper) { 341 RootInlineBox* line = m_inlineBoxWrapper->root(); 342 if (line) 343 line->setHasSelectedChildren(isSelected()); 344 } 345 } 346 347 bool RenderReplaced::isSelected() const 348 { 349 SelectionState s = selectionState(); 350 if (s == SelectionNone) 351 return false; 352 if (s == SelectionInside) 353 return true; 354 355 int selectionStart, selectionEnd; 356 selectionStartEnd(selectionStart, selectionEnd); 357 if (s == SelectionStart) 358 return selectionStart == 0; 359 360 int end = node()->hasChildNodes() ? node()->childNodeCount() : 1; 361 if (s == SelectionEnd) 362 return selectionEnd == end; 363 if (s == SelectionBoth) 364 return selectionStart == 0 && selectionEnd == end; 365 366 ASSERT(0); 367 return false; 368 } 369 370 IntSize RenderReplaced::intrinsicSize() const 371 { 372 return m_intrinsicSize; 373 } 374 375 void RenderReplaced::setIntrinsicSize(const IntSize& size) 376 { 377 ASSERT(m_hasIntrinsicSize); 378 m_intrinsicSize = size; 379 } 380 381 IntRect RenderReplaced::clippedOverflowRectForRepaint(RenderBoxModelObject* repaintContainer) 382 { 383 if (style()->visibility() != VISIBLE && !enclosingLayer()->hasVisibleContent()) 384 return IntRect(); 385 386 // The selectionRect can project outside of the overflowRect, so take their union 387 // for repainting to avoid selection painting glitches. 388 IntRect r = unionRect(localSelectionRect(false), visualOverflowRect()); 389 390 RenderView* v = view(); 391 if (v) { 392 // FIXME: layoutDelta needs to be applied in parts before/after transforms and 393 // repaint containers. https://bugs.webkit.org/show_bug.cgi?id=23308 394 r.move(v->layoutDelta()); 395 } 396 397 if (style()) { 398 if (style()->hasAppearance()) 399 // The theme may wish to inflate the rect used when repainting. 400 theme()->adjustRepaintRect(this, r); 401 if (v) 402 r.inflate(style()->outlineSize()); 403 } 404 computeRectForRepaint(repaintContainer, r); 405 return r; 406 } 407 408 } 409