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