| OLD | NEW |
| (Empty) |
| 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 blink { | |
| 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 selecte
d 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)), RenderObjectPo
dForHashMultipleOfUchar); | |
| 72 | |
| 73 #ifdef AUTOSIZING_DOM_DEBUG_INFO | |
| 74 static void writeDebugInfo(RenderObject* renderObject, const AtomicString& outpu
t) | |
| 75 { | |
| 76 Node* node = renderObject->node(); | |
| 77 if (node && node->isElementNode()) | |
| 78 toElement(node)->setAttribute("data-autosizing", output, ASSERT_NO_EXCEP
TION); | |
| 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 = ances
tor->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), len
gth / 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(generatingE
lementNode)->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 putInCa
cheIfAbsent) | |
| 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(rendererPare
nt, 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("blink", "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()->textAutosizingWindowSizeOver
ride(); | |
| 209 if (windowInfo.windowSize.isEmpty()) | |
| 210 windowInfo.windowSize = mainFrame->view()->unscaledVisibleContentSize(In
cludeScrollbars); | |
| 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(toLocalFram
e(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(layoutR
oot) : layoutRoot->containingBlock(); | |
| 221 while (container && !isAutosizingContainer(container)) | |
| 222 container = container->containingBlock(); | |
| 223 | |
| 224 RenderBlock* cluster = container; | |
| 225 while (cluster && (!isAutosizingContainer(cluster) || !isIndependentDescenda
nt(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 anywa
y. | |
| 231 if (!cluster || clusterMultiplier(cluster->style()->writingMode(), windowInf
o, | |
| 232 std::numeric_limits<float>::infinity()) == 1.0f) | |
| 233 return false; | |
| 234 | |
| 235 TRACE_EVENT0("blink", "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 TextAutosi
zingWindowInfo& windowInfo, float textWidth) const | |
| 254 { | |
| 255 int logicalWindowWidth = isHorizontalWritingMode(writingMode) ? windowInfo.w
indowSize.width() : windowInfo.windowSize.height(); | |
| 256 int logicalLayoutWidth = isHorizontalWritingMode(writingMode) ? windowInfo.m
inLayoutSize.width() : windowInfo.minLayoutSize.height(); | |
| 257 // Ignore box width in excess of the layout width, to avoid extreme multipli
ers. | |
| 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 scal
e adjustment. | |
| 264 const ViewportDescription& viewportDescription = m_document->page()->depreca
tedLocalMainFrame()->document()->viewportDescription(); | |
| 265 if (!viewportDescription.isSpecifiedByAuthor()) { | |
| 266 float deviceScaleAdjustment = m_document->settings()->deviceScaleAdjustm
ent(); | |
| 267 multiplier *= deviceScaleAdjustment; | |
| 268 } | |
| 269 return std::max(1.0f, multiplier); | |
| 270 } | |
| 271 | |
| 272 void TextAutosizer::processClusterInternal(TextAutosizingClusterInfo& clusterInf
o, RenderBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindow
Info& 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", m
ultiplier))); | |
| 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<TextAutosizingCluster
Info>& clusterInfos) | |
| 286 { | |
| 287 if (clusterInfos.size() == 1 && getGeneratingElementNode(clusterInfos[0].roo
t)) | |
| 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, TextAutosizingClusterIn
fo& value) | |
| 295 { | |
| 296 HashMap<unsigned, OwnPtr<Vector<TextAutosizingClusterInfo> > >::const_iterat
or it = m_nonAutosizedClusters.find(key); | |
| 297 if (it == m_nonAutosizedClusters.end()) { | |
| 298 m_nonAutosizedClusters.add(key, adoptPtr(new Vector<TextAutosizingCluste
rInfo>(1, value))); | |
| 299 return; | |
| 300 } | |
| 301 it->value->append(value); | |
| 302 } | |
| 303 | |
| 304 float TextAutosizer::computeMultiplier(Vector<TextAutosizingClusterInfo>& cluste
rInfos, const TextAutosizingWindowInfo& windowInfo, float textWidth) | |
| 305 { | |
| 306 #ifdef AUTOSIZING_CLUSTER_HASH | |
| 307 // When hashing is enabled this function returns a multiplier based on previ
ously seen clusters. | |
| 308 // It will return a non-unit multiplier if a cluster with the same hash valu
e 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(clusterH
ash); | |
| 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()->writ
ingMode(), 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, Rende
rBlock* container, RenderObject* subtreeRoot, const TextAutosizingWindowInfo& wi
ndowInfo) | |
| 338 { | |
| 339 // Many pages set a max-width on their content. So especially for the Render
View, 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(clust
erInfo.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, mult
iplier); | |
| 350 } | |
| 351 | |
| 352 void TextAutosizer::processCompositeCluster(Vector<TextAutosizingClusterInfo>& c
lusterInfos, 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(c
lusterInfo.root); | |
| 361 maxTextWidth = max<float>(maxTextWidth, clusterInfo.blockContainingAllTe
xt->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].r
oot->style()->writingMode()); | |
| 367 processClusterInternal(clusterInfos[i], clusterInfos[i].root, clusterInf
os[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 n
ot 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(clu
ster, cluster); | |
| 392 while (descendant) { | |
| 393 if (descendant->isText()) { | |
| 394 if (localMultiplier != 1 && descendant->style()->textAutosizingMulti
plier() == 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, c
luster); | |
| 404 } | |
| 405 } | |
| 406 | |
| 407 void TextAutosizer::processContainer(float multiplier, RenderBlock* container, T
extAutosizingClusterInfo& clusterInfo, RenderObject* subtreeRoot, const TextAuto
sizingWindowInfo& 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(contai
ner)) ? multiplier: 1; | |
| 415 | |
| 416 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(sub
treeRoot, subtreeRoot); | |
| 417 while (descendant) { | |
| 418 if (descendant->isText()) { | |
| 419 if (localMultiplier != 1 && descendant->style()->textAutosizingMulti
plier() == 1) { | |
| 420 setMultiplier(descendant, localMultiplier); | |
| 421 setMultiplier(descendant->parent(), localMultiplier); // Parent
does line spacing. | |
| 422 | |
| 423 if (RenderListItem* listItemAncestor = getAncestorListItem(desce
ndant)) { | |
| 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) || isIndependent
Descendant(descendantBlock)) | |
| 434 processCluster(descendantClusterInfo, descendantBlock, descendan
tBlock, 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, desce
ndantBlock, windowInfo); | |
| 441 } | |
| 442 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, s
ubtreeRoot); | |
| 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 multiplie
r) | |
| 455 { | |
| 456 #if ENABLE(ASSERT) | |
| 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 bool TextAutosizer::isAutosizingContainer(const RenderObject* renderer) | |
| 471 { | |
| 472 // "Autosizing containers" are the smallest unit for which we can | |
| 473 // enable/disable Text Autosizing. | |
| 474 // - Must not be inline, as different multipliers on one line looks terrible
. | |
| 475 // Exceptions are inline-block and alike elements (inline-table, -webkit-i
nline-*), | |
| 476 // as they often contain entire multi-line columns of text. | |
| 477 // - Must not be list items, as items in the same list should look consisten
t (*). | |
| 478 // - Must not be normal list items, as items in the same list should look | |
| 479 // consistent, unless they are floating or position:absolute/fixed. | |
| 480 Node* node = renderer->generatingNode(); | |
| 481 if ((node && !node->hasChildren()) | |
| 482 || !renderer->isRenderBlock() | |
| 483 || (renderer->isInline() && !renderer->style()->isDisplayReplacedType())
) | |
| 484 return false; | |
| 485 if (renderer->isListItem()) | |
| 486 return renderer->isFloating() || renderer->isOutOfFlowPositioned(); | |
| 487 // Avoid creating containers for text within text controls, buttons, or <sel
ect> buttons. | |
| 488 Node* parentNode = renderer->parent() ? renderer->parent()->generatingNode()
: 0; | |
| 489 if (parentNode && parentNode->isElementNode() && formInputTags().contains(to
Element(parentNode)->tagQName())) | |
| 490 return false; | |
| 491 | |
| 492 return true; | |
| 493 } | |
| 494 | |
| 495 bool TextAutosizer::isNarrowDescendant(const RenderBlock* renderer, TextAutosizi
ngClusterInfo& parentClusterInfo) | |
| 496 { | |
| 497 ASSERT(isAutosizingContainer(renderer)); | |
| 498 | |
| 499 // Autosizing containers that are significantly narrower than the |blockCont
ainingAllText| of | |
| 500 // their enclosing cluster may be acting as separate columns, hence must be
autosized | |
| 501 // separately. For example the 2nd div in: | |
| 502 // <body> | |
| 503 // <div style="float: right; width: 50%"></div> | |
| 504 // <div style="width: 50%"></div> | |
| 505 // <body> | |
| 506 // is the left column, and should be autosized differently from the body. | |
| 507 // If however the container is only narrower by 150px or less, it's consider
ed part of | |
| 508 // the enclosing cluster. This 150px limit is adjusted whenever a descendant
container is | |
| 509 // less than 50px narrower than the current limit. | |
| 510 const float differenceFromMaxWidthDifference = 50; | |
| 511 LayoutUnit contentWidth = renderer->contentLogicalWidth(); | |
| 512 LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->cont
entLogicalWidth(); | |
| 513 LayoutUnit widthDifference = clusterTextWidth - contentWidth; | |
| 514 | |
| 515 if (widthDifference - parentClusterInfo.maxAllowedDifferenceFromTextWidth >
differenceFromMaxWidthDifference) | |
| 516 return true; | |
| 517 | |
| 518 parentClusterInfo.maxAllowedDifferenceFromTextWidth = std::max(widthDifferen
ce.toFloat(), parentClusterInfo.maxAllowedDifferenceFromTextWidth); | |
| 519 return false; | |
| 520 } | |
| 521 | |
| 522 bool TextAutosizer::isWiderDescendant(const RenderBlock* renderer, const TextAut
osizingClusterInfo& parentClusterInfo) | |
| 523 { | |
| 524 ASSERT(isAutosizingContainer(renderer)); | |
| 525 | |
| 526 // Autosizing containers that are wider than the |blockContainingAllText| of
their enclosing | |
| 527 // cluster are treated the same way as autosizing clusters to be autosized s
eparately. | |
| 528 LayoutUnit contentWidth = renderer->contentLogicalWidth(); | |
| 529 LayoutUnit clusterTextWidth = parentClusterInfo.blockContainingAllText->cont
entLogicalWidth(); | |
| 530 return contentWidth > clusterTextWidth; | |
| 531 } | |
| 532 | |
| 533 bool TextAutosizer::isIndependentDescendant(const RenderBlock* renderer) | |
| 534 { | |
| 535 ASSERT(isAutosizingContainer(renderer)); | |
| 536 | |
| 537 // "Autosizing clusters" are special autosizing containers within which we | |
| 538 // want to enforce a uniform text size multiplier, in the hopes of making | |
| 539 // the major sections of the page look internally consistent. | |
| 540 // All their descendants (including other autosizing containers) must share | |
| 541 // the same multiplier, except for subtrees which are themselves clusters, | |
| 542 // and some of their descendant containers might not be autosized at all | |
| 543 // (for example if their height is constrained). | |
| 544 // Additionally, clusterShouldBeAutosized requires each cluster to contain a | |
| 545 // minimum amount of text, without which it won't be autosized. | |
| 546 // | |
| 547 // Clusters are chosen using very similar criteria to CSS flow roots, aka | |
| 548 // block formatting contexts (http://w3.org/TR/css3-box/#flow-root), since | |
| 549 // flow roots correspond to box containers that behave somewhat | |
| 550 // independently from their parent (for example they don't overlap floats). | |
| 551 // The definition of a flow root also conveniently includes most of the | |
| 552 // ways that a box and its children can have significantly different width | |
| 553 // from the box's parent (we want to avoid having significantly different | |
| 554 // width blocks within a cluster, since the narrower blocks would end up | |
| 555 // larger than would otherwise be necessary). | |
| 556 RenderBlock* containingBlock = renderer->containingBlock(); | |
| 557 return renderer->isRenderView() | |
| 558 || renderer->isFloating() | |
| 559 || renderer->isOutOfFlowPositioned() | |
| 560 || renderer->isTableCell() | |
| 561 || renderer->isTableCaption() | |
| 562 || renderer->isFlexibleBoxIncludingDeprecated() | |
| 563 || renderer->hasColumns() | |
| 564 || (containingBlock && containingBlock->isHorizontalWritingMode() != ren
derer->isHorizontalWritingMode()) | |
| 565 || renderer->style()->isDisplayReplacedType() | |
| 566 || renderer->isTextArea() | |
| 567 || renderer->style()->userModify() != READ_ONLY; | |
| 568 // FIXME: Tables need special handling to multiply all their columns by | |
| 569 // the same amount even if they're different widths; so do hasColumns() | |
| 570 // containers, and probably flexboxes... | |
| 571 } | |
| 572 | |
| 573 bool TextAutosizer::isAutosizingCluster(const RenderBlock* renderer, TextAutosiz
ingClusterInfo& parentClusterInfo) | |
| 574 { | |
| 575 ASSERT(isAutosizingContainer(renderer)); | |
| 576 | |
| 577 return isNarrowDescendant(renderer, parentClusterInfo) | |
| 578 || isWiderDescendant(renderer, parentClusterInfo) | |
| 579 || isIndependentDescendant(renderer); | |
| 580 } | |
| 581 | |
| 582 bool TextAutosizer::containerShouldBeAutosized(const RenderBlock* container) | |
| 583 { | |
| 584 if (containerContainsOneOfTags(container, formInputTags())) | |
| 585 return false; | |
| 586 | |
| 587 if (containerIsRowOfLinks(container)) | |
| 588 return false; | |
| 589 | |
| 590 // Don't autosize block-level text that can't wrap (as it's likely to | |
| 591 // expand sideways and break the page's layout). | |
| 592 if (!container->style()->autoWrap()) | |
| 593 return false; | |
| 594 | |
| 595 return !contentHeightIsConstrained(container); | |
| 596 } | |
| 597 | |
| 598 bool TextAutosizer::containerContainsOneOfTags(const RenderBlock* container, con
st Vector<QualifiedName>& tags) | |
| 599 { | |
| 600 const RenderObject* renderer = container; | |
| 601 while (renderer) { | |
| 602 const Node* rendererNode = renderer->node(); | |
| 603 if (rendererNode && rendererNode->isElementNode()) { | |
| 604 if (tags.contains(toElement(rendererNode)->tagQName())) | |
| 605 return true; | |
| 606 } | |
| 607 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, conta
iner); | |
| 608 } | |
| 609 | |
| 610 return false; | |
| 611 } | |
| 612 | |
| 613 bool TextAutosizer::containerIsRowOfLinks(const RenderObject* container) | |
| 614 { | |
| 615 // A "row of links" is a container for which holds: | |
| 616 // 1. it should not contain non-link text elements longer than 3 characters | |
| 617 // 2. it should contain min. 3 inline links and all links should | |
| 618 // have the same specified font size | |
| 619 // 3. it should not contain <br> elements | |
| 620 // 4. it should contain only inline elements unless they are containers, | |
| 621 // children of link elements or children of sub-containers. | |
| 622 int linkCount = 0; | |
| 623 RenderObject* renderer = container->nextInPreOrder(container); | |
| 624 float matchingFontSize = -1; | |
| 625 | |
| 626 while (renderer) { | |
| 627 if (!isAutosizingContainer(renderer)) { | |
| 628 if (renderer->isText() && toRenderText(renderer)->text().impl()->str
ipWhiteSpace()->length() > 3) | |
| 629 return false; | |
| 630 if (!renderer->isInline()) | |
| 631 return false; | |
| 632 if (renderer->isBR()) | |
| 633 return false; | |
| 634 } | |
| 635 if (renderer->style()->isLink()) { | |
| 636 if (matchingFontSize < 0) | |
| 637 matchingFontSize = renderer->style()->specifiedFontSize(); | |
| 638 else { | |
| 639 if (matchingFontSize != renderer->style()->specifiedFontSize()) | |
| 640 return false; | |
| 641 } | |
| 642 | |
| 643 linkCount++; | |
| 644 // Skip traversing descendants of the link. | |
| 645 renderer = renderer->nextInPreOrderAfterChildren(container); | |
| 646 } else | |
| 647 renderer = nextInPreOrderSkippingDescendantsOfContainers(renderer, c
ontainer); | |
| 648 } | |
| 649 | |
| 650 return (linkCount >= 3); | |
| 651 } | |
| 652 | |
| 653 bool TextAutosizer::contentHeightIsConstrained(const RenderBlock* container) | |
| 654 { | |
| 655 // FIXME: Propagate constrainedness down the tree, to avoid inefficiently wa
lking back up from each box. | |
| 656 // FIXME: This code needs to take into account vertical writing modes. | |
| 657 // FIXME: Consider additional heuristics, such as ignoring fixed heights if
the content is already overflowing before autosizing kicks in. | |
| 658 for (; container; container = container->containingBlock()) { | |
| 659 RenderStyle* style = container->style(); | |
| 660 if (style->overflowY() >= OSCROLL) | |
| 661 return false; | |
| 662 if (style->height().isSpecified() || style->maxHeight().isSpecified() ||
container->isOutOfFlowPositioned()) { | |
| 663 // Some sites (e.g. wikipedia) set their html and/or body elements t
o height:100%, | |
| 664 // without intending to constrain the height of the content within t
hem. | |
| 665 return !container->isDocumentElement() && !container->isBody(); | |
| 666 } | |
| 667 if (container->isFloating()) | |
| 668 return false; | |
| 669 } | |
| 670 return false; | |
| 671 } | |
| 672 | |
| 673 bool TextAutosizer::compositeClusterShouldBeAutosized(Vector<TextAutosizingClust
erInfo>& clusterInfos, float blockWidth) | |
| 674 { | |
| 675 // Don't autosize clusters that contain less than 4 lines of text (in | |
| 676 // practice less lines are required, since measureDescendantTextWidth | |
| 677 // assumes that characters are 1em wide, but most characters are narrower | |
| 678 // than that, so we're overestimating their contribution to the linecount). | |
| 679 // | |
| 680 // This is to reduce the likelihood of autosizing things like headers and | |
| 681 // footers, which can be quite visually distracting. The rationale is that | |
| 682 // if a cluster contains very few lines of text then it's ok to have to zoom | |
| 683 // in and pan from side to side to read each line, since if there are very | |
| 684 // few lines of text you'll only need to pan across once or twice. | |
| 685 // | |
| 686 // An exception to the 4 lines of text are the textarea and contenteditable | |
| 687 // clusters, which are always autosized by default (i.e. threated as if they | |
| 688 // contain more than 4 lines of text). This is to ensure that the text does | |
| 689 // not suddenly get autosized when the user enters more than 4 lines of text
. | |
| 690 float totalTextWidth = 0; | |
| 691 const float minLinesOfText = 4; | |
| 692 float minTextWidth = blockWidth * minLinesOfText; | |
| 693 for (size_t i = 0; i < clusterInfos.size(); ++i) { | |
| 694 if (clusterInfos[i].root->isTextArea() || (clusterInfos[i].root->style()
&& clusterInfos[i].root->style()->userModify() != READ_ONLY)) | |
| 695 return true; | |
| 696 measureDescendantTextWidth(clusterInfos[i].blockContainingAllText, clust
erInfos[i], minTextWidth, totalTextWidth); | |
| 697 if (totalTextWidth >= minTextWidth) | |
| 698 return true; | |
| 699 } | |
| 700 return false; | |
| 701 } | |
| 702 | |
| 703 void TextAutosizer::measureDescendantTextWidth(const RenderBlock* container, Tex
tAutosizingClusterInfo& clusterInfo, float minTextWidth, float& textWidth) | |
| 704 { | |
| 705 bool skipLocalText = !containerShouldBeAutosized(container); | |
| 706 | |
| 707 RenderObject* descendant = nextInPreOrderSkippingDescendantsOfContainers(con
tainer, container); | |
| 708 while (descendant) { | |
| 709 if (!skipLocalText && descendant->isText()) { | |
| 710 textWidth += toRenderText(descendant)->renderedTextLength() * descen
dant->style()->specifiedFontSize(); | |
| 711 } else if (isAutosizingContainer(descendant)) { | |
| 712 RenderBlock* descendantBlock = toRenderBlock(descendant); | |
| 713 if (!isAutosizingCluster(descendantBlock, clusterInfo)) | |
| 714 measureDescendantTextWidth(descendantBlock, clusterInfo, minText
Width, textWidth); | |
| 715 } | |
| 716 if (textWidth >= minTextWidth) | |
| 717 return; | |
| 718 descendant = nextInPreOrderSkippingDescendantsOfContainers(descendant, c
ontainer); | |
| 719 } | |
| 720 } | |
| 721 | |
| 722 RenderObject* TextAutosizer::nextInPreOrderSkippingDescendantsOfContainers(const
RenderObject* current, const RenderObject* stayWithin) | |
| 723 { | |
| 724 if (current == stayWithin || !isAutosizingContainer(current)) | |
| 725 return current->nextInPreOrder(stayWithin); | |
| 726 return current->nextInPreOrderAfterChildren(stayWithin); | |
| 727 } | |
| 728 | |
| 729 const RenderBlock* TextAutosizer::findDeepestBlockContainingAllText(const Render
Block* cluster) | |
| 730 { | |
| 731 size_t firstDepth = 0; | |
| 732 const RenderObject* firstTextLeaf = findFirstTextLeafNotInCluster(cluster, f
irstDepth, FirstToLast); | |
| 733 if (!firstTextLeaf) | |
| 734 return cluster; | |
| 735 | |
| 736 size_t lastDepth = 0; | |
| 737 const RenderObject* lastTextLeaf = findFirstTextLeafNotInCluster(cluster, la
stDepth, LastToFirst); | |
| 738 ASSERT(lastTextLeaf); | |
| 739 | |
| 740 // Equalize the depths if necessary. Only one of the while loops below will
get executed. | |
| 741 const RenderObject* firstNode = firstTextLeaf; | |
| 742 const RenderObject* lastNode = lastTextLeaf; | |
| 743 while (firstDepth > lastDepth) { | |
| 744 firstNode = firstNode->parent(); | |
| 745 --firstDepth; | |
| 746 } | |
| 747 while (lastDepth > firstDepth) { | |
| 748 lastNode = lastNode->parent(); | |
| 749 --lastDepth; | |
| 750 } | |
| 751 | |
| 752 // Go up from both nodes until the parent is the same. Both pointers will po
int to the LCA then. | |
| 753 while (firstNode != lastNode) { | |
| 754 firstNode = firstNode->parent(); | |
| 755 lastNode = lastNode->parent(); | |
| 756 } | |
| 757 | |
| 758 if (firstNode->isRenderBlock()) | |
| 759 return toRenderBlock(firstNode); | |
| 760 | |
| 761 // containingBlock() should never leave the cluster, since it only skips anc
estors when finding the | |
| 762 // container of position:absolute/fixed blocks, and those cannot exist betwe
en a cluster and its text | |
| 763 // nodes lowest common ancestor as isAutosizingCluster would have made them
into their own independent | |
| 764 // cluster. | |
| 765 RenderBlock* containingBlock = firstNode->containingBlock(); | |
| 766 ASSERT(containingBlock->isDescendantOf(cluster)); | |
| 767 | |
| 768 return containingBlock; | |
| 769 } | |
| 770 | |
| 771 const RenderObject* TextAutosizer::findFirstTextLeafNotInCluster(const RenderObj
ect* parent, size_t& depth, TraversalDirection direction) | |
| 772 { | |
| 773 if (parent->isText()) | |
| 774 return parent; | |
| 775 | |
| 776 ++depth; | |
| 777 const RenderObject* child = (direction == FirstToLast) ? parent->slowFirstCh
ild() : parent->slowLastChild(); | |
| 778 while (child) { | |
| 779 if (!isAutosizingContainer(child) || !isIndependentDescendant(toRenderBl
ock(child))) { | |
| 780 const RenderObject* leaf = findFirstTextLeafNotInCluster(child, dept
h, direction); | |
| 781 if (leaf) | |
| 782 return leaf; | |
| 783 } | |
| 784 child = (direction == FirstToLast) ? child->nextSibling() : child->previ
ousSibling(); | |
| 785 } | |
| 786 --depth; | |
| 787 | |
| 788 return 0; | |
| 789 } | |
| 790 | |
| 791 namespace { | |
| 792 | |
| 793 // Compares the width of the specified cluster's roots in descending order. | |
| 794 bool clusterWiderThanComparisonFn(const TextAutosizingClusterInfo& first, const
TextAutosizingClusterInfo& second) | |
| 795 { | |
| 796 return first.root->contentLogicalWidth() > second.root->contentLogicalWidth(
); | |
| 797 } | |
| 798 | |
| 799 } // namespace | |
| 800 | |
| 801 void TextAutosizer::getNarrowDescendantsGroupedByWidth(const TextAutosizingClust
erInfo& parentClusterInfo, Vector<Vector<TextAutosizingClusterInfo> >& groups) | |
| 802 { | |
| 803 ASSERT(parentClusterInfo.blockContainingAllText); | |
| 804 ASSERT(groups.isEmpty()); | |
| 805 | |
| 806 Vector<TextAutosizingClusterInfo> clusterInfos(parentClusterInfo.narrowDesce
ndants); | |
| 807 if (clusterInfos.isEmpty()) | |
| 808 return; | |
| 809 | |
| 810 std::sort(clusterInfos.begin(), clusterInfos.end(), &clusterWiderThanCompari
sonFn); | |
| 811 groups.grow(1); | |
| 812 | |
| 813 // If the width difference between two consecutive elements of |clusterInfos
| is greater than | |
| 814 // this empirically determined value, the next element should start a new gr
oup. | |
| 815 const float maxWidthDifferenceWithinGroup = 100; | |
| 816 for (size_t i = 0; i < clusterInfos.size(); ++i) { | |
| 817 groups.last().append(clusterInfos[i]); | |
| 818 | |
| 819 if (i + 1 < clusterInfos.size()) { | |
| 820 LayoutUnit currentWidth = clusterInfos[i].root->contentLogicalWidth(
); | |
| 821 LayoutUnit nextWidth = clusterInfos[i + 1].root->contentLogicalWidth
(); | |
| 822 if (currentWidth - nextWidth > maxWidthDifferenceWithinGroup) | |
| 823 groups.grow(groups.size() + 1); | |
| 824 } | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 void TextAutosizer::trace(Visitor* visitor) | |
| 829 { | |
| 830 visitor->trace(m_document); | |
| 831 } | |
| 832 | |
| 833 } // namespace blink | |
| OLD | NEW |