1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * Copyright (C) 2012 Apple Inc. All rights reserved. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 */ 20 21 #include "config.h" 22 #include "core/rendering/TextAutosizer.h" 23 24 #include <algorithm> 25 26 #include "core/dom/Document.h" 27 #include "core/html/HTMLElement.h" 28 #include "core/inspector/InspectorInstrumentation.h" 29 #include "core/page/Settings.h" 30 #include "core/platform/chromium/TraceEvent.h" 31 #include "core/platform/graphics/IntSize.h" 32 #include "core/rendering/RenderListItem.h" 33 #include "core/rendering/RenderObject.h" 34 #include "core/rendering/RenderText.h" 35 #include "core/rendering/RenderView.h" 36 #include "core/rendering/style/RenderStyle.h" 37 #include "core/rendering/style/StyleInheritedData.h" 38 #include "wtf/StdLibExtras.h" 39 #include "wtf/Vector.h" 40 41 namespace WebCore { 42 43 using namespace HTMLNames; 44 45 struct TextAutosizingWindowInfo { 46 IntSize windowSize; 47 IntSize minLayoutSize; 48 }; 49 50 // Represents cluster related data. Instances should not persist between calls to processSubtree. 51 struct TextAutosizingClusterInfo { 52 explicit TextAutosizingClusterInfo(RenderBlock* root) 53 : root(root) 54 , blockContainingAllText(0) 55 , maxAllowedDifferenceFromTextWidth(150) 56 { 57 } 58 59 RenderBlock* root; 60 const RenderBlock* blockContainingAllText; 61 62 // Upper limit on the difference between the width of the cluster's block containing all 63 // text and that of a narrow child before the child becomes a separate cluster. 64 float maxAllowedDifferenceFromTextWidth; 65 66 // Descendants of the cluster that are narrower than the block containing all text and must be 67 // processed together. 68 Vector<TextAutosizingClusterInfo> narrowDescendants; 69 }; 70 71 72 static const Vector<QualifiedName>& formInputTags() 73 { 74 // Returns the tags for the form input elements. 75 DEFINE_STATIC_LOCAL(Vector<QualifiedName>, formInputTags, ()); 76 if (formInputTags.isEmpty()) { 77 formInputTags.append(inputTag); 78 formInputTags.append(buttonTag); 79 formInputTags.append(selectTag); 80 } 81 return formInputTags; 82 } 83 84 static RenderListItem* getAncestorListItem(const RenderObject* renderer) 85 { 86 RenderObject* ancestor = renderer->parent(); 87 while (ancestor && (ancestor->isRenderInline() || ancestor->isAnonymousBlock())) 88 ancestor = ancestor->parent(); 89 90 return (ancestor && ancestor->isListItem()) ? toRenderListItem(ancestor) : 0; 91 } 92 93 static RenderObject* getAncestorList(const RenderObject* renderer) 94 { 95 // FIXME: Add support for <menu> elements as a possible ancestor of an <li> element, 96 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#the-li-element 97 for (RenderObject* ancestor = renderer->parent(); ancestor; ancestor = ancestor->parent()) { 98 Node* parentNode = ancestor->generatingNode(); 99 if (parentNode && (parentNode->hasTagName(olTag) || parentNode->hasTagName(ulTag))) 100 return ancestor; 101 } 102 return 0; 103 } 104 105 TextAutosizer::TextAutosizer(Document* document) 106 : m_document(document) 107 { 108 } 109 110 TextAutosizer::~TextAutosizer() 111 { 112 } 113 114 void TextAutosizer::recalculateMultipliers() 115 { 116 RenderObject* renderer = m_document->renderer(); 117 while (renderer) { 118 if (renderer->style() && renderer->style()->textAutosizingMultiplier() != 1) 119 setMultiplier(renderer, 1); 120 renderer = renderer->nextInPreOrder(); 121 } 122 } 123 124 bool TextAutosizer::processSubtree(RenderObject* layoutRoot) 125 { 126 TRACE_EVENT0("webkit", "TextAutosizer::processSubtree"); 127 128 // FIXME: Text Autosizing should only be enabled when m_document->page()->mainFrame()->view()->useFixedLayout() 129 // is true, but for now it's useful to ignore this so that it can be tested on desktop. 130 if (!m_document->settings() || !m_document->settings()->textAutosizingEnabled() || layoutRoot->view()->printing() || !m_document->page()) 131 return false; 132 133 Frame* mainFrame = m_document->page()->mainFrame(); 134 135 TextAutosizingWindowInfo windowInfo; 136 137 // Window area, in logical (density-independent) pixels. 138 windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride(); 139 if (windowInfo.windowSize.isEmpty()) { 140 bool includeScrollbars = !InspectorInstrumentation::shouldApplyScreenWidthOverride(mainFrame); 141 windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(includeScrollbars ? ScrollableArea::IncludeScrollbars : ScrollableArea::ExcludeScrollbars); 142 } 143 144 // Largest area of block that can be visible at once (assuming the main 145 // frame doesn't get scaled to less than overview scale), in CSS pixels. 146 windowInfo.minLayoutSize = mainFrame->view()->layoutSize(); 147 for (Frame* frame = m_document->frame(); frame; frame = frame->tree()->parent()) 148 windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(frame->view()->layoutSize()); 149 150 // The layoutRoot could be neither a container nor a cluster, so walk up the tree till we find each of these. 151 RenderBlock* container = layoutRoot->isRenderBlock() ? toRenderBlock(layoutRoot) : layoutRoot->containingBlock(); 152 while (container && !isAutosizingContainer(container)) 153 container = container->containingBlock(); 154 155 RenderBlock* cluster = container; 156 while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster))) 157 cluster = cluster->containingBlock(); 158 159 TextAutosizingClusterInfo clusterInfo(cluster); 160 processCluster(clusterInfo, container, layoutRoot, windowInfo); 161 return true; 162 } 163 164 float TextAutosizer::clusterMultiplier(WritingMode writingMode, const TextAutosizingWindowInfo& windowInfo, float textWidth) const 165 { 166 int logicalWindowWidth = isHorizontalWritingMode(writingMode) ? windowInfo.windowSize.width() : windowInfo.windowSize.height(); 167 int logicalLayoutWidth = isHorizontalWritingMode(writingMode) ? windowInfo.minLayoutSize.width() : windowInfo.minLayoutSize.height(); 168 // Ignore box width in excess of the layout width, to avoid extreme multipliers. 169 float logicalClusterWidth = std::min<float>(textWidth, logicalLayoutWidth); 170 171 float multiplier = logicalClusterWidth / logicalWindowWidth; 172 multiplier *= m_document->settings()->textAutosizingFontScaleFactor(); 173 return std::max(1.0f, multiplier); 174 } 175 176 void TextAutosizer::processClusterInternal(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo, float multiplier) 177 { 178 processContainer(multiplier, container, clusterInfo, subtreeRoot, windowInfo); 179 180 Vector<Vector<TextAutosizingClusterInfo> > narrowDescendantsGroups; 181 getNarrowDescendantsGroupedByWidth(clusterInfo, narrowDescendantsGroups); 182 for (size_t i = 0; i < narrowDescendantsGroups.size(); ++i) 183 processCompositeCluster(narrowDescendantsGroups[i], windowInfo); 184 } 185 186 void TextAutosizer::processCluster(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo) 187 { 188 // Many pages set a max-width on their content. So especially for the RenderView, instead of 189 // just taking the width of |cluster| we find the lowest common ancestor of the first and last 190 // descendant text node of the cluster (i.e. the deepest wrapper block that contains all the 191 // text), and use its width instead. 192 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root); 193 float textWidth = clusterInfo.blockContainingAllText->contentLogicalWidth(); 194 float multiplier = 1.0; 195 if (clusterShouldBeAutosized(clusterInfo, textWidth)) 196 multiplier = clusterMultiplier(clusterInfo.root->style()->writingMode(), windowInfo, textWidth); 197 processClusterInternal(clusterInfo, container, subtreeRoot, windowInfo, multiplier); 198 } 199 200 void TextAutosizer::processCompositeCluster(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo) 201 { 202 if (clusterInfos.isEmpty()) 203 return; 204 205 float maxTextWidth = 0; 206 for (size_t i = 0; i < clusterInfos.size(); ++i) { 207 TextAutosizingClusterInfo& clusterInfo = clusterInfos[i]; 208 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root); 209 maxTextWidth = max<float>(maxTextWidth, clusterInfo.blockContainingAllText->contentLogicalWidth()); 210 } 211 212 float multiplier = 1.0; 213 if (compositeClusterShouldBeAutosized(clusterInfos, maxTextWidth)) 214 multiplier = clusterMultiplier(clusterInfos[0].root->style()->writingMode(), windowInfo, maxTextWidth); 215 for (size_t i = 0; i < clusterInfos.size(); ++i) { 216 ASSERT(clusterInfos[i].root->style()->writingMode() == clusterInfos[0].root->style()->writingMode()); 217 processClusterInternal(clusterInfos[i], clusterInfos[i].root, clusterInfos[i].root, windowInfo, multiplier); 218 } 219 } 220 221 void TextAutosizer::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo) 222 { 223 ASSERT(isAutosizingContainer(container)); 224 225 float localMultiplier = containerShouldBeAutosized(container) ? multiplier: 1; 226 227 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot); 228 while (descendant) { 229 if (descendant->isText()) { 230 if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) { 231 setMultiplier(descendant, localMultiplier); 232 setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing. 233 234 if (RenderListItem* listItemAncestor = getAncestorListItem(descendant)) { 235 if (RenderObject* list = getAncestorList(listItemAncestor)) { 236 if (list->style()->textAutosizingMultiplier() == 1) 237 setMultiplierForList(list, localMultiplier); 238 } 239 } 240 } 241 } else if (isAutosizingContainer(descendant)) { 242 RenderBlock* descendantBlock = toRenderBlock(descendant); 243 TextAutosizingClusterInfo descendantClusterInfo(descendantBlock); 244 if (isWiderDescendant(descendantBlock, clusterInfo) || isIndependentDescendant(descendantBlock)) 245 processCluster(descendantClusterInfo, descendantBlock, descendantBlock, windowInfo); 246 else if (isNarrowDescendant(descendantBlock, clusterInfo)) { 247 // Narrow descendants are processed together later to be able to apply the same multiplier 248 // to each of them if necessary. 249 clusterInfo.narrowDescendants.append(descendantClusterInfo); 250 } else 251 processContainer(multiplier, descendantBlock, clusterInfo, descendantBlock, windowInfo); 252 } 253 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, subtreeRoot); 254 } 255 } 256 257 void TextAutosizer::setMultiplier(RenderObject* renderer, float multiplier) 258 { 259 // FIXME: Investigate if a clone() is needed and whether it does the right thing w.r.t. style sharing. 260 RefPtr<RenderStyle> newStyle = RenderStyle::clone(renderer->style()); 261 newStyle->setTextAutosizingMultiplier(multiplier); 262 renderer->setStyle(newStyle.release()); 263 } 264 265 void TextAutosizer::setMultiplierForList(RenderObject* renderer, float multiplier) 266 { 267 #ifndef NDEBUG 268 Node* parentNode = renderer->generatingNode(); 269 ASSERT(parentNode); 270 ASSERT(parentNode->hasTagName(olTag) || parentNode->hasTagName(ulTag)); 271 #endif 272 setMultiplier(renderer, multiplier); 273 274 // Make sure all list items are autosized consistently. 275 for (RenderObject* child = renderer->firstChild(); child; child = child->nextSibling()) { 276 if (child->isListItem() && child->style()->textAutosizingMultiplier() == 1) 277 setMultiplier(child, multiplier); 278 } 279 } 280 281 float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier) 282 { 283 // Somewhat arbitrary "pleasant" font size. 284 const float pleasantSize = 16; 285 286 // Multiply fonts that the page author has specified to be larger than 287 // pleasantSize by less and less, until huge fonts are not increased at all. 288 // For specifiedSize between 0 and pleasantSize we directly apply the 289 // multiplier; hence for specifiedSize == pleasantSize, computedSize will be 290 // multiplier * pleasantSize. For greater specifiedSizes we want to 291 // gradually fade out the multiplier, so for every 1px increase in 292 // specifiedSize beyond pleasantSize we will only increase computedSize 293 // by gradientAfterPleasantSize px until we meet the 294 // computedSize = specifiedSize line, after which we stay on that line (so 295 // then every 1px increase in specifiedSize increases computedSize by 1px). 296 const float gradientAfterPleasantSize = 0.5; 297 298 float computedSize; 299 if (specifiedSize <= pleasantSize) 300 computedSize = multiplier * specifiedSize; 301 else { 302 computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize); 303 if (computedSize < specifiedSize) 304 computedSize = specifiedSize; 305 } 306 return computedSize; 307 } 308 309 bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer) 310 { 311 // "Autosizing containers" are the smallest unit for which we can 312 // enable/disable Text Autosizing. 313 // - Must not be inline, as different multipliers on one line looks terrible. 314 // Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*), 315 // as they often contain entire multi-line columns of text. 316 // - Must not be list items, as items in the same list should look consistent (*). 317 // - Must not be normal list items, as items in the same list should look 318 // consistent, unless they are floating or position:absolute/fixed. 319 if (!renderer->isRenderBlock() || (renderer->isInline() && !renderer->style()->isDisplayReplacedType())) 320 return false; 321 if (renderer->isListItem()) 322 return renderer->isFloating() || renderer->isOutOfFlowPositioned(); 323 // Avoid creating containers for text within text controls, buttons, or <select> buttons. 324 Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode() : 0; 325 if (parentNode && parentNode->isElementNode() && formInputTags().contains(toElement(parentNode)->tagQName())) 326 return false; 327 328 return true; 329 } 330 331 bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo) 332 { 333 ASSERT(isAutosizingContainer(renderer)); 334 335 // Autosizing containers that are significantly narrower than the |blockContainingAllText| of 336 // their enclosing cluster may be acting as separate columns, hence must be autosized 337 // separately. For example the 2nd div in: 338 // <body> 339 // <div style="float: right; width: 50%"></div> 340 // <div style="width: 50%"></div> 341 // <body> 342 // is the left column, and should be autosized differently from the body. 343 // If however the container is only narrower by 150px or less, it's considered part of 344 // the enclosing cluster. This 150px limit is adjusted whenever a descendant container is 345 // less than 50px narrower than the current limit. 346 const float differenceFromMaxWidthDifference = 50; 347 float contentWidth = renderer->contentLogicalWidth(); 348 float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth(); 349 float widthDifference = clusterTextWidth - contentWidth; 350 351 if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth > differenceFromMaxWidthDifference) 352 return true; 353 354 parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifference, parentClusterInfo.maxAllowedDifferenceFromTextWidth); 355 return false; 356 } 357 358 bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAutosizingClusterInfo& parentClusterInfo) 359 { 360 ASSERT(isAutosizingContainer(renderer)); 361 362 // Autosizing containers that are wider than the |blockContainingAllText| of their enclosing 363 // cluster are treated the same way as autosizing clusters to be autosized separately. 364 float contentWidth = renderer->contentLogicalWidth(); 365 float clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth(); 366 return contentWidth > clusterTextWidth; 367 } 368 369 bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer) 370 { 371 ASSERT(isAutosizingContainer(renderer)); 372 373 // "Autosizing clusters" are special autosizing containers within which we 374 // want to enforce a uniform text size multiplier, in the hopes of making 375 // the major sections of the page look internally consistent. 376 // All their descendants (including other autosizing containers) must share 377 // the same multiplier, except for subtrees which are themselves clusters, 378 // and some of their descendant containers might not be autosized at all 379 // (for example if their height is constrained). 380 // Additionally, clusterShouldBeAutosized requires each cluster to contain a 381 // minimum amount of text, without which it won't be autosized. 382 // 383 // Clusters are chosen using very similar criteria to CSS flow roots, aka 384 // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since 385 // flow roots correspond to box containers that behave somewhat 386 // independently from their parent (for example they don't overlap floats). 387 // The definition of a flow root also conveniently includes most of the 388 // ways that a box and its children can have significantly different width 389 // from the box's parent (we want to avoid having significantly different 390 // width blocks within a cluster, since the narrower blocks would end up 391 // larger than would otherwise be necessary). 392 return renderer->isRenderView() 393 || renderer->isFloating() 394 || renderer->isOutOfFlowPositioned() 395 || renderer->isTableCell() 396 || renderer->isTableCaption() 397 || renderer->isFlexibleBoxIncludingDeprecated() 398 || renderer->hasColumns() 399 || renderer->containingBlock()->isHorizontalWritingMode() != renderer->isHorizontalWritingMode() 400 || renderer->style()->isDisplayReplacedType() 401 || renderer->isTextArea() 402 || renderer->style()->userModify() != READ_ONLY; 403 // FIXME: Tables need special handling to multiply all their columns by 404 // the same amount even if they're different widths; so do hasColumns() 405 // containers, and probably flexboxes... 406 } 407 408 bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo) 409 { 410 ASSERT(isAutosizingContainer(renderer)); 411 412 return isNarrowDescendant(renderer, parentClusterInfo) 413 || isWiderDescendant(renderer, parentClusterInfo) 414 || isIndependentDescendant(renderer); 415 } 416 417 bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container) 418 { 419 if (containerContainsOneOfTags(container, formInputTags())) 420 return false; 421 422 if (containerIsRowOfLinks(container)) 423 return false; 424 425 // Don't autosize block-level text that can't wrap (as it's likely to 426 // expand sideways and break the page's layout). 427 if (!container->style()->autoWrap()) 428 return false; 429 430 return !contentHeightIsConstrained(container); 431 } 432 433 bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, const Vector<QualifiedName>& tags) 434 { 435 const RenderObject* renderer = container; 436 while (renderer) { 437 const Node* rendererNode = renderer->node(); 438 if (rendererNode && rendererNode->isElementNode()) { 439 if (tags.contains(toElement(rendererNode)->tagQName())) 440 return true; 441 } 442 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container); 443 } 444 445 return false; 446 } 447 448 bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container) 449 { 450 // A "row of links" is a container for which holds: 451 // 1. it should not contain non-link text elements longer than 3 characters 452 // 2. it should contain min. 3 inline links and all links should 453 // have the same specified font size 454 // 3. it should not contain <br> elements 455 // 4. it should contain only inline elements unless they are containers, 456 // children of link elements or children of sub-containers. 457 int linkCount = 0; 458 RenderObject* renderer = container->nextInPreOrder(container); 459 float matchingFontSize = -1; 460 461 while (renderer) { 462 if (!isAutosizingContainer(renderer)) { 463 if (renderer->isText() && toRenderText(renderer)->text().impl()->stripWhiteSpace()->length() > 3) 464 return false; 465 if (!renderer->isInline()) 466 return false; 467 if (renderer->isBR()) 468 return false; 469 } 470 if (renderer->style()->isLink()) { 471 if (matchingFontSize < 0) 472 matchingFontSize = renderer->style()->specifiedFontSize(); 473 else { 474 if (matchingFontSize != renderer->style()->specifiedFontSize()) 475 return false; 476 } 477 478 linkCount++; 479 // Skip traversing descendants of the link. 480 renderer = renderer->nextInPreOrderAfterChildren(container); 481 } else 482 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container); 483 } 484 485 return (linkCount >= 3); 486 } 487 488 bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container) 489 { 490 // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box. 491 // FIXME: This code needs to take into account vertical writing modes. 492 // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in. 493 for (; container; container = container->containingBlock()) { 494 RenderStyle* style = container->style(); 495 if (style->overflowY() >= OSCROLL) 496 return false; 497 if (style->height().isSpecified() || style->maxHeight().isSpecified() || container->isOutOfFlowPositioned()) { 498 // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%, 499 // without intending to constrain the height of the content within them. 500 return !container->isRoot() && !container->isBody(); 501 } 502 if (container->isFloating()) 503 return false; 504 } 505 return false; 506 } 507 508 bool TextAutosizer::clusterShouldBeAutosized(TextAutosizingClusterInfo& clusterInfo, float blockWidth) 509 { 510 Vector<TextAutosizingClusterInfo> clusterInfos(1, clusterInfo); 511 return compositeClusterShouldBeAutosized(clusterInfos, blockWidth); 512 } 513 514 bool TextAutosizer::compositeClusterShouldBeAutosized(Vector<TextAutosizingClusterInfo>& clusterInfos, float blockWidth) 515 { 516 // Don't autosize clusters that contain less than 4 lines of text (in 517 // practice less lines are required, since measureDescendantTextWidth 518 // assumes that characters are 1em wide, but most characters are narrower 519 // than that, so we're overestimating their contribution to the linecount). 520 // 521 // This is to reduce the likelihood of autosizing things like headers and 522 // footers, which can be quite visually distracting. The rationale is that 523 // if a cluster contains very few lines of text then it's ok to have to zoom 524 // in and pan from side to side to read each line, since if there are very 525 // few lines of text you'll only need to pan across once or twice. 526 // 527 // An exception to the 4 lines of text are the textarea and contenteditable 528 // clusters, which are always autosized by default (i.e. threated as if they 529 // contain more than 4 lines of text). This is to ensure that the text does 530 // not suddenly get autosized when the user enters more than 4 lines of text. 531 float totalTextWidth = 0; 532 const float minLinesOfText = 4; 533 float minTextWidth = blockWidth * minLinesOfText; 534 for (size_t i = 0; i < clusterInfos.size(); ++i) { 535 if (clusterInfos[i].root->isTextArea() || (clusterInfos[i].root->style() && clusterInfos[i].root->style()->userModify() != READ_ONLY)) 536 return true; 537 measureDescendantTextWidth(clusterInfos[i].blockContainingAllText, clusterInfos[i], minTextWidth, totalTextWidth); 538 if (totalTextWidth >= minTextWidth) 539 return true; 540 } 541 return false; 542 } 543 544 void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth) 545 { 546 bool skipLocalText = !containerShouldBeAutosized(container); 547 548 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(container, container); 549 while (descendant) { 550 if (!skipLocalText && descendant->isText()) { 551 textWidth += toRenderText(descendant)->renderedTextLength() * descendant->style()->specifiedFontSize(); 552 } else if (isAutosizingContainer(descendant)) { 553 RenderBlock* descendantBlock = toRenderBlock(descendant); 554 if (!isAutosizingCluster(descendantBlock, clusterInfo)) 555 measureDescendantTextWidth(descendantBlock, clusterInfo, minTextWidth, textWidth); 556 } 557 if (textWidth >= minTextWidth) 558 return; 559 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, container); 560 } 561 } 562 563 RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const RenderObject* current, const RenderObject* stayWithin) 564 { 565 if (current == stayWithin || !isAutosizingContainer(current)) 566 return current->nextInPreOrder(stayWithin); 567 return current->nextInPreOrderAfterChildren(stayWithin); 568 } 569 570 const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const RenderBlock* cluster) 571 { 572 size_t firstDepth = 0; 573 const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, firstDepth, FirstToLast); 574 if (!firstTextLeaf) 575 return cluster; 576 577 size_t lastDepth = 0; 578 const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, lastDepth, LastToFirst); 579 ASSERT(lastTextLeaf); 580 581 // Equalize the depths if necessary. Only one of the while loops below will get executed. 582 const RenderObject* firstNode = firstTextLeaf; 583 const RenderObject* lastNode = lastTextLeaf; 584 while (firstDepth > lastDepth) { 585 firstNode = firstNode->parent(); 586 --firstDepth; 587 } 588 while (lastDepth > firstDepth) { 589 lastNode = lastNode->parent(); 590 --lastDepth; 591 } 592 593 // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then. 594 while (firstNode != lastNode) { 595 firstNode = firstNode->parent(); 596 lastNode = lastNode->parent(); 597 } 598 599 if (firstNode->isRenderBlock()) 600 return toRenderBlock(firstNode); 601 602 // containingBlock() should never leave the cluster, since it only skips ancestors when finding the 603 // container of position:absolute/fixed blocks, and those cannot exist between a cluster and its text 604 // nodes lowest common ancestor as isAutosizingCluster would have made them into their own independent 605 // cluster. 606 RenderBlock* containingBlock = firstNode->containingBlock(); 607 ASSERT(containingBlock->isDescendantOf(cluster)); 608 609 return containingBlock; 610 } 611 612 const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObject* parent, size_t& depth, TraversalDirection direction) 613 { 614 if (parent->isEmpty()) 615 return parent->isText() ? parent : 0; 616 617 ++depth; 618 const RenderObject* child = (direction == FirstToLast) ? parent->firstChild() : parent->lastChild(); 619 while (child) { 620 if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBlock(child))) { 621 const RenderObject* leaf = findFirstTextLeafNotInCluster(child, depth, direction); 622 if (leaf) 623 return leaf; 624 } 625 child = (direction == FirstToLast) ? child->nextSibling() : child->previousSibling(); 626 } 627 --depth; 628 629 return 0; 630 } 631 632 namespace { 633 634 // Compares the width of the specified cluster's roots in descending order. 635 bool clusterWiderThanComparisonFn(const TextAutosizingClusterInfo& first, const TextAutosizingClusterInfo& second) 636 { 637 return first.root->contentLogicalWidth() > second.root->contentLogicalWidth(); 638 } 639 640 } // namespace 641 642 void TextAutosizer::getNarrowDescendantsGroupedByWidth(const TextAutosizingClusterInfo& parentClusterInfo, Vector<Vector<TextAutosizingClusterInfo> >& groups) 643 { 644 ASSERT(parentClusterInfo.blockContainingAllText); 645 ASSERT(groups.isEmpty()); 646 647 Vector<TextAutosizingClusterInfo> clusterInfos(parentClusterInfo.narrowDescendants); 648 if (clusterInfos.isEmpty()) 649 return; 650 651 std::sort(clusterInfos.begin(), clusterInfos.end(), &clusterWiderThanComparisonFn); 652 groups.grow(1); 653 654 // If the width difference between two consecutive elements of |clusterInfos| is greater than 655 // this empirically determined value, the next element should start a new group. 656 const float maxWidthDifferenceWithinGroup = 100; 657 for (size_t i = 0; i < clusterInfos.size(); ++i) { 658 groups.last().append(clusterInfos[i]); 659 660 if (i + 1 < clusterInfos.size()) { 661 float currentWidth = clusterInfos[i].root->contentLogicalWidth(); 662 float nextWidth = clusterInfos[i + 1].root->contentLogicalWidth(); 663 if (currentWidth - nextWidth > maxWidthDifferenceWithinGroup) 664 groups.grow(groups.size() + 1); 665 } 666 } 667 } 668 669 } // namespace WebCore 670