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/frame/LocalFrame.h" 28 #include "core/frame/Settings.h" 29 #include "core/frame/UseCounter.h" 30 #include "core/html/HTMLElement.h" 31 #include "core/page/Page.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 "platform/TraceEvent.h" 39 #include "platform/geometry/IntSize.h" 40 #include "wtf/StdLibExtras.h" 41 42 namespace WebCore { 43 44 #define AUTOSIZING_CLUSTER_HASH 45 46 using namespace HTMLNames; 47 48 struct TextAutosizingWindowInfo { 49 IntSize windowSize; 50 IntSize minLayoutSize; 51 }; 52 53 // Represents a POD of a selection of fields for hashing. The fields are selected to detect similar 54 // nodes in the Render Tree from the viewpoint of text autosizing. 55 struct RenderObjectPodForHash { 56 RenderObjectPodForHash() 57 : qualifiedNameHash(0) 58 , packedStyleProperties(0) 59 , width(0) 60 { 61 } 62 ~RenderObjectPodForHash() { } 63 64 unsigned qualifiedNameHash; 65 66 // Style specific selection of signals 67 unsigned packedStyleProperties; 68 float width; 69 }; 70 // To allow for efficient hashing using StringHasher. 71 COMPILE_ASSERT(!(sizeof(RenderObjectPodForHash) % sizeof(UChar)), RenderObjectPodForHashMultipleOfUchar); 72 73 #ifdef AUTOSIZING_DOM_DEBUG_INFO 74 static void writeDebugInfo(RenderObject* renderObject, const AtomicString& output) 75 { 76 Node* node = renderObject->node(); 77 if (node && node->isElementNode()) 78 toElement(node)->setAttribute("data-autosizing", output, ASSERT_NO_EXCEPTION); 79 } 80 #endif 81 82 static const Vector<QualifiedName>& formInputTags() 83 { 84 // Returns the tags for the form input elements. 85 DEFINE_STATIC_LOCAL(Vector<QualifiedName>, formInputTags, ()); 86 if (formInputTags.isEmpty()) { 87 formInputTags.append(inputTag); 88 formInputTags.append(buttonTag); 89 formInputTags.append(selectTag); 90 } 91 return formInputTags; 92 } 93 94 static RenderListItem* getAncestorListItem(const RenderObject* renderer) 95 { 96 RenderObject* ancestor = renderer->parent(); 97 while (ancestor && (ancestor->isRenderInline() || ancestor->isAnonymousBlock())) 98 ancestor = ancestor->parent(); 99 100 return (ancestor && ancestor->isListItem()) ? toRenderListItem(ancestor) : 0; 101 } 102 103 static RenderObject* getAncestorList(const RenderObject* renderer) 104 { 105 // FIXME: Add support for <menu> elements as a possible ancestor of an <li> element, 106 // see http://www.whatwg.org/specs/web-apps/current-work/multipage/grouping-content.html#the-li-element 107 for (RenderObject* ancestor = renderer->parent(); ancestor; ancestor = ancestor->parent()) { 108 Node* parentNode = ancestor->generatingNode(); 109 if (parentNode && (isHTMLOListElement(*parentNode) || isHTMLUListElement(*parentNode))) 110 return ancestor; 111 } 112 return 0; 113 } 114 115 static Node* getGeneratingElementNode(const RenderObject* renderer) 116 { 117 Node* node = renderer->generatingNode(); 118 return (node && node->isElementNode()) ? node : 0; 119 } 120 121 static unsigned hashMemory(const void* data, size_t length) 122 { 123 return StringHasher::computeHash<UChar>(static_cast<const UChar*>(data), length / sizeof(UChar)); 124 } 125 126 static unsigned computeLocalHash(const RenderObject* renderer) 127 { 128 Node* generatingElementNode = getGeneratingElementNode(renderer); 129 ASSERT(generatingElementNode); 130 131 RenderObjectPodForHash podForHash; 132 podForHash.qualifiedNameHash = QualifiedNameHash::hash(toElement(generatingElementNode)->tagQName()); 133 134 if (RenderStyle* style = renderer->style()) { 135 podForHash.packedStyleProperties = style->direction(); 136 podForHash.packedStyleProperties |= (style->position() << 1); 137 podForHash.packedStyleProperties |= (style->floating() << 4); 138 podForHash.packedStyleProperties |= (style->display() << 6); 139 podForHash.packedStyleProperties |= (style->width().type() << 11); 140 // packedStyleProperties effectively using 15 bits now. 141 142 // consider for adding: writing mode, padding. 143 144 podForHash.width = style->width().getFloatValue(); 145 } 146 147 return hashMemory(&podForHash, sizeof(podForHash)); 148 } 149 150 TextAutosizer::TextAutosizer(Document* document) 151 : m_document(document) 152 , m_previouslyAutosized(false) 153 { 154 } 155 156 unsigned TextAutosizer::getCachedHash(const RenderObject* renderer, bool putInCacheIfAbsent) 157 { 158 HashMap<const RenderObject*, unsigned>::const_iterator it = m_hashCache.find(renderer); 159 if (it != m_hashCache.end()) 160 return it->value; 161 162 RenderObject* rendererParent = renderer->parent(); 163 while (rendererParent && !getGeneratingElementNode(rendererParent)) 164 rendererParent = rendererParent->parent(); 165 166 const unsigned parentHashValue = rendererParent ? getCachedHash(rendererParent, true) : 0; 167 const unsigned hashes[2] = { parentHashValue, computeLocalHash(renderer) }; 168 const unsigned combinedHashValue = hashMemory(hashes, sizeof(hashes)); 169 if (putInCacheIfAbsent) 170 m_hashCache.add(renderer, combinedHashValue); 171 return combinedHashValue; 172 } 173 174 bool TextAutosizer::isApplicable() const 175 { 176 return m_document->settings() 177 && m_document->settings()->textAutosizingEnabled() 178 && m_document->page() 179 && m_document->page()->mainFrame() 180 && m_document->page()->deprecatedLocalMainFrame()->loader().stateMachine()->committedFirstRealDocumentLoad(); 181 } 182 183 void TextAutosizer::recalculateMultipliers() 184 { 185 if (!isApplicable() && !m_previouslyAutosized) 186 return; 187 188 RenderObject* renderer = m_document->renderView(); 189 while (renderer) { 190 if (renderer->style() && renderer->style()->textAutosizingMultiplier() != 1) 191 setMultiplier(renderer, 1); 192 renderer = renderer->nextInPreOrder(); 193 } 194 m_previouslyAutosized = false; 195 } 196 197 bool TextAutosizer::processSubtree(RenderObject* layoutRoot) 198 { 199 TRACE_EVENT0("webkit", "TextAutosizer: check if needed"); 200 201 if (!isApplicable() || layoutRoot->view()->document().printing()) 202 return false; 203 204 LocalFrame* mainFrame = m_document->page()->deprecatedLocalMainFrame(); 205 TextAutosizingWindowInfo windowInfo; 206 207 // Window area, in logical (density-independent) pixels. 208 windowInfo.windowSize = m_document->settings()->textAutosizingWindowSizeOverride(); 209 if (windowInfo.windowSize.isEmpty()) 210 windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(IncludeScrollbars); 211 212 // Largest area of block that can be visible at once (assuming the main 213 // frame doesn't get scaled to less than overview scale), in CSS pixels. 214 windowInfo.minLayoutSize = mainFrame->view()->layoutSize(); 215 for (Frame* frame = m_document->frame(); frame; frame = frame->tree().parent()) { 216 windowInfo.minLayoutSize = windowInfo.minLayoutSize.shrunkTo(toLocalFrame(frame)->view()->layoutSize()); 217 } 218 219 // The layoutRoot could be neither a container nor a cluster, so walk up the tree till we find each of these. 220 RenderBlock* container = layoutRoot->isRenderBlock() ? toRenderBlock(layoutRoot) : layoutRoot->containingBlock(); 221 while (container && !isAutosizingContainer(container)) 222 container = container->containingBlock(); 223 224 RenderBlock* cluster = container; 225 while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescendant(cluster))) 226 cluster = cluster->containingBlock(); 227 228 // Skip autosizing for orphaned trees, or if it will have no effect. 229 // Note: this might suppress autosizing of an inner cluster with a different writing mode. 230 // It's not clear what the correct behavior is for mixed writing modes anyway. 231 if (!cluster || clusterMultiplier(cluster->style()->writingMode(), windowInfo, 232 std::numeric_limits<float>::infinity()) == 1.0f) 233 return false; 234 235 TRACE_EVENT0("webkit", "TextAutosizer: process root cluster"); 236 UseCounter::count(*m_document, UseCounter::TextAutosizing); 237 238 TextAutosizingClusterInfo clusterInfo(cluster); 239 processCluster(clusterInfo, container, layoutRoot, windowInfo); 240 241 #ifdef AUTOSIZING_CLUSTER_HASH 242 // Second pass to autosize stale non-autosized clusters for consistency. 243 secondPassProcessStaleNonAutosizedClusters(); 244 m_hashCache.clear(); 245 m_hashToMultiplier.clear(); 246 m_hashesToAutosizeSecondPass.clear(); 247 m_nonAutosizedClusters.clear(); 248 #endif 249 m_previouslyAutosized = true; 250 return true; 251 } 252 253 float TextAutosizer::clusterMultiplier(WritingMode writingMode, const TextAutosizingWindowInfo& windowInfo, float textWidth) const 254 { 255 int logicalWindowWidth = isHorizontalWritingMode(writingMode) ? windowInfo.windowSize.width() : windowInfo.windowSize.height(); 256 int logicalLayoutWidth = isHorizontalWritingMode(writingMode) ? windowInfo.minLayoutSize.width() : windowInfo.minLayoutSize.height(); 257 // Ignore box width in excess of the layout width, to avoid extreme multipliers. 258 float logicalClusterWidth = std::min<float>(textWidth, logicalLayoutWidth); 259 260 float multiplier = logicalClusterWidth / logicalWindowWidth; 261 multiplier *= m_document->settings()->accessibilityFontScaleFactor(); 262 263 // If the page has a meta viewport or @viewport, don't apply the device scale adjustment. 264 const ViewportDescription& viewportDescription = m_document->page()->deprecatedLocalMainFrame()->document()->viewportDescription(); 265 if (!viewportDescription.isSpecifiedByAuthor()) { 266 float deviceScaleAdjustment = m_document->settings()->deviceScaleAdjustment(); 267 multiplier *= deviceScaleAdjustment; 268 } 269 return std::max(1.0f, multiplier); 270 } 271 272 void TextAutosizer::processClusterInternal(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo, float multiplier) 273 { 274 processContainer(multiplier, container, clusterInfo, subtreeRoot, windowInfo); 275 #ifdef AUTOSIZING_DOM_DEBUG_INFO 276 writeDebugInfo(clusterInfo.root, AtomicString(String::format("cluster:%f", multiplier))); 277 #endif 278 279 Vector<Vector<TextAutosizingClusterInfo> > narrowDescendantsGroups; 280 getNarrowDescendantsGroupedByWidth(clusterInfo, narrowDescendantsGroups); 281 for (size_t i = 0; i < narrowDescendantsGroups.size(); ++i) 282 processCompositeCluster(narrowDescendantsGroups[i], windowInfo); 283 } 284 285 unsigned TextAutosizer::computeCompositeClusterHash(Vector<TextAutosizingClusterInfo>& clusterInfos) 286 { 287 if (clusterInfos.size() == 1 && getGeneratingElementNode(clusterInfos[0].root)) 288 return getCachedHash(clusterInfos[0].root, false); 289 290 // FIXME: consider hashing clusters for which clusterInfos.size() > 1 291 return 0; 292 } 293 294 void TextAutosizer::addNonAutosizedCluster(unsigned key, TextAutosizingClusterInfo& value) 295 { 296 HashMap<unsigned, OwnPtr<Vector<TextAutosizingClusterInfo> > >::const_iterator it = m_nonAutosizedClusters.find(key); 297 if (it == m_nonAutosizedClusters.end()) { 298 m_nonAutosizedClusters.add(key, adoptPtr(new Vector<TextAutosizingClusterInfo>(1, value))); 299 return; 300 } 301 it->value->append(value); 302 } 303 304 float TextAutosizer::computeMultiplier(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo, float textWidth) 305 { 306 #ifdef AUTOSIZING_CLUSTER_HASH 307 // When hashing is enabled this function returns a multiplier based on previously seen clusters. 308 // It will return a non-unit multiplier if a cluster with the same hash value has been previously 309 // autosized. 310 unsigned clusterHash = computeCompositeClusterHash(clusterInfos); 311 #else 312 unsigned clusterHash = 0; 313 #endif 314 315 if (clusterHash) { 316 HashMap<unsigned, float>::iterator it = m_hashToMultiplier.find(clusterHash); 317 if (it != m_hashToMultiplier.end()) 318 return it->value; 319 } 320 321 if (compositeClusterShouldBeAutosized(clusterInfos, textWidth)) { 322 float multiplier = clusterMultiplier(clusterInfos[0].root->style()->writingMode(), windowInfo, textWidth); 323 if (clusterHash) { 324 if (multiplier > 1 && m_nonAutosizedClusters.contains(clusterHash)) 325 m_hashesToAutosizeSecondPass.append(clusterHash); 326 m_hashToMultiplier.add(clusterHash, multiplier); 327 } 328 return multiplier; 329 } 330 331 if (clusterHash) 332 addNonAutosizedCluster(clusterHash, clusterInfos[0]); 333 334 return 1.0f; 335 } 336 337 void TextAutosizer::processCluster(TextAutosizingClusterInfo& clusterInfo, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo) 338 { 339 // Many pages set a max-width on their content. So especially for the RenderView, instead of 340 // just taking the width of |cluster| we find the lowest common ancestor of the first and last 341 // descendant text node of the cluster (i.e. the deepest wrapper block that contains all the 342 // text), and use its width instead. 343 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root); 344 float textWidth = clusterInfo.blockContainingAllText->contentLogicalWidth().toFloat(); 345 346 Vector<TextAutosizingClusterInfo> clusterInfos(1, clusterInfo); 347 float multiplier = computeMultiplier(clusterInfos, windowInfo, textWidth); 348 349 processClusterInternal(clusterInfo, container, subtreeRoot, windowInfo, multiplier); 350 } 351 352 void TextAutosizer::processCompositeCluster(Vector<TextAutosizingClusterInfo>& clusterInfos, const TextAutosizingWindowInfo& windowInfo) 353 { 354 if (clusterInfos.isEmpty()) 355 return; 356 357 float maxTextWidth = 0; 358 for (size_t i = 0; i < clusterInfos.size(); ++i) { 359 TextAutosizingClusterInfo& clusterInfo = clusterInfos[i]; 360 clusterInfo.blockContainingAllText = findDeepestBlockContainingAllText(clusterInfo.root); 361 maxTextWidth = max<float>(maxTextWidth, clusterInfo.blockContainingAllText->contentLogicalWidth().toFloat()); 362 } 363 364 float multiplier = computeMultiplier(clusterInfos, windowInfo, maxTextWidth); 365 for (size_t i = 0; i < clusterInfos.size(); ++i) { 366 ASSERT(clusterInfos[i].root->style()->writingMode() == clusterInfos[0].root->style()->writingMode()); 367 processClusterInternal(clusterInfos[i], clusterInfos[i].root, clusterInfos[i].root, windowInfo, multiplier); 368 } 369 } 370 371 void TextAutosizer::secondPassProcessStaleNonAutosizedClusters() 372 { 373 for (size_t i = 0; i < m_hashesToAutosizeSecondPass.size(); ++i) { 374 unsigned hash = m_hashesToAutosizeSecondPass[i]; 375 float multiplier = m_hashToMultiplier.get(hash); 376 Vector<TextAutosizingClusterInfo>* val = m_nonAutosizedClusters.get(hash); 377 for (Vector<TextAutosizingClusterInfo>::iterator it2 = val->begin(); it2 != val->end(); ++it2) 378 processStaleContainer(multiplier, (*it2).root, *it2); 379 } 380 } 381 382 void TextAutosizer::processStaleContainer(float multiplier, RenderBlock* cluster, TextAutosizingClusterInfo& clusterInfo) 383 { 384 ASSERT(isAutosizingContainer(cluster)); 385 386 // This method is different from processContainer() mainly in that it does not recurse into sub-clusters. 387 // Multiplier updates are restricted to the specified cluster only. Also the multiplier > 1 by construction 388 // of m_hashesToAutosizeSecondPass, so we don't need to check it explicitly. 389 float localMultiplier = containerShouldBeAutosized(cluster) ? multiplier : 1; 390 391 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(cluster, cluster); 392 while (descendant) { 393 if (descendant->isText()) { 394 if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) { 395 setMultiplier(descendant, localMultiplier); 396 setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing. 397 } 398 } else if (isAutosizingContainer(descendant)) { 399 RenderBlock* descendantBlock = toRenderBlock(descendant); 400 if (!isAutosizingCluster(descendantBlock, clusterInfo)) 401 processStaleContainer(multiplier, descendantBlock, clusterInfo); 402 } 403 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, cluster); 404 } 405 } 406 407 void TextAutosizer::processContainer(float multiplier, RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& windowInfo) 408 { 409 ASSERT(isAutosizingContainer(container)); 410 #ifdef AUTOSIZING_DOM_DEBUG_INFO 411 writeDebugInfo(container, "container"); 412 #endif 413 414 float localMultiplier = (multiplier > 1 && containerShouldBeAutosized(container)) ? multiplier: 1; 415 416 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(subtreeRoot, subtreeRoot); 417 while (descendant) { 418 if (descendant->isText()) { 419 if (localMultiplier != 1 && descendant->style()->textAutosizingMultiplier() == 1) { 420 setMultiplier(descendant, localMultiplier); 421 setMultiplier(descendant->parent(), localMultiplier); // Parent does line spacing. 422 423 if (RenderListItem* listItemAncestor = getAncestorListItem(descendant)) { 424 if (RenderObject* list = getAncestorList(listItemAncestor)) { 425 if (list->style()->textAutosizingMultiplier() == 1) 426 setMultiplierForList(list, localMultiplier); 427 } 428 } 429 } 430 } else if (isAutosizingContainer(descendant)) { 431 RenderBlock* descendantBlock = toRenderBlock(descendant); 432 TextAutosizingClusterInfo descendantClusterInfo(descendantBlock); 433 if (isWiderDescendant(descendantBlock, clusterInfo) || isIndependentDescendant(descendantBlock)) 434 processCluster(descendantClusterInfo, descendantBlock, descendantBlock, windowInfo); 435 else if (isNarrowDescendant(descendantBlock, clusterInfo)) { 436 // Narrow descendants are processed together later to be able to apply the same multiplier 437 // to each of them if necessary. 438 clusterInfo.narrowDescendants.append(descendantClusterInfo); 439 } else 440 processContainer(multiplier, descendantBlock, clusterInfo, descendantBlock, windowInfo); 441 } 442 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, subtreeRoot); 443 } 444 } 445 446 void TextAutosizer::setMultiplier(RenderObject* renderer, float multiplier) 447 { 448 RefPtr<RenderStyle> newStyle = RenderStyle::clone(renderer->style()); 449 newStyle->setTextAutosizingMultiplier(multiplier); 450 newStyle->setUnique(); 451 renderer->setStyle(newStyle.release()); 452 } 453 454 void TextAutosizer::setMultiplierForList(RenderObject* renderer, float multiplier) 455 { 456 #ifndef NDEBUG 457 Node* parentNode = renderer->generatingNode(); 458 ASSERT(parentNode); 459 ASSERT(isHTMLOListElement(parentNode) || isHTMLUListElement(parentNode)); 460 #endif 461 setMultiplier(renderer, multiplier); 462 463 // Make sure all list items are autosized consistently. 464 for (RenderObject* child = renderer->slowFirstChild(); child; child = child->nextSibling()) { 465 if (child->isListItem() && child->style()->textAutosizingMultiplier() == 1) 466 setMultiplier(child, multiplier); 467 } 468 } 469 470 float TextAutosizer::computeAutosizedFontSize(float specifiedSize, float multiplier) 471 { 472 // Somewhat arbitrary "pleasant" font size. 473 const float pleasantSize = 16; 474 475 // Multiply fonts that the page author has specified to be larger than 476 // pleasantSize by less and less, until huge fonts are not increased at all. 477 // For specifiedSize between 0 and pleasantSize we directly apply the 478 // multiplier; hence for specifiedSize == pleasantSize, computedSize will be 479 // multiplier * pleasantSize. For greater specifiedSizes we want to 480 // gradually fade out the multiplier, so for every 1px increase in 481 // specifiedSize beyond pleasantSize we will only increase computedSize 482 // by gradientAfterPleasantSize px until we meet the 483 // computedSize = specifiedSize line, after which we stay on that line (so 484 // then every 1px increase in specifiedSize increases computedSize by 1px). 485 const float gradientAfterPleasantSize = 0.5; 486 487 float computedSize; 488 if (specifiedSize <= pleasantSize) 489 computedSize = multiplier * specifiedSize; 490 else { 491 computedSize = multiplier * pleasantSize + gradientAfterPleasantSize * (specifiedSize - pleasantSize); 492 if (computedSize < specifiedSize) 493 computedSize = specifiedSize; 494 } 495 return computedSize; 496 } 497 498 bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer) 499 { 500 // "Autosizing containers" are the smallest unit for which we can 501 // enable/disable Text Autosizing. 502 // - Must not be inline, as different multipliers on one line looks terrible. 503 // Exceptions are inline-block and alike elements (inline-table, -webkit-inline-*), 504 // as they often contain entire multi-line columns of text. 505 // - Must not be list items, as items in the same list should look consistent (*). 506 // - Must not be normal list items, as items in the same list should look 507 // consistent, unless they are floating or position:absolute/fixed. 508 Node* node = renderer->generatingNode(); 509 if ((node && !node->hasChildren()) 510 || !renderer->isRenderBlock() 511 || (renderer->isInline() && !renderer->style()->isDisplayReplacedType())) 512 return false; 513 if (renderer->isListItem()) 514 return renderer->isFloating() || renderer->isOutOfFlowPositioned(); 515 // Avoid creating containers for text within text controls, buttons, or <select> buttons. 516 Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode() : 0; 517 if (parentNode && parentNode->isElementNode() && formInputTags().contains(toElement(parentNode)->tagQName())) 518 return false; 519 520 return true; 521 } 522 523 bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo) 524 { 525 ASSERT(isAutosizingContainer(renderer)); 526 527 // Autosizing containers that are significantly narrower than the |blockContainingAllText| of 528 // their enclosing cluster may be acting as separate columns, hence must be autosized 529 // separately. For example the 2nd div in: 530 // <body> 531 // <div style="float: right; width: 50%"></div> 532 // <div style="width: 50%"></div> 533 // <body> 534 // is the left column, and should be autosized differently from the body. 535 // If however the container is only narrower by 150px or less, it's considered part of 536 // the enclosing cluster. This 150px limit is adjusted whenever a descendant container is 537 // less than 50px narrower than the current limit. 538 const float differenceFromMaxWidthDifference = 50; 539 LayoutUnit contentWidth = renderer->contentLogicalWidth(); 540 LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth(); 541 LayoutUnit widthDifference = clusterTextWidth - contentWidth; 542 543 if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth > differenceFromMaxWidthDifference) 544 return true; 545 546 parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifference.toFloat(), parentClusterInfo.maxAllowedDifferenceFromTextWidth); 547 return false; 548 } 549 550 bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAutosizingClusterInfo& parentClusterInfo) 551 { 552 ASSERT(isAutosizingContainer(renderer)); 553 554 // Autosizing containers that are wider than the |blockContainingAllText| of their enclosing 555 // cluster are treated the same way as autosizing clusters to be autosized separately. 556 LayoutUnit contentWidth = renderer->contentLogicalWidth(); 557 LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->contentLogicalWidth(); 558 return contentWidth > clusterTextWidth; 559 } 560 561 bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer) 562 { 563 ASSERT(isAutosizingContainer(renderer)); 564 565 // "Autosizing clusters" are special autosizing containers within which we 566 // want to enforce a uniform text size multiplier, in the hopes of making 567 // the major sections of the page look internally consistent. 568 // All their descendants (including other autosizing containers) must share 569 // the same multiplier, except for subtrees which are themselves clusters, 570 // and some of their descendant containers might not be autosized at all 571 // (for example if their height is constrained). 572 // Additionally, clusterShouldBeAutosized requires each cluster to contain a 573 // minimum amount of text, without which it won't be autosized. 574 // 575 // Clusters are chosen using very similar criteria to CSS flow roots, aka 576 // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since 577 // flow roots correspond to box containers that behave somewhat 578 // independently from their parent (for example they don't overlap floats). 579 // The definition of a flow root also conveniently includes most of the 580 // ways that a box and its children can have significantly different width 581 // from the box's parent (we want to avoid having significantly different 582 // width blocks within a cluster, since the narrower blocks would end up 583 // larger than would otherwise be necessary). 584 RenderBlock* containingBlock = renderer->containingBlock(); 585 return renderer->isRenderView() 586 || renderer->isFloating() 587 || renderer->isOutOfFlowPositioned() 588 || renderer->isTableCell() 589 || renderer->isTableCaption() 590 || renderer->isFlexibleBoxIncludingDeprecated() 591 || renderer->hasColumns() 592 || (containingBlock && containingBlock->isHorizontalWritingMode() != renderer->isHorizontalWritingMode()) 593 || renderer->style()->isDisplayReplacedType() 594 || renderer->isTextArea() 595 || renderer->style()->userModify() != READ_ONLY; 596 // FIXME: Tables need special handling to multiply all their columns by 597 // the same amount even if they're different widths; so do hasColumns() 598 // containers, and probably flexboxes... 599 } 600 601 bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosizingClusterInfo& parentClusterInfo) 602 { 603 ASSERT(isAutosizingContainer(renderer)); 604 605 return isNarrowDescendant(renderer, parentClusterInfo) 606 || isWiderDescendant(renderer, parentClusterInfo) 607 || isIndependentDescendant(renderer); 608 } 609 610 bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container) 611 { 612 if (containerContainsOneOfTags(container, formInputTags())) 613 return false; 614 615 if (containerIsRowOfLinks(container)) 616 return false; 617 618 // Don't autosize block-level text that can't wrap (as it's likely to 619 // expand sideways and break the page's layout). 620 if (!container->style()->autoWrap()) 621 return false; 622 623 return !contentHeightIsConstrained(container); 624 } 625 626 bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, const Vector<QualifiedName>& tags) 627 { 628 const RenderObject* renderer = container; 629 while (renderer) { 630 const Node* rendererNode = renderer->node(); 631 if (rendererNode && rendererNode->isElementNode()) { 632 if (tags.contains(toElement(rendererNode)->tagQName())) 633 return true; 634 } 635 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container); 636 } 637 638 return false; 639 } 640 641 bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container) 642 { 643 // A "row of links" is a container for which holds: 644 // 1. it should not contain non-link text elements longer than 3 characters 645 // 2. it should contain min. 3 inline links and all links should 646 // have the same specified font size 647 // 3. it should not contain <br> elements 648 // 4. it should contain only inline elements unless they are containers, 649 // children of link elements or children of sub-containers. 650 int linkCount = 0; 651 RenderObject* renderer = container->nextInPreOrder(container); 652 float matchingFontSize = -1; 653 654 while (renderer) { 655 if (!isAutosizingContainer(renderer)) { 656 if (renderer->isText() && toRenderText(renderer)->text().impl()->stripWhiteSpace()->length() > 3) 657 return false; 658 if (!renderer->isInline()) 659 return false; 660 if (renderer->isBR()) 661 return false; 662 } 663 if (renderer->style()->isLink()) { 664 if (matchingFontSize < 0) 665 matchingFontSize = renderer->style()->specifiedFontSize(); 666 else { 667 if (matchingFontSize != renderer->style()->specifiedFontSize()) 668 return false; 669 } 670 671 linkCount++; 672 // Skip traversing descendants of the link. 673 renderer = renderer->nextInPreOrderAfterChildren(container); 674 } else 675 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, container); 676 } 677 678 return (linkCount >= 3); 679 } 680 681 bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container) 682 { 683 // FIXME: Propagate constrainedness down the tree, to avoid inefficiently walking back up from each box. 684 // FIXME: This code needs to take into account vertical writing modes. 685 // FIXME: Consider additional heuristics, such as ignoring fixed heights if the content is already overflowing before autosizing kicks in. 686 for (; container; container = container->containingBlock()) { 687 RenderStyle* style = container->style(); 688 if (style->overflowY() >= OSCROLL) 689 return false; 690 if (style->height().isSpecified() || style->maxHeight().isSpecified() || container->isOutOfFlowPositioned()) { 691 // Some sites (e.g. wikipedia) set their html and/or body elements to height:100%, 692 // without intending to constrain the height of the content within them. 693 return !container->isDocumentElement() && !container->isBody(); 694 } 695 if (container->isFloating()) 696 return false; 697 } 698 return false; 699 } 700 701 bool TextAutosizer::compositeClusterShouldBeAutosized(Vector<TextAutosizingClusterInfo>& clusterInfos, float blockWidth) 702 { 703 // Don't autosize clusters that contain less than 4 lines of text (in 704 // practice less lines are required, since measureDescendantTextWidth 705 // assumes that characters are 1em wide, but most characters are narrower 706 // than that, so we're overestimating their contribution to the linecount). 707 // 708 // This is to reduce the likelihood of autosizing things like headers and 709 // footers, which can be quite visually distracting. The rationale is that 710 // if a cluster contains very few lines of text then it's ok to have to zoom 711 // in and pan from side to side to read each line, since if there are very 712 // few lines of text you'll only need to pan across once or twice. 713 // 714 // An exception to the 4 lines of text are the textarea and contenteditable 715 // clusters, which are always autosized by default (i.e. threated as if they 716 // contain more than 4 lines of text). This is to ensure that the text does 717 // not suddenly get autosized when the user enters more than 4 lines of text. 718 float totalTextWidth = 0; 719 const float minLinesOfText = 4; 720 float minTextWidth = blockWidth * minLinesOfText; 721 for (size_t i = 0; i < clusterInfos.size(); ++i) { 722 if (clusterInfos[i].root->isTextArea() || (clusterInfos[i].root->style() && clusterInfos[i].root->style()->userModify() != READ_ONLY)) 723 return true; 724 measureDescendantTextWidth(clusterInfos[i].blockContainingAllText, clusterInfos[i], minTextWidth, totalTextWidth); 725 if (totalTextWidth >= minTextWidth) 726 return true; 727 } 728 return false; 729 } 730 731 void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, TextAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth) 732 { 733 bool skipLocalText = !containerShouldBeAutosized(container); 734 735 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(container, container); 736 while (descendant) { 737 if (!skipLocalText && descendant->isText()) { 738 textWidth += toRenderText(descendant)->renderedTextLength() * descendant->style()->specifiedFontSize(); 739 } else if (isAutosizingContainer(descendant)) { 740 RenderBlock* descendantBlock = toRenderBlock(descendant); 741 if (!isAutosizingCluster(descendantBlock, clusterInfo)) 742 measureDescendantTextWidth(descendantBlock, clusterInfo, minTextWidth, textWidth); 743 } 744 if (textWidth >= minTextWidth) 745 return; 746 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, container); 747 } 748 } 749 750 RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const RenderObject* current, const RenderObject* stayWithin) 751 { 752 if (current == stayWithin || !isAutosizingContainer(current)) 753 return current->nextInPreOrder(stayWithin); 754 return current->nextInPreOrderAfterChildren(stayWithin); 755 } 756 757 const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const RenderBlock* cluster) 758 { 759 size_t firstDepth = 0; 760 const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, firstDepth, FirstToLast); 761 if (!firstTextLeaf) 762 return cluster; 763 764 size_t lastDepth = 0; 765 const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, lastDepth, LastToFirst); 766 ASSERT(lastTextLeaf); 767 768 // Equalize the depths if necessary. Only one of the while loops below will get executed. 769 const RenderObject* firstNode = firstTextLeaf; 770 const RenderObject* lastNode = lastTextLeaf; 771 while (firstDepth > lastDepth) { 772 firstNode = firstNode->parent(); 773 --firstDepth; 774 } 775 while (lastDepth > firstDepth) { 776 lastNode = lastNode->parent(); 777 --lastDepth; 778 } 779 780 // Go up from both nodes until the parent is the same. Both pointers will point to the LCA then. 781 while (firstNode != lastNode) { 782 firstNode = firstNode->parent(); 783 lastNode = lastNode->parent(); 784 } 785 786 if (firstNode->isRenderBlock()) 787 return toRenderBlock(firstNode); 788 789 // containingBlock() should never leave the cluster, since it only skips ancestors when finding the 790 // container of position:absolute/fixed blocks, and those cannot exist between a cluster and its text 791 // nodes lowest common ancestor as isAutosizingCluster would have made them into their own independent 792 // cluster. 793 RenderBlock* containingBlock = firstNode->containingBlock(); 794 ASSERT(containingBlock->isDescendantOf(cluster)); 795 796 return containingBlock; 797 } 798 799 const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObject* parent, size_t& depth, TraversalDirection direction) 800 { 801 if (parent->isText()) 802 return parent; 803 804 ++depth; 805 const RenderObject* child = (direction == FirstToLast) ? parent->slowFirstChild() : parent->slowLastChild(); 806 while (child) { 807 if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBlock(child))) { 808 const RenderObject* leaf = findFirstTextLeafNotInCluster(child, depth, direction); 809 if (leaf) 810 return leaf; 811 } 812 child = (direction == FirstToLast) ? child->nextSibling() : child->previousSibling(); 813 } 814 --depth; 815 816 return 0; 817 } 818 819 namespace { 820 821 // Compares the width of the specified cluster's roots in descending order. 822 bool clusterWiderThanComparisonFn(const TextAutosizingClusterInfo& first, const TextAutosizingClusterInfo& second) 823 { 824 return first.root->contentLogicalWidth() > second.root->contentLogicalWidth(); 825 } 826 827 } // namespace 828 829 void TextAutosizer::getNarrowDescendantsGroupedByWidth(const TextAutosizingClusterInfo& parentClusterInfo, Vector<Vector<TextAutosizingClusterInfo> >& groups) 830 { 831 ASSERT(parentClusterInfo.blockContainingAllText); 832 ASSERT(groups.isEmpty()); 833 834 Vector<TextAutosizingClusterInfo> clusterInfos(parentClusterInfo.narrowDescendants); 835 if (clusterInfos.isEmpty()) 836 return; 837 838 std::sort(clusterInfos.begin(), clusterInfos.end(), &clusterWiderThanComparisonFn); 839 groups.grow(1); 840 841 // If the width difference between two consecutive elements of |clusterInfos| is greater than 842 // this empirically determined value, the next element should start a new group. 843 const float maxWidthDifferenceWithinGroup = 100; 844 for (size_t i = 0; i < clusterInfos.size(); ++i) { 845 groups.last().append(clusterInfos[i]); 846 847 if (i + 1 < clusterInfos.size()) { 848 LayoutUnit currentWidth = clusterInfos[i].root->contentLogicalWidth(); 849 LayoutUnit nextWidth = clusterInfos[i + 1].root->contentLogicalWidth(); 850 if (currentWidth - nextWidth > maxWidthDifferenceWithinGroup) 851 groups.grow(groups.size() + 1); 852 } 853 } 854 } 855 856 } // namespace WebCore 857