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