| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * (C) 1999 Lars Knoll (knoll@kde.org) | |
| 3 * (C) 2000 Dirk Mueller (mueller@kde.org) | |
| 4 * Copyright (C) 2004, 2005, 2006, 2007 Apple Inc. All rights reserved. | |
| 5 * Copyright (C) 2006 Andrew Wellington (proton@wiretapped.net) | |
| 6 * Copyright (C) 2006 Graham Dennis (graham.dennis@gmail.com) | |
| 7 * | |
| 8 * This library is free software; you can redistribute it and/or | |
| 9 * modify it under the terms of the GNU Library General Public | |
| 10 * License as published by the Free Software Foundation; either | |
| 11 * version 2 of the License, or (at your option) any later version. | |
| 12 * | |
| 13 * This library is distributed in the hope that it will be useful, | |
| 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 16 * Library General Public License for more details. | |
| 17 * | |
| 18 * You should have received a copy of the GNU Library General Public License | |
| 19 * along with this library; see the file COPYING.LIB. If not, write to | |
| 20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | |
| 21 * Boston, MA 02110-1301, USA. | |
| 22 * | |
| 23 */ | |
| 24 | |
| 25 #include "config.h" | |
| 26 #include "core/rendering/RenderText.h" | |
| 27 | |
| 28 #include "core/dom/AXObjectCache.h" | |
| 29 #include "core/dom/Text.h" | |
| 30 #include "core/editing/VisiblePosition.h" | |
| 31 #include "core/editing/iterators/TextIterator.h" | |
| 32 #include "core/frame/FrameView.h" | |
| 33 #include "core/frame/Settings.h" | |
| 34 #include "core/html/parser/TextResourceDecoder.h" | |
| 35 #include "core/layout/Layer.h" | |
| 36 #include "core/layout/LayoutBlock.h" | |
| 37 #include "core/layout/LayoutView.h" | |
| 38 #include "core/layout/TextRunConstructor.h" | |
| 39 #include "core/layout/line/AbstractInlineTextBox.h" | |
| 40 #include "core/layout/line/EllipsisBox.h" | |
| 41 #include "core/layout/line/InlineTextBox.h" | |
| 42 #include "core/rendering/RenderCombineText.h" | |
| 43 #include "platform/fonts/Character.h" | |
| 44 #include "platform/fonts/FontCache.h" | |
| 45 #include "platform/geometry/FloatQuad.h" | |
| 46 #include "platform/graphics/paint/DisplayItemList.h" | |
| 47 #include "platform/text/BidiResolver.h" | |
| 48 #include "platform/text/TextBreakIterator.h" | |
| 49 #include "platform/text/TextRunIterator.h" | |
| 50 #include "wtf/text/StringBuffer.h" | |
| 51 #include "wtf/text/StringBuilder.h" | |
| 52 #include "wtf/unicode/CharacterNames.h" | |
| 53 | |
| 54 using namespace WTF; | |
| 55 using namespace Unicode; | |
| 56 | |
| 57 namespace blink { | |
| 58 | |
| 59 struct SameSizeAsRenderText : public LayoutObject { | |
| 60 uint32_t bitfields : 16; | |
| 61 float widths[4]; | |
| 62 String text; | |
| 63 void* pointers[2]; | |
| 64 }; | |
| 65 | |
| 66 static_assert(sizeof(RenderText) == sizeof(SameSizeAsRenderText), "RenderText sh
ould stay small"); | |
| 67 | |
| 68 class SecureTextTimer; | |
| 69 typedef HashMap<RenderText*, SecureTextTimer*> SecureTextTimerMap; | |
| 70 static SecureTextTimerMap* gSecureTextTimers = 0; | |
| 71 | |
| 72 class SecureTextTimer final : public TimerBase { | |
| 73 public: | |
| 74 SecureTextTimer(RenderText* renderText) | |
| 75 : m_renderText(renderText) | |
| 76 , m_lastTypedCharacterOffset(-1) | |
| 77 { | |
| 78 } | |
| 79 | |
| 80 void restartWithNewText(unsigned lastTypedCharacterOffset) | |
| 81 { | |
| 82 m_lastTypedCharacterOffset = lastTypedCharacterOffset; | |
| 83 if (Settings* settings = m_renderText->document().settings()) | |
| 84 startOneShot(settings->passwordEchoDurationInSeconds(), FROM_HERE); | |
| 85 } | |
| 86 void invalidate() { m_lastTypedCharacterOffset = -1; } | |
| 87 unsigned lastTypedCharacterOffset() { return m_lastTypedCharacterOffset; } | |
| 88 | |
| 89 private: | |
| 90 virtual void fired() override | |
| 91 { | |
| 92 ASSERT(gSecureTextTimers->contains(m_renderText)); | |
| 93 m_renderText->setText(m_renderText->text().impl(), true /* forcing setti
ng text as it may be masked later */); | |
| 94 } | |
| 95 | |
| 96 RenderText* m_renderText; | |
| 97 int m_lastTypedCharacterOffset; | |
| 98 }; | |
| 99 | |
| 100 static void makeCapitalized(String* string, UChar previous) | |
| 101 { | |
| 102 if (string->isNull()) | |
| 103 return; | |
| 104 | |
| 105 unsigned length = string->length(); | |
| 106 const StringImpl& input = *string->impl(); | |
| 107 | |
| 108 if (length >= std::numeric_limits<unsigned>::max()) | |
| 109 CRASH(); | |
| 110 | |
| 111 StringBuffer<UChar> stringWithPrevious(length + 1); | |
| 112 stringWithPrevious[0] = previous == noBreakSpace ? space : previous; | |
| 113 for (unsigned i = 1; i < length + 1; i++) { | |
| 114 // Replace   with a real space since ICU no longer treats   as a
word separator. | |
| 115 if (input[i - 1] == noBreakSpace) | |
| 116 stringWithPrevious[i] = space; | |
| 117 else | |
| 118 stringWithPrevious[i] = input[i - 1]; | |
| 119 } | |
| 120 | |
| 121 TextBreakIterator* boundary = wordBreakIterator(stringWithPrevious.character
s(), length + 1); | |
| 122 if (!boundary) | |
| 123 return; | |
| 124 | |
| 125 StringBuilder result; | |
| 126 result.reserveCapacity(length); | |
| 127 | |
| 128 int32_t endOfWord; | |
| 129 int32_t startOfWord = boundary->first(); | |
| 130 for (endOfWord = boundary->next(); endOfWord != TextBreakDone; startOfWord =
endOfWord, endOfWord = boundary->next()) { | |
| 131 if (startOfWord) // Ignore first char of previous string | |
| 132 result.append(input[startOfWord - 1] == noBreakSpace ? noBreakSpace
: toTitleCase(stringWithPrevious[startOfWord])); | |
| 133 for (int i = startOfWord + 1; i < endOfWord; i++) | |
| 134 result.append(input[i - 1]); | |
| 135 } | |
| 136 | |
| 137 *string = result.toString(); | |
| 138 } | |
| 139 | |
| 140 RenderText::RenderText(Node* node, PassRefPtr<StringImpl> str) | |
| 141 : LayoutObject(!node || node->isDocumentNode() ? 0 : node) | |
| 142 , m_hasTab(false) | |
| 143 , m_linesDirty(false) | |
| 144 , m_containsReversedText(false) | |
| 145 , m_knownToHaveNoOverflowAndNoFallbackFonts(false) | |
| 146 , m_minWidth(-1) | |
| 147 , m_maxWidth(-1) | |
| 148 , m_firstLineMinWidth(0) | |
| 149 , m_lastLineLineMinWidth(0) | |
| 150 , m_text(str) | |
| 151 , m_firstTextBox(0) | |
| 152 , m_lastTextBox(0) | |
| 153 { | |
| 154 ASSERT(m_text); | |
| 155 // FIXME: Some clients of RenderText (and subclasses) pass Document as node
to create anonymous renderer. | |
| 156 // They should be switched to passing null and using setDocumentForAnonymous
. | |
| 157 if (node && node->isDocumentNode()) | |
| 158 setDocumentForAnonymous(toDocument(node)); | |
| 159 | |
| 160 m_isAllASCII = m_text.containsOnlyASCII(); | |
| 161 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath(); | |
| 162 setIsText(); | |
| 163 | |
| 164 view()->frameView()->incrementVisuallyNonEmptyCharacterCount(m_text.length()
); | |
| 165 } | |
| 166 | |
| 167 #if ENABLE(ASSERT) | |
| 168 | |
| 169 RenderText::~RenderText() | |
| 170 { | |
| 171 ASSERT(!m_firstTextBox); | |
| 172 ASSERT(!m_lastTextBox); | |
| 173 } | |
| 174 | |
| 175 #endif | |
| 176 | |
| 177 const char* RenderText::renderName() const | |
| 178 { | |
| 179 return "RenderText"; | |
| 180 } | |
| 181 | |
| 182 bool RenderText::isTextFragment() const | |
| 183 { | |
| 184 return false; | |
| 185 } | |
| 186 | |
| 187 bool RenderText::isWordBreak() const | |
| 188 { | |
| 189 return false; | |
| 190 } | |
| 191 | |
| 192 void RenderText::styleDidChange(StyleDifference diff, const LayoutStyle* oldStyl
e) | |
| 193 { | |
| 194 // There is no need to ever schedule paint invalidations from a style change
of a text run, since | |
| 195 // we already did this for the parent of the text run. | |
| 196 // We do have to schedule layouts, though, since a style change can force us
to | |
| 197 // need to relayout. | |
| 198 if (diff.needsFullLayout()) { | |
| 199 setNeedsLayoutAndPrefWidthsRecalc(); | |
| 200 m_knownToHaveNoOverflowAndNoFallbackFonts = false; | |
| 201 } | |
| 202 | |
| 203 const LayoutStyle& newStyle = styleRef(); | |
| 204 ETextTransform oldTransform = oldStyle ? oldStyle->textTransform() : TTNONE; | |
| 205 ETextSecurity oldSecurity = oldStyle ? oldStyle->textSecurity() : TSNONE; | |
| 206 if (oldTransform != newStyle.textTransform() || oldSecurity != newStyle.text
Security()) | |
| 207 transformText(); | |
| 208 | |
| 209 // This is an optimization that kicks off font load before layout. | |
| 210 // In order to make it fast, we only check if the first character of the | |
| 211 // text is included in the unicode ranges of the fonts. | |
| 212 if (!text().containsOnlyWhitespace()) | |
| 213 newStyle.font().willUseFontData(text().characterStartingAt(0)); | |
| 214 } | |
| 215 | |
| 216 void RenderText::removeAndDestroyTextBoxes() | |
| 217 { | |
| 218 if (!documentBeingDestroyed()) { | |
| 219 if (firstTextBox()) { | |
| 220 if (isBR()) { | |
| 221 RootInlineBox* next = firstTextBox()->root().nextRootBox(); | |
| 222 if (next) | |
| 223 next->markDirty(); | |
| 224 } | |
| 225 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBo
x()) | |
| 226 box->remove(); | |
| 227 } else if (parent()) | |
| 228 parent()->dirtyLinesFromChangedChild(this); | |
| 229 } | |
| 230 deleteTextBoxes(); | |
| 231 } | |
| 232 | |
| 233 void RenderText::willBeDestroyed() | |
| 234 { | |
| 235 if (SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers
->take(this) : 0) | |
| 236 delete secureTextTimer; | |
| 237 | |
| 238 removeAndDestroyTextBoxes(); | |
| 239 LayoutObject::willBeDestroyed(); | |
| 240 } | |
| 241 | |
| 242 void RenderText::extractTextBox(InlineTextBox* box) | |
| 243 { | |
| 244 checkConsistency(); | |
| 245 | |
| 246 m_lastTextBox = box->prevTextBox(); | |
| 247 if (box == m_firstTextBox) | |
| 248 m_firstTextBox = 0; | |
| 249 if (box->prevTextBox()) | |
| 250 box->prevTextBox()->setNextTextBox(0); | |
| 251 box->setPreviousTextBox(0); | |
| 252 for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) | |
| 253 curr->setExtracted(); | |
| 254 | |
| 255 checkConsistency(); | |
| 256 } | |
| 257 | |
| 258 void RenderText::attachTextBox(InlineTextBox* box) | |
| 259 { | |
| 260 checkConsistency(); | |
| 261 | |
| 262 if (m_lastTextBox) { | |
| 263 m_lastTextBox->setNextTextBox(box); | |
| 264 box->setPreviousTextBox(m_lastTextBox); | |
| 265 } else | |
| 266 m_firstTextBox = box; | |
| 267 InlineTextBox* last = box; | |
| 268 for (InlineTextBox* curr = box; curr; curr = curr->nextTextBox()) { | |
| 269 curr->setExtracted(false); | |
| 270 last = curr; | |
| 271 } | |
| 272 m_lastTextBox = last; | |
| 273 | |
| 274 checkConsistency(); | |
| 275 } | |
| 276 | |
| 277 void RenderText::removeTextBox(InlineTextBox* box) | |
| 278 { | |
| 279 checkConsistency(); | |
| 280 | |
| 281 if (box == m_firstTextBox) | |
| 282 m_firstTextBox = box->nextTextBox(); | |
| 283 if (box == m_lastTextBox) | |
| 284 m_lastTextBox = box->prevTextBox(); | |
| 285 if (box->nextTextBox()) | |
| 286 box->nextTextBox()->setPreviousTextBox(box->prevTextBox()); | |
| 287 if (box->prevTextBox()) | |
| 288 box->prevTextBox()->setNextTextBox(box->nextTextBox()); | |
| 289 | |
| 290 checkConsistency(); | |
| 291 } | |
| 292 | |
| 293 void RenderText::deleteTextBoxes() | |
| 294 { | |
| 295 if (firstTextBox()) { | |
| 296 InlineTextBox* next; | |
| 297 for (InlineTextBox* curr = firstTextBox(); curr; curr = next) { | |
| 298 next = curr->nextTextBox(); | |
| 299 curr->destroy(); | |
| 300 } | |
| 301 m_firstTextBox = m_lastTextBox = 0; | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 PassRefPtr<StringImpl> RenderText::originalText() const | |
| 306 { | |
| 307 Node* e = node(); | |
| 308 return (e && e->isTextNode()) ? toText(e)->dataImpl() : 0; | |
| 309 } | |
| 310 | |
| 311 String RenderText::plainText() const | |
| 312 { | |
| 313 if (node()) | |
| 314 return blink::plainText(rangeOfContents(node()).get()); | |
| 315 | |
| 316 // FIXME: this is just a stopgap until TextIterator is adapted to support ge
nerated text. | |
| 317 StringBuilder plainTextBuilder; | |
| 318 for (InlineTextBox* textBox = firstTextBox(); textBox; textBox = textBox->ne
xtTextBox()) { | |
| 319 String text = m_text.substring(textBox->start(), textBox->len()).simplif
yWhiteSpace(WTF::DoNotStripWhiteSpace); | |
| 320 plainTextBuilder.append(text); | |
| 321 if (textBox->nextTextBox() && textBox->nextTextBox()->start() > textBox-
>end() && text.length() && !text.right(1).containsOnlyWhitespace()) | |
| 322 plainTextBuilder.append(space); | |
| 323 } | |
| 324 return plainTextBuilder.toString(); | |
| 325 } | |
| 326 | |
| 327 void RenderText::absoluteRects(Vector<IntRect>& rects, const LayoutPoint& accumu
latedOffset) const | |
| 328 { | |
| 329 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) | |
| 330 rects.append(enclosingIntRect(FloatRect(FloatPoint(accumulatedOffset) +
box->topLeft().toFloatPoint(), box->size().toFloatSize()))); | |
| 331 } | |
| 332 | |
| 333 static FloatRect localQuadForTextBox(InlineTextBox* box, unsigned start, unsigne
d end, bool useSelectionHeight) | |
| 334 { | |
| 335 unsigned realEnd = std::min(box->end() + 1, end); | |
| 336 LayoutRect r = box->localSelectionRect(start, realEnd); | |
| 337 if (r.height()) { | |
| 338 if (!useSelectionHeight) { | |
| 339 // Change the height and y position (or width and x for vertical tex
t) | |
| 340 // because selectionRect uses selection-specific values. | |
| 341 if (box->isHorizontal()) { | |
| 342 r.setHeight(box->height()); | |
| 343 r.setY(box->y()); | |
| 344 } else { | |
| 345 r.setWidth(box->width()); | |
| 346 r.setX(box->x()); | |
| 347 } | |
| 348 } | |
| 349 return FloatRect(r); | |
| 350 } | |
| 351 return FloatRect(); | |
| 352 } | |
| 353 | |
| 354 void RenderText::absoluteRectsForRange(Vector<IntRect>& rects, unsigned start, u
nsigned end, bool useSelectionHeight, bool* wasFixed) | |
| 355 { | |
| 356 // Work around signed/unsigned issues. This function takes unsigneds, and is
often passed UINT_MAX | |
| 357 // to mean "all the way to the end". InlineTextBox coordinates are unsigneds
, so changing this | |
| 358 // function to take ints causes various internal mismatches. But selectionRe
ct takes ints, and | |
| 359 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect
to take unsigneds, but | |
| 360 // that would cause many ripple effects, so for now we'll just clamp our uns
igned parameters to INT_MAX. | |
| 361 ASSERT(end == UINT_MAX || end <= INT_MAX); | |
| 362 ASSERT(start <= INT_MAX); | |
| 363 start = std::min(start, static_cast<unsigned>(INT_MAX)); | |
| 364 end = std::min(end, static_cast<unsigned>(INT_MAX)); | |
| 365 | |
| 366 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { | |
| 367 // Note: box->end() returns the index of the last character, not the ind
ex past it | |
| 368 if (start <= box->start() && box->end() < end) { | |
| 369 FloatRect r = box->calculateBoundaries().toFloatRect(); | |
| 370 if (useSelectionHeight) { | |
| 371 LayoutRect selectionRect = box->localSelectionRect(start, end); | |
| 372 if (box->isHorizontal()) { | |
| 373 r.setHeight(selectionRect.height().toFloat()); | |
| 374 r.setY(selectionRect.y().toFloat()); | |
| 375 } else { | |
| 376 r.setWidth(selectionRect.width().toFloat()); | |
| 377 r.setX(selectionRect.x().toFloat()); | |
| 378 } | |
| 379 } | |
| 380 rects.append(localToAbsoluteQuad(r, 0, wasFixed).enclosingBoundingBo
x()); | |
| 381 } else { | |
| 382 // FIXME: This code is wrong. It's converting local to absolute twic
e. http://webkit.org/b/65722 | |
| 383 FloatRect rect = localQuadForTextBox(box, start, end, useSelectionHe
ight); | |
| 384 if (!rect.isZero()) | |
| 385 rects.append(localToAbsoluteQuad(rect, 0, wasFixed).enclosingBou
ndingBox()); | |
| 386 } | |
| 387 } | |
| 388 } | |
| 389 | |
| 390 static IntRect ellipsisRectForBox(InlineTextBox* box, unsigned startPos, unsigne
d endPos) | |
| 391 { | |
| 392 if (!box) | |
| 393 return IntRect(); | |
| 394 | |
| 395 unsigned short truncation = box->truncation(); | |
| 396 if (truncation == cNoTruncation) | |
| 397 return IntRect(); | |
| 398 | |
| 399 IntRect rect; | |
| 400 if (EllipsisBox* ellipsis = box->root().ellipsisBox()) { | |
| 401 int ellipsisStartPosition = std::max<int>(startPos - box->start(), 0); | |
| 402 int ellipsisEndPosition = std::min<int>(endPos - box->start(), box->len(
)); | |
| 403 | |
| 404 // The ellipsis should be considered to be selected if the end of | |
| 405 // the selection is past the beginning of the truncation and the | |
| 406 // beginning of the selection is before or at the beginning of the trunc
ation. | |
| 407 if (ellipsisEndPosition >= truncation && ellipsisStartPosition <= trunca
tion) | |
| 408 return ellipsis->selectionRect(); | |
| 409 } | |
| 410 | |
| 411 return IntRect(); | |
| 412 } | |
| 413 | |
| 414 void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed, Clippin
gOption option) const | |
| 415 { | |
| 416 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { | |
| 417 FloatRect boundaries = box->calculateBoundaries().toFloatRect(); | |
| 418 | |
| 419 // Shorten the width of this text box if it ends in an ellipsis. | |
| 420 // FIXME: ellipsisRectForBox should switch to return FloatRect soon with
the subpixellayout branch. | |
| 421 IntRect ellipsisRect = (option == ClipToEllipsis) ? ellipsisRectForBox(b
ox, 0, textLength()) : IntRect(); | |
| 422 if (!ellipsisRect.isEmpty()) { | |
| 423 if (style()->isHorizontalWritingMode()) | |
| 424 boundaries.setWidth(ellipsisRect.maxX() - boundaries.x()); | |
| 425 else | |
| 426 boundaries.setHeight(ellipsisRect.maxY() - boundaries.y()); | |
| 427 } | |
| 428 quads.append(localToAbsoluteQuad(boundaries, 0, wasFixed)); | |
| 429 } | |
| 430 } | |
| 431 | |
| 432 void RenderText::absoluteQuads(Vector<FloatQuad>& quads, bool* wasFixed) const | |
| 433 { | |
| 434 absoluteQuads(quads, wasFixed, NoClipping); | |
| 435 } | |
| 436 | |
| 437 void RenderText::absoluteQuadsForRange(Vector<FloatQuad>& quads, unsigned start,
unsigned end, bool useSelectionHeight, bool* wasFixed) | |
| 438 { | |
| 439 // Work around signed/unsigned issues. This function takes unsigneds, and is
often passed UINT_MAX | |
| 440 // to mean "all the way to the end". InlineTextBox coordinates are unsigneds
, so changing this | |
| 441 // function to take ints causes various internal mismatches. But selectionRe
ct takes ints, and | |
| 442 // passing UINT_MAX to it causes trouble. Ideally we'd change selectionRect
to take unsigneds, but | |
| 443 // that would cause many ripple effects, so for now we'll just clamp our uns
igned parameters to INT_MAX. | |
| 444 ASSERT(end == UINT_MAX || end <= INT_MAX); | |
| 445 ASSERT(start <= INT_MAX); | |
| 446 start = std::min(start, static_cast<unsigned>(INT_MAX)); | |
| 447 end = std::min(end, static_cast<unsigned>(INT_MAX)); | |
| 448 | |
| 449 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { | |
| 450 // Note: box->end() returns the index of the last character, not the ind
ex past it | |
| 451 if (start <= box->start() && box->end() < end) { | |
| 452 FloatRect r = box->calculateBoundaries().toFloatRect(); | |
| 453 if (useSelectionHeight) { | |
| 454 LayoutRect selectionRect = box->localSelectionRect(start, end); | |
| 455 if (box->isHorizontal()) { | |
| 456 r.setHeight(selectionRect.height().toFloat()); | |
| 457 r.setY(selectionRect.y().toFloat()); | |
| 458 } else { | |
| 459 r.setWidth(selectionRect.width().toFloat()); | |
| 460 r.setX(selectionRect.x().toFloat()); | |
| 461 } | |
| 462 } | |
| 463 quads.append(localToAbsoluteQuad(r, 0, wasFixed)); | |
| 464 } else { | |
| 465 FloatRect rect = localQuadForTextBox(box, start, end, useSelectionHe
ight); | |
| 466 if (!rect.isZero()) | |
| 467 quads.append(localToAbsoluteQuad(rect, 0, wasFixed)); | |
| 468 } | |
| 469 } | |
| 470 } | |
| 471 | |
| 472 enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPo
sitionIsNotAtStart }; | |
| 473 | |
| 474 static bool lineDirectionPointFitsInBox(int pointLineDirection, InlineTextBox* b
ox, ShouldAffinityBeDownstream& shouldAffinityBeDownstream) | |
| 475 { | |
| 476 shouldAffinityBeDownstream = AlwaysDownstream; | |
| 477 | |
| 478 // the x coordinate is equal to the left edge of this box | |
| 479 // the affinity must be downstream so the position doesn't jump back to the
previous line | |
| 480 // except when box is the first box in the line | |
| 481 if (pointLineDirection <= box->logicalLeft()) { | |
| 482 shouldAffinityBeDownstream = !box->prevLeafChild() ? UpstreamIfPositionI
sNotAtStart : AlwaysDownstream; | |
| 483 return true; | |
| 484 } | |
| 485 | |
| 486 // and the x coordinate is to the left of the right edge of this box | |
| 487 // check to see if position goes in this box | |
| 488 if (pointLineDirection < box->logicalRight()) { | |
| 489 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; | |
| 490 return true; | |
| 491 } | |
| 492 | |
| 493 // box is first on line | |
| 494 // and the x coordinate is to the left of the first text box left edge | |
| 495 if (!box->prevLeafChildIgnoringLineBreak() && pointLineDirection < box->logi
calLeft()) | |
| 496 return true; | |
| 497 | |
| 498 if (!box->nextLeafChildIgnoringLineBreak()) { | |
| 499 // box is last on line | |
| 500 // and the x coordinate is to the right of the last text box right edge | |
| 501 // generate VisiblePosition, use UPSTREAM affinity if possible | |
| 502 shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; | |
| 503 return true; | |
| 504 } | |
| 505 | |
| 506 return false; | |
| 507 } | |
| 508 | |
| 509 static PositionWithAffinity createPositionWithAffinityForBox(const InlineBox* bo
x, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) | |
| 510 { | |
| 511 EAffinity affinity = VP_DEFAULT_AFFINITY; | |
| 512 switch (shouldAffinityBeDownstream) { | |
| 513 case AlwaysDownstream: | |
| 514 affinity = DOWNSTREAM; | |
| 515 break; | |
| 516 case AlwaysUpstream: | |
| 517 affinity = VP_UPSTREAM_IF_POSSIBLE; | |
| 518 break; | |
| 519 case UpstreamIfPositionIsNotAtStart: | |
| 520 affinity = offset > box->caretMinOffset() ? VP_UPSTREAM_IF_POSSIBLE : DO
WNSTREAM; | |
| 521 break; | |
| 522 } | |
| 523 int textStartOffset = box->renderer().isText() ? toRenderText(box->renderer(
)).textStartOffset() : 0; | |
| 524 return box->renderer().createPositionWithAffinity(offset + textStartOffset,
affinity); | |
| 525 } | |
| 526 | |
| 527 static PositionWithAffinity createPositionWithAffinityForBoxAfterAdjustingOffset
ForBiDi(const InlineTextBox* box, int offset, ShouldAffinityBeDownstream shouldA
ffinityBeDownstream) | |
| 528 { | |
| 529 ASSERT(box); | |
| 530 ASSERT(offset >= 0); | |
| 531 | |
| 532 if (offset && static_cast<unsigned>(offset) < box->len()) | |
| 533 return createPositionWithAffinityForBox(box, box->start() + offset, shou
ldAffinityBeDownstream); | |
| 534 | |
| 535 bool positionIsAtStartOfBox = !offset; | |
| 536 if (positionIsAtStartOfBox == box->isLeftToRightDirection()) { | |
| 537 // offset is on the left edge | |
| 538 | |
| 539 const InlineBox* prevBox = box->prevLeafChildIgnoringLineBreak(); | |
| 540 if ((prevBox && prevBox->bidiLevel() == box->bidiLevel()) | |
| 541 || box->renderer().containingBlock()->style()->direction() == box->d
irection()) // FIXME: left on 12CBA | |
| 542 return createPositionWithAffinityForBox(box, box->caretLeftmostOffse
t(), shouldAffinityBeDownstream); | |
| 543 | |
| 544 if (prevBox && prevBox->bidiLevel() > box->bidiLevel()) { | |
| 545 // e.g. left of B in aDC12BAb | |
| 546 const InlineBox* leftmostBox; | |
| 547 do { | |
| 548 leftmostBox = prevBox; | |
| 549 prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); | |
| 550 } while (prevBox && prevBox->bidiLevel() > box->bidiLevel()); | |
| 551 return createPositionWithAffinityForBox(leftmostBox, leftmostBox->ca
retRightmostOffset(), shouldAffinityBeDownstream); | |
| 552 } | |
| 553 | |
| 554 if (!prevBox || prevBox->bidiLevel() < box->bidiLevel()) { | |
| 555 // e.g. left of D in aDC12BAb | |
| 556 const InlineBox* rightmostBox; | |
| 557 const InlineBox* nextBox = box; | |
| 558 do { | |
| 559 rightmostBox = nextBox; | |
| 560 nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); | |
| 561 } while (nextBox && nextBox->bidiLevel() >= box->bidiLevel()); | |
| 562 return createPositionWithAffinityForBox(rightmostBox, | |
| 563 box->isLeftToRightDirection() ? rightmostBox->caretMaxOffset() :
rightmostBox->caretMinOffset(), shouldAffinityBeDownstream); | |
| 564 } | |
| 565 | |
| 566 return createPositionWithAffinityForBox(box, box->caretRightmostOffset()
, shouldAffinityBeDownstream); | |
| 567 } | |
| 568 | |
| 569 const InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak(); | |
| 570 if ((nextBox && nextBox->bidiLevel() == box->bidiLevel()) | |
| 571 || box->renderer().containingBlock()->style()->direction() == box->direc
tion()) | |
| 572 return createPositionWithAffinityForBox(box, box->caretRightmostOffset()
, shouldAffinityBeDownstream); | |
| 573 | |
| 574 // offset is on the right edge | |
| 575 if (nextBox && nextBox->bidiLevel() > box->bidiLevel()) { | |
| 576 // e.g. right of C in aDC12BAb | |
| 577 const InlineBox* rightmostBox; | |
| 578 do { | |
| 579 rightmostBox = nextBox; | |
| 580 nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); | |
| 581 } while (nextBox && nextBox->bidiLevel() > box->bidiLevel()); | |
| 582 return createPositionWithAffinityForBox(rightmostBox, rightmostBox->care
tLeftmostOffset(), shouldAffinityBeDownstream); | |
| 583 } | |
| 584 | |
| 585 if (!nextBox || nextBox->bidiLevel() < box->bidiLevel()) { | |
| 586 // e.g. right of A in aDC12BAb | |
| 587 const InlineBox* leftmostBox; | |
| 588 const InlineBox* prevBox = box; | |
| 589 do { | |
| 590 leftmostBox = prevBox; | |
| 591 prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); | |
| 592 } while (prevBox && prevBox->bidiLevel() >= box->bidiLevel()); | |
| 593 return createPositionWithAffinityForBox(leftmostBox, | |
| 594 box->isLeftToRightDirection() ? leftmostBox->caretMinOffset() : left
mostBox->caretMaxOffset(), shouldAffinityBeDownstream); | |
| 595 } | |
| 596 | |
| 597 return createPositionWithAffinityForBox(box, box->caretLeftmostOffset(), sho
uldAffinityBeDownstream); | |
| 598 } | |
| 599 | |
| 600 PositionWithAffinity RenderText::positionForPoint(const LayoutPoint& point) | |
| 601 { | |
| 602 if (!firstTextBox() || textLength() == 0) | |
| 603 return createPositionWithAffinity(0, DOWNSTREAM); | |
| 604 | |
| 605 LayoutUnit pointLineDirection = firstTextBox()->isHorizontal() ? point.x() :
point.y(); | |
| 606 LayoutUnit pointBlockDirection = firstTextBox()->isHorizontal() ? point.y()
: point.x(); | |
| 607 bool blocksAreFlipped = style()->isFlippedBlocksWritingMode(); | |
| 608 | |
| 609 InlineTextBox* lastBox = 0; | |
| 610 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { | |
| 611 if (box->isLineBreak() && !box->prevLeafChild() && box->nextLeafChild()
&& !box->nextLeafChild()->isLineBreak()) | |
| 612 box = box->nextTextBox(); | |
| 613 | |
| 614 RootInlineBox& rootBox = box->root(); | |
| 615 LayoutUnit top = std::min(rootBox.selectionTop(), rootBox.lineTop()); | |
| 616 if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirecti
on == top)) { | |
| 617 LayoutUnit bottom = rootBox.selectionBottom(); | |
| 618 if (rootBox.nextRootBox()) | |
| 619 bottom = std::min(bottom, rootBox.nextRootBox()->lineTop()); | |
| 620 | |
| 621 if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockD
irection == bottom)) { | |
| 622 ShouldAffinityBeDownstream shouldAffinityBeDownstream; | |
| 623 if (lineDirectionPointFitsInBox(pointLineDirection, box, shouldA
ffinityBeDownstream)) | |
| 624 return createPositionWithAffinityForBoxAfterAdjustingOffsetF
orBiDi(box, box->offsetForPosition(pointLineDirection.toFloat()), shouldAffinity
BeDownstream); | |
| 625 } | |
| 626 } | |
| 627 lastBox = box; | |
| 628 } | |
| 629 | |
| 630 if (lastBox) { | |
| 631 ShouldAffinityBeDownstream shouldAffinityBeDownstream; | |
| 632 lineDirectionPointFitsInBox(pointLineDirection, lastBox, shouldAffinityB
eDownstream); | |
| 633 return createPositionWithAffinityForBoxAfterAdjustingOffsetForBiDi(lastB
ox, lastBox->offsetForPosition(pointLineDirection.toFloat()) + lastBox->start(),
shouldAffinityBeDownstream); | |
| 634 } | |
| 635 return createPositionWithAffinity(0, DOWNSTREAM); | |
| 636 } | |
| 637 | |
| 638 LayoutRect RenderText::localCaretRect(InlineBox* inlineBox, int caretOffset, Lay
outUnit* extraWidthToEndOfLine) | |
| 639 { | |
| 640 if (!inlineBox) | |
| 641 return LayoutRect(); | |
| 642 | |
| 643 ASSERT(inlineBox->isInlineTextBox()); | |
| 644 if (!inlineBox->isInlineTextBox()) | |
| 645 return LayoutRect(); | |
| 646 | |
| 647 InlineTextBox* box = toInlineTextBox(inlineBox); | |
| 648 | |
| 649 int height = box->root().selectionHeight(); | |
| 650 int top = box->root().selectionTop(); | |
| 651 | |
| 652 // Go ahead and round left to snap it to the nearest pixel. | |
| 653 float left = box->positionForOffset(caretOffset); | |
| 654 | |
| 655 // Distribute the caret's width to either side of the offset. | |
| 656 int caretWidthLeftOfOffset = caretWidth / 2; | |
| 657 left -= caretWidthLeftOfOffset; | |
| 658 int caretWidthRightOfOffset = caretWidth - caretWidthLeftOfOffset; | |
| 659 | |
| 660 left = roundf(left); | |
| 661 | |
| 662 float rootLeft = box->root().logicalLeft(); | |
| 663 float rootRight = box->root().logicalRight(); | |
| 664 | |
| 665 // FIXME: should we use the width of the root inline box or the | |
| 666 // width of the containing block for this? | |
| 667 if (extraWidthToEndOfLine) | |
| 668 *extraWidthToEndOfLine = (box->root().logicalWidth() + rootLeft) - (left
+ 1); | |
| 669 | |
| 670 LayoutBlock* cb = containingBlock(); | |
| 671 const LayoutStyle& cbStyle = cb->styleRef(); | |
| 672 | |
| 673 float leftEdge; | |
| 674 float rightEdge; | |
| 675 leftEdge = std::min<float>(0, rootLeft); | |
| 676 rightEdge = std::max<float>(cb->logicalWidth().toFloat(), rootRight); | |
| 677 | |
| 678 bool rightAligned = false; | |
| 679 switch (cbStyle.textAlign()) { | |
| 680 case RIGHT: | |
| 681 case WEBKIT_RIGHT: | |
| 682 rightAligned = true; | |
| 683 break; | |
| 684 case LEFT: | |
| 685 case WEBKIT_LEFT: | |
| 686 case CENTER: | |
| 687 case WEBKIT_CENTER: | |
| 688 break; | |
| 689 case JUSTIFY: | |
| 690 case TASTART: | |
| 691 rightAligned = !cbStyle.isLeftToRightDirection(); | |
| 692 break; | |
| 693 case TAEND: | |
| 694 rightAligned = cbStyle.isLeftToRightDirection(); | |
| 695 break; | |
| 696 } | |
| 697 | |
| 698 // for dir=auto, use inlineBoxBidiLevel() to test the correct direction for
the cursor. | |
| 699 if (rightAligned && (node() && node()->selfOrAncestorHasDirAutoAttribute()))
{ | |
| 700 if (inlineBox->bidiLevel()%2 != 1) | |
| 701 rightAligned = false; | |
| 702 } | |
| 703 | |
| 704 if (rightAligned) { | |
| 705 left = std::max(left, leftEdge); | |
| 706 left = std::min(left, rootRight - caretWidth); | |
| 707 } else { | |
| 708 left = std::min(left, rightEdge - caretWidthRightOfOffset); | |
| 709 left = std::max(left, rootLeft); | |
| 710 } | |
| 711 | |
| 712 return LayoutRect(style()->isHorizontalWritingMode() ? IntRect(left, top, ca
retWidth, height) : IntRect(top, left, height, caretWidth)); | |
| 713 } | |
| 714 | |
| 715 ALWAYS_INLINE float RenderText::widthFromCache(const Font& f, int start, int len
, float xPos, TextDirection textDirection, HashSet<const SimpleFontData*>* fallb
ackFonts, GlyphOverflow* glyphOverflow) const | |
| 716 { | |
| 717 if (style()->hasTextCombine() && isCombineText()) { | |
| 718 const RenderCombineText* combineText = toRenderCombineText(this); | |
| 719 if (combineText->isCombined()) | |
| 720 return combineText->combinedTextWidth(f); | |
| 721 } | |
| 722 | |
| 723 if (f.isFixedPitch() && f.fontDescription().variant() == FontVariantNormal &
& m_isAllASCII && (!glyphOverflow || !glyphOverflow->computeBounds)) { | |
| 724 bool missingGlyph = false; | |
| 725 float monospaceCharacterWidth = f.spaceWidth(); | |
| 726 float w = 0; | |
| 727 bool isSpace; | |
| 728 ASSERT(m_text); | |
| 729 StringImpl& text = *m_text.impl(); | |
| 730 const LayoutStyle& layoutStyle = styleRef(); | |
| 731 for (int i = start; i < start + len; i++) { | |
| 732 char c = text[i]; | |
| 733 // If glyph is not present in primary font then we cannot calculate
width based on primary | |
| 734 // font property, we need to call "width" method of Font Object. | |
| 735 if (!f.primaryFontHasGlyphForCharacter(text[i])) { | |
| 736 missingGlyph = true; | |
| 737 break; | |
| 738 } | |
| 739 if (c <= space) { | |
| 740 if (c == space || c == newlineCharacter) { | |
| 741 w += monospaceCharacterWidth; | |
| 742 isSpace = true; | |
| 743 } else if (c == characterTabulation) { | |
| 744 if (layoutStyle.collapseWhiteSpace()) { | |
| 745 w += monospaceCharacterWidth; | |
| 746 isSpace = true; | |
| 747 } else { | |
| 748 w += f.tabWidth(layoutStyle.tabSize(), xPos + w); | |
| 749 isSpace = false; | |
| 750 } | |
| 751 } else | |
| 752 isSpace = false; | |
| 753 } else { | |
| 754 w += monospaceCharacterWidth; | |
| 755 isSpace = false; | |
| 756 } | |
| 757 if (isSpace && i > start) | |
| 758 w += f.fontDescription().wordSpacing(); | |
| 759 } | |
| 760 if (!missingGlyph) | |
| 761 return w; | |
| 762 } | |
| 763 | |
| 764 TextRun run = constructTextRun(const_cast<RenderText*>(this), f, this, start
, len, styleRef(), textDirection); | |
| 765 run.setCharactersLength(textLength() - start); | |
| 766 ASSERT(run.charactersLength() >= run.length()); | |
| 767 run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun:
:ForceComplex); | |
| 768 run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize()); | |
| 769 run.setXPos(xPos); | |
| 770 return f.width(run, fallbackFonts, glyphOverflow); | |
| 771 } | |
| 772 | |
| 773 void RenderText::trimmedPrefWidths(FloatWillBeLayoutUnit leadWidth, | |
| 774 FloatWillBeLayoutUnit& firstLineMinWidth, bool& hasBreakableStart, | |
| 775 FloatWillBeLayoutUnit& lastLineMinWidth, bool& hasBreakableEnd, | |
| 776 bool& hasBreakableChar, bool& hasBreak, | |
| 777 FloatWillBeLayoutUnit& firstLineMaxWidth, FloatWillBeLayoutUnit& lastLineMax
Width, | |
| 778 FloatWillBeLayoutUnit& minWidth, FloatWillBeLayoutUnit& maxWidth, bool& stri
pFrontSpaces, | |
| 779 TextDirection direction) | |
| 780 { | |
| 781 bool collapseWhiteSpace = style()->collapseWhiteSpace(); | |
| 782 if (!collapseWhiteSpace) | |
| 783 stripFrontSpaces = false; | |
| 784 | |
| 785 if (m_hasTab || preferredLogicalWidthsDirty()) | |
| 786 computePreferredLogicalWidths(leadWidth); | |
| 787 | |
| 788 hasBreakableStart = !stripFrontSpaces && m_hasBreakableStart; | |
| 789 hasBreakableEnd = m_hasBreakableEnd; | |
| 790 | |
| 791 int len = textLength(); | |
| 792 | |
| 793 if (!len || (stripFrontSpaces && text().impl()->containsOnlyWhitespace())) { | |
| 794 firstLineMinWidth = FloatWillBeLayoutUnit(); | |
| 795 lastLineMinWidth = FloatWillBeLayoutUnit(); | |
| 796 firstLineMaxWidth = FloatWillBeLayoutUnit(); | |
| 797 lastLineMaxWidth = FloatWillBeLayoutUnit(); | |
| 798 minWidth = FloatWillBeLayoutUnit(); | |
| 799 maxWidth = FloatWillBeLayoutUnit(); | |
| 800 hasBreak = false; | |
| 801 return; | |
| 802 } | |
| 803 | |
| 804 minWidth = m_minWidth; | |
| 805 maxWidth = m_maxWidth; | |
| 806 | |
| 807 firstLineMinWidth = m_firstLineMinWidth; | |
| 808 lastLineMinWidth = m_lastLineLineMinWidth; | |
| 809 | |
| 810 hasBreakableChar = m_hasBreakableChar; | |
| 811 hasBreak = m_hasBreak; | |
| 812 | |
| 813 ASSERT(m_text); | |
| 814 StringImpl& text = *m_text.impl(); | |
| 815 if (text[0] == space || (text[0] == newlineCharacter && !style()->preserveNe
wline()) || text[0] == characterTabulation) { | |
| 816 const Font& font = style()->font(); // FIXME: This ignores first-line. | |
| 817 if (stripFrontSpaces) { | |
| 818 const UChar spaceChar = space; | |
| 819 TextRun run = constructTextRun(this, font, &spaceChar, 1, styleRef()
, direction); | |
| 820 run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple :
TextRun::ForceComplex); | |
| 821 float spaceWidth = font.width(run); | |
| 822 maxWidth -= spaceWidth; | |
| 823 } else { | |
| 824 maxWidth += font.fontDescription().wordSpacing(); | |
| 825 } | |
| 826 } | |
| 827 | |
| 828 stripFrontSpaces = collapseWhiteSpace && m_hasEndWhiteSpace; | |
| 829 | |
| 830 if (!style()->autoWrap() || minWidth > maxWidth) | |
| 831 minWidth = maxWidth; | |
| 832 | |
| 833 // Compute our max widths by scanning the string for newlines. | |
| 834 if (hasBreak) { | |
| 835 const Font& f = style()->font(); // FIXME: This ignores first-line. | |
| 836 bool firstLine = true; | |
| 837 firstLineMaxWidth = maxWidth; | |
| 838 lastLineMaxWidth = maxWidth; | |
| 839 for (int i = 0; i < len; i++) { | |
| 840 int linelen = 0; | |
| 841 while (i + linelen < len && text[i + linelen] != newlineCharacter) | |
| 842 linelen++; | |
| 843 | |
| 844 if (linelen) { | |
| 845 lastLineMaxWidth = widthFromCache(f, i, linelen, leadWidth + las
tLineMaxWidth, direction, 0, 0); | |
| 846 if (firstLine) { | |
| 847 firstLine = false; | |
| 848 leadWidth = FloatWillBeLayoutUnit(); | |
| 849 firstLineMaxWidth = lastLineMaxWidth; | |
| 850 } | |
| 851 i += linelen; | |
| 852 } else if (firstLine) { | |
| 853 firstLineMaxWidth = FloatWillBeLayoutUnit(); | |
| 854 firstLine = false; | |
| 855 leadWidth = FloatWillBeLayoutUnit(); | |
| 856 } | |
| 857 | |
| 858 if (i == len - 1) { | |
| 859 // A <pre> run that ends with a newline, as in, e.g., | |
| 860 // <pre>Some text\n\n<span>More text</pre> | |
| 861 lastLineMaxWidth = FloatWillBeLayoutUnit(); | |
| 862 } | |
| 863 } | |
| 864 } | |
| 865 } | |
| 866 | |
| 867 float RenderText::minLogicalWidth() const | |
| 868 { | |
| 869 if (preferredLogicalWidthsDirty()) | |
| 870 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); | |
| 871 | |
| 872 return m_minWidth; | |
| 873 } | |
| 874 | |
| 875 float RenderText::maxLogicalWidth() const | |
| 876 { | |
| 877 if (preferredLogicalWidthsDirty()) | |
| 878 const_cast<RenderText*>(this)->computePreferredLogicalWidths(0); | |
| 879 | |
| 880 return m_maxWidth; | |
| 881 } | |
| 882 | |
| 883 void RenderText::computePreferredLogicalWidths(float leadWidth) | |
| 884 { | |
| 885 HashSet<const SimpleFontData*> fallbackFonts; | |
| 886 GlyphOverflow glyphOverflow; | |
| 887 computePreferredLogicalWidths(leadWidth, fallbackFonts, glyphOverflow); | |
| 888 | |
| 889 // We shouldn't change our mind once we "know". | |
| 890 ASSERT(!m_knownToHaveNoOverflowAndNoFallbackFonts || (fallbackFonts.isEmpty(
) && glyphOverflow.isZero())); | |
| 891 m_knownToHaveNoOverflowAndNoFallbackFonts = fallbackFonts.isEmpty() && glyph
Overflow.isZero(); | |
| 892 } | |
| 893 | |
| 894 static inline float hyphenWidth(RenderText* renderer, const Font& font, TextDire
ction direction) | |
| 895 { | |
| 896 const LayoutStyle& style = renderer->styleRef(); | |
| 897 return font.width(constructTextRun(renderer, font, style.hyphenString().stri
ng(), style, direction)); | |
| 898 } | |
| 899 | |
| 900 void RenderText::computePreferredLogicalWidths(float leadWidth, HashSet<const Si
mpleFontData*>& fallbackFonts, GlyphOverflow& glyphOverflow) | |
| 901 { | |
| 902 ASSERT(m_hasTab || preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflow
AndNoFallbackFonts); | |
| 903 | |
| 904 m_minWidth = 0; | |
| 905 m_maxWidth = 0; | |
| 906 m_firstLineMinWidth = 0; | |
| 907 m_lastLineLineMinWidth = 0; | |
| 908 | |
| 909 if (isBR()) | |
| 910 return; | |
| 911 | |
| 912 float currMinWidth = 0; | |
| 913 float currMaxWidth = 0; | |
| 914 m_hasBreakableChar = false; | |
| 915 m_hasBreak = false; | |
| 916 m_hasTab = false; | |
| 917 m_hasBreakableStart = false; | |
| 918 m_hasBreakableEnd = false; | |
| 919 m_hasEndWhiteSpace = false; | |
| 920 | |
| 921 const LayoutStyle& styleToUse = styleRef(); | |
| 922 const Font& f = styleToUse.font(); // FIXME: This ignores first-line. | |
| 923 float wordSpacing = styleToUse.wordSpacing(); | |
| 924 int len = textLength(); | |
| 925 LazyLineBreakIterator breakIterator(m_text, styleToUse.locale()); | |
| 926 bool needsWordSpacing = false; | |
| 927 bool ignoringSpaces = false; | |
| 928 bool isSpace = false; | |
| 929 bool firstWord = true; | |
| 930 bool firstLine = true; | |
| 931 int nextBreakable = -1; | |
| 932 int lastWordBoundary = 0; | |
| 933 float cachedWordTrailingSpaceWidth[2] = { 0, 0 }; // LTR, RTL | |
| 934 | |
| 935 int firstGlyphLeftOverflow = -1; | |
| 936 | |
| 937 bool breakAll = (styleToUse.wordBreak() == BreakAllWordBreak || styleToUse.w
ordBreak() == BreakWordBreak) && styleToUse.autoWrap(); | |
| 938 | |
| 939 TextRun textRun(text()); | |
| 940 BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; | |
| 941 BidiCharacterRun* run; | |
| 942 TextDirection textDirection = styleToUse.direction(); | |
| 943 if (isOverride(styleToUse.unicodeBidi())) { | |
| 944 run = 0; | |
| 945 } else { | |
| 946 BidiStatus status(textDirection, false); | |
| 947 bidiResolver.setStatus(status); | |
| 948 bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&textRun,
0)); | |
| 949 bool hardLineBreak = false; | |
| 950 bool reorderRuns = false; | |
| 951 bidiResolver.createBidiRunsForLine(TextRunIterator(&textRun, textRun.len
gth()), NoVisualOverride, hardLineBreak, reorderRuns); | |
| 952 BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs(); | |
| 953 run = bidiRuns.firstRun(); | |
| 954 } | |
| 955 | |
| 956 for (int i = 0; i < len; i++) { | |
| 957 UChar c = uncheckedCharacterAt(i); | |
| 958 | |
| 959 if (run) { | |
| 960 // Treat adjacent runs with the same resolved directionality | |
| 961 // (TextDirection as opposed to WTF::Unicode::Direction) as belongin
g | |
| 962 // to the same run to avoid breaking unnecessarily. | |
| 963 while (i >= run->stop() || (run->next() && run->next()->direction()
== run->direction())) | |
| 964 run = run->next(); | |
| 965 | |
| 966 ASSERT(run); | |
| 967 ASSERT(i <= run->stop()); | |
| 968 textDirection = run->direction(); | |
| 969 } | |
| 970 | |
| 971 bool previousCharacterIsSpace = isSpace; | |
| 972 bool isNewline = false; | |
| 973 if (c == newlineCharacter) { | |
| 974 if (styleToUse.preserveNewline()) { | |
| 975 m_hasBreak = true; | |
| 976 isNewline = true; | |
| 977 isSpace = false; | |
| 978 } else | |
| 979 isSpace = true; | |
| 980 } else if (c == characterTabulation) { | |
| 981 if (!styleToUse.collapseWhiteSpace()) { | |
| 982 m_hasTab = true; | |
| 983 isSpace = false; | |
| 984 } else | |
| 985 isSpace = true; | |
| 986 } else { | |
| 987 isSpace = c == space; | |
| 988 } | |
| 989 | |
| 990 bool isBreakableLocation = isNewline || (isSpace && styleToUse.autoWrap(
)); | |
| 991 if (!i) | |
| 992 m_hasBreakableStart = isBreakableLocation; | |
| 993 if (i == len - 1) { | |
| 994 m_hasBreakableEnd = isBreakableLocation; | |
| 995 m_hasEndWhiteSpace = isNewline || isSpace; | |
| 996 } | |
| 997 | |
| 998 if (!ignoringSpaces && styleToUse.collapseWhiteSpace() && previousCharac
terIsSpace && isSpace) | |
| 999 ignoringSpaces = true; | |
| 1000 | |
| 1001 if (ignoringSpaces && !isSpace) | |
| 1002 ignoringSpaces = false; | |
| 1003 | |
| 1004 // Ignore spaces and soft hyphens | |
| 1005 if (ignoringSpaces) { | |
| 1006 ASSERT(lastWordBoundary == i); | |
| 1007 lastWordBoundary++; | |
| 1008 continue; | |
| 1009 } else if (c == softHyphen) { | |
| 1010 currMaxWidth += widthFromCache(f, lastWordBoundary, i - lastWordBoun
dary, leadWidth + currMaxWidth, textDirection, &fallbackFonts, &glyphOverflow); | |
| 1011 if (firstGlyphLeftOverflow < 0) | |
| 1012 firstGlyphLeftOverflow = glyphOverflow.left; | |
| 1013 lastWordBoundary = i + 1; | |
| 1014 continue; | |
| 1015 } | |
| 1016 | |
| 1017 bool hasBreak = breakIterator.isBreakable(i, nextBreakable, breakAll ? L
ineBreakType::BreakAll : LineBreakType::Normal); | |
| 1018 bool betweenWords = true; | |
| 1019 int j = i; | |
| 1020 while (c != newlineCharacter && c != space && c != characterTabulation &
& (c != softHyphen)) { | |
| 1021 j++; | |
| 1022 if (j == len) | |
| 1023 break; | |
| 1024 c = uncheckedCharacterAt(j); | |
| 1025 if (breakIterator.isBreakable(j, nextBreakable) && characterAt(j - 1
) != softHyphen) | |
| 1026 break; | |
| 1027 if (breakAll) { | |
| 1028 betweenWords = false; | |
| 1029 break; | |
| 1030 } | |
| 1031 } | |
| 1032 | |
| 1033 // Terminate word boundary at bidi run boundary. | |
| 1034 if (run) | |
| 1035 j = std::min(j, run->stop() + 1); | |
| 1036 int wordLen = j - i; | |
| 1037 if (wordLen) { | |
| 1038 bool isSpace = (j < len) && c == space; | |
| 1039 | |
| 1040 // Non-zero only when kerning is enabled, in which case we measure w
ords with their trailing | |
| 1041 // space, then subtract its width. | |
| 1042 float wordTrailingSpaceWidth = 0; | |
| 1043 if (isSpace && (f.fontDescription().typesettingFeatures() & Kerning)
) { | |
| 1044 ASSERT(textDirection >=0 && textDirection <= 1); | |
| 1045 if (!cachedWordTrailingSpaceWidth[textDirection]) | |
| 1046 cachedWordTrailingSpaceWidth[textDirection] = f.width(constr
uctTextRun(this, f, &space, 1, styleToUse, textDirection)) + wordSpacing; | |
| 1047 wordTrailingSpaceWidth = cachedWordTrailingSpaceWidth[textDirect
ion]; | |
| 1048 } | |
| 1049 | |
| 1050 float w; | |
| 1051 if (wordTrailingSpaceWidth && isSpace) | |
| 1052 w = widthFromCache(f, i, wordLen + 1, leadWidth + currMaxWidth,
textDirection, &fallbackFonts, &glyphOverflow) - wordTrailingSpaceWidth; | |
| 1053 else { | |
| 1054 w = widthFromCache(f, i, wordLen, leadWidth + currMaxWidth, text
Direction, &fallbackFonts, &glyphOverflow); | |
| 1055 if (c == softHyphen) | |
| 1056 currMinWidth += hyphenWidth(this, f, textDirection); | |
| 1057 } | |
| 1058 | |
| 1059 if (firstGlyphLeftOverflow < 0) | |
| 1060 firstGlyphLeftOverflow = glyphOverflow.left; | |
| 1061 currMinWidth += w; | |
| 1062 if (betweenWords) { | |
| 1063 if (lastWordBoundary == i) | |
| 1064 currMaxWidth += w; | |
| 1065 else | |
| 1066 currMaxWidth += widthFromCache(f, lastWordBoundary, j - last
WordBoundary, leadWidth + currMaxWidth, textDirection, &fallbackFonts, &glyphOve
rflow); | |
| 1067 lastWordBoundary = j; | |
| 1068 } | |
| 1069 | |
| 1070 bool isCollapsibleWhiteSpace = (j < len) && styleToUse.isCollapsible
WhiteSpace(c); | |
| 1071 if (j < len && styleToUse.autoWrap()) | |
| 1072 m_hasBreakableChar = true; | |
| 1073 | |
| 1074 // Add in wordSpacing to our currMaxWidth, but not if this is the la
st word on a line or the | |
| 1075 // last word in the run. | |
| 1076 if (wordSpacing && (isSpace || isCollapsibleWhiteSpace) && !contains
OnlyWhitespace(j, len-j)) | |
| 1077 currMaxWidth += wordSpacing; | |
| 1078 | |
| 1079 if (firstWord) { | |
| 1080 firstWord = false; | |
| 1081 // If the first character in the run is breakable, then we consi
der ourselves to have a beginning | |
| 1082 // minimum width of 0, since a break could occur right before ou
r run starts, preventing us from ever | |
| 1083 // being appended to a previous text run when considering the to
tal minimum width of the containing block. | |
| 1084 if (hasBreak) | |
| 1085 m_hasBreakableChar = true; | |
| 1086 m_firstLineMinWidth = hasBreak ? 0 : currMinWidth; | |
| 1087 } | |
| 1088 m_lastLineLineMinWidth = currMinWidth; | |
| 1089 | |
| 1090 if (currMinWidth > m_minWidth) | |
| 1091 m_minWidth = currMinWidth; | |
| 1092 currMinWidth = 0; | |
| 1093 | |
| 1094 i += wordLen - 1; | |
| 1095 } else { | |
| 1096 // Nowrap can never be broken, so don't bother setting the | |
| 1097 // breakable character boolean. Pre can only be broken if we encount
er a newline. | |
| 1098 if (style()->autoWrap() || isNewline) | |
| 1099 m_hasBreakableChar = true; | |
| 1100 | |
| 1101 if (currMinWidth > m_minWidth) | |
| 1102 m_minWidth = currMinWidth; | |
| 1103 currMinWidth = 0; | |
| 1104 | |
| 1105 if (isNewline) { // Only set if preserveNewline was true and we saw
a newline. | |
| 1106 if (firstLine) { | |
| 1107 firstLine = false; | |
| 1108 leadWidth = 0; | |
| 1109 if (!styleToUse.autoWrap()) | |
| 1110 m_firstLineMinWidth = currMaxWidth; | |
| 1111 } | |
| 1112 | |
| 1113 if (currMaxWidth > m_maxWidth) | |
| 1114 m_maxWidth = currMaxWidth; | |
| 1115 currMaxWidth = 0; | |
| 1116 } else { | |
| 1117 TextRun run = constructTextRun(this, f, this, i, 1, styleToUse,
textDirection); | |
| 1118 run.setCharactersLength(len - i); | |
| 1119 run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimpl
e : TextRun::ForceComplex); | |
| 1120 ASSERT(run.charactersLength() >= run.length()); | |
| 1121 run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize(
)); | |
| 1122 run.setXPos(leadWidth + currMaxWidth); | |
| 1123 | |
| 1124 currMaxWidth += f.width(run); | |
| 1125 glyphOverflow.right = 0; | |
| 1126 needsWordSpacing = isSpace && !previousCharacterIsSpace && i ==
len - 1; | |
| 1127 } | |
| 1128 ASSERT(lastWordBoundary == i); | |
| 1129 lastWordBoundary++; | |
| 1130 } | |
| 1131 } | |
| 1132 if (run) | |
| 1133 bidiResolver.runs().deleteRuns(); | |
| 1134 | |
| 1135 if (firstGlyphLeftOverflow > 0) | |
| 1136 glyphOverflow.left = firstGlyphLeftOverflow; | |
| 1137 | |
| 1138 if ((needsWordSpacing && len > 1) || (ignoringSpaces && !firstWord)) | |
| 1139 currMaxWidth += wordSpacing; | |
| 1140 | |
| 1141 m_minWidth = std::max(currMinWidth, m_minWidth); | |
| 1142 m_maxWidth = std::max(currMaxWidth, m_maxWidth); | |
| 1143 | |
| 1144 if (!styleToUse.autoWrap()) | |
| 1145 m_minWidth = m_maxWidth; | |
| 1146 | |
| 1147 if (styleToUse.whiteSpace() == PRE) { | |
| 1148 if (firstLine) | |
| 1149 m_firstLineMinWidth = m_maxWidth; | |
| 1150 m_lastLineLineMinWidth = currMaxWidth; | |
| 1151 } | |
| 1152 | |
| 1153 clearPreferredLogicalWidthsDirty(); | |
| 1154 } | |
| 1155 | |
| 1156 bool RenderText::isAllCollapsibleWhitespace() const | |
| 1157 { | |
| 1158 unsigned length = textLength(); | |
| 1159 if (is8Bit()) { | |
| 1160 for (unsigned i = 0; i < length; ++i) { | |
| 1161 if (!style()->isCollapsibleWhiteSpace(characters8()[i])) | |
| 1162 return false; | |
| 1163 } | |
| 1164 return true; | |
| 1165 } | |
| 1166 for (unsigned i = 0; i < length; ++i) { | |
| 1167 if (!style()->isCollapsibleWhiteSpace(characters16()[i])) | |
| 1168 return false; | |
| 1169 } | |
| 1170 return true; | |
| 1171 } | |
| 1172 | |
| 1173 bool RenderText::containsOnlyWhitespace(unsigned from, unsigned len) const | |
| 1174 { | |
| 1175 ASSERT(m_text); | |
| 1176 StringImpl& text = *m_text.impl(); | |
| 1177 unsigned currPos; | |
| 1178 for (currPos = from; | |
| 1179 currPos < from + len && (text[currPos] == newlineCharacter || text[currPos]
== space || text[currPos] == characterTabulation); | |
| 1180 currPos++) { } | |
| 1181 return currPos >= (from + len); | |
| 1182 } | |
| 1183 | |
| 1184 FloatPoint RenderText::firstRunOrigin() const | |
| 1185 { | |
| 1186 return IntPoint(firstRunX(), firstRunY()); | |
| 1187 } | |
| 1188 | |
| 1189 float RenderText::firstRunX() const | |
| 1190 { | |
| 1191 return m_firstTextBox ? m_firstTextBox->x().toFloat() : 0; | |
| 1192 } | |
| 1193 | |
| 1194 float RenderText::firstRunY() const | |
| 1195 { | |
| 1196 return m_firstTextBox ? m_firstTextBox->y().toFloat() : 0; | |
| 1197 } | |
| 1198 | |
| 1199 void RenderText::setSelectionState(SelectionState state) | |
| 1200 { | |
| 1201 LayoutObject::setSelectionState(state); | |
| 1202 | |
| 1203 if (canUpdateSelectionOnRootLineBoxes()) { | |
| 1204 if (state == SelectionStart || state == SelectionEnd || state == Selecti
onBoth) { | |
| 1205 int startPos, endPos; | |
| 1206 selectionStartEnd(startPos, endPos); | |
| 1207 if (selectionState() == SelectionStart) { | |
| 1208 endPos = textLength(); | |
| 1209 | |
| 1210 // to handle selection from end of text to end of line | |
| 1211 if (startPos && startPos == endPos) | |
| 1212 startPos = endPos - 1; | |
| 1213 } else if (selectionState() == SelectionEnd) | |
| 1214 startPos = 0; | |
| 1215 | |
| 1216 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBo
x()) { | |
| 1217 if (box->isSelected(startPos, endPos)) { | |
| 1218 box->root().setHasSelectedChildren(true); | |
| 1219 } | |
| 1220 } | |
| 1221 } else { | |
| 1222 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBo
x()) { | |
| 1223 box->root().setHasSelectedChildren(state == SelectionInside); | |
| 1224 } | |
| 1225 } | |
| 1226 } | |
| 1227 | |
| 1228 // The containing block can be null in case of an orphaned tree. | |
| 1229 LayoutBlock* containingBlock = this->containingBlock(); | |
| 1230 if (containingBlock && !containingBlock->isLayoutView()) | |
| 1231 containingBlock->setSelectionState(state); | |
| 1232 } | |
| 1233 | |
| 1234 void RenderText::setTextWithOffset(PassRefPtr<StringImpl> text, unsigned offset,
unsigned len, bool force) | |
| 1235 { | |
| 1236 if (!force && equal(m_text.impl(), text.get())) | |
| 1237 return; | |
| 1238 | |
| 1239 unsigned oldLen = textLength(); | |
| 1240 unsigned newLen = text->length(); | |
| 1241 int delta = newLen - oldLen; | |
| 1242 unsigned end = len ? offset + len - 1 : offset; | |
| 1243 | |
| 1244 RootInlineBox* firstRootBox = 0; | |
| 1245 RootInlineBox* lastRootBox = 0; | |
| 1246 | |
| 1247 bool dirtiedLines = false; | |
| 1248 | |
| 1249 // Dirty all text boxes that include characters in between offset and offset
+len. | |
| 1250 for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox())
{ | |
| 1251 // FIXME: This shouldn't rely on the end of a dirty line box. See https:
//bugs.webkit.org/show_bug.cgi?id=97264 | |
| 1252 // Text run is entirely before the affected range. | |
| 1253 if (curr->end() < offset) | |
| 1254 continue; | |
| 1255 | |
| 1256 // Text run is entirely after the affected range. | |
| 1257 if (curr->start() > end) { | |
| 1258 curr->offsetRun(delta); | |
| 1259 RootInlineBox* root = &curr->root(); | |
| 1260 if (!firstRootBox) { | |
| 1261 firstRootBox = root; | |
| 1262 // The affected area was in between two runs. Go ahead and mark
the root box of | |
| 1263 // the run after the affected area as dirty. | |
| 1264 firstRootBox->markDirty(); | |
| 1265 dirtiedLines = true; | |
| 1266 } | |
| 1267 lastRootBox = root; | |
| 1268 } else if (curr->end() >= offset && curr->end() <= end) { | |
| 1269 // Text run overlaps with the left end of the affected range. | |
| 1270 curr->dirtyLineBoxes(); | |
| 1271 dirtiedLines = true; | |
| 1272 } else if (curr->start() <= offset && curr->end() >= end) { | |
| 1273 // Text run subsumes the affected range. | |
| 1274 curr->dirtyLineBoxes(); | |
| 1275 dirtiedLines = true; | |
| 1276 } else if (curr->start() <= end && curr->end() >= end) { | |
| 1277 // Text run overlaps with right end of the affected range. | |
| 1278 curr->dirtyLineBoxes(); | |
| 1279 dirtiedLines = true; | |
| 1280 } | |
| 1281 } | |
| 1282 | |
| 1283 // Now we have to walk all of the clean lines and adjust their cached line b
reak information | |
| 1284 // to reflect our updated offsets. | |
| 1285 if (lastRootBox) | |
| 1286 lastRootBox = lastRootBox->nextRootBox(); | |
| 1287 if (firstRootBox) { | |
| 1288 RootInlineBox* prev = firstRootBox->prevRootBox(); | |
| 1289 if (prev) | |
| 1290 firstRootBox = prev; | |
| 1291 } else if (lastTextBox()) { | |
| 1292 ASSERT(!lastRootBox); | |
| 1293 firstRootBox = &lastTextBox()->root(); | |
| 1294 firstRootBox->markDirty(); | |
| 1295 dirtiedLines = true; | |
| 1296 } | |
| 1297 for (RootInlineBox* curr = firstRootBox; curr && curr != lastRootBox; curr =
curr->nextRootBox()) { | |
| 1298 if (curr->lineBreakObj() == this && curr->lineBreakPos() > end) | |
| 1299 curr->setLineBreakPos(clampTo<int>(curr->lineBreakPos() + delta)); | |
| 1300 } | |
| 1301 | |
| 1302 // If the text node is empty, dirty the line where new text will be inserted
. | |
| 1303 if (!firstTextBox() && parent()) { | |
| 1304 parent()->dirtyLinesFromChangedChild(this); | |
| 1305 dirtiedLines = true; | |
| 1306 } | |
| 1307 | |
| 1308 m_linesDirty = dirtiedLines; | |
| 1309 setText(text, force || dirtiedLines); | |
| 1310 } | |
| 1311 | |
| 1312 void RenderText::transformText() | |
| 1313 { | |
| 1314 if (RefPtr<StringImpl> textToTransform = originalText()) | |
| 1315 setText(textToTransform.release(), true); | |
| 1316 } | |
| 1317 | |
| 1318 static inline bool isInlineFlowOrEmptyText(const LayoutObject* o) | |
| 1319 { | |
| 1320 if (o->isLayoutInline()) | |
| 1321 return true; | |
| 1322 if (!o->isText()) | |
| 1323 return false; | |
| 1324 return toRenderText(o)->text().isEmpty(); | |
| 1325 } | |
| 1326 | |
| 1327 UChar RenderText::previousCharacter() const | |
| 1328 { | |
| 1329 // find previous text renderer if one exists | |
| 1330 const LayoutObject* previousText = previousInPreOrder(); | |
| 1331 for (; previousText; previousText = previousText->previousInPreOrder()) | |
| 1332 if (!isInlineFlowOrEmptyText(previousText)) | |
| 1333 break; | |
| 1334 UChar prev = space; | |
| 1335 if (previousText && previousText->isText()) | |
| 1336 if (StringImpl* previousString = toRenderText(previousText)->text().impl
()) | |
| 1337 prev = (*previousString)[previousString->length() - 1]; | |
| 1338 return prev; | |
| 1339 } | |
| 1340 | |
| 1341 void RenderText::addLayerHitTestRects(LayerHitTestRects&, const Layer* currentLa
yer, const LayoutPoint& layerOffset, const LayoutRect& containerRect) const | |
| 1342 { | |
| 1343 // Text nodes aren't event targets, so don't descend any further. | |
| 1344 } | |
| 1345 | |
| 1346 void applyTextTransform(const LayoutStyle* style, String& text, UChar previousCh
aracter) | |
| 1347 { | |
| 1348 if (!style) | |
| 1349 return; | |
| 1350 | |
| 1351 switch (style->textTransform()) { | |
| 1352 case TTNONE: | |
| 1353 break; | |
| 1354 case CAPITALIZE: | |
| 1355 makeCapitalized(&text, previousCharacter); | |
| 1356 break; | |
| 1357 case UPPERCASE: | |
| 1358 text = text.upper(style->locale()); | |
| 1359 break; | |
| 1360 case LOWERCASE: | |
| 1361 text = text.lower(style->locale()); | |
| 1362 break; | |
| 1363 } | |
| 1364 } | |
| 1365 | |
| 1366 void RenderText::setTextInternal(PassRefPtr<StringImpl> text) | |
| 1367 { | |
| 1368 ASSERT(text); | |
| 1369 m_text = text; | |
| 1370 | |
| 1371 if (style()) { | |
| 1372 applyTextTransform(style(), m_text, previousCharacter()); | |
| 1373 | |
| 1374 // We use the same characters here as for list markers. | |
| 1375 // See the listMarkerText function in LayoutListMarker.cpp. | |
| 1376 switch (style()->textSecurity()) { | |
| 1377 case TSNONE: | |
| 1378 break; | |
| 1379 case TSCIRCLE: | |
| 1380 secureText(whiteBullet); | |
| 1381 break; | |
| 1382 case TSDISC: | |
| 1383 secureText(bullet); | |
| 1384 break; | |
| 1385 case TSSQUARE: | |
| 1386 secureText(blackSquare); | |
| 1387 } | |
| 1388 } | |
| 1389 | |
| 1390 ASSERT(m_text); | |
| 1391 ASSERT(!isBR() || (textLength() == 1 && m_text[0] == newlineCharacter)); | |
| 1392 | |
| 1393 m_isAllASCII = m_text.containsOnlyASCII(); | |
| 1394 m_canUseSimpleFontCodePath = computeCanUseSimpleFontCodePath(); | |
| 1395 } | |
| 1396 | |
| 1397 void RenderText::secureText(UChar mask) | |
| 1398 { | |
| 1399 if (!m_text.length()) | |
| 1400 return; | |
| 1401 | |
| 1402 int lastTypedCharacterOffsetToReveal = -1; | |
| 1403 UChar revealedText; | |
| 1404 SecureTextTimer* secureTextTimer = gSecureTextTimers ? gSecureTextTimers->ge
t(this) : 0; | |
| 1405 if (secureTextTimer && secureTextTimer->isActive()) { | |
| 1406 lastTypedCharacterOffsetToReveal = secureTextTimer->lastTypedCharacterOf
fset(); | |
| 1407 if (lastTypedCharacterOffsetToReveal >= 0) | |
| 1408 revealedText = m_text[lastTypedCharacterOffsetToReveal]; | |
| 1409 } | |
| 1410 | |
| 1411 m_text.fill(mask); | |
| 1412 if (lastTypedCharacterOffsetToReveal >= 0) { | |
| 1413 m_text.replace(lastTypedCharacterOffsetToReveal, 1, String(&revealedText
, 1)); | |
| 1414 // m_text may be updated later before timer fires. We invalidate the las
tTypedCharacterOffset to avoid inconsistency. | |
| 1415 secureTextTimer->invalidate(); | |
| 1416 } | |
| 1417 } | |
| 1418 | |
| 1419 void RenderText::setText(PassRefPtr<StringImpl> text, bool force) | |
| 1420 { | |
| 1421 ASSERT(text); | |
| 1422 | |
| 1423 if (!force && equal(m_text.impl(), text.get())) | |
| 1424 return; | |
| 1425 | |
| 1426 setTextInternal(text); | |
| 1427 // If preferredLogicalWidthsDirty() of an orphan child is true, LayoutObject
ChildList:: | |
| 1428 // insertChildNode() fails to set true to owner. To avoid that, we call | |
| 1429 // setNeedsLayoutAndPrefWidthsRecalc() only if this RenderText has parent. | |
| 1430 if (parent()) | |
| 1431 setNeedsLayoutAndPrefWidthsRecalc(); | |
| 1432 m_knownToHaveNoOverflowAndNoFallbackFonts = false; | |
| 1433 | |
| 1434 if (AXObjectCache* cache = document().existingAXObjectCache()) | |
| 1435 cache->textChanged(this); | |
| 1436 } | |
| 1437 | |
| 1438 void RenderText::dirtyOrDeleteLineBoxesIfNeeded(bool fullLayout) | |
| 1439 { | |
| 1440 if (fullLayout) | |
| 1441 deleteTextBoxes(); | |
| 1442 else if (!m_linesDirty) | |
| 1443 dirtyLineBoxes(); | |
| 1444 m_linesDirty = false; | |
| 1445 } | |
| 1446 | |
| 1447 void RenderText::dirtyLineBoxes() | |
| 1448 { | |
| 1449 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) | |
| 1450 box->dirtyLineBoxes(); | |
| 1451 m_linesDirty = false; | |
| 1452 } | |
| 1453 | |
| 1454 InlineTextBox* RenderText::createTextBox(int start, unsigned short length) | |
| 1455 { | |
| 1456 return new InlineTextBox(*this, start, length); | |
| 1457 } | |
| 1458 | |
| 1459 InlineTextBox* RenderText::createInlineTextBox(int start, unsigned short length) | |
| 1460 { | |
| 1461 InlineTextBox* textBox = createTextBox(start, length); | |
| 1462 if (!m_firstTextBox) | |
| 1463 m_firstTextBox = m_lastTextBox = textBox; | |
| 1464 else { | |
| 1465 m_lastTextBox->setNextTextBox(textBox); | |
| 1466 textBox->setPreviousTextBox(m_lastTextBox); | |
| 1467 m_lastTextBox = textBox; | |
| 1468 } | |
| 1469 return textBox; | |
| 1470 } | |
| 1471 | |
| 1472 void RenderText::positionLineBox(InlineBox* box) | |
| 1473 { | |
| 1474 InlineTextBox* s = toInlineTextBox(box); | |
| 1475 | |
| 1476 // FIXME: should not be needed!!! | |
| 1477 if (!s->len()) { | |
| 1478 // We want the box to be destroyed. | |
| 1479 s->remove(DontMarkLineBoxes); | |
| 1480 if (m_firstTextBox == s) | |
| 1481 m_firstTextBox = s->nextTextBox(); | |
| 1482 else | |
| 1483 s->prevTextBox()->setNextTextBox(s->nextTextBox()); | |
| 1484 if (m_lastTextBox == s) | |
| 1485 m_lastTextBox = s->prevTextBox(); | |
| 1486 else | |
| 1487 s->nextTextBox()->setPreviousTextBox(s->prevTextBox()); | |
| 1488 s->destroy(); | |
| 1489 return; | |
| 1490 } | |
| 1491 | |
| 1492 m_containsReversedText |= !s->isLeftToRightDirection(); | |
| 1493 } | |
| 1494 | |
| 1495 float RenderText::width(unsigned from, unsigned len, float xPos, TextDirection t
extDirection, bool firstLine, HashSet<const SimpleFontData*>* fallbackFonts, Gly
phOverflow* glyphOverflow) const | |
| 1496 { | |
| 1497 if (from >= textLength()) | |
| 1498 return 0; | |
| 1499 | |
| 1500 if (from + len > textLength()) | |
| 1501 len = textLength() - from; | |
| 1502 | |
| 1503 return width(from, len, style(firstLine)->font(), xPos, textDirection, fallb
ackFonts, glyphOverflow); | |
| 1504 } | |
| 1505 | |
| 1506 float RenderText::width(unsigned from, unsigned len, const Font& f, float xPos,
TextDirection textDirection, HashSet<const SimpleFontData*>* fallbackFonts, Glyp
hOverflow* glyphOverflow) const | |
| 1507 { | |
| 1508 ASSERT(from + len <= textLength()); | |
| 1509 if (!textLength()) | |
| 1510 return 0; | |
| 1511 | |
| 1512 float w; | |
| 1513 if (&f == &style()->font()) { | |
| 1514 if (!style()->preserveNewline() && !from && len == textLength() && (!gly
phOverflow || !glyphOverflow->computeBounds)) { | |
| 1515 if (fallbackFonts) { | |
| 1516 ASSERT(glyphOverflow); | |
| 1517 if (preferredLogicalWidthsDirty() || !m_knownToHaveNoOverflowAnd
NoFallbackFonts) { | |
| 1518 const_cast<RenderText*>(this)->computePreferredLogicalWidths
(0, *fallbackFonts, *glyphOverflow); | |
| 1519 // We shouldn't change our mind once we "know". | |
| 1520 ASSERT(!m_knownToHaveNoOverflowAndNoFallbackFonts | |
| 1521 || (fallbackFonts->isEmpty() && glyphOverflow->isZero())
); | |
| 1522 m_knownToHaveNoOverflowAndNoFallbackFonts = fallbackFonts->i
sEmpty() && glyphOverflow->isZero(); | |
| 1523 } | |
| 1524 w = m_maxWidth; | |
| 1525 } else { | |
| 1526 w = maxLogicalWidth(); | |
| 1527 } | |
| 1528 } else { | |
| 1529 w = widthFromCache(f, from, len, xPos, textDirection, fallbackFonts,
glyphOverflow); | |
| 1530 } | |
| 1531 } else { | |
| 1532 TextRun run = constructTextRun(const_cast<RenderText*>(this), f, this, f
rom, len, styleRef(), textDirection); | |
| 1533 run.setCharactersLength(textLength() - from); | |
| 1534 ASSERT(run.charactersLength() >= run.length()); | |
| 1535 | |
| 1536 run.setCodePath(canUseSimpleFontCodePath() ? TextRun::ForceSimple : Text
Run::ForceComplex); | |
| 1537 run.setTabSize(!style()->collapseWhiteSpace(), style()->tabSize()); | |
| 1538 run.setXPos(xPos); | |
| 1539 w = f.width(run, fallbackFonts, glyphOverflow); | |
| 1540 } | |
| 1541 | |
| 1542 return w; | |
| 1543 } | |
| 1544 | |
| 1545 IntRect RenderText::linesBoundingBox() const | |
| 1546 { | |
| 1547 IntRect result; | |
| 1548 | |
| 1549 ASSERT(!firstTextBox() == !lastTextBox()); // Either both are null or both
exist. | |
| 1550 if (firstTextBox() && lastTextBox()) { | |
| 1551 // Return the width of the minimal left side and the maximal right side. | |
| 1552 float logicalLeftSide = 0; | |
| 1553 float logicalRightSide = 0; | |
| 1554 for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBo
x()) { | |
| 1555 if (curr == firstTextBox() || curr->logicalLeft() < logicalLeftSide) | |
| 1556 logicalLeftSide = curr->logicalLeft(); | |
| 1557 if (curr == firstTextBox() || curr->logicalRight() > logicalRightSid
e) | |
| 1558 logicalRightSide = curr->logicalRight(); | |
| 1559 } | |
| 1560 | |
| 1561 bool isHorizontal = style()->isHorizontalWritingMode(); | |
| 1562 | |
| 1563 float x = isHorizontal ? logicalLeftSide : firstTextBox()->x().toFloat()
; | |
| 1564 float y = isHorizontal ? firstTextBox()->y().toFloat() : logicalLeftSide
; | |
| 1565 float width = isHorizontal ? logicalRightSide - logicalLeftSide : lastTe
xtBox()->logicalBottom() - x; | |
| 1566 float height = isHorizontal ? lastTextBox()->logicalBottom() - y : logic
alRightSide - logicalLeftSide; | |
| 1567 result = enclosingIntRect(FloatRect(x, y, width, height)); | |
| 1568 } | |
| 1569 | |
| 1570 return result; | |
| 1571 } | |
| 1572 | |
| 1573 LayoutRect RenderText::linesVisualOverflowBoundingBox() const | |
| 1574 { | |
| 1575 if (!firstTextBox()) | |
| 1576 return LayoutRect(); | |
| 1577 | |
| 1578 // Return the width of the minimal left side and the maximal right side. | |
| 1579 LayoutUnit logicalLeftSide = LayoutUnit::max(); | |
| 1580 LayoutUnit logicalRightSide = LayoutUnit::min(); | |
| 1581 for (InlineTextBox* curr = firstTextBox(); curr; curr = curr->nextTextBox())
{ | |
| 1582 LayoutRect logicalVisualOverflow = curr->logicalOverflowRect(); | |
| 1583 logicalLeftSide = std::min(logicalLeftSide, logicalVisualOverflow.x()); | |
| 1584 logicalRightSide = std::max(logicalRightSide, logicalVisualOverflow.maxX
()); | |
| 1585 } | |
| 1586 | |
| 1587 LayoutUnit logicalTop = firstTextBox()->logicalTopVisualOverflow(); | |
| 1588 LayoutUnit logicalWidth = logicalRightSide - logicalLeftSide; | |
| 1589 LayoutUnit logicalHeight = lastTextBox()->logicalBottomVisualOverflow() - lo
gicalTop; | |
| 1590 | |
| 1591 LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight); | |
| 1592 if (!style()->isHorizontalWritingMode()) | |
| 1593 rect = rect.transposedRect(); | |
| 1594 return rect; | |
| 1595 } | |
| 1596 | |
| 1597 LayoutRect RenderText::clippedOverflowRectForPaintInvalidation(const LayoutBoxMo
delObject* paintInvalidationContainer, const PaintInvalidationState* paintInvali
dationState) const | |
| 1598 { | |
| 1599 if (style()->visibility() != VISIBLE) | |
| 1600 return LayoutRect(); | |
| 1601 | |
| 1602 LayoutRect paintInvalidationRect(linesVisualOverflowBoundingBox()); | |
| 1603 mapRectToPaintInvalidationBacking(paintInvalidationContainer, paintInvalidat
ionRect, paintInvalidationState); | |
| 1604 return paintInvalidationRect; | |
| 1605 } | |
| 1606 | |
| 1607 LayoutRect RenderText::selectionRectForPaintInvalidation(const LayoutBoxModelObj
ect* paintInvalidationContainer) const | |
| 1608 { | |
| 1609 ASSERT(!needsLayout()); | |
| 1610 | |
| 1611 if (selectionState() == SelectionNone) | |
| 1612 return LayoutRect(); | |
| 1613 LayoutBlock* cb = containingBlock(); | |
| 1614 if (!cb) | |
| 1615 return LayoutRect(); | |
| 1616 | |
| 1617 // Now calculate startPos and endPos for painting selection. | |
| 1618 // We include a selection while endPos > 0 | |
| 1619 int startPos, endPos; | |
| 1620 if (selectionState() == SelectionInside) { | |
| 1621 // We are fully selected. | |
| 1622 startPos = 0; | |
| 1623 endPos = textLength(); | |
| 1624 } else { | |
| 1625 selectionStartEnd(startPos, endPos); | |
| 1626 if (selectionState() == SelectionStart) | |
| 1627 endPos = textLength(); | |
| 1628 else if (selectionState() == SelectionEnd) | |
| 1629 startPos = 0; | |
| 1630 } | |
| 1631 | |
| 1632 LayoutRect rect; | |
| 1633 | |
| 1634 if (startPos == endPos) | |
| 1635 return rect; | |
| 1636 | |
| 1637 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) { | |
| 1638 rect.unite(box->localSelectionRect(startPos, endPos)); | |
| 1639 rect.unite(LayoutRect(ellipsisRectForBox(box, startPos, endPos))); | |
| 1640 } | |
| 1641 | |
| 1642 mapRectToPaintInvalidationBacking(paintInvalidationContainer, rect, 0); | |
| 1643 // FIXME: groupedMapping() leaks the squashing abstraction. | |
| 1644 if (paintInvalidationContainer->layer()->groupedMapping()) | |
| 1645 Layer::mapRectToPaintBackingCoordinates(paintInvalidationContainer, rect
); | |
| 1646 return rect; | |
| 1647 } | |
| 1648 | |
| 1649 int RenderText::caretMinOffset() const | |
| 1650 { | |
| 1651 InlineTextBox* box = firstTextBox(); | |
| 1652 if (!box) | |
| 1653 return 0; | |
| 1654 int minOffset = box->start(); | |
| 1655 for (box = box->nextTextBox(); box; box = box->nextTextBox()) | |
| 1656 minOffset = std::min<int>(minOffset, box->start()); | |
| 1657 return minOffset; | |
| 1658 } | |
| 1659 | |
| 1660 int RenderText::caretMaxOffset() const | |
| 1661 { | |
| 1662 InlineTextBox* box = lastTextBox(); | |
| 1663 if (!lastTextBox()) | |
| 1664 return textLength(); | |
| 1665 | |
| 1666 int maxOffset = box->start() + box->len(); | |
| 1667 for (box = box->prevTextBox(); box; box = box->prevTextBox()) | |
| 1668 maxOffset = std::max<int>(maxOffset, box->start() + box->len()); | |
| 1669 return maxOffset; | |
| 1670 } | |
| 1671 | |
| 1672 unsigned RenderText::renderedTextLength() const | |
| 1673 { | |
| 1674 int l = 0; | |
| 1675 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) | |
| 1676 l += box->len(); | |
| 1677 return l; | |
| 1678 } | |
| 1679 | |
| 1680 int RenderText::previousOffset(int current) const | |
| 1681 { | |
| 1682 if (isAllASCII() || m_text.is8Bit()) | |
| 1683 return current - 1; | |
| 1684 | |
| 1685 StringImpl* textImpl = m_text.impl(); | |
| 1686 TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(
), textImpl->length()); | |
| 1687 if (!iterator) | |
| 1688 return current - 1; | |
| 1689 | |
| 1690 long result = iterator->preceding(current); | |
| 1691 if (result == TextBreakDone) | |
| 1692 result = current - 1; | |
| 1693 | |
| 1694 | |
| 1695 return result; | |
| 1696 } | |
| 1697 | |
| 1698 #if OS(POSIX) | |
| 1699 | |
| 1700 #define HANGUL_CHOSEONG_START (0x1100) | |
| 1701 #define HANGUL_CHOSEONG_END (0x115F) | |
| 1702 #define HANGUL_JUNGSEONG_START (0x1160) | |
| 1703 #define HANGUL_JUNGSEONG_END (0x11A2) | |
| 1704 #define HANGUL_JONGSEONG_START (0x11A8) | |
| 1705 #define HANGUL_JONGSEONG_END (0x11F9) | |
| 1706 #define HANGUL_SYLLABLE_START (0xAC00) | |
| 1707 #define HANGUL_SYLLABLE_END (0xD7AF) | |
| 1708 #define HANGUL_JONGSEONG_COUNT (28) | |
| 1709 | |
| 1710 enum HangulState { | |
| 1711 HangulStateL, | |
| 1712 HangulStateV, | |
| 1713 HangulStateT, | |
| 1714 HangulStateLV, | |
| 1715 HangulStateLVT, | |
| 1716 HangulStateBreak | |
| 1717 }; | |
| 1718 | |
| 1719 inline bool isHangulLVT(UChar32 character) | |
| 1720 { | |
| 1721 return (character - HANGUL_SYLLABLE_START) % HANGUL_JONGSEONG_COUNT; | |
| 1722 } | |
| 1723 | |
| 1724 inline bool isMark(UChar32 c) | |
| 1725 { | |
| 1726 int8_t charType = u_charType(c); | |
| 1727 return charType == U_NON_SPACING_MARK || charType == U_ENCLOSING_MARK || cha
rType == U_COMBINING_SPACING_MARK; | |
| 1728 } | |
| 1729 | |
| 1730 inline bool isRegionalIndicator(UChar32 c) | |
| 1731 { | |
| 1732 // National flag emoji each consists of a pair of regional indicator symbols
. | |
| 1733 return 0x1F1E6 <= c && c <= 0x1F1FF; | |
| 1734 } | |
| 1735 | |
| 1736 #endif | |
| 1737 | |
| 1738 int RenderText::previousOffsetForBackwardDeletion(int current) const | |
| 1739 { | |
| 1740 #if OS(POSIX) | |
| 1741 ASSERT(m_text); | |
| 1742 StringImpl& text = *m_text.impl(); | |
| 1743 UChar32 character; | |
| 1744 bool sawRegionalIndicator = false; | |
| 1745 while (current > 0) { | |
| 1746 if (U16_IS_TRAIL(text[--current])) | |
| 1747 --current; | |
| 1748 if (current < 0) | |
| 1749 break; | |
| 1750 | |
| 1751 UChar32 character = text.characterStartingAt(current); | |
| 1752 | |
| 1753 if (sawRegionalIndicator) { | |
| 1754 // We don't check if the pair of regional indicator symbols before c
urrent position can actually be combined | |
| 1755 // into a flag, and just delete it. This may not agree with how the
pair is rendered in edge cases, | |
| 1756 // but is good enough in practice. | |
| 1757 if (isRegionalIndicator(character)) | |
| 1758 break; | |
| 1759 // Don't delete a preceding character that isn't a regional indicato
r symbol. | |
| 1760 U16_FWD_1_UNSAFE(text, current); | |
| 1761 } | |
| 1762 | |
| 1763 // We don't combine characters in Armenian ... Limbu range for backward
deletion. | |
| 1764 if ((character >= 0x0530) && (character < 0x1950)) | |
| 1765 break; | |
| 1766 | |
| 1767 if (isRegionalIndicator(character)) { | |
| 1768 sawRegionalIndicator = true; | |
| 1769 continue; | |
| 1770 } | |
| 1771 | |
| 1772 if (!isMark(character) && (character != 0xFF9E) && (character != 0xFF9F)
) | |
| 1773 break; | |
| 1774 } | |
| 1775 | |
| 1776 if (current <= 0) | |
| 1777 return current; | |
| 1778 | |
| 1779 // Hangul | |
| 1780 character = text.characterStartingAt(current); | |
| 1781 if (((character >= HANGUL_CHOSEONG_START) && (character <= HANGUL_JONGSEONG_
END)) || ((character >= HANGUL_SYLLABLE_START) && (character <= HANGUL_SYLLABLE_
END))) { | |
| 1782 HangulState state; | |
| 1783 | |
| 1784 if (character < HANGUL_JUNGSEONG_START) | |
| 1785 state = HangulStateL; | |
| 1786 else if (character < HANGUL_JONGSEONG_START) | |
| 1787 state = HangulStateV; | |
| 1788 else if (character < HANGUL_SYLLABLE_START) | |
| 1789 state = HangulStateT; | |
| 1790 else | |
| 1791 state = isHangulLVT(character) ? HangulStateLVT : HangulStateLV; | |
| 1792 | |
| 1793 while (current > 0 && ((character = text.characterStartingAt(current - 1
)) >= HANGUL_CHOSEONG_START) && (character <= HANGUL_SYLLABLE_END) && ((characte
r <= HANGUL_JONGSEONG_END) || (character >= HANGUL_SYLLABLE_START))) { | |
| 1794 switch (state) { | |
| 1795 case HangulStateV: | |
| 1796 if (character <= HANGUL_CHOSEONG_END) | |
| 1797 state = HangulStateL; | |
| 1798 else if ((character >= HANGUL_SYLLABLE_START) && (character <= H
ANGUL_SYLLABLE_END) && !isHangulLVT(character)) | |
| 1799 state = HangulStateLV; | |
| 1800 else if (character > HANGUL_JUNGSEONG_END) | |
| 1801 state = HangulStateBreak; | |
| 1802 break; | |
| 1803 case HangulStateT: | |
| 1804 if ((character >= HANGUL_JUNGSEONG_START) && (character <= HANGU
L_JUNGSEONG_END)) | |
| 1805 state = HangulStateV; | |
| 1806 else if ((character >= HANGUL_SYLLABLE_START) && (character <= H
ANGUL_SYLLABLE_END)) | |
| 1807 state = (isHangulLVT(character) ? HangulStateLVT : HangulSta
teLV); | |
| 1808 else if (character < HANGUL_JUNGSEONG_START) | |
| 1809 state = HangulStateBreak; | |
| 1810 break; | |
| 1811 default: | |
| 1812 state = (character < HANGUL_JUNGSEONG_START) ? HangulStateL : Ha
ngulStateBreak; | |
| 1813 break; | |
| 1814 } | |
| 1815 if (state == HangulStateBreak) | |
| 1816 break; | |
| 1817 | |
| 1818 --current; | |
| 1819 } | |
| 1820 } | |
| 1821 | |
| 1822 return current; | |
| 1823 #else | |
| 1824 // Platforms other than Unix-like delete by one code point. | |
| 1825 if (U16_IS_TRAIL(m_text[--current])) | |
| 1826 --current; | |
| 1827 if (current < 0) | |
| 1828 current = 0; | |
| 1829 return current; | |
| 1830 #endif | |
| 1831 } | |
| 1832 | |
| 1833 int RenderText::nextOffset(int current) const | |
| 1834 { | |
| 1835 if (isAllASCII() || m_text.is8Bit()) | |
| 1836 return current + 1; | |
| 1837 | |
| 1838 StringImpl* textImpl = m_text.impl(); | |
| 1839 TextBreakIterator* iterator = cursorMovementIterator(textImpl->characters16(
), textImpl->length()); | |
| 1840 if (!iterator) | |
| 1841 return current + 1; | |
| 1842 | |
| 1843 long result = iterator->following(current); | |
| 1844 if (result == TextBreakDone) | |
| 1845 result = current + 1; | |
| 1846 | |
| 1847 return result; | |
| 1848 } | |
| 1849 | |
| 1850 bool RenderText::computeCanUseSimpleFontCodePath() const | |
| 1851 { | |
| 1852 if (isAllASCII() || m_text.is8Bit()) | |
| 1853 return true; | |
| 1854 return Character::characterRangeCodePath(characters16(), length()) == Simple
Path; | |
| 1855 } | |
| 1856 | |
| 1857 #if ENABLE(ASSERT) | |
| 1858 | |
| 1859 void RenderText::checkConsistency() const | |
| 1860 { | |
| 1861 #ifdef CHECK_CONSISTENCY | |
| 1862 const InlineTextBox* prev = 0; | |
| 1863 for (const InlineTextBox* child = m_firstTextBox; child != 0; child = child-
>nextTextBox()) { | |
| 1864 ASSERT(child->renderer() == this); | |
| 1865 ASSERT(child->prevTextBox() == prev); | |
| 1866 prev = child; | |
| 1867 } | |
| 1868 ASSERT(prev == m_lastTextBox); | |
| 1869 #endif | |
| 1870 } | |
| 1871 | |
| 1872 #endif | |
| 1873 | |
| 1874 void RenderText::momentarilyRevealLastTypedCharacter(unsigned lastTypedCharacter
Offset) | |
| 1875 { | |
| 1876 if (!gSecureTextTimers) | |
| 1877 gSecureTextTimers = new SecureTextTimerMap; | |
| 1878 | |
| 1879 SecureTextTimer* secureTextTimer = gSecureTextTimers->get(this); | |
| 1880 if (!secureTextTimer) { | |
| 1881 secureTextTimer = new SecureTextTimer(this); | |
| 1882 gSecureTextTimers->add(this, secureTextTimer); | |
| 1883 } | |
| 1884 secureTextTimer->restartWithNewText(lastTypedCharacterOffset); | |
| 1885 } | |
| 1886 | |
| 1887 PassRefPtr<AbstractInlineTextBox> RenderText::firstAbstractInlineTextBox() | |
| 1888 { | |
| 1889 return AbstractInlineTextBox::getOrCreate(this, m_firstTextBox); | |
| 1890 } | |
| 1891 | |
| 1892 void RenderText::invalidateDisplayItemClients(DisplayItemList* displayItemList)
const | |
| 1893 { | |
| 1894 LayoutObject::invalidateDisplayItemClients(displayItemList); | |
| 1895 for (InlineTextBox* box = firstTextBox(); box; box = box->nextTextBox()) | |
| 1896 displayItemList->invalidate(box->displayItemClient()); | |
| 1897 } | |
| 1898 | |
| 1899 } // namespace blink | |
| OLD | NEW |