layout/generic/nsTextFrame.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /* rendering object for textual content of elements */
michael@0 7
michael@0 8 #include "nsTextFrame.h"
michael@0 9
michael@0 10 #include "mozilla/Attributes.h"
michael@0 11 #include "mozilla/DebugOnly.h"
michael@0 12 #include "mozilla/Likely.h"
michael@0 13 #include "mozilla/MathAlgorithms.h"
michael@0 14 #include "mozilla/TextEvents.h"
michael@0 15
michael@0 16 #include "nsCOMPtr.h"
michael@0 17 #include "nsBlockFrame.h"
michael@0 18 #include "nsCRT.h"
michael@0 19 #include "nsSplittableFrame.h"
michael@0 20 #include "nsLineLayout.h"
michael@0 21 #include "nsString.h"
michael@0 22 #include "nsUnicharUtils.h"
michael@0 23 #include "nsPresContext.h"
michael@0 24 #include "nsIContent.h"
michael@0 25 #include "nsStyleConsts.h"
michael@0 26 #include "nsStyleContext.h"
michael@0 27 #include "nsStyleStruct.h"
michael@0 28 #include "nsStyleStructInlines.h"
michael@0 29 #include "SVGTextFrame.h"
michael@0 30 #include "nsCoord.h"
michael@0 31 #include "nsRenderingContext.h"
michael@0 32 #include "nsIPresShell.h"
michael@0 33 #include "nsTArray.h"
michael@0 34 #include "nsCSSPseudoElements.h"
michael@0 35 #include "nsCSSFrameConstructor.h"
michael@0 36 #include "nsCompatibility.h"
michael@0 37 #include "nsCSSColorUtils.h"
michael@0 38 #include "nsLayoutUtils.h"
michael@0 39 #include "nsDisplayList.h"
michael@0 40 #include "nsFrame.h"
michael@0 41 #include "nsIMathMLFrame.h"
michael@0 42 #include "nsPlaceholderFrame.h"
michael@0 43 #include "nsTextFrameUtils.h"
michael@0 44 #include "nsTextRunTransformations.h"
michael@0 45 #include "MathMLTextRunFactory.h"
michael@0 46 #include "nsExpirationTracker.h"
michael@0 47 #include "nsUnicodeProperties.h"
michael@0 48
michael@0 49 #include "nsTextFragment.h"
michael@0 50 #include "nsGkAtoms.h"
michael@0 51 #include "nsFrameSelection.h"
michael@0 52 #include "nsRange.h"
michael@0 53 #include "nsCSSRendering.h"
michael@0 54 #include "nsContentUtils.h"
michael@0 55 #include "nsLineBreaker.h"
michael@0 56 #include "nsIWordBreaker.h"
michael@0 57 #include "nsGenericDOMDataNode.h"
michael@0 58 #include "nsIFrameInlines.h"
michael@0 59
michael@0 60 #include <algorithm>
michael@0 61 #ifdef ACCESSIBILITY
michael@0 62 #include "nsAccessibilityService.h"
michael@0 63 #endif
michael@0 64 #include "nsAutoPtr.h"
michael@0 65
michael@0 66 #include "nsPrintfCString.h"
michael@0 67
michael@0 68 #include "gfxFont.h"
michael@0 69 #include "gfxContext.h"
michael@0 70
michael@0 71 #include "mozilla/dom/Element.h"
michael@0 72 #include "mozilla/LookAndFeel.h"
michael@0 73
michael@0 74 #include "GeckoProfiler.h"
michael@0 75
michael@0 76 #ifdef DEBUG
michael@0 77 #undef NOISY_REFLOW
michael@0 78 #undef NOISY_TRIM
michael@0 79 #else
michael@0 80 #undef NOISY_REFLOW
michael@0 81 #undef NOISY_TRIM
michael@0 82 #endif
michael@0 83
michael@0 84 #ifdef DrawText
michael@0 85 #undef DrawText
michael@0 86 #endif
michael@0 87
michael@0 88 using namespace mozilla;
michael@0 89 using namespace mozilla::dom;
michael@0 90
michael@0 91 struct TabWidth {
michael@0 92 TabWidth(uint32_t aOffset, uint32_t aWidth)
michael@0 93 : mOffset(aOffset), mWidth(float(aWidth))
michael@0 94 { }
michael@0 95
michael@0 96 uint32_t mOffset; // DOM offset relative to the current frame's offset.
michael@0 97 float mWidth; // extra space to be added at this position (in app units)
michael@0 98 };
michael@0 99
michael@0 100 struct TabWidthStore {
michael@0 101 TabWidthStore(int32_t aValidForContentOffset)
michael@0 102 : mLimit(0)
michael@0 103 , mValidForContentOffset(aValidForContentOffset)
michael@0 104 { }
michael@0 105
michael@0 106 // Apply tab widths to the aSpacing array, which corresponds to characters
michael@0 107 // beginning at aOffset and has length aLength. (Width records outside this
michael@0 108 // range will be ignored.)
michael@0 109 void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
michael@0 110 uint32_t aOffset, uint32_t aLength);
michael@0 111
michael@0 112 // Offset up to which tabs have been measured; positions beyond this have not
michael@0 113 // been calculated yet but may be appended if needed later. It's a DOM
michael@0 114 // offset relative to the current frame's offset.
michael@0 115 uint32_t mLimit;
michael@0 116
michael@0 117 // Need to recalc tab offsets if frame content offset differs from this.
michael@0 118 int32_t mValidForContentOffset;
michael@0 119
michael@0 120 // A TabWidth record for each tab character measured so far.
michael@0 121 nsTArray<TabWidth> mWidths;
michael@0 122 };
michael@0 123
michael@0 124 void
michael@0 125 TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
michael@0 126 uint32_t aOffset, uint32_t aLength)
michael@0 127 {
michael@0 128 uint32_t i = 0, len = mWidths.Length();
michael@0 129
michael@0 130 // If aOffset is non-zero, do a binary search to find where to start
michael@0 131 // processing the tab widths, in case the list is really long. (See bug
michael@0 132 // 953247.)
michael@0 133 // We need to start from the first entry where mOffset >= aOffset.
michael@0 134 if (aOffset > 0) {
michael@0 135 uint32_t lo = 0, hi = len;
michael@0 136 while (lo < hi) {
michael@0 137 i = (lo + hi) / 2;
michael@0 138 const TabWidth& tw = mWidths[i];
michael@0 139 if (tw.mOffset < aOffset) {
michael@0 140 // mWidths[i] precedes the target range; new search range
michael@0 141 // will be [i+1, hi)
michael@0 142 lo = ++i;
michael@0 143 continue;
michael@0 144 }
michael@0 145 if (tw.mOffset > aOffset) {
michael@0 146 // mWidths[i] is within (or beyond) the target range;
michael@0 147 // new search range is [lo, i). If it turns out that
michael@0 148 // mWidths[i] was the first entry within the range,
michael@0 149 // we'll never move hi any further, and end up exiting
michael@0 150 // when i == lo == this value of hi.
michael@0 151 hi = i;
michael@0 152 continue;
michael@0 153 }
michael@0 154 // Found an exact match for aOffset, so end search now
michael@0 155 break;
michael@0 156 }
michael@0 157 }
michael@0 158
michael@0 159 uint32_t limit = aOffset + aLength;
michael@0 160 while (i < len) {
michael@0 161 const TabWidth& tw = mWidths[i];
michael@0 162 if (tw.mOffset >= limit) {
michael@0 163 break;
michael@0 164 }
michael@0 165 aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
michael@0 166 i++;
michael@0 167 }
michael@0 168 }
michael@0 169
michael@0 170 static void DestroyTabWidth(void* aPropertyValue)
michael@0 171 {
michael@0 172 delete static_cast<TabWidthStore*>(aPropertyValue);
michael@0 173 }
michael@0 174
michael@0 175 NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth)
michael@0 176
michael@0 177 NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr)
michael@0 178
michael@0 179 // text runs are destroyed by the text run cache
michael@0 180 NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr)
michael@0 181
michael@0 182 NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr)
michael@0 183
michael@0 184 class GlyphObserver : public gfxFont::GlyphChangeObserver {
michael@0 185 public:
michael@0 186 GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame)
michael@0 187 : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {}
michael@0 188 virtual void NotifyGlyphsChanged() MOZ_OVERRIDE;
michael@0 189 private:
michael@0 190 nsTextFrame* mFrame;
michael@0 191 };
michael@0 192
michael@0 193 static void DestroyGlyphObserverList(void* aPropertyValue)
michael@0 194 {
michael@0 195 delete static_cast<nsTArray<nsAutoPtr<GlyphObserver> >*>(aPropertyValue);
michael@0 196 }
michael@0 197
michael@0 198 /**
michael@0 199 * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that
michael@0 200 * have potentially-animated glyphs.
michael@0 201 * The only reason this list is in a property is to automatically destroy the
michael@0 202 * list when the frame is deleted, unregistering the observers.
michael@0 203 */
michael@0 204 NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList);
michael@0 205
michael@0 206 #define TEXT_REFLOW_FLAGS \
michael@0 207 (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
michael@0 208 TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
michael@0 209 TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
michael@0 210
michael@0 211 #define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \
michael@0 212 TEXT_ISNOT_ONLY_WHITESPACE)
michael@0 213
michael@0 214 /*
michael@0 215 * Some general notes
michael@0 216 *
michael@0 217 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
michael@0 218 * transforms text to positioned glyphs. It can report the geometry of the
michael@0 219 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
michael@0 220 * spacing, language, and other information.
michael@0 221 *
michael@0 222 * A gfxTextRun can cover more than one DOM text node. This is necessary to
michael@0 223 * get kerning, ligatures and shaping for text that spans multiple text nodes
michael@0 224 * but is all the same font. The userdata for a gfxTextRun object is a
michael@0 225 * TextRunUserData* or an nsIFrame*.
michael@0 226 *
michael@0 227 * We go to considerable effort to make sure things work even if in-flow
michael@0 228 * siblings have different style contexts (i.e., first-letter and first-line).
michael@0 229 *
michael@0 230 * Our convention is that unsigned integer character offsets are offsets into
michael@0 231 * the transformed string. Signed integer character offsets are offsets into
michael@0 232 * the DOM string.
michael@0 233 *
michael@0 234 * XXX currently we don't handle hyphenated breaks between text frames where the
michael@0 235 * hyphen occurs at the end of the first text frame, e.g.
michael@0 236 * <b>Kit&shy;</b>ty
michael@0 237 */
michael@0 238
michael@0 239 /**
michael@0 240 * We use an array of these objects to record which text frames
michael@0 241 * are associated with the textrun. mStartFrame is the start of a list of
michael@0 242 * text frames. Some sequence of its continuations are covered by the textrun.
michael@0 243 * A content textnode can have at most one TextRunMappedFlow associated with it
michael@0 244 * for a given textrun.
michael@0 245 *
michael@0 246 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
michael@0 247 * the offset into the before-transformation text of the textrun. It can be
michael@0 248 * positive (when a text node starts in the middle of a text run) or
michael@0 249 * negative (when a text run starts in the middle of a text node). Of course
michael@0 250 * it can also be zero.
michael@0 251 */
michael@0 252 struct TextRunMappedFlow {
michael@0 253 nsTextFrame* mStartFrame;
michael@0 254 int32_t mDOMOffsetToBeforeTransformOffset;
michael@0 255 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
michael@0 256 uint32_t mContentLength;
michael@0 257 };
michael@0 258
michael@0 259 /**
michael@0 260 * This is our user data for the textrun, when textRun->GetFlags() does not
michael@0 261 * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is
michael@0 262 * just one flow, the textrun's user data pointer is a pointer to mStartFrame
michael@0 263 * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
michael@0 264 * is the length of the text node.
michael@0 265 */
michael@0 266 struct TextRunUserData {
michael@0 267 TextRunMappedFlow* mMappedFlows;
michael@0 268 uint32_t mMappedFlowCount;
michael@0 269 uint32_t mLastFlowIndex;
michael@0 270 };
michael@0 271
michael@0 272 /**
michael@0 273 * This helper object computes colors used for painting, and also IME
michael@0 274 * underline information. The data is computed lazily and cached as necessary.
michael@0 275 * These live for just the duration of one paint operation.
michael@0 276 */
michael@0 277 class nsTextPaintStyle {
michael@0 278 public:
michael@0 279 nsTextPaintStyle(nsTextFrame* aFrame);
michael@0 280
michael@0 281 void SetResolveColors(bool aResolveColors) {
michael@0 282 NS_ASSERTION(mFrame->IsSVGText() || aResolveColors,
michael@0 283 "must resolve colors is frame is not for SVG text");
michael@0 284 mResolveColors = aResolveColors;
michael@0 285 }
michael@0 286
michael@0 287 nscolor GetTextColor();
michael@0 288 /**
michael@0 289 * Compute the colors for normally-selected text. Returns false if
michael@0 290 * the normal selection is not being displayed.
michael@0 291 */
michael@0 292 bool GetSelectionColors(nscolor* aForeColor,
michael@0 293 nscolor* aBackColor);
michael@0 294 void GetHighlightColors(nscolor* aForeColor,
michael@0 295 nscolor* aBackColor);
michael@0 296 void GetURLSecondaryColor(nscolor* aForeColor);
michael@0 297 void GetIMESelectionColors(int32_t aIndex,
michael@0 298 nscolor* aForeColor,
michael@0 299 nscolor* aBackColor);
michael@0 300 // if this returns false, we don't need to draw underline.
michael@0 301 bool GetSelectionUnderlineForPaint(int32_t aIndex,
michael@0 302 nscolor* aLineColor,
michael@0 303 float* aRelativeSize,
michael@0 304 uint8_t* aStyle);
michael@0 305
michael@0 306 // if this returns false, we don't need to draw underline.
michael@0 307 static bool GetSelectionUnderline(nsPresContext* aPresContext,
michael@0 308 int32_t aIndex,
michael@0 309 nscolor* aLineColor,
michael@0 310 float* aRelativeSize,
michael@0 311 uint8_t* aStyle);
michael@0 312
michael@0 313 // if this returns false, no text-shadow was specified for the selection
michael@0 314 // and the *aShadow parameter was not modified.
michael@0 315 bool GetSelectionShadow(nsCSSShadowArray** aShadow);
michael@0 316
michael@0 317 nsPresContext* PresContext() const { return mPresContext; }
michael@0 318
michael@0 319 enum {
michael@0 320 eIndexRawInput = 0,
michael@0 321 eIndexSelRawText,
michael@0 322 eIndexConvText,
michael@0 323 eIndexSelConvText,
michael@0 324 eIndexSpellChecker
michael@0 325 };
michael@0 326
michael@0 327 static int32_t GetUnderlineStyleIndexForSelectionType(int32_t aSelectionType)
michael@0 328 {
michael@0 329 switch (aSelectionType) {
michael@0 330 case nsISelectionController::SELECTION_IME_RAWINPUT:
michael@0 331 return eIndexRawInput;
michael@0 332 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
michael@0 333 return eIndexSelRawText;
michael@0 334 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
michael@0 335 return eIndexConvText;
michael@0 336 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
michael@0 337 return eIndexSelConvText;
michael@0 338 case nsISelectionController::SELECTION_SPELLCHECK:
michael@0 339 return eIndexSpellChecker;
michael@0 340 default:
michael@0 341 NS_WARNING("non-IME selection type");
michael@0 342 return eIndexRawInput;
michael@0 343 }
michael@0 344 }
michael@0 345
michael@0 346 protected:
michael@0 347 nsTextFrame* mFrame;
michael@0 348 nsPresContext* mPresContext;
michael@0 349 bool mInitCommonColors;
michael@0 350 bool mInitSelectionColorsAndShadow;
michael@0 351 bool mResolveColors;
michael@0 352
michael@0 353 // Selection data
michael@0 354
michael@0 355 int16_t mSelectionStatus; // see nsIDocument.h SetDisplaySelection()
michael@0 356 nscolor mSelectionTextColor;
michael@0 357 nscolor mSelectionBGColor;
michael@0 358 nsRefPtr<nsCSSShadowArray> mSelectionShadow;
michael@0 359 bool mHasSelectionShadow;
michael@0 360
michael@0 361 // Common data
michael@0 362
michael@0 363 int32_t mSufficientContrast;
michael@0 364 nscolor mFrameBackgroundColor;
michael@0 365
michael@0 366 // selection colors and underline info, the colors are resolved colors if
michael@0 367 // mResolveColors is true (which is the default), i.e., the foreground color
michael@0 368 // and background color are swapped if it's needed. And also line color will
michael@0 369 // be resolved from them.
michael@0 370 struct nsSelectionStyle {
michael@0 371 bool mInit;
michael@0 372 nscolor mTextColor;
michael@0 373 nscolor mBGColor;
michael@0 374 nscolor mUnderlineColor;
michael@0 375 uint8_t mUnderlineStyle;
michael@0 376 float mUnderlineRelativeSize;
michael@0 377 };
michael@0 378 nsSelectionStyle mSelectionStyle[5];
michael@0 379
michael@0 380 // Color initializations
michael@0 381 void InitCommonColors();
michael@0 382 bool InitSelectionColorsAndShadow();
michael@0 383
michael@0 384 nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
michael@0 385 void InitSelectionStyle(int32_t aIndex);
michael@0 386
michael@0 387 bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
michael@0 388
michael@0 389 nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
michael@0 390 nscolor aBackColor);
michael@0 391 };
michael@0 392
michael@0 393 static void
michael@0 394 DestroyUserData(void* aUserData)
michael@0 395 {
michael@0 396 TextRunUserData* userData = static_cast<TextRunUserData*>(aUserData);
michael@0 397 if (userData) {
michael@0 398 nsMemory::Free(userData);
michael@0 399 }
michael@0 400 }
michael@0 401
michael@0 402 /**
michael@0 403 * Remove |aTextRun| from the frame continuation chain starting at
michael@0 404 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
michael@0 405 * Unmark |aFrame| as a text run owner if it's the frame we start at.
michael@0 406 * Return true if |aStartContinuation| is non-null and was found
michael@0 407 * in the next-continuation chain of |aFrame|.
michael@0 408 */
michael@0 409 static bool
michael@0 410 ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
michael@0 411 nsTextFrame* aStartContinuation,
michael@0 412 nsFrameState aWhichTextRunState)
michael@0 413 {
michael@0 414 NS_PRECONDITION(aFrame, "");
michael@0 415 NS_PRECONDITION(!aStartContinuation ||
michael@0 416 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
michael@0 417 aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
michael@0 418 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
michael@0 419 aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
michael@0 420 "wrong aStartContinuation for this text run");
michael@0 421
michael@0 422 if (!aStartContinuation || aStartContinuation == aFrame) {
michael@0 423 aFrame->RemoveStateBits(aWhichTextRunState);
michael@0 424 } else {
michael@0 425 do {
michael@0 426 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
michael@0 427 aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
michael@0 428 } while (aFrame && aFrame != aStartContinuation);
michael@0 429 }
michael@0 430 bool found = aStartContinuation == aFrame;
michael@0 431 while (aFrame) {
michael@0 432 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
michael@0 433 if (!aFrame->RemoveTextRun(aTextRun)) {
michael@0 434 break;
michael@0 435 }
michael@0 436 aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
michael@0 437 }
michael@0 438 NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?");
michael@0 439 return found;
michael@0 440 }
michael@0 441
michael@0 442 /**
michael@0 443 * Kill all references to |aTextRun| starting at |aStartContinuation|.
michael@0 444 * It could be referenced by any of its owners, and all their in-flows.
michael@0 445 * If |aStartContinuation| is null then process all userdata frames
michael@0 446 * and their continuations.
michael@0 447 * @note the caller is expected to take care of possibly destroying the
michael@0 448 * text run if all userdata frames were reset (userdata is deallocated
michael@0 449 * by this function though). The caller can detect this has occured by
michael@0 450 * checking |aTextRun->GetUserData() == nullptr|.
michael@0 451 */
michael@0 452 static void
michael@0 453 UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
michael@0 454 {
michael@0 455 if (!aTextRun->GetUserData())
michael@0 456 return;
michael@0 457
michael@0 458 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
michael@0 459 nsTextFrame* userDataFrame = static_cast<nsTextFrame*>(
michael@0 460 static_cast<nsIFrame*>(aTextRun->GetUserData()));
michael@0 461 nsFrameState whichTextRunState =
michael@0 462 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
michael@0 463 ? TEXT_IN_TEXTRUN_USER_DATA
michael@0 464 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
michael@0 465 DebugOnly<bool> found =
michael@0 466 ClearAllTextRunReferences(userDataFrame, aTextRun,
michael@0 467 aStartContinuation, whichTextRunState);
michael@0 468 NS_ASSERTION(!aStartContinuation || found,
michael@0 469 "aStartContinuation wasn't found in simple flow text run");
michael@0 470 if (!(userDataFrame->GetStateBits() & whichTextRunState)) {
michael@0 471 aTextRun->SetUserData(nullptr);
michael@0 472 }
michael@0 473 } else {
michael@0 474 TextRunUserData* userData =
michael@0 475 static_cast<TextRunUserData*>(aTextRun->GetUserData());
michael@0 476 int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
michael@0 477 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
michael@0 478 nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame;
michael@0 479 nsFrameState whichTextRunState =
michael@0 480 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
michael@0 481 ? TEXT_IN_TEXTRUN_USER_DATA
michael@0 482 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
michael@0 483 bool found =
michael@0 484 ClearAllTextRunReferences(userDataFrame, aTextRun,
michael@0 485 aStartContinuation, whichTextRunState);
michael@0 486 if (found) {
michael@0 487 if (userDataFrame->GetStateBits() & whichTextRunState) {
michael@0 488 destroyFromIndex = i + 1;
michael@0 489 }
michael@0 490 else {
michael@0 491 destroyFromIndex = i;
michael@0 492 }
michael@0 493 aStartContinuation = nullptr;
michael@0 494 }
michael@0 495 }
michael@0 496 NS_ASSERTION(destroyFromIndex >= 0,
michael@0 497 "aStartContinuation wasn't found in multi flow text run");
michael@0 498 if (destroyFromIndex == 0) {
michael@0 499 DestroyUserData(userData);
michael@0 500 aTextRun->SetUserData(nullptr);
michael@0 501 }
michael@0 502 else {
michael@0 503 userData->mMappedFlowCount = uint32_t(destroyFromIndex);
michael@0 504 if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
michael@0 505 userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
michael@0 506 }
michael@0 507 }
michael@0 508 }
michael@0 509 }
michael@0 510
michael@0 511 void
michael@0 512 GlyphObserver::NotifyGlyphsChanged()
michael@0 513 {
michael@0 514 nsIPresShell* shell = mFrame->PresContext()->PresShell();
michael@0 515 for (nsIFrame* f = mFrame; f;
michael@0 516 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
michael@0 517 if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) {
michael@0 518 // f will have its own GlyphObserver (if needed) so we can stop here.
michael@0 519 break;
michael@0 520 }
michael@0 521 f->InvalidateFrame();
michael@0 522 // Theoretically we could just update overflow areas, perhaps using
michael@0 523 // OverflowChangedTracker, but that would do a bunch of work eagerly that
michael@0 524 // we should probably do lazily here since there could be a lot
michael@0 525 // of text frames affected and we'd like to coalesce the work. So that's
michael@0 526 // not easy to do well.
michael@0 527 shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
michael@0 528 }
michael@0 529 }
michael@0 530
michael@0 531 class FrameTextRunCache;
michael@0 532
michael@0 533 static FrameTextRunCache *gTextRuns = nullptr;
michael@0 534
michael@0 535 /*
michael@0 536 * Cache textruns and expire them after 3*10 seconds of no use.
michael@0 537 */
michael@0 538 class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker<gfxTextRun,3> {
michael@0 539 public:
michael@0 540 enum { TIMEOUT_SECONDS = 10 };
michael@0 541 FrameTextRunCache()
michael@0 542 : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
michael@0 543 ~FrameTextRunCache() {
michael@0 544 AgeAllGenerations();
michael@0 545 }
michael@0 546
michael@0 547 void RemoveFromCache(gfxTextRun* aTextRun) {
michael@0 548 if (aTextRun->GetExpirationState()->IsTracked()) {
michael@0 549 RemoveObject(aTextRun);
michael@0 550 }
michael@0 551 }
michael@0 552
michael@0 553 // This gets called when the timeout has expired on a gfxTextRun
michael@0 554 virtual void NotifyExpired(gfxTextRun* aTextRun) {
michael@0 555 UnhookTextRunFromFrames(aTextRun, nullptr);
michael@0 556 RemoveFromCache(aTextRun);
michael@0 557 delete aTextRun;
michael@0 558 }
michael@0 559 };
michael@0 560
michael@0 561 // Helper to create a textrun and remember it in the textframe cache,
michael@0 562 // for either 8-bit or 16-bit text strings
michael@0 563 template<typename T>
michael@0 564 gfxTextRun *
michael@0 565 MakeTextRun(const T *aText, uint32_t aLength,
michael@0 566 gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
michael@0 567 uint32_t aFlags)
michael@0 568 {
michael@0 569 nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength,
michael@0 570 aParams, aFlags));
michael@0 571 if (!textRun) {
michael@0 572 return nullptr;
michael@0 573 }
michael@0 574 nsresult rv = gTextRuns->AddObject(textRun);
michael@0 575 if (NS_FAILED(rv)) {
michael@0 576 gTextRuns->RemoveFromCache(textRun);
michael@0 577 return nullptr;
michael@0 578 }
michael@0 579 #ifdef NOISY_BIDI
michael@0 580 printf("Created textrun\n");
michael@0 581 #endif
michael@0 582 return textRun.forget();
michael@0 583 }
michael@0 584
michael@0 585 void
michael@0 586 nsTextFrameTextRunCache::Init() {
michael@0 587 gTextRuns = new FrameTextRunCache();
michael@0 588 }
michael@0 589
michael@0 590 void
michael@0 591 nsTextFrameTextRunCache::Shutdown() {
michael@0 592 delete gTextRuns;
michael@0 593 gTextRuns = nullptr;
michael@0 594 }
michael@0 595
michael@0 596 int32_t nsTextFrame::GetContentEnd() const {
michael@0 597 nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation());
michael@0 598 return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
michael@0 599 }
michael@0 600
michael@0 601 struct FlowLengthProperty {
michael@0 602 int32_t mStartOffset;
michael@0 603 // The offset of the next fixed continuation after mStartOffset, or
michael@0 604 // of the end of the text if there is none
michael@0 605 int32_t mEndFlowOffset;
michael@0 606 };
michael@0 607
michael@0 608 int32_t nsTextFrame::GetInFlowContentLength() {
michael@0 609 if (!(mState & NS_FRAME_IS_BIDI)) {
michael@0 610 return mContent->TextLength() - mContentOffset;
michael@0 611 }
michael@0 612
michael@0 613 FlowLengthProperty* flowLength =
michael@0 614 static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength));
michael@0 615
michael@0 616 /**
michael@0 617 * This frame must start inside the cached flow. If the flow starts at
michael@0 618 * mContentOffset but this frame is empty, logically it might be before the
michael@0 619 * start of the cached flow.
michael@0 620 */
michael@0 621 if (flowLength &&
michael@0 622 (flowLength->mStartOffset < mContentOffset ||
michael@0 623 (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
michael@0 624 flowLength->mEndFlowOffset > mContentOffset) {
michael@0 625 #ifdef DEBUG
michael@0 626 NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
michael@0 627 "frame crosses fixed continuation boundary");
michael@0 628 #endif
michael@0 629 return flowLength->mEndFlowOffset - mContentOffset;
michael@0 630 }
michael@0 631
michael@0 632 nsTextFrame* nextBidi = static_cast<nsTextFrame*>(LastInFlow()->GetNextContinuation());
michael@0 633 int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
michael@0 634
michael@0 635 if (!flowLength) {
michael@0 636 flowLength = new FlowLengthProperty;
michael@0 637 if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
michael@0 638 nsINode::DeleteProperty<FlowLengthProperty>))) {
michael@0 639 delete flowLength;
michael@0 640 flowLength = nullptr;
michael@0 641 }
michael@0 642 }
michael@0 643 if (flowLength) {
michael@0 644 flowLength->mStartOffset = mContentOffset;
michael@0 645 flowLength->mEndFlowOffset = endFlow;
michael@0 646 }
michael@0 647
michael@0 648 return endFlow - mContentOffset;
michael@0 649 }
michael@0 650
michael@0 651 // Smarter versions of dom::IsSpaceCharacter.
michael@0 652 // Unicode is really annoying; sometimes a space character isn't whitespace ---
michael@0 653 // when it combines with another character
michael@0 654 // So we have several versions of IsSpace for use in different contexts.
michael@0 655
michael@0 656 static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos)
michael@0 657 {
michael@0 658 NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
michael@0 659 if (!aFrag->Is2b())
michael@0 660 return false;
michael@0 661 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
michael@0 662 aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
michael@0 663 }
michael@0 664
michael@0 665 // Check whether aPos is a space for CSS 'word-spacing' purposes
michael@0 666 static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag,
michael@0 667 uint32_t aPos, const nsStyleText* aStyleText)
michael@0 668 {
michael@0 669 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
michael@0 670
michael@0 671 char16_t ch = aFrag->CharAt(aPos);
michael@0 672 switch (ch) {
michael@0 673 case ' ':
michael@0 674 case CH_NBSP:
michael@0 675 return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
michael@0 676 case '\r':
michael@0 677 case '\t': return !aStyleText->WhiteSpaceIsSignificant();
michael@0 678 case '\n': return !aStyleText->NewlineIsSignificant() &&
michael@0 679 !aStyleText->NewlineIsDiscarded();
michael@0 680 default: return false;
michael@0 681 }
michael@0 682 }
michael@0 683
michael@0 684 // Check whether the string aChars/aLength starts with space that's
michael@0 685 // trimmable according to CSS 'white-space:normal/nowrap'.
michael@0 686 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength)
michael@0 687 {
michael@0 688 NS_ASSERTION(aLength > 0, "No text for IsSpace!");
michael@0 689
michael@0 690 char16_t ch = *aChars;
michael@0 691 if (ch == ' ')
michael@0 692 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
michael@0 693 return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
michael@0 694 }
michael@0 695
michael@0 696 // Check whether the character aCh is trimmable according to CSS
michael@0 697 // 'white-space:normal/nowrap'
michael@0 698 static bool IsTrimmableSpace(char aCh)
michael@0 699 {
michael@0 700 return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
michael@0 701 }
michael@0 702
michael@0 703 static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
michael@0 704 const nsStyleText* aStyleText)
michael@0 705 {
michael@0 706 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
michael@0 707
michael@0 708 switch (aFrag->CharAt(aPos)) {
michael@0 709 case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
michael@0 710 !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
michael@0 711 case '\n': return !aStyleText->NewlineIsSignificant() &&
michael@0 712 !aStyleText->NewlineIsDiscarded();
michael@0 713 case '\t':
michael@0 714 case '\r':
michael@0 715 case '\f': return !aStyleText->WhiteSpaceIsSignificant();
michael@0 716 default: return false;
michael@0 717 }
michael@0 718 }
michael@0 719
michael@0 720 static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos)
michael@0 721 {
michael@0 722 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
michael@0 723 char16_t ch = aFrag->CharAt(aPos);
michael@0 724 if (ch == ' ' || ch == CH_NBSP)
michael@0 725 return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
michael@0 726 return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
michael@0 727 }
michael@0 728
michael@0 729 // Count the amount of trimmable whitespace (as per CSS
michael@0 730 // 'white-space:normal/nowrap') in a text fragment. The first
michael@0 731 // character is at offset aStartOffset; the maximum number of characters
michael@0 732 // to check is aLength. aDirection is -1 or 1 depending on whether we should
michael@0 733 // progress backwards or forwards.
michael@0 734 static uint32_t
michael@0 735 GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
michael@0 736 int32_t aStartOffset, int32_t aLength,
michael@0 737 int32_t aDirection)
michael@0 738 {
michael@0 739 int32_t count = 0;
michael@0 740 if (aFrag->Is2b()) {
michael@0 741 const char16_t* str = aFrag->Get2b() + aStartOffset;
michael@0 742 int32_t fragLen = aFrag->GetLength() - aStartOffset;
michael@0 743 for (; count < aLength; ++count) {
michael@0 744 if (!IsTrimmableSpace(str, fragLen))
michael@0 745 break;
michael@0 746 str += aDirection;
michael@0 747 fragLen -= aDirection;
michael@0 748 }
michael@0 749 } else {
michael@0 750 const char* str = aFrag->Get1b() + aStartOffset;
michael@0 751 for (; count < aLength; ++count) {
michael@0 752 if (!IsTrimmableSpace(*str))
michael@0 753 break;
michael@0 754 str += aDirection;
michael@0 755 }
michael@0 756 }
michael@0 757 return count;
michael@0 758 }
michael@0 759
michael@0 760 static bool
michael@0 761 IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
michael@0 762 {
michael@0 763 if (aFrag->Is2b())
michael@0 764 return false;
michael@0 765 int32_t len = aFrag->GetLength();
michael@0 766 const char* str = aFrag->Get1b();
michael@0 767 for (int32_t i = 0; i < len; ++i) {
michael@0 768 char ch = str[i];
michael@0 769 if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
michael@0 770 continue;
michael@0 771 return false;
michael@0 772 }
michael@0 773 return true;
michael@0 774 }
michael@0 775
michael@0 776 static bool
michael@0 777 IsAllNewlines(const nsTextFragment* aFrag)
michael@0 778 {
michael@0 779 if (aFrag->Is2b())
michael@0 780 return false;
michael@0 781 int32_t len = aFrag->GetLength();
michael@0 782 const char* str = aFrag->Get1b();
michael@0 783 for (int32_t i = 0; i < len; ++i) {
michael@0 784 char ch = str[i];
michael@0 785 if (ch != '\n')
michael@0 786 return false;
michael@0 787 }
michael@0 788 return true;
michael@0 789 }
michael@0 790
michael@0 791 static void
michael@0 792 CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray<gfxFont*>& aFonts)
michael@0 793 {
michael@0 794 if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) {
michael@0 795 // Maybe the textrun was created for uninflated text.
michael@0 796 return;
michael@0 797 }
michael@0 798
michael@0 799 nsTArray<nsAutoPtr<GlyphObserver> >* observers =
michael@0 800 new nsTArray<nsAutoPtr<GlyphObserver> >();
michael@0 801 for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) {
michael@0 802 observers->AppendElement(new GlyphObserver(aFonts[i], aFrame));
michael@0 803 }
michael@0 804 aFrame->Properties().Set(TextFrameGlyphObservers(), observers);
michael@0 805 // We are lazy and don't try to remove a property value that might be
michael@0 806 // obsolete due to style changes or font selection changes. That is
michael@0 807 // likely to be rarely needed, and we don't want to eat the overhead of
michael@0 808 // doing it for the overwhelmingly common case of no property existing.
michael@0 809 // (And we're out of state bits to conveniently use for a fast property
michael@0 810 // existence check.) The only downside is that in some rare cases we might
michael@0 811 // keep fonts alive for longer than necessary, or unnecessarily invalidate
michael@0 812 // frames.
michael@0 813 }
michael@0 814
michael@0 815 static void
michael@0 816 CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
michael@0 817 {
michael@0 818 if (!aTextRun->GetUserData()) {
michael@0 819 return;
michael@0 820 }
michael@0 821 nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
michael@0 822 uint32_t numGlyphRuns;
michael@0 823 const gfxTextRun::GlyphRun* glyphRuns =
michael@0 824 aTextRun->GetGlyphRuns(&numGlyphRuns);
michael@0 825 for (uint32_t i = 0; i < numGlyphRuns; ++i) {
michael@0 826 gfxFont* font = glyphRuns[i].mFont;
michael@0 827 if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
michael@0 828 fontsWithAnimatedGlyphs.AppendElement(font);
michael@0 829 }
michael@0 830 }
michael@0 831 if (fontsWithAnimatedGlyphs.IsEmpty()) {
michael@0 832 return;
michael@0 833 }
michael@0 834
michael@0 835 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
michael@0 836 CreateObserverForAnimatedGlyphs(static_cast<nsTextFrame*>(
michael@0 837 static_cast<nsIFrame*>(aTextRun->GetUserData())), fontsWithAnimatedGlyphs);
michael@0 838 } else {
michael@0 839 TextRunUserData* userData =
michael@0 840 static_cast<TextRunUserData*>(aTextRun->GetUserData());
michael@0 841 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
michael@0 842 CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame,
michael@0 843 fontsWithAnimatedGlyphs);
michael@0 844 }
michael@0 845 }
michael@0 846 }
michael@0 847
michael@0 848 /**
michael@0 849 * This class accumulates state as we scan a paragraph of text. It detects
michael@0 850 * textrun boundaries (changes from text to non-text, hard
michael@0 851 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
michael@0 852 * It also detects linebreaker run boundaries (changes from text to non-text,
michael@0 853 * and hard line breaks) and at each boundary runs the linebreaker to compute
michael@0 854 * potential line breaks. It also records actual line breaks to store them in
michael@0 855 * the textruns.
michael@0 856 */
michael@0 857 class BuildTextRunsScanner {
michael@0 858 public:
michael@0 859 BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
michael@0 860 nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
michael@0 861 mCurrentFramesAllSameTextRun(nullptr),
michael@0 862 mContext(aContext),
michael@0 863 mLineContainer(aLineContainer),
michael@0 864 mBidiEnabled(aPresContext->BidiEnabled()),
michael@0 865 mSkipIncompleteTextRuns(false),
michael@0 866 mWhichTextRun(aWhichTextRun),
michael@0 867 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
michael@0 868 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
michael@0 869 ResetRunInfo();
michael@0 870 }
michael@0 871 ~BuildTextRunsScanner() {
michael@0 872 NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
michael@0 873 NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared");
michael@0 874 NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
michael@0 875 NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
michael@0 876 }
michael@0 877
michael@0 878 void SetAtStartOfLine() {
michael@0 879 mStartOfLine = true;
michael@0 880 mCanStopOnThisLine = false;
michael@0 881 }
michael@0 882 void SetSkipIncompleteTextRuns(bool aSkip) {
michael@0 883 mSkipIncompleteTextRuns = aSkip;
michael@0 884 }
michael@0 885 void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
michael@0 886 mCommonAncestorWithLastFrame = aFrame;
michael@0 887 }
michael@0 888 bool CanStopOnThisLine() {
michael@0 889 return mCanStopOnThisLine;
michael@0 890 }
michael@0 891 nsIFrame* GetCommonAncestorWithLastFrame() {
michael@0 892 return mCommonAncestorWithLastFrame;
michael@0 893 }
michael@0 894 void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
michael@0 895 if (mCommonAncestorWithLastFrame &&
michael@0 896 mCommonAncestorWithLastFrame->GetParent() == aFrame) {
michael@0 897 mCommonAncestorWithLastFrame = aFrame;
michael@0 898 }
michael@0 899 }
michael@0 900 void ScanFrame(nsIFrame* aFrame);
michael@0 901 bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun);
michael@0 902 void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
michael@0 903 void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
michael@0 904 void ResetRunInfo() {
michael@0 905 mLastFrame = nullptr;
michael@0 906 mMappedFlows.Clear();
michael@0 907 mLineBreakBeforeFrames.Clear();
michael@0 908 mMaxTextLength = 0;
michael@0 909 mDoubleByteText = false;
michael@0 910 }
michael@0 911 void AccumulateRunInfo(nsTextFrame* aFrame);
michael@0 912 /**
michael@0 913 * @return null to indicate either textrun construction failed or
michael@0 914 * we constructed just a partial textrun to set up linebreaker and other
michael@0 915 * state for following textruns.
michael@0 916 */
michael@0 917 gfxTextRun* BuildTextRunForFrames(void* aTextBuffer);
michael@0 918 bool SetupLineBreakerContext(gfxTextRun *aTextRun);
michael@0 919 void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
michael@0 920 nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
michael@0 921 enum SetupBreakSinksFlags {
michael@0 922 SBS_DOUBLE_BYTE = (1 << 0),
michael@0 923 SBS_EXISTING_TEXTRUN = (1 << 1),
michael@0 924 SBS_SUPPRESS_SINK = (1 << 2)
michael@0 925 };
michael@0 926 void SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
michael@0 927 const void* aTextPtr,
michael@0 928 uint32_t aFlags);
michael@0 929 struct FindBoundaryState {
michael@0 930 nsIFrame* mStopAtFrame;
michael@0 931 nsTextFrame* mFirstTextFrame;
michael@0 932 nsTextFrame* mLastTextFrame;
michael@0 933 bool mSeenTextRunBoundaryOnLaterLine;
michael@0 934 bool mSeenTextRunBoundaryOnThisLine;
michael@0 935 bool mSeenSpaceForLineBreakingOnThisLine;
michael@0 936 };
michael@0 937 enum FindBoundaryResult {
michael@0 938 FB_CONTINUE,
michael@0 939 FB_STOPPED_AT_STOP_FRAME,
michael@0 940 FB_FOUND_VALID_TEXTRUN_BOUNDARY
michael@0 941 };
michael@0 942 FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
michael@0 943
michael@0 944 bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
michael@0 945
michael@0 946 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
michael@0 947 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
michael@0 948 // continuations starting from mStartFrame are a sequence of in-flow frames).
michael@0 949 struct MappedFlow {
michael@0 950 nsTextFrame* mStartFrame;
michael@0 951 nsTextFrame* mEndFrame;
michael@0 952 // When we consider breaking between elements, the nearest common
michael@0 953 // ancestor of the elements containing the characters is the one whose
michael@0 954 // CSS 'white-space' property governs. So this records the nearest common
michael@0 955 // ancestor of mStartFrame and the previous text frame, or null if there
michael@0 956 // was no previous text frame on this line.
michael@0 957 nsIFrame* mAncestorControllingInitialBreak;
michael@0 958
michael@0 959 int32_t GetContentEnd() {
michael@0 960 return mEndFrame ? mEndFrame->GetContentOffset()
michael@0 961 : mStartFrame->GetContent()->GetText()->GetLength();
michael@0 962 }
michael@0 963 };
michael@0 964
michael@0 965 class BreakSink MOZ_FINAL : public nsILineBreakSink {
michael@0 966 public:
michael@0 967 BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, uint32_t aOffsetIntoTextRun,
michael@0 968 bool aExistingTextRun) :
michael@0 969 mTextRun(aTextRun), mContext(aContext),
michael@0 970 mOffsetIntoTextRun(aOffsetIntoTextRun),
michael@0 971 mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {}
michael@0 972
michael@0 973 virtual void SetBreaks(uint32_t aOffset, uint32_t aLength,
michael@0 974 uint8_t* aBreakBefore) MOZ_OVERRIDE {
michael@0 975 if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
michael@0 976 aBreakBefore, mContext)) {
michael@0 977 mChangedBreaks = true;
michael@0 978 // Be conservative and assume that some breaks have been set
michael@0 979 mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
michael@0 980 }
michael@0 981 }
michael@0 982
michael@0 983 virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength,
michael@0 984 bool* aCapitalize) MOZ_OVERRIDE {
michael@0 985 NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
michael@0 986 "Text run should be transformed!");
michael@0 987 nsTransformedTextRun* transformedTextRun =
michael@0 988 static_cast<nsTransformedTextRun*>(mTextRun);
michael@0 989 transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
michael@0 990 aCapitalize, mContext);
michael@0 991 }
michael@0 992
michael@0 993 void Finish() {
michael@0 994 NS_ASSERTION(!(mTextRun->GetFlags() &
michael@0 995 (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
michael@0 996 nsTextFrameUtils::TEXT_UNUSED_FLAG)),
michael@0 997 "Flag set that should never be set! (memory safety error?)");
michael@0 998 if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
michael@0 999 nsTransformedTextRun* transformedTextRun =
michael@0 1000 static_cast<nsTransformedTextRun*>(mTextRun);
michael@0 1001 transformedTextRun->FinishSettingProperties(mContext);
michael@0 1002 }
michael@0 1003 // The way nsTransformedTextRun is implemented, its glyph runs aren't
michael@0 1004 // available until after nsTransformedTextRun::FinishSettingProperties()
michael@0 1005 // is called. So that's why we defer checking for animated glyphs to here.
michael@0 1006 CreateObserversForAnimatedGlyphs(mTextRun);
michael@0 1007 }
michael@0 1008
michael@0 1009 gfxTextRun* mTextRun;
michael@0 1010 gfxContext* mContext;
michael@0 1011 uint32_t mOffsetIntoTextRun;
michael@0 1012 bool mChangedBreaks;
michael@0 1013 bool mExistingTextRun;
michael@0 1014 };
michael@0 1015
michael@0 1016 private:
michael@0 1017 nsAutoTArray<MappedFlow,10> mMappedFlows;
michael@0 1018 nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
michael@0 1019 nsAutoTArray<nsAutoPtr<BreakSink>,10> mBreakSinks;
michael@0 1020 nsAutoTArray<gfxTextRun*,5> mTextRunsToDelete;
michael@0 1021 nsLineBreaker mLineBreaker;
michael@0 1022 gfxTextRun* mCurrentFramesAllSameTextRun;
michael@0 1023 gfxContext* mContext;
michael@0 1024 nsIFrame* mLineContainer;
michael@0 1025 nsTextFrame* mLastFrame;
michael@0 1026 // The common ancestor of the current frame and the previous leaf frame
michael@0 1027 // on the line, or null if there was no previous leaf frame.
michael@0 1028 nsIFrame* mCommonAncestorWithLastFrame;
michael@0 1029 // mMaxTextLength is an upper bound on the size of the text in all mapped frames
michael@0 1030 // The value UINT32_MAX represents overflow; text will be discarded
michael@0 1031 uint32_t mMaxTextLength;
michael@0 1032 bool mDoubleByteText;
michael@0 1033 bool mBidiEnabled;
michael@0 1034 bool mStartOfLine;
michael@0 1035 bool mSkipIncompleteTextRuns;
michael@0 1036 bool mCanStopOnThisLine;
michael@0 1037 nsTextFrame::TextRunType mWhichTextRun;
michael@0 1038 uint8_t mNextRunContextInfo;
michael@0 1039 uint8_t mCurrentRunContextInfo;
michael@0 1040 };
michael@0 1041
michael@0 1042 static nsIFrame*
michael@0 1043 FindLineContainer(nsIFrame* aFrame)
michael@0 1044 {
michael@0 1045 while (aFrame && aFrame->CanContinueTextRun()) {
michael@0 1046 aFrame = aFrame->GetParent();
michael@0 1047 }
michael@0 1048 return aFrame;
michael@0 1049 }
michael@0 1050
michael@0 1051 static bool
michael@0 1052 IsLineBreakingWhiteSpace(char16_t aChar)
michael@0 1053 {
michael@0 1054 // 0x0A (\n) is not handled as white-space by the line breaker, since
michael@0 1055 // we break before it, if it isn't transformed to a normal space.
michael@0 1056 // (If we treat it as normal white-space then we'd only break after it.)
michael@0 1057 // However, it does induce a line break or is converted to a regular
michael@0 1058 // space, and either way it can be used to bound the region of text
michael@0 1059 // that needs to be analyzed for line breaking.
michael@0 1060 return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
michael@0 1061 }
michael@0 1062
michael@0 1063 static bool
michael@0 1064 TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
michael@0 1065 bool aIsDoubleByte)
michael@0 1066 {
michael@0 1067 if (aIsDoubleByte) {
michael@0 1068 const char16_t* chars = static_cast<const char16_t*>(aText);
michael@0 1069 for (uint32_t i = 0; i < aLength; ++i) {
michael@0 1070 if (IsLineBreakingWhiteSpace(chars[i]))
michael@0 1071 return true;
michael@0 1072 }
michael@0 1073 return false;
michael@0 1074 } else {
michael@0 1075 const uint8_t* chars = static_cast<const uint8_t*>(aText);
michael@0 1076 for (uint32_t i = 0; i < aLength; ++i) {
michael@0 1077 if (IsLineBreakingWhiteSpace(chars[i]))
michael@0 1078 return true;
michael@0 1079 }
michael@0 1080 return false;
michael@0 1081 }
michael@0 1082 }
michael@0 1083
michael@0 1084 struct FrameTextTraversal {
michael@0 1085 // These fields identify which frames should be recursively scanned
michael@0 1086 // The first normal frame to scan (or null, if no such frame should be scanned)
michael@0 1087 nsIFrame* mFrameToScan;
michael@0 1088 // The first overflow frame to scan (or null, if no such frame should be scanned)
michael@0 1089 nsIFrame* mOverflowFrameToScan;
michael@0 1090 // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
michael@0 1091 bool mScanSiblings;
michael@0 1092
michael@0 1093 // These identify the boundaries of the context required for
michael@0 1094 // line breaking or textrun construction
michael@0 1095 bool mLineBreakerCanCrossFrameBoundary;
michael@0 1096 bool mTextRunCanCrossFrameBoundary;
michael@0 1097
michael@0 1098 nsIFrame* NextFrameToScan() {
michael@0 1099 nsIFrame* f;
michael@0 1100 if (mFrameToScan) {
michael@0 1101 f = mFrameToScan;
michael@0 1102 mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
michael@0 1103 } else if (mOverflowFrameToScan) {
michael@0 1104 f = mOverflowFrameToScan;
michael@0 1105 mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
michael@0 1106 } else {
michael@0 1107 f = nullptr;
michael@0 1108 }
michael@0 1109 return f;
michael@0 1110 }
michael@0 1111 };
michael@0 1112
michael@0 1113 static FrameTextTraversal
michael@0 1114 CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType)
michael@0 1115 {
michael@0 1116 NS_ASSERTION(aType == aFrame->GetType(), "Wrong type");
michael@0 1117
michael@0 1118 FrameTextTraversal result;
michael@0 1119
michael@0 1120 bool continuesTextRun = aFrame->CanContinueTextRun();
michael@0 1121 if (aType == nsGkAtoms::placeholderFrame) {
michael@0 1122 // placeholders are "invisible", so a text run should be able to span
michael@0 1123 // across one. But don't descend into the out-of-flow.
michael@0 1124 result.mLineBreakerCanCrossFrameBoundary = true;
michael@0 1125 result.mOverflowFrameToScan = nullptr;
michael@0 1126 if (continuesTextRun) {
michael@0 1127 // ... Except for first-letter floats, which are really in-flow
michael@0 1128 // from the point of view of capitalization etc, so we'd better
michael@0 1129 // descend into them. But we actually need to break the textrun for
michael@0 1130 // first-letter floats since things look bad if, say, we try to make a
michael@0 1131 // ligature across the float boundary.
michael@0 1132 result.mFrameToScan =
michael@0 1133 (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
michael@0 1134 result.mScanSiblings = false;
michael@0 1135 result.mTextRunCanCrossFrameBoundary = false;
michael@0 1136 } else {
michael@0 1137 result.mFrameToScan = nullptr;
michael@0 1138 result.mTextRunCanCrossFrameBoundary = true;
michael@0 1139 }
michael@0 1140 } else {
michael@0 1141 if (continuesTextRun) {
michael@0 1142 result.mFrameToScan = aFrame->GetFirstPrincipalChild();
michael@0 1143 result.mOverflowFrameToScan =
michael@0 1144 aFrame->GetFirstChild(nsIFrame::kOverflowList);
michael@0 1145 NS_WARN_IF_FALSE(!result.mOverflowFrameToScan,
michael@0 1146 "Scanning overflow inline frames is something we should avoid");
michael@0 1147 result.mScanSiblings = true;
michael@0 1148 result.mTextRunCanCrossFrameBoundary = true;
michael@0 1149 result.mLineBreakerCanCrossFrameBoundary = true;
michael@0 1150 } else {
michael@0 1151 result.mFrameToScan = nullptr;
michael@0 1152 result.mOverflowFrameToScan = nullptr;
michael@0 1153 result.mTextRunCanCrossFrameBoundary = false;
michael@0 1154 result.mLineBreakerCanCrossFrameBoundary = false;
michael@0 1155 }
michael@0 1156 }
michael@0 1157 return result;
michael@0 1158 }
michael@0 1159
michael@0 1160 BuildTextRunsScanner::FindBoundaryResult
michael@0 1161 BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
michael@0 1162 {
michael@0 1163 nsIAtom* frameType = aFrame->GetType();
michael@0 1164 nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame
michael@0 1165 ? static_cast<nsTextFrame*>(aFrame) : nullptr;
michael@0 1166 if (textFrame) {
michael@0 1167 if (aState->mLastTextFrame &&
michael@0 1168 textFrame != aState->mLastTextFrame->GetNextInFlow() &&
michael@0 1169 !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
michael@0 1170 aState->mSeenTextRunBoundaryOnThisLine = true;
michael@0 1171 if (aState->mSeenSpaceForLineBreakingOnThisLine)
michael@0 1172 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
michael@0 1173 }
michael@0 1174 if (!aState->mFirstTextFrame) {
michael@0 1175 aState->mFirstTextFrame = textFrame;
michael@0 1176 }
michael@0 1177 aState->mLastTextFrame = textFrame;
michael@0 1178 }
michael@0 1179
michael@0 1180 if (aFrame == aState->mStopAtFrame)
michael@0 1181 return FB_STOPPED_AT_STOP_FRAME;
michael@0 1182
michael@0 1183 if (textFrame) {
michael@0 1184 if (!aState->mSeenSpaceForLineBreakingOnThisLine) {
michael@0 1185 const nsTextFragment* frag = textFrame->GetContent()->GetText();
michael@0 1186 uint32_t start = textFrame->GetContentOffset();
michael@0 1187 const void* text = frag->Is2b()
michael@0 1188 ? static_cast<const void*>(frag->Get2b() + start)
michael@0 1189 : static_cast<const void*>(frag->Get1b() + start);
michael@0 1190 if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(),
michael@0 1191 frag->Is2b())) {
michael@0 1192 aState->mSeenSpaceForLineBreakingOnThisLine = true;
michael@0 1193 if (aState->mSeenTextRunBoundaryOnLaterLine)
michael@0 1194 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
michael@0 1195 }
michael@0 1196 }
michael@0 1197 return FB_CONTINUE;
michael@0 1198 }
michael@0 1199
michael@0 1200 FrameTextTraversal traversal =
michael@0 1201 CanTextCrossFrameBoundary(aFrame, frameType);
michael@0 1202 if (!traversal.mTextRunCanCrossFrameBoundary) {
michael@0 1203 aState->mSeenTextRunBoundaryOnThisLine = true;
michael@0 1204 if (aState->mSeenSpaceForLineBreakingOnThisLine)
michael@0 1205 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
michael@0 1206 }
michael@0 1207
michael@0 1208 for (nsIFrame* f = traversal.NextFrameToScan(); f;
michael@0 1209 f = traversal.NextFrameToScan()) {
michael@0 1210 FindBoundaryResult result = FindBoundaries(f, aState);
michael@0 1211 if (result != FB_CONTINUE)
michael@0 1212 return result;
michael@0 1213 }
michael@0 1214
michael@0 1215 if (!traversal.mTextRunCanCrossFrameBoundary) {
michael@0 1216 aState->mSeenTextRunBoundaryOnThisLine = true;
michael@0 1217 if (aState->mSeenSpaceForLineBreakingOnThisLine)
michael@0 1218 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
michael@0 1219 }
michael@0 1220
michael@0 1221 return FB_CONTINUE;
michael@0 1222 }
michael@0 1223
michael@0 1224 // build text runs for the 200 lines following aForFrame, and stop after that
michael@0 1225 // when we get a chance.
michael@0 1226 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
michael@0 1227
michael@0 1228 /**
michael@0 1229 * General routine for building text runs. This is hairy because of the need
michael@0 1230 * to build text runs that span content nodes.
michael@0 1231 *
michael@0 1232 * @param aContext The gfxContext we're using to construct this text run.
michael@0 1233 * @param aForFrame The nsTextFrame for which we're building this text run.
michael@0 1234 * @param aLineContainer the line container containing aForFrame; if null,
michael@0 1235 * we'll walk the ancestors to find it. It's required to be non-null
michael@0 1236 * when aForFrameLine is non-null.
michael@0 1237 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
michael@0 1238 * out the line (slowly)
michael@0 1239 * @param aWhichTextRun The type of text run we want to build. If font inflation
michael@0 1240 * is enabled, this will be eInflated, otherwise it's eNotInflated.
michael@0 1241 */
michael@0 1242 static void
michael@0 1243 BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame,
michael@0 1244 nsIFrame* aLineContainer,
michael@0 1245 const nsLineList::iterator* aForFrameLine,
michael@0 1246 nsTextFrame::TextRunType aWhichTextRun)
michael@0 1247 {
michael@0 1248 NS_ASSERTION(aForFrame || aLineContainer,
michael@0 1249 "One of aForFrame or aLineContainer must be set!");
michael@0 1250 NS_ASSERTION(!aForFrameLine || aLineContainer,
michael@0 1251 "line but no line container");
michael@0 1252
michael@0 1253 nsIFrame* lineContainerChild = aForFrame;
michael@0 1254 if (!aLineContainer) {
michael@0 1255 if (aForFrame->IsFloatingFirstLetterChild()) {
michael@0 1256 lineContainerChild = aForFrame->PresContext()->PresShell()->
michael@0 1257 GetPlaceholderFrameFor(aForFrame->GetParent());
michael@0 1258 }
michael@0 1259 aLineContainer = FindLineContainer(lineContainerChild);
michael@0 1260 } else {
michael@0 1261 NS_ASSERTION(!aForFrame ||
michael@0 1262 (aLineContainer == FindLineContainer(aForFrame) ||
michael@0 1263 (aLineContainer->GetType() == nsGkAtoms::letterFrame &&
michael@0 1264 aLineContainer->IsFloating())),
michael@0 1265 "Wrong line container hint");
michael@0 1266 }
michael@0 1267
michael@0 1268 if (aForFrame) {
michael@0 1269 if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
michael@0 1270 aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
michael@0 1271 if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
michael@0 1272 aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
michael@0 1273 }
michael@0 1274 }
michael@0 1275 if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
michael@0 1276 aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
michael@0 1277 }
michael@0 1278 }
michael@0 1279
michael@0 1280 nsPresContext* presContext = aLineContainer->PresContext();
michael@0 1281 BuildTextRunsScanner scanner(presContext, aContext, aLineContainer,
michael@0 1282 aWhichTextRun);
michael@0 1283
michael@0 1284 nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
michael@0 1285
michael@0 1286 if (!block) {
michael@0 1287 NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
michael@0 1288 "Breakable non-block line containers not supported");
michael@0 1289 // Just loop through all the children of the linecontainer ... it's really
michael@0 1290 // just one line
michael@0 1291 scanner.SetAtStartOfLine();
michael@0 1292 scanner.SetCommonAncestorWithLastFrame(nullptr);
michael@0 1293 nsIFrame* child = aLineContainer->GetFirstPrincipalChild();
michael@0 1294 while (child) {
michael@0 1295 scanner.ScanFrame(child);
michael@0 1296 child = child->GetNextSibling();
michael@0 1297 }
michael@0 1298 // Set mStartOfLine so FlushFrames knows its textrun ends a line
michael@0 1299 scanner.SetAtStartOfLine();
michael@0 1300 scanner.FlushFrames(true, false);
michael@0 1301 return;
michael@0 1302 }
michael@0 1303
michael@0 1304 // Find the line containing 'lineContainerChild'.
michael@0 1305
michael@0 1306 bool isValid = true;
michael@0 1307 nsBlockInFlowLineIterator backIterator(block, &isValid);
michael@0 1308 if (aForFrameLine) {
michael@0 1309 backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
michael@0 1310 } else {
michael@0 1311 backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
michael@0 1312 NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
michael@0 1313 NS_ASSERTION(backIterator.GetContainer() == block,
michael@0 1314 "Someone lied to us about the block");
michael@0 1315 }
michael@0 1316 nsBlockFrame::line_iterator startLine = backIterator.GetLine();
michael@0 1317
michael@0 1318 // Find a line where we can start building text runs. We choose the last line
michael@0 1319 // where:
michael@0 1320 // -- there is a textrun boundary between the start of the line and the
michael@0 1321 // start of aForFrame
michael@0 1322 // -- there is a space between the start of the line and the textrun boundary
michael@0 1323 // (this is so we can be sure the line breaks will be set properly
michael@0 1324 // on the textruns we construct).
michael@0 1325 // The possibly-partial text runs up to and including the first space
michael@0 1326 // are not reconstructed. We construct partial text runs for that text ---
michael@0 1327 // for the sake of simplifying the code and feeding the linebreaker ---
michael@0 1328 // but we discard them instead of assigning them to frames.
michael@0 1329 // This is a little awkward because we traverse lines in the reverse direction
michael@0 1330 // but we traverse the frames in each line in the forward direction.
michael@0 1331 nsBlockInFlowLineIterator forwardIterator = backIterator;
michael@0 1332 nsIFrame* stopAtFrame = lineContainerChild;
michael@0 1333 nsTextFrame* nextLineFirstTextFrame = nullptr;
michael@0 1334 bool seenTextRunBoundaryOnLaterLine = false;
michael@0 1335 bool mayBeginInTextRun = true;
michael@0 1336 while (true) {
michael@0 1337 forwardIterator = backIterator;
michael@0 1338 nsBlockFrame::line_iterator line = backIterator.GetLine();
michael@0 1339 if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
michael@0 1340 mayBeginInTextRun = false;
michael@0 1341 break;
michael@0 1342 }
michael@0 1343
michael@0 1344 BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
michael@0 1345 bool(seenTextRunBoundaryOnLaterLine), false, false };
michael@0 1346 nsIFrame* child = line->mFirstChild;
michael@0 1347 bool foundBoundary = false;
michael@0 1348 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
michael@0 1349 BuildTextRunsScanner::FindBoundaryResult result =
michael@0 1350 scanner.FindBoundaries(child, &state);
michael@0 1351 if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
michael@0 1352 foundBoundary = true;
michael@0 1353 break;
michael@0 1354 } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
michael@0 1355 break;
michael@0 1356 }
michael@0 1357 child = child->GetNextSibling();
michael@0 1358 }
michael@0 1359 if (foundBoundary)
michael@0 1360 break;
michael@0 1361 if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
michael@0 1362 !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
michael@0 1363 // Found a usable textrun boundary at the end of the line
michael@0 1364 if (state.mSeenSpaceForLineBreakingOnThisLine)
michael@0 1365 break;
michael@0 1366 seenTextRunBoundaryOnLaterLine = true;
michael@0 1367 } else if (state.mSeenTextRunBoundaryOnThisLine) {
michael@0 1368 seenTextRunBoundaryOnLaterLine = true;
michael@0 1369 }
michael@0 1370 stopAtFrame = nullptr;
michael@0 1371 if (state.mFirstTextFrame) {
michael@0 1372 nextLineFirstTextFrame = state.mFirstTextFrame;
michael@0 1373 }
michael@0 1374 }
michael@0 1375 scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
michael@0 1376
michael@0 1377 // Now iterate over all text frames starting from the current line. First-in-flow
michael@0 1378 // text frames will be accumulated into textRunFrames as we go. When a
michael@0 1379 // text run boundary is required we flush textRunFrames ((re)building their
michael@0 1380 // gfxTextRuns as necessary).
michael@0 1381 bool seenStartLine = false;
michael@0 1382 uint32_t linesAfterStartLine = 0;
michael@0 1383 do {
michael@0 1384 nsBlockFrame::line_iterator line = forwardIterator.GetLine();
michael@0 1385 if (line->IsBlock())
michael@0 1386 break;
michael@0 1387 line->SetInvalidateTextRuns(false);
michael@0 1388 scanner.SetAtStartOfLine();
michael@0 1389 scanner.SetCommonAncestorWithLastFrame(nullptr);
michael@0 1390 nsIFrame* child = line->mFirstChild;
michael@0 1391 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
michael@0 1392 scanner.ScanFrame(child);
michael@0 1393 child = child->GetNextSibling();
michael@0 1394 }
michael@0 1395 if (line.get() == startLine.get()) {
michael@0 1396 seenStartLine = true;
michael@0 1397 }
michael@0 1398 if (seenStartLine) {
michael@0 1399 ++linesAfterStartLine;
michael@0 1400 if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
michael@0 1401 // Don't flush frames; we may be in the middle of a textrun
michael@0 1402 // that we can't end here. That's OK, we just won't build it.
michael@0 1403 // Note that we must already have finished the textrun for aForFrame,
michael@0 1404 // because we've seen the end of a textrun in a line after the line
michael@0 1405 // containing aForFrame.
michael@0 1406 scanner.FlushLineBreaks(nullptr);
michael@0 1407 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
michael@0 1408 // silences assertions in the scanner destructor.
michael@0 1409 scanner.ResetRunInfo();
michael@0 1410 return;
michael@0 1411 }
michael@0 1412 }
michael@0 1413 } while (forwardIterator.Next());
michael@0 1414
michael@0 1415 // Set mStartOfLine so FlushFrames knows its textrun ends a line
michael@0 1416 scanner.SetAtStartOfLine();
michael@0 1417 scanner.FlushFrames(true, false);
michael@0 1418 }
michael@0 1419
michael@0 1420 static char16_t*
michael@0 1421 ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount)
michael@0 1422 {
michael@0 1423 while (aCount) {
michael@0 1424 *aDest = *aSrc;
michael@0 1425 ++aDest;
michael@0 1426 ++aSrc;
michael@0 1427 --aCount;
michael@0 1428 }
michael@0 1429 return aDest;
michael@0 1430 }
michael@0 1431
michael@0 1432 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun)
michael@0 1433 {
michael@0 1434 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW)
michael@0 1435 return mMappedFlows.Length() == 1 &&
michael@0 1436 mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) &&
michael@0 1437 mMappedFlows[0].mEndFrame == nullptr;
michael@0 1438
michael@0 1439 TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
michael@0 1440 if (userData->mMappedFlowCount != mMappedFlows.Length())
michael@0 1441 return false;
michael@0 1442 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 1443 if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
michael@0 1444 int32_t(userData->mMappedFlows[i].mContentLength) !=
michael@0 1445 mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
michael@0 1446 return false;
michael@0 1447 }
michael@0 1448 return true;
michael@0 1449 }
michael@0 1450
michael@0 1451 /**
michael@0 1452 * This gets called when we need to make a text run for the current list of
michael@0 1453 * frames.
michael@0 1454 */
michael@0 1455 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
michael@0 1456 {
michael@0 1457 gfxTextRun* textRun = nullptr;
michael@0 1458 if (!mMappedFlows.IsEmpty()) {
michael@0 1459 if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
michael@0 1460 ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
michael@0 1461 ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) &&
michael@0 1462 ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) ==
michael@0 1463 ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) &&
michael@0 1464 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
michael@0 1465 // Optimization: We do not need to (re)build the textrun.
michael@0 1466 textRun = mCurrentFramesAllSameTextRun;
michael@0 1467
michael@0 1468 // Feed this run's text into the linebreaker to provide context.
michael@0 1469 if (!SetupLineBreakerContext(textRun)) {
michael@0 1470 return;
michael@0 1471 }
michael@0 1472
michael@0 1473 // Update mNextRunContextInfo appropriately
michael@0 1474 mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
michael@0 1475 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) {
michael@0 1476 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
michael@0 1477 }
michael@0 1478 if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) {
michael@0 1479 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
michael@0 1480 }
michael@0 1481 } else {
michael@0 1482 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
michael@0 1483 uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
michael@0 1484 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
michael@0 1485 !buffer.AppendElements(bufferSize)) {
michael@0 1486 return;
michael@0 1487 }
michael@0 1488 textRun = BuildTextRunForFrames(buffer.Elements());
michael@0 1489 }
michael@0 1490 }
michael@0 1491
michael@0 1492 if (aFlushLineBreaks) {
michael@0 1493 FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun);
michael@0 1494 }
michael@0 1495
michael@0 1496 mCanStopOnThisLine = true;
michael@0 1497 ResetRunInfo();
michael@0 1498 }
michael@0 1499
michael@0 1500 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
michael@0 1501 {
michael@0 1502 bool trailingLineBreak;
michael@0 1503 nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
michael@0 1504 // textRun may be null for various reasons, including because we constructed
michael@0 1505 // a partial textrun just to get the linebreaker and other state set up
michael@0 1506 // to build the next textrun.
michael@0 1507 if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
michael@0 1508 aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
michael@0 1509 }
michael@0 1510
michael@0 1511 for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
michael@0 1512 if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
michael@0 1513 // TODO cause frames associated with the textrun to be reflowed, if they
michael@0 1514 // aren't being reflowed already!
michael@0 1515 }
michael@0 1516 mBreakSinks[i]->Finish();
michael@0 1517 }
michael@0 1518 mBreakSinks.Clear();
michael@0 1519
michael@0 1520 for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) {
michael@0 1521 gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
michael@0 1522 gTextRuns->RemoveFromCache(deleteTextRun);
michael@0 1523 delete deleteTextRun;
michael@0 1524 }
michael@0 1525 mTextRunsToDelete.Clear();
michael@0 1526 }
michael@0 1527
michael@0 1528 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
michael@0 1529 {
michael@0 1530 if (mMaxTextLength != UINT32_MAX) {
michael@0 1531 NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
michael@0 1532 if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
michael@0 1533 mMaxTextLength = UINT32_MAX;
michael@0 1534 } else {
michael@0 1535 mMaxTextLength += aFrame->GetContentLength();
michael@0 1536 }
michael@0 1537 }
michael@0 1538 mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
michael@0 1539 mLastFrame = aFrame;
michael@0 1540 mCommonAncestorWithLastFrame = aFrame->GetParent();
michael@0 1541
michael@0 1542 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
michael@0 1543 NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
michael@0 1544 mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
michael@0 1545 "Overlapping or discontiguous frames => BAD");
michael@0 1546 mappedFlow->mEndFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
michael@0 1547 if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
michael@0 1548 mCurrentFramesAllSameTextRun = nullptr;
michael@0 1549 }
michael@0 1550
michael@0 1551 if (mStartOfLine) {
michael@0 1552 mLineBreakBeforeFrames.AppendElement(aFrame);
michael@0 1553 mStartOfLine = false;
michael@0 1554 }
michael@0 1555 }
michael@0 1556
michael@0 1557 static nscoord StyleToCoord(const nsStyleCoord& aCoord)
michael@0 1558 {
michael@0 1559 if (eStyleUnit_Coord == aCoord.GetUnit()) {
michael@0 1560 return aCoord.GetCoordValue();
michael@0 1561 } else {
michael@0 1562 return 0;
michael@0 1563 }
michael@0 1564 }
michael@0 1565
michael@0 1566 static bool
michael@0 1567 HasTerminalNewline(const nsTextFrame* aFrame)
michael@0 1568 {
michael@0 1569 if (aFrame->GetContentLength() == 0)
michael@0 1570 return false;
michael@0 1571 const nsTextFragment* frag = aFrame->GetContent()->GetText();
michael@0 1572 return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
michael@0 1573 }
michael@0 1574
michael@0 1575 static nscoord
michael@0 1576 LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
michael@0 1577 {
michael@0 1578 if (aFrame->IsSVGText()) {
michael@0 1579 return 0;
michael@0 1580 }
michael@0 1581 if (!aStyleText) {
michael@0 1582 aStyleText = aFrame->StyleText();
michael@0 1583 }
michael@0 1584 return StyleToCoord(aStyleText->mLetterSpacing);
michael@0 1585 }
michael@0 1586
michael@0 1587 static nscoord
michael@0 1588 WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
michael@0 1589 {
michael@0 1590 if (aFrame->IsSVGText()) {
michael@0 1591 return 0;
michael@0 1592 }
michael@0 1593 if (!aStyleText) {
michael@0 1594 aStyleText = aFrame->StyleText();
michael@0 1595 }
michael@0 1596 return aStyleText->mWordSpacing;
michael@0 1597 }
michael@0 1598
michael@0 1599 bool
michael@0 1600 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
michael@0 1601 {
michael@0 1602 // We don't need to check font size inflation, since
michael@0 1603 // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
michael@0 1604 // ensures that text runs never cross block boundaries. This means
michael@0 1605 // that the font size inflation on all text frames in the text run is
michael@0 1606 // already guaranteed to be the same as each other (and for the line
michael@0 1607 // container).
michael@0 1608 if (mBidiEnabled &&
michael@0 1609 (NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2) ||
michael@0 1610 NS_GET_PARAGRAPH_DEPTH(aFrame1) != NS_GET_PARAGRAPH_DEPTH(aFrame2)))
michael@0 1611 return false;
michael@0 1612
michael@0 1613 nsStyleContext* sc1 = aFrame1->StyleContext();
michael@0 1614 const nsStyleText* textStyle1 = sc1->StyleText();
michael@0 1615 // If the first frame ends in a preformatted newline, then we end the textrun
michael@0 1616 // here. This avoids creating giant textruns for an entire plain text file.
michael@0 1617 // Note that we create a single text frame for a preformatted text node,
michael@0 1618 // even if it has newlines in it, so typically we won't see trailing newlines
michael@0 1619 // until after reflow has broken up the frame into one (or more) frames per
michael@0 1620 // line. That's OK though.
michael@0 1621 if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1))
michael@0 1622 return false;
michael@0 1623
michael@0 1624 if (aFrame1->GetContent() == aFrame2->GetContent() &&
michael@0 1625 aFrame1->GetNextInFlow() != aFrame2) {
michael@0 1626 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
michael@0 1627 // sometimes when the unicode-bidi property is used; the bidi resolver
michael@0 1628 // breaks text into different frames even though the text has the same
michael@0 1629 // direction. We can't allow these two frames to share the same textrun
michael@0 1630 // because that would violate our invariant that two flows in the same
michael@0 1631 // textrun have different content elements.
michael@0 1632 return false;
michael@0 1633 }
michael@0 1634
michael@0 1635 nsStyleContext* sc2 = aFrame2->StyleContext();
michael@0 1636 const nsStyleText* textStyle2 = sc2->StyleText();
michael@0 1637 if (sc1 == sc2)
michael@0 1638 return true;
michael@0 1639
michael@0 1640 const nsStyleFont* fontStyle1 = sc1->StyleFont();
michael@0 1641 const nsStyleFont* fontStyle2 = sc2->StyleFont();
michael@0 1642 nscoord letterSpacing1 = LetterSpacing(aFrame1);
michael@0 1643 nscoord letterSpacing2 = LetterSpacing(aFrame2);
michael@0 1644 return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) &&
michael@0 1645 sc1->StyleFont()->mLanguage == sc2->StyleFont()->mLanguage &&
michael@0 1646 textStyle1->mTextTransform == textStyle2->mTextTransform &&
michael@0 1647 nsLayoutUtils::GetTextRunFlagsForStyle(sc1, fontStyle1, textStyle1, letterSpacing1) ==
michael@0 1648 nsLayoutUtils::GetTextRunFlagsForStyle(sc2, fontStyle2, textStyle2, letterSpacing2);
michael@0 1649 }
michael@0 1650
michael@0 1651 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
michael@0 1652 {
michael@0 1653 // First check if we can extend the current mapped frame block. This is common.
michael@0 1654 if (mMappedFlows.Length() > 0) {
michael@0 1655 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
michael@0 1656 if (mappedFlow->mEndFrame == aFrame &&
michael@0 1657 (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
michael@0 1658 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
michael@0 1659 "Flow-sibling of a text frame is not a text frame?");
michael@0 1660
michael@0 1661 // Don't do this optimization if mLastFrame has a terminal newline...
michael@0 1662 // it's quite likely preformatted and we might want to end the textrun here.
michael@0 1663 // This is almost always true:
michael@0 1664 if (mLastFrame->StyleContext() == aFrame->StyleContext() &&
michael@0 1665 !HasTerminalNewline(mLastFrame)) {
michael@0 1666 AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
michael@0 1667 return;
michael@0 1668 }
michael@0 1669 }
michael@0 1670 }
michael@0 1671
michael@0 1672 nsIAtom* frameType = aFrame->GetType();
michael@0 1673 // Now see if we can add a new set of frames to the current textrun
michael@0 1674 if (frameType == nsGkAtoms::textFrame) {
michael@0 1675 nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
michael@0 1676
michael@0 1677 if (mLastFrame) {
michael@0 1678 if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
michael@0 1679 FlushFrames(false, false);
michael@0 1680 } else {
michael@0 1681 if (mLastFrame->GetContent() == frame->GetContent()) {
michael@0 1682 AccumulateRunInfo(frame);
michael@0 1683 return;
michael@0 1684 }
michael@0 1685 }
michael@0 1686 }
michael@0 1687
michael@0 1688 MappedFlow* mappedFlow = mMappedFlows.AppendElement();
michael@0 1689 if (!mappedFlow)
michael@0 1690 return;
michael@0 1691
michael@0 1692 mappedFlow->mStartFrame = frame;
michael@0 1693 mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
michael@0 1694
michael@0 1695 AccumulateRunInfo(frame);
michael@0 1696 if (mMappedFlows.Length() == 1) {
michael@0 1697 mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
michael@0 1698 mCurrentRunContextInfo = mNextRunContextInfo;
michael@0 1699 }
michael@0 1700 return;
michael@0 1701 }
michael@0 1702
michael@0 1703 FrameTextTraversal traversal =
michael@0 1704 CanTextCrossFrameBoundary(aFrame, frameType);
michael@0 1705 bool isBR = frameType == nsGkAtoms::brFrame;
michael@0 1706 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
michael@0 1707 // BR frames are special. We do not need or want to record a break opportunity
michael@0 1708 // before a BR frame.
michael@0 1709 FlushFrames(true, isBR);
michael@0 1710 mCommonAncestorWithLastFrame = aFrame;
michael@0 1711 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
michael@0 1712 mStartOfLine = false;
michael@0 1713 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
michael@0 1714 FlushFrames(false, false);
michael@0 1715 }
michael@0 1716
michael@0 1717 for (nsIFrame* f = traversal.NextFrameToScan(); f;
michael@0 1718 f = traversal.NextFrameToScan()) {
michael@0 1719 ScanFrame(f);
michael@0 1720 }
michael@0 1721
michael@0 1722 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
michael@0 1723 // Really if we're a BR frame this is unnecessary since descendInto will be
michael@0 1724 // false. In fact this whole "if" statement should move into the descendInto.
michael@0 1725 FlushFrames(true, isBR);
michael@0 1726 mCommonAncestorWithLastFrame = aFrame;
michael@0 1727 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
michael@0 1728 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
michael@0 1729 FlushFrames(false, false);
michael@0 1730 }
michael@0 1731
michael@0 1732 LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
michael@0 1733 }
michael@0 1734
michael@0 1735 nsTextFrame*
michael@0 1736 BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex)
michael@0 1737 {
michael@0 1738 uint32_t index = *aIndex;
michael@0 1739 if (index >= mLineBreakBeforeFrames.Length())
michael@0 1740 return nullptr;
michael@0 1741 *aIndex = index + 1;
michael@0 1742 return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
michael@0 1743 }
michael@0 1744
michael@0 1745 static uint32_t
michael@0 1746 GetSpacingFlags(nscoord spacing)
michael@0 1747 {
michael@0 1748 return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0;
michael@0 1749 }
michael@0 1750
michael@0 1751 static gfxFontGroup*
michael@0 1752 GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation,
michael@0 1753 nsFontMetrics** aOutFontMetrics = nullptr)
michael@0 1754 {
michael@0 1755 if (aOutFontMetrics)
michael@0 1756 *aOutFontMetrics = nullptr;
michael@0 1757
michael@0 1758 nsRefPtr<nsFontMetrics> metrics;
michael@0 1759 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics),
michael@0 1760 aFontSizeInflation);
michael@0 1761
michael@0 1762 if (!metrics)
michael@0 1763 return nullptr;
michael@0 1764
michael@0 1765 if (aOutFontMetrics) {
michael@0 1766 *aOutFontMetrics = metrics;
michael@0 1767 NS_ADDREF(*aOutFontMetrics);
michael@0 1768 }
michael@0 1769 // XXX this is a bit bogus, we're releasing 'metrics' so the
michael@0 1770 // returned font-group might actually be torn down, although because
michael@0 1771 // of the way the device context caches font metrics, this seems to
michael@0 1772 // not actually happen. But we should fix this.
michael@0 1773 return metrics->GetThebesFontGroup();
michael@0 1774 }
michael@0 1775
michael@0 1776 static already_AddRefed<gfxContext>
michael@0 1777 CreateReferenceThebesContext(nsTextFrame* aTextFrame)
michael@0 1778 {
michael@0 1779 nsRefPtr<nsRenderingContext> tmp =
michael@0 1780 aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
michael@0 1781
michael@0 1782 nsRefPtr<gfxContext> ctx = tmp->ThebesContext();
michael@0 1783 return ctx.forget();
michael@0 1784 }
michael@0 1785
michael@0 1786 /**
michael@0 1787 * The returned textrun must be deleted when no longer needed.
michael@0 1788 */
michael@0 1789 static gfxTextRun*
michael@0 1790 GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame)
michael@0 1791 {
michael@0 1792 nsRefPtr<gfxContext> ctx = aContext;
michael@0 1793 if (!ctx) {
michael@0 1794 ctx = CreateReferenceThebesContext(aTextFrame);
michael@0 1795 }
michael@0 1796 if (!ctx)
michael@0 1797 return nullptr;
michael@0 1798
michael@0 1799 return aTextRun->GetFontGroup()->
michael@0 1800 MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit());
michael@0 1801 }
michael@0 1802
michael@0 1803 static gfxFont::Metrics
michael@0 1804 GetFirstFontMetrics(gfxFontGroup* aFontGroup)
michael@0 1805 {
michael@0 1806 if (!aFontGroup)
michael@0 1807 return gfxFont::Metrics();
michael@0 1808 gfxFont* font = aFontGroup->GetFontAt(0);
michael@0 1809 if (!font)
michael@0 1810 return gfxFont::Metrics();
michael@0 1811 return font->GetMetrics();
michael@0 1812 }
michael@0 1813
michael@0 1814 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
michael@0 1815 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
michael@0 1816 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
michael@0 1817 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
michael@0 1818 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
michael@0 1819 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES == 5);
michael@0 1820
michael@0 1821 static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] =
michael@0 1822 {
michael@0 1823 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
michael@0 1824 nsTextFrameUtils::COMPRESS_NONE, // pre
michael@0 1825 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
michael@0 1826 nsTextFrameUtils::COMPRESS_NONE, // pre-wrap
michael@0 1827 nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line
michael@0 1828 nsTextFrameUtils::DISCARD_NEWLINE // -moz-pre-discard-newlines
michael@0 1829 };
michael@0 1830
michael@0 1831 gfxTextRun*
michael@0 1832 BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
michael@0 1833 {
michael@0 1834 gfxSkipChars skipChars;
michael@0 1835
michael@0 1836 const void* textPtr = aTextBuffer;
michael@0 1837 bool anySmallcapsStyle = false;
michael@0 1838 bool anyTextTransformStyle = false;
michael@0 1839 bool anyMathMLStyling = false;
michael@0 1840 uint8_t sstyScriptLevel = 0;
michael@0 1841 uint32_t textFlags = nsTextFrameUtils::TEXT_NO_BREAKS;
michael@0 1842
michael@0 1843 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
michael@0 1844 textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
michael@0 1845 }
michael@0 1846 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
michael@0 1847 textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR;
michael@0 1848 }
michael@0 1849
michael@0 1850 nsAutoTArray<int32_t,50> textBreakPoints;
michael@0 1851 TextRunUserData dummyData;
michael@0 1852 TextRunMappedFlow dummyMappedFlow;
michael@0 1853
michael@0 1854 TextRunUserData* userData;
michael@0 1855 TextRunUserData* userDataToDestroy;
michael@0 1856 // If the situation is particularly simple (and common) we don't need to
michael@0 1857 // allocate userData.
michael@0 1858 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
michael@0 1859 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
michael@0 1860 userData = &dummyData;
michael@0 1861 userDataToDestroy = nullptr;
michael@0 1862 dummyData.mMappedFlows = &dummyMappedFlow;
michael@0 1863 } else {
michael@0 1864 userData = static_cast<TextRunUserData*>
michael@0 1865 (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
michael@0 1866 userDataToDestroy = userData;
michael@0 1867 userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
michael@0 1868 }
michael@0 1869 userData->mMappedFlowCount = mMappedFlows.Length();
michael@0 1870 userData->mLastFlowIndex = 0;
michael@0 1871
michael@0 1872 uint32_t currentTransformedTextOffset = 0;
michael@0 1873
michael@0 1874 uint32_t nextBreakIndex = 0;
michael@0 1875 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
michael@0 1876 bool enabledJustification = mLineContainer &&
michael@0 1877 (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
michael@0 1878 mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) &&
michael@0 1879 !mLineContainer->IsSVGText();
michael@0 1880
michael@0 1881 // for word-break style
michael@0 1882 switch (mLineContainer->StyleText()->mWordBreak) {
michael@0 1883 case NS_STYLE_WORDBREAK_BREAK_ALL:
michael@0 1884 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_BreakAll);
michael@0 1885 break;
michael@0 1886 case NS_STYLE_WORDBREAK_KEEP_ALL:
michael@0 1887 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll);
michael@0 1888 break;
michael@0 1889 default:
michael@0 1890 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal);
michael@0 1891 break;
michael@0 1892 }
michael@0 1893
michael@0 1894 const nsStyleText* textStyle = nullptr;
michael@0 1895 const nsStyleFont* fontStyle = nullptr;
michael@0 1896 nsStyleContext* lastStyleContext = nullptr;
michael@0 1897 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 1898 MappedFlow* mappedFlow = &mMappedFlows[i];
michael@0 1899 nsTextFrame* f = mappedFlow->mStartFrame;
michael@0 1900
michael@0 1901 lastStyleContext = f->StyleContext();
michael@0 1902 // Detect use of text-transform or font-variant anywhere in the run
michael@0 1903 textStyle = f->StyleText();
michael@0 1904 if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
michael@0 1905 anyTextTransformStyle = true;
michael@0 1906 }
michael@0 1907 textFlags |= GetSpacingFlags(LetterSpacing(f));
michael@0 1908 textFlags |= GetSpacingFlags(WordSpacing(f));
michael@0 1909 nsTextFrameUtils::CompressionMode compression =
michael@0 1910 CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
michael@0 1911 if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) {
michael@0 1912 textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
michael@0 1913 }
michael@0 1914 fontStyle = f->StyleFont();
michael@0 1915 if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) {
michael@0 1916 anySmallcapsStyle = true;
michael@0 1917 }
michael@0 1918 if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
michael@0 1919 anyMathMLStyling = true;
michael@0 1920 } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) {
michael@0 1921 textFlags |= nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI;
michael@0 1922 anyMathMLStyling = true;
michael@0 1923 }
michael@0 1924 nsIFrame* parent = mLineContainer->GetParent();
michael@0 1925 if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
michael@0 1926 // All MathML tokens except <mtext> use 'math' script.
michael@0 1927 if (!(parent && parent->GetContent() &&
michael@0 1928 parent->GetContent()->Tag() == nsGkAtoms::mtext_)) {
michael@0 1929 textFlags |= gfxTextRunFactory::TEXT_USE_MATH_SCRIPT;
michael@0 1930 }
michael@0 1931 }
michael@0 1932 nsIFrame* child = mLineContainer;
michael@0 1933 uint8_t oldScriptLevel = 0;
michael@0 1934 while (parent &&
michael@0 1935 child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
michael@0 1936 // Reconstruct the script level ignoring any user overrides. It is
michael@0 1937 // calculated this way instead of using scriptlevel to ensure the
michael@0 1938 // correct ssty font feature setting is used even if the user sets a
michael@0 1939 // different (especially negative) scriptlevel.
michael@0 1940 nsIMathMLFrame* mathFrame= do_QueryFrame(parent);
michael@0 1941 if (mathFrame) {
michael@0 1942 sstyScriptLevel += mathFrame->ScriptIncrement(child);
michael@0 1943 }
michael@0 1944 if (sstyScriptLevel < oldScriptLevel) {
michael@0 1945 // overflow
michael@0 1946 sstyScriptLevel = UINT8_MAX;
michael@0 1947 break;
michael@0 1948 }
michael@0 1949 child = parent;
michael@0 1950 parent = parent->GetParent();
michael@0 1951 oldScriptLevel = sstyScriptLevel;
michael@0 1952 }
michael@0 1953 if (sstyScriptLevel) {
michael@0 1954 anyMathMLStyling = true;
michael@0 1955 }
michael@0 1956
michael@0 1957 // Figure out what content is included in this flow.
michael@0 1958 nsIContent* content = f->GetContent();
michael@0 1959 const nsTextFragment* frag = content->GetText();
michael@0 1960 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
michael@0 1961 int32_t contentEnd = mappedFlow->GetContentEnd();
michael@0 1962 int32_t contentLength = contentEnd - contentStart;
michael@0 1963
michael@0 1964 TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
michael@0 1965 newFlow->mStartFrame = mappedFlow->mStartFrame;
michael@0 1966 newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
michael@0 1967 mappedFlow->mStartFrame->GetContentOffset();
michael@0 1968 newFlow->mContentLength = contentLength;
michael@0 1969
michael@0 1970 while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
michael@0 1971 textBreakPoints.AppendElement(
michael@0 1972 nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
michael@0 1973 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
michael@0 1974 }
michael@0 1975
michael@0 1976 uint32_t analysisFlags;
michael@0 1977 if (frag->Is2b()) {
michael@0 1978 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
michael@0 1979 char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
michael@0 1980 char16_t* bufEnd = nsTextFrameUtils::TransformText(
michael@0 1981 frag->Get2b() + contentStart, contentLength, bufStart,
michael@0 1982 compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 1983 aTextBuffer = bufEnd;
michael@0 1984 currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr);
michael@0 1985 } else {
michael@0 1986 if (mDoubleByteText) {
michael@0 1987 // Need to expand the text. First transform it into a temporary buffer,
michael@0 1988 // then expand.
michael@0 1989 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
michael@0 1990 uint8_t* bufStart = tempBuf.AppendElements(contentLength);
michael@0 1991 if (!bufStart) {
michael@0 1992 DestroyUserData(userDataToDestroy);
michael@0 1993 return nullptr;
michael@0 1994 }
michael@0 1995 uint8_t* end = nsTextFrameUtils::TransformText(
michael@0 1996 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
michael@0 1997 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 1998 aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
michael@0 1999 tempBuf.Elements(), end - tempBuf.Elements());
michael@0 2000 currentTransformedTextOffset =
michael@0 2001 static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr);
michael@0 2002 } else {
michael@0 2003 uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
michael@0 2004 uint8_t* end = nsTextFrameUtils::TransformText(
michael@0 2005 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
michael@0 2006 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 2007 aTextBuffer = end;
michael@0 2008 currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr);
michael@0 2009 }
michael@0 2010 }
michael@0 2011 textFlags |= analysisFlags;
michael@0 2012 }
michael@0 2013
michael@0 2014 void* finalUserData;
michael@0 2015 if (userData == &dummyData) {
michael@0 2016 textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW;
michael@0 2017 userData = nullptr;
michael@0 2018 finalUserData = mMappedFlows[0].mStartFrame;
michael@0 2019 } else {
michael@0 2020 finalUserData = userData;
michael@0 2021 }
michael@0 2022
michael@0 2023 uint32_t transformedLength = currentTransformedTextOffset;
michael@0 2024
michael@0 2025 // Now build the textrun
michael@0 2026 nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
michael@0 2027 float fontInflation;
michael@0 2028 if (mWhichTextRun == nsTextFrame::eNotInflated) {
michael@0 2029 fontInflation = 1.0f;
michael@0 2030 } else {
michael@0 2031 fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
michael@0 2032 }
michael@0 2033
michael@0 2034 gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
michael@0 2035 if (!fontGroup) {
michael@0 2036 DestroyUserData(userDataToDestroy);
michael@0 2037 return nullptr;
michael@0 2038 }
michael@0 2039
michael@0 2040 if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
michael@0 2041 textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
michael@0 2042 }
michael@0 2043 if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
michael@0 2044 textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
michael@0 2045 }
michael@0 2046 if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) {
michael@0 2047 textFlags |= gfxTextRunFactory::TEXT_IS_RTL;
michael@0 2048 }
michael@0 2049 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
michael@0 2050 textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE;
michael@0 2051 }
michael@0 2052 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
michael@0 2053 textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR;
michael@0 2054 }
michael@0 2055 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
michael@0 2056 // frame's style is used, so we use a mixture of the first frame and
michael@0 2057 // last frame's style
michael@0 2058 textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext,
michael@0 2059 fontStyle, textStyle, LetterSpacing(firstFrame, textStyle));
michael@0 2060 // XXX this is a bit of a hack. For performance reasons, if we're favouring
michael@0 2061 // performance over quality, don't try to get accurate glyph extents.
michael@0 2062 if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) {
michael@0 2063 textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
michael@0 2064 }
michael@0 2065
michael@0 2066 // Convert linebreak coordinates to transformed string offsets
michael@0 2067 NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
michael@0 2068 "Didn't find all the frames to break-before...");
michael@0 2069 gfxSkipCharsIterator iter(skipChars);
michael@0 2070 nsAutoTArray<uint32_t,50> textBreakPointsAfterTransform;
michael@0 2071 for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
michael@0 2072 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
michael@0 2073 iter.ConvertOriginalToSkipped(textBreakPoints[i]));
michael@0 2074 }
michael@0 2075 if (mStartOfLine) {
michael@0 2076 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
michael@0 2077 transformedLength);
michael@0 2078 }
michael@0 2079
michael@0 2080 // Setup factory chain
michael@0 2081 nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
michael@0 2082 if (anySmallcapsStyle) {
michael@0 2083 transformingFactory = new nsFontVariantTextRunFactory();
michael@0 2084 }
michael@0 2085 if (anyTextTransformStyle) {
michael@0 2086 transformingFactory =
michael@0 2087 new nsCaseTransformTextRunFactory(transformingFactory.forget());
michael@0 2088 }
michael@0 2089 if (anyMathMLStyling) {
michael@0 2090 transformingFactory =
michael@0 2091 new MathMLTextRunFactory(transformingFactory.forget(), sstyScriptLevel);
michael@0 2092 }
michael@0 2093 nsTArray<nsStyleContext*> styles;
michael@0 2094 if (transformingFactory) {
michael@0 2095 iter.SetOriginalOffset(0);
michael@0 2096 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 2097 MappedFlow* mappedFlow = &mMappedFlows[i];
michael@0 2098 nsTextFrame* f;
michael@0 2099 for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
michael@0 2100 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
michael@0 2101 uint32_t offset = iter.GetSkippedOffset();
michael@0 2102 iter.AdvanceOriginal(f->GetContentLength());
michael@0 2103 uint32_t end = iter.GetSkippedOffset();
michael@0 2104 nsStyleContext* sc = f->StyleContext();
michael@0 2105 uint32_t j;
michael@0 2106 for (j = offset; j < end; ++j) {
michael@0 2107 styles.AppendElement(sc);
michael@0 2108 }
michael@0 2109 }
michael@0 2110 }
michael@0 2111 textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED;
michael@0 2112 NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
michael@0 2113 "We didn't cover all the characters in the text run!");
michael@0 2114 }
michael@0 2115
michael@0 2116 gfxTextRun* textRun;
michael@0 2117 gfxTextRunFactory::Parameters params =
michael@0 2118 { mContext, finalUserData, &skipChars,
michael@0 2119 textBreakPointsAfterTransform.Elements(),
michael@0 2120 textBreakPointsAfterTransform.Length(),
michael@0 2121 int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
michael@0 2122
michael@0 2123 if (mDoubleByteText) {
michael@0 2124 const char16_t* text = static_cast<const char16_t*>(textPtr);
michael@0 2125 if (transformingFactory) {
michael@0 2126 textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
michael@0 2127 fontGroup, textFlags, styles.Elements());
michael@0 2128 if (textRun) {
michael@0 2129 // ownership of the factory has passed to the textrun
michael@0 2130 transformingFactory.forget();
michael@0 2131 }
michael@0 2132 } else {
michael@0 2133 textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
michael@0 2134 }
michael@0 2135 } else {
michael@0 2136 const uint8_t* text = static_cast<const uint8_t*>(textPtr);
michael@0 2137 textFlags |= gfxFontGroup::TEXT_IS_8BIT;
michael@0 2138 if (transformingFactory) {
michael@0 2139 textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
michael@0 2140 fontGroup, textFlags, styles.Elements());
michael@0 2141 if (textRun) {
michael@0 2142 // ownership of the factory has passed to the textrun
michael@0 2143 transformingFactory.forget();
michael@0 2144 }
michael@0 2145 } else {
michael@0 2146 textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
michael@0 2147 }
michael@0 2148 }
michael@0 2149 if (!textRun) {
michael@0 2150 DestroyUserData(userDataToDestroy);
michael@0 2151 return nullptr;
michael@0 2152 }
michael@0 2153
michael@0 2154 // We have to set these up after we've created the textrun, because
michael@0 2155 // the breaks may be stored in the textrun during this very call.
michael@0 2156 // This is a bit annoying because it requires another loop over the frames
michael@0 2157 // making up the textrun, but I don't see a way to avoid this.
michael@0 2158 uint32_t flags = 0;
michael@0 2159 if (mDoubleByteText) {
michael@0 2160 flags |= SBS_DOUBLE_BYTE;
michael@0 2161 }
michael@0 2162 if (mSkipIncompleteTextRuns) {
michael@0 2163 flags |= SBS_SUPPRESS_SINK;
michael@0 2164 }
michael@0 2165 SetupBreakSinksForTextRun(textRun, textPtr, flags);
michael@0 2166
michael@0 2167 if (mSkipIncompleteTextRuns) {
michael@0 2168 mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
michael@0 2169 transformedLength, mDoubleByteText);
michael@0 2170 // Arrange for this textrun to be deleted the next time the linebreaker
michael@0 2171 // is flushed out
michael@0 2172 mTextRunsToDelete.AppendElement(textRun);
michael@0 2173 // Since we're doing to destroy the user data now, avoid a dangling
michael@0 2174 // pointer. Strictly speaking we don't need to do this since it should
michael@0 2175 // not be used (since this textrun will not be used and will be
michael@0 2176 // itself deleted soon), but it's always better to not have dangling
michael@0 2177 // pointers around.
michael@0 2178 textRun->SetUserData(nullptr);
michael@0 2179 DestroyUserData(userDataToDestroy);
michael@0 2180 return nullptr;
michael@0 2181 }
michael@0 2182
michael@0 2183 // Actually wipe out the textruns associated with the mapped frames and associate
michael@0 2184 // those frames with this text run.
michael@0 2185 AssignTextRun(textRun, fontInflation);
michael@0 2186 return textRun;
michael@0 2187 }
michael@0 2188
michael@0 2189 // This is a cut-down version of BuildTextRunForFrames used to set up
michael@0 2190 // context for the line-breaker, when the textrun has already been created.
michael@0 2191 // So it does the same walk over the mMappedFlows, but doesn't actually
michael@0 2192 // build a new textrun.
michael@0 2193 bool
michael@0 2194 BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
michael@0 2195 {
michael@0 2196 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
michael@0 2197 uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
michael@0 2198 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
michael@0 2199 return false;
michael@0 2200 }
michael@0 2201 void *textPtr = buffer.AppendElements(bufferSize);
michael@0 2202 if (!textPtr) {
michael@0 2203 return false;
michael@0 2204 }
michael@0 2205
michael@0 2206 gfxSkipChars skipChars;
michael@0 2207
michael@0 2208 nsAutoTArray<int32_t,50> textBreakPoints;
michael@0 2209 TextRunUserData dummyData;
michael@0 2210 TextRunMappedFlow dummyMappedFlow;
michael@0 2211
michael@0 2212 TextRunUserData* userData;
michael@0 2213 TextRunUserData* userDataToDestroy;
michael@0 2214 // If the situation is particularly simple (and common) we don't need to
michael@0 2215 // allocate userData.
michael@0 2216 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
michael@0 2217 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
michael@0 2218 userData = &dummyData;
michael@0 2219 userDataToDestroy = nullptr;
michael@0 2220 dummyData.mMappedFlows = &dummyMappedFlow;
michael@0 2221 } else {
michael@0 2222 userData = static_cast<TextRunUserData*>
michael@0 2223 (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
michael@0 2224 userDataToDestroy = userData;
michael@0 2225 userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
michael@0 2226 }
michael@0 2227 userData->mMappedFlowCount = mMappedFlows.Length();
michael@0 2228 userData->mLastFlowIndex = 0;
michael@0 2229
michael@0 2230 uint32_t nextBreakIndex = 0;
michael@0 2231 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
michael@0 2232
michael@0 2233 const nsStyleText* textStyle = nullptr;
michael@0 2234 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 2235 MappedFlow* mappedFlow = &mMappedFlows[i];
michael@0 2236 nsTextFrame* f = mappedFlow->mStartFrame;
michael@0 2237
michael@0 2238 textStyle = f->StyleText();
michael@0 2239 nsTextFrameUtils::CompressionMode compression =
michael@0 2240 CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
michael@0 2241
michael@0 2242 // Figure out what content is included in this flow.
michael@0 2243 nsIContent* content = f->GetContent();
michael@0 2244 const nsTextFragment* frag = content->GetText();
michael@0 2245 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
michael@0 2246 int32_t contentEnd = mappedFlow->GetContentEnd();
michael@0 2247 int32_t contentLength = contentEnd - contentStart;
michael@0 2248
michael@0 2249 TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
michael@0 2250 newFlow->mStartFrame = mappedFlow->mStartFrame;
michael@0 2251 newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
michael@0 2252 mappedFlow->mStartFrame->GetContentOffset();
michael@0 2253 newFlow->mContentLength = contentLength;
michael@0 2254
michael@0 2255 while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
michael@0 2256 textBreakPoints.AppendElement(
michael@0 2257 nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
michael@0 2258 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
michael@0 2259 }
michael@0 2260
michael@0 2261 uint32_t analysisFlags;
michael@0 2262 if (frag->Is2b()) {
michael@0 2263 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
michael@0 2264 char16_t* bufStart = static_cast<char16_t*>(textPtr);
michael@0 2265 char16_t* bufEnd = nsTextFrameUtils::TransformText(
michael@0 2266 frag->Get2b() + contentStart, contentLength, bufStart,
michael@0 2267 compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 2268 textPtr = bufEnd;
michael@0 2269 } else {
michael@0 2270 if (mDoubleByteText) {
michael@0 2271 // Need to expand the text. First transform it into a temporary buffer,
michael@0 2272 // then expand.
michael@0 2273 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
michael@0 2274 uint8_t* bufStart = tempBuf.AppendElements(contentLength);
michael@0 2275 if (!bufStart) {
michael@0 2276 DestroyUserData(userDataToDestroy);
michael@0 2277 return false;
michael@0 2278 }
michael@0 2279 uint8_t* end = nsTextFrameUtils::TransformText(
michael@0 2280 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
michael@0 2281 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 2282 textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
michael@0 2283 tempBuf.Elements(), end - tempBuf.Elements());
michael@0 2284 } else {
michael@0 2285 uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
michael@0 2286 uint8_t* end = nsTextFrameUtils::TransformText(
michael@0 2287 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
michael@0 2288 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
michael@0 2289 textPtr = end;
michael@0 2290 }
michael@0 2291 }
michael@0 2292 }
michael@0 2293
michael@0 2294 // We have to set these up after we've created the textrun, because
michael@0 2295 // the breaks may be stored in the textrun during this very call.
michael@0 2296 // This is a bit annoying because it requires another loop over the frames
michael@0 2297 // making up the textrun, but I don't see a way to avoid this.
michael@0 2298 uint32_t flags = 0;
michael@0 2299 if (mDoubleByteText) {
michael@0 2300 flags |= SBS_DOUBLE_BYTE;
michael@0 2301 }
michael@0 2302 if (mSkipIncompleteTextRuns) {
michael@0 2303 flags |= SBS_SUPPRESS_SINK;
michael@0 2304 }
michael@0 2305 SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags);
michael@0 2306
michael@0 2307 DestroyUserData(userDataToDestroy);
michael@0 2308
michael@0 2309 return true;
michael@0 2310 }
michael@0 2311
michael@0 2312 static bool
michael@0 2313 HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
michael@0 2314 int32_t aContentEndOffset,
michael@0 2315 const gfxSkipCharsIterator& aIterator)
michael@0 2316 {
michael@0 2317 if (!aIterator.IsOriginalCharSkipped())
michael@0 2318 return false;
michael@0 2319
michael@0 2320 gfxSkipCharsIterator iter = aIterator;
michael@0 2321 int32_t frameContentOffset = aFrame->GetContentOffset();
michael@0 2322 const nsTextFragment* frag = aFrame->GetContent()->GetText();
michael@0 2323 while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
michael@0 2324 if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
michael@0 2325 return true;
michael@0 2326 ++frameContentOffset;
michael@0 2327 iter.AdvanceOriginal(1);
michael@0 2328 }
michael@0 2329 return false;
michael@0 2330 }
michael@0 2331
michael@0 2332 void
michael@0 2333 BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
michael@0 2334 const void* aTextPtr,
michael@0 2335 uint32_t aFlags)
michael@0 2336 {
michael@0 2337 // textruns have uniform language
michael@0 2338 const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont();
michael@0 2339 // We should only use a language for hyphenation if it was specified
michael@0 2340 // explicitly.
michael@0 2341 nsIAtom* hyphenationLanguage =
michael@0 2342 styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr;
michael@0 2343 // We keep this pointed at the skip-chars data for the current mappedFlow.
michael@0 2344 // This lets us cheaply check whether the flow has compressed initial
michael@0 2345 // whitespace...
michael@0 2346 gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
michael@0 2347
michael@0 2348 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 2349 MappedFlow* mappedFlow = &mMappedFlows[i];
michael@0 2350 uint32_t offset = iter.GetSkippedOffset();
michael@0 2351 gfxSkipCharsIterator iterNext = iter;
michael@0 2352 iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
michael@0 2353 mappedFlow->mStartFrame->GetContentOffset());
michael@0 2354
michael@0 2355 nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
michael@0 2356 new BreakSink(aTextRun, mContext, offset,
michael@0 2357 (aFlags & SBS_EXISTING_TEXTRUN) != 0));
michael@0 2358 if (!breakSink || !*breakSink)
michael@0 2359 return;
michael@0 2360
michael@0 2361 uint32_t length = iterNext.GetSkippedOffset() - offset;
michael@0 2362 uint32_t flags = 0;
michael@0 2363 nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
michael@0 2364 if (!initialBreakController) {
michael@0 2365 initialBreakController = mLineContainer;
michael@0 2366 }
michael@0 2367 if (!initialBreakController->StyleText()->
michael@0 2368 WhiteSpaceCanWrap(initialBreakController)) {
michael@0 2369 flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
michael@0 2370 }
michael@0 2371 nsTextFrame* startFrame = mappedFlow->mStartFrame;
michael@0 2372 const nsStyleText* textStyle = startFrame->StyleText();
michael@0 2373 if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
michael@0 2374 flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
michael@0 2375 }
michael@0 2376 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
michael@0 2377 flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
michael@0 2378 }
michael@0 2379 if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
michael@0 2380 flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
michael@0 2381 }
michael@0 2382 if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
michael@0 2383 flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
michael@0 2384 }
michael@0 2385
michael@0 2386 if (HasCompressedLeadingWhitespace(startFrame, textStyle,
michael@0 2387 mappedFlow->GetContentEnd(), iter)) {
michael@0 2388 mLineBreaker.AppendInvisibleWhitespace(flags);
michael@0 2389 }
michael@0 2390
michael@0 2391 if (length > 0) {
michael@0 2392 BreakSink* sink =
michael@0 2393 (aFlags & SBS_SUPPRESS_SINK) ? nullptr : (*breakSink).get();
michael@0 2394 if (aFlags & SBS_DOUBLE_BYTE) {
michael@0 2395 const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
michael@0 2396 mLineBreaker.AppendText(hyphenationLanguage, text + offset,
michael@0 2397 length, flags, sink);
michael@0 2398 } else {
michael@0 2399 const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
michael@0 2400 mLineBreaker.AppendText(hyphenationLanguage, text + offset,
michael@0 2401 length, flags, sink);
michael@0 2402 }
michael@0 2403 }
michael@0 2404
michael@0 2405 iter = iterNext;
michael@0 2406 }
michael@0 2407 }
michael@0 2408
michael@0 2409 // Find the flow corresponding to aContent in aUserData
michael@0 2410 static inline TextRunMappedFlow*
michael@0 2411 FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent)
michael@0 2412 {
michael@0 2413 // Find the flow that contains us
michael@0 2414 int32_t i = aUserData->mLastFlowIndex;
michael@0 2415 int32_t delta = 1;
michael@0 2416 int32_t sign = 1;
michael@0 2417 // Search starting at the current position and examine close-by
michael@0 2418 // positions first, moving further and further away as we go.
michael@0 2419 while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
michael@0 2420 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
michael@0 2421 if (flow->mStartFrame->GetContent() == aContent) {
michael@0 2422 return flow;
michael@0 2423 }
michael@0 2424
michael@0 2425 i += delta;
michael@0 2426 sign = -sign;
michael@0 2427 delta = -delta + sign;
michael@0 2428 }
michael@0 2429
michael@0 2430 // We ran into an array edge. Add |delta| to |i| once more to get
michael@0 2431 // back to the side where we still need to search, then step in
michael@0 2432 // the |sign| direction.
michael@0 2433 i += delta;
michael@0 2434 if (sign > 0) {
michael@0 2435 for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
michael@0 2436 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
michael@0 2437 if (flow->mStartFrame->GetContent() == aContent) {
michael@0 2438 return flow;
michael@0 2439 }
michael@0 2440 }
michael@0 2441 } else {
michael@0 2442 for (; i >= 0; --i) {
michael@0 2443 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
michael@0 2444 if (flow->mStartFrame->GetContent() == aContent) {
michael@0 2445 return flow;
michael@0 2446 }
michael@0 2447 }
michael@0 2448 }
michael@0 2449
michael@0 2450 return nullptr;
michael@0 2451 }
michael@0 2452
michael@0 2453 void
michael@0 2454 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
michael@0 2455 {
michael@0 2456 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
michael@0 2457 MappedFlow* mappedFlow = &mMappedFlows[i];
michael@0 2458 nsTextFrame* startFrame = mappedFlow->mStartFrame;
michael@0 2459 nsTextFrame* endFrame = mappedFlow->mEndFrame;
michael@0 2460 nsTextFrame* f;
michael@0 2461 for (f = startFrame; f != endFrame;
michael@0 2462 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
michael@0 2463 #ifdef DEBUG_roc
michael@0 2464 if (f->GetTextRun(mWhichTextRun)) {
michael@0 2465 gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
michael@0 2466 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
michael@0 2467 if (mMappedFlows[0].mStartFrame != static_cast<nsTextFrame*>(textRun->GetUserData())) {
michael@0 2468 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
michael@0 2469 }
michael@0 2470 } else {
michael@0 2471 TextRunUserData* userData =
michael@0 2472 static_cast<TextRunUserData*>(textRun->GetUserData());
michael@0 2473
michael@0 2474 if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
michael@0 2475 userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
michael@0 2476 mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) {
michael@0 2477 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
michael@0 2478 }
michael@0 2479 }
michael@0 2480 }
michael@0 2481 #endif
michael@0 2482
michael@0 2483 gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
michael@0 2484 if (oldTextRun) {
michael@0 2485 nsTextFrame* firstFrame = nullptr;
michael@0 2486 uint32_t startOffset = 0;
michael@0 2487 if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
michael@0 2488 firstFrame = static_cast<nsTextFrame*>(oldTextRun->GetUserData());
michael@0 2489 }
michael@0 2490 else {
michael@0 2491 TextRunUserData* userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
michael@0 2492 firstFrame = userData->mMappedFlows[0].mStartFrame;
michael@0 2493 if (MOZ_UNLIKELY(f != firstFrame)) {
michael@0 2494 TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent());
michael@0 2495 if (flow) {
michael@0 2496 startOffset = flow->mDOMOffsetToBeforeTransformOffset;
michael@0 2497 }
michael@0 2498 else {
michael@0 2499 NS_ERROR("Can't find flow containing frame 'f'");
michael@0 2500 }
michael@0 2501 }
michael@0 2502 }
michael@0 2503
michael@0 2504 // Optimization: if |f| is the first frame in the flow then there are no
michael@0 2505 // prev-continuations that use |oldTextRun|.
michael@0 2506 nsTextFrame* clearFrom = nullptr;
michael@0 2507 if (MOZ_UNLIKELY(f != firstFrame)) {
michael@0 2508 // If all the frames in the mapped flow starting at |f| (inclusive)
michael@0 2509 // are empty then we let the prev-continuations keep the old text run.
michael@0 2510 gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
michael@0 2511 uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
michael@0 2512 clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
michael@0 2513 }
michael@0 2514 f->ClearTextRun(clearFrom, mWhichTextRun);
michael@0 2515
michael@0 2516 #ifdef DEBUG
michael@0 2517 if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
michael@0 2518 // oldTextRun was destroyed - assert that we don't reference it.
michael@0 2519 for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
michael@0 2520 NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
michael@0 2521 "destroyed text run is still in use");
michael@0 2522 }
michael@0 2523 }
michael@0 2524 #endif
michael@0 2525 }
michael@0 2526 f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
michael@0 2527 }
michael@0 2528 // Set this bit now; we can't set it any earlier because
michael@0 2529 // f->ClearTextRun() might clear it out.
michael@0 2530 nsFrameState whichTextRunState =
michael@0 2531 startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
michael@0 2532 ? TEXT_IN_TEXTRUN_USER_DATA
michael@0 2533 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
michael@0 2534 startFrame->AddStateBits(whichTextRunState);
michael@0 2535 }
michael@0 2536 }
michael@0 2537
michael@0 2538 NS_QUERYFRAME_HEAD(nsTextFrame)
michael@0 2539 NS_QUERYFRAME_ENTRY(nsTextFrame)
michael@0 2540 NS_QUERYFRAME_TAIL_INHERITING(nsTextFrameBase)
michael@0 2541
michael@0 2542 gfxSkipCharsIterator
michael@0 2543 nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
michael@0 2544 gfxContext* aReferenceContext,
michael@0 2545 nsIFrame* aLineContainer,
michael@0 2546 const nsLineList::iterator* aLine,
michael@0 2547 uint32_t* aFlowEndInTextRun)
michael@0 2548 {
michael@0 2549 gfxTextRun *textRun = GetTextRun(aWhichTextRun);
michael@0 2550 if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) {
michael@0 2551 if (textRun->GetExpirationState()->IsTracked()) {
michael@0 2552 gTextRuns->MarkUsed(textRun);
michael@0 2553 }
michael@0 2554 } else {
michael@0 2555 nsRefPtr<gfxContext> ctx = aReferenceContext;
michael@0 2556 if (!ctx) {
michael@0 2557 ctx = CreateReferenceThebesContext(this);
michael@0 2558 }
michael@0 2559 if (ctx) {
michael@0 2560 BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun);
michael@0 2561 }
michael@0 2562 textRun = GetTextRun(aWhichTextRun);
michael@0 2563 if (!textRun) {
michael@0 2564 // A text run was not constructed for this frame. This is bad. The caller
michael@0 2565 // will check mTextRun.
michael@0 2566 static const gfxSkipChars emptySkipChars;
michael@0 2567 return gfxSkipCharsIterator(emptySkipChars, 0);
michael@0 2568 }
michael@0 2569 TabWidthStore* tabWidths =
michael@0 2570 static_cast<TabWidthStore*>(Properties().Get(TabWidthProperty()));
michael@0 2571 if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
michael@0 2572 Properties().Delete(TabWidthProperty());
michael@0 2573 }
michael@0 2574 }
michael@0 2575
michael@0 2576 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
michael@0 2577 if (aFlowEndInTextRun) {
michael@0 2578 *aFlowEndInTextRun = textRun->GetLength();
michael@0 2579 }
michael@0 2580 return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
michael@0 2581 }
michael@0 2582
michael@0 2583 TextRunUserData* userData = static_cast<TextRunUserData*>(textRun->GetUserData());
michael@0 2584 TextRunMappedFlow* flow = FindFlowForContent(userData, mContent);
michael@0 2585 if (flow) {
michael@0 2586 // Since textruns can only contain one flow for a given content element,
michael@0 2587 // this must be our flow.
michael@0 2588 uint32_t flowIndex = flow - userData->mMappedFlows;
michael@0 2589 userData->mLastFlowIndex = flowIndex;
michael@0 2590 gfxSkipCharsIterator iter(textRun->GetSkipChars(),
michael@0 2591 flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
michael@0 2592 if (aFlowEndInTextRun) {
michael@0 2593 if (flowIndex + 1 < userData->mMappedFlowCount) {
michael@0 2594 gfxSkipCharsIterator end(textRun->GetSkipChars());
michael@0 2595 *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
michael@0 2596 flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
michael@0 2597 } else {
michael@0 2598 *aFlowEndInTextRun = textRun->GetLength();
michael@0 2599 }
michael@0 2600 }
michael@0 2601 return iter;
michael@0 2602 }
michael@0 2603
michael@0 2604 NS_ERROR("Can't find flow containing this frame???");
michael@0 2605 static const gfxSkipChars emptySkipChars;
michael@0 2606 return gfxSkipCharsIterator(emptySkipChars, 0);
michael@0 2607 }
michael@0 2608
michael@0 2609 static uint32_t
michael@0 2610 GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
michael@0 2611 uint32_t aStart, uint32_t aEnd,
michael@0 2612 gfxSkipCharsIterator* aIterator)
michael@0 2613 {
michael@0 2614 aIterator->SetSkippedOffset(aEnd);
michael@0 2615 while (aIterator->GetSkippedOffset() > aStart) {
michael@0 2616 aIterator->AdvanceSkipped(-1);
michael@0 2617 if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
michael@0 2618 return aIterator->GetSkippedOffset() + 1;
michael@0 2619 }
michael@0 2620 return aStart;
michael@0 2621 }
michael@0 2622
michael@0 2623 nsTextFrame::TrimmedOffsets
michael@0 2624 nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
michael@0 2625 bool aTrimAfter, bool aPostReflow)
michael@0 2626 {
michael@0 2627 NS_ASSERTION(mTextRun, "Need textrun here");
michael@0 2628 if (aPostReflow) {
michael@0 2629 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
michael@0 2630 // to be set correctly. If our parent wasn't reflowed due to the frame
michael@0 2631 // tree being too deep then the return value doesn't matter.
michael@0 2632 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
michael@0 2633 (GetParent()->GetStateBits() &
michael@0 2634 NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
michael@0 2635 "Can only call this on frames that have been reflowed");
michael@0 2636 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
michael@0 2637 "Can only call this on frames that are not being reflowed");
michael@0 2638 }
michael@0 2639
michael@0 2640 TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
michael@0 2641 const nsStyleText* textStyle = StyleText();
michael@0 2642 // Note that pre-line newlines should still allow us to trim spaces
michael@0 2643 // for display
michael@0 2644 if (textStyle->WhiteSpaceIsSignificant())
michael@0 2645 return offsets;
michael@0 2646
michael@0 2647 if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
michael@0 2648 int32_t whitespaceCount =
michael@0 2649 GetTrimmableWhitespaceCount(aFrag,
michael@0 2650 offsets.mStart, offsets.mLength, 1);
michael@0 2651 offsets.mStart += whitespaceCount;
michael@0 2652 offsets.mLength -= whitespaceCount;
michael@0 2653 }
michael@0 2654
michael@0 2655 if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
michael@0 2656 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
michael@0 2657 // it's actually what we want since we want whitespace before it to
michael@0 2658 // be trimmed.
michael@0 2659 int32_t whitespaceCount =
michael@0 2660 GetTrimmableWhitespaceCount(aFrag,
michael@0 2661 offsets.GetEnd() - 1, offsets.mLength, -1);
michael@0 2662 offsets.mLength -= whitespaceCount;
michael@0 2663 }
michael@0 2664 return offsets;
michael@0 2665 }
michael@0 2666
michael@0 2667 /*
michael@0 2668 * Currently only Unicode characters below 0x10000 have their spacing modified
michael@0 2669 * by justification. If characters above 0x10000 turn out to need
michael@0 2670 * justification spacing, that will require extra work. Currently,
michael@0 2671 * this function must not include 0xd800 to 0xdbff because these characters
michael@0 2672 * are surrogates.
michael@0 2673 */
michael@0 2674 static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos,
michael@0 2675 bool aLangIsCJ)
michael@0 2676 {
michael@0 2677 char16_t ch = aFrag->CharAt(aPos);
michael@0 2678 if (ch == '\n' || ch == '\t' || ch == '\r')
michael@0 2679 return true;
michael@0 2680 if (ch == ' ' || ch == CH_NBSP) {
michael@0 2681 // Don't justify spaces that are combined with diacriticals
michael@0 2682 if (!aFrag->Is2b())
michael@0 2683 return true;
michael@0 2684 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
michael@0 2685 aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
michael@0 2686 }
michael@0 2687 if (ch < 0x2150u)
michael@0 2688 return false;
michael@0 2689 if (aLangIsCJ && (
michael@0 2690 (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
michael@0 2691 (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
michael@0 2692 (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
michael@0 2693 (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
michael@0 2694 // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
michael@0 2695 // Miscellaneous Symbols and Arrows
michael@0 2696 (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
michael@0 2697 // Ideographic Description Characters, CJK Symbols and Punctuation,
michael@0 2698 // Hiragana, Katakana, Bopomofo
michael@0 2699 (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
michael@0 2700 // Enclosed CJK Letters and Months, CJK Compatibility,
michael@0 2701 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
michael@0 2702 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
michael@0 2703 (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
michael@0 2704 (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part)
michael@0 2705 ))
michael@0 2706 return true;
michael@0 2707 return false;
michael@0 2708 }
michael@0 2709
michael@0 2710 void
michael@0 2711 nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics)
michael@0 2712 {
michael@0 2713 aMetrics.Width() = 0;
michael@0 2714 aMetrics.Height() = 0;
michael@0 2715 aMetrics.SetTopAscent(0);
michael@0 2716 mAscent = 0;
michael@0 2717 }
michael@0 2718
michael@0 2719 static int32_t FindChar(const nsTextFragment* frag,
michael@0 2720 int32_t aOffset, int32_t aLength, char16_t ch)
michael@0 2721 {
michael@0 2722 int32_t i = 0;
michael@0 2723 if (frag->Is2b()) {
michael@0 2724 const char16_t* str = frag->Get2b() + aOffset;
michael@0 2725 for (; i < aLength; ++i) {
michael@0 2726 if (*str == ch)
michael@0 2727 return i + aOffset;
michael@0 2728 ++str;
michael@0 2729 }
michael@0 2730 } else {
michael@0 2731 if (uint16_t(ch) <= 0xFF) {
michael@0 2732 const char* str = frag->Get1b() + aOffset;
michael@0 2733 const void* p = memchr(str, ch, aLength);
michael@0 2734 if (p)
michael@0 2735 return (static_cast<const char*>(p) - str) + aOffset;
michael@0 2736 }
michael@0 2737 }
michael@0 2738 return -1;
michael@0 2739 }
michael@0 2740
michael@0 2741 static bool IsChineseOrJapanese(nsIFrame* aFrame)
michael@0 2742 {
michael@0 2743 nsIAtom* language = aFrame->StyleFont()->mLanguage;
michael@0 2744 if (!language) {
michael@0 2745 return false;
michael@0 2746 }
michael@0 2747 const char16_t *lang = language->GetUTF16String();
michael@0 2748 return (!nsCRT::strncmp(lang, MOZ_UTF16("ja"), 2) ||
michael@0 2749 !nsCRT::strncmp(lang, MOZ_UTF16("zh"), 2)) &&
michael@0 2750 (language->GetLength() == 2 || lang[2] == '-');
michael@0 2751 }
michael@0 2752
michael@0 2753 #ifdef DEBUG
michael@0 2754 static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
michael@0 2755 uint32_t aOffset, uint32_t aLength) {
michael@0 2756 if (aStart.GetSkippedOffset() > aOffset)
michael@0 2757 return false;
michael@0 2758 if (aContentLength == INT32_MAX)
michael@0 2759 return true;
michael@0 2760 gfxSkipCharsIterator iter(aStart);
michael@0 2761 iter.AdvanceOriginal(aContentLength);
michael@0 2762 return iter.GetSkippedOffset() >= aOffset + aLength;
michael@0 2763 }
michael@0 2764 #endif
michael@0 2765
michael@0 2766 class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
michael@0 2767 public:
michael@0 2768 /**
michael@0 2769 * Use this constructor for reflow, when we don't know what text is
michael@0 2770 * really mapped by the frame and we have a lot of other data around.
michael@0 2771 *
michael@0 2772 * @param aLength can be INT32_MAX to indicate we cover all the text
michael@0 2773 * associated with aFrame up to where its flow chain ends in the given
michael@0 2774 * textrun. If INT32_MAX is passed, justification and hyphen-related methods
michael@0 2775 * cannot be called, nor can GetOriginalLength().
michael@0 2776 */
michael@0 2777 PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
michael@0 2778 const nsTextFragment* aFrag, nsTextFrame* aFrame,
michael@0 2779 const gfxSkipCharsIterator& aStart, int32_t aLength,
michael@0 2780 nsIFrame* aLineContainer,
michael@0 2781 nscoord aOffsetFromBlockOriginForTabs,
michael@0 2782 nsTextFrame::TextRunType aWhichTextRun)
michael@0 2783 : mTextRun(aTextRun), mFontGroup(nullptr),
michael@0 2784 mTextStyle(aTextStyle), mFrag(aFrag),
michael@0 2785 mLineContainer(aLineContainer),
michael@0 2786 mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
michael@0 2787 mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
michael@0 2788 mLength(aLength),
michael@0 2789 mWordSpacing(WordSpacing(aFrame, aTextStyle)),
michael@0 2790 mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
michael@0 2791 mJustificationSpacing(0),
michael@0 2792 mHyphenWidth(-1),
michael@0 2793 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
michael@0 2794 mReflowing(true),
michael@0 2795 mWhichTextRun(aWhichTextRun)
michael@0 2796 {
michael@0 2797 NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
michael@0 2798 }
michael@0 2799
michael@0 2800 /**
michael@0 2801 * Use this constructor after the frame has been reflowed and we don't
michael@0 2802 * have other data around. Gets everything from the frame. EnsureTextRun
michael@0 2803 * *must* be called before this!!!
michael@0 2804 */
michael@0 2805 PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
michael@0 2806 nsTextFrame::TextRunType aWhichTextRun)
michael@0 2807 : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
michael@0 2808 mTextStyle(aFrame->StyleText()),
michael@0 2809 mFrag(aFrame->GetContent()->GetText()),
michael@0 2810 mLineContainer(nullptr),
michael@0 2811 mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
michael@0 2812 mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
michael@0 2813 mLength(aFrame->GetContentLength()),
michael@0 2814 mWordSpacing(WordSpacing(aFrame)),
michael@0 2815 mLetterSpacing(LetterSpacing(aFrame)),
michael@0 2816 mJustificationSpacing(0),
michael@0 2817 mHyphenWidth(-1),
michael@0 2818 mOffsetFromBlockOriginForTabs(0),
michael@0 2819 mReflowing(false),
michael@0 2820 mWhichTextRun(aWhichTextRun)
michael@0 2821 {
michael@0 2822 NS_ASSERTION(mTextRun, "Textrun not initialized!");
michael@0 2823 }
michael@0 2824
michael@0 2825 // Call this after construction if you're not going to reflow the text
michael@0 2826 void InitializeForDisplay(bool aTrimAfter);
michael@0 2827
michael@0 2828 void InitializeForMeasure();
michael@0 2829
michael@0 2830 virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing);
michael@0 2831 virtual gfxFloat GetHyphenWidth();
michael@0 2832 virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
michael@0 2833 bool* aBreakBefore);
michael@0 2834 virtual int8_t GetHyphensOption() {
michael@0 2835 return mTextStyle->mHyphens;
michael@0 2836 }
michael@0 2837
michael@0 2838 virtual already_AddRefed<gfxContext> GetContext() {
michael@0 2839 return CreateReferenceThebesContext(GetFrame());
michael@0 2840 }
michael@0 2841
michael@0 2842 virtual uint32_t GetAppUnitsPerDevUnit() {
michael@0 2843 return mTextRun->GetAppUnitsPerDevUnit();
michael@0 2844 }
michael@0 2845
michael@0 2846 void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing,
michael@0 2847 bool aIgnoreTabs);
michael@0 2848
michael@0 2849 /**
michael@0 2850 * Count the number of justifiable characters in the given DOM range
michael@0 2851 */
michael@0 2852 uint32_t ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength);
michael@0 2853 /**
michael@0 2854 * Find the start and end of the justifiable characters. Does not depend on the
michael@0 2855 * position of aStart or aEnd, although it's most efficient if they are near the
michael@0 2856 * start and end of the text frame.
michael@0 2857 */
michael@0 2858 void FindJustificationRange(gfxSkipCharsIterator* aStart,
michael@0 2859 gfxSkipCharsIterator* aEnd);
michael@0 2860
michael@0 2861 const nsStyleText* StyleText() { return mTextStyle; }
michael@0 2862 nsTextFrame* GetFrame() { return mFrame; }
michael@0 2863 // This may not be equal to the frame offset/length in because we may have
michael@0 2864 // adjusted for whitespace trimming according to the state bits set in the frame
michael@0 2865 // (for the static provider)
michael@0 2866 const gfxSkipCharsIterator& GetStart() { return mStart; }
michael@0 2867 // May return INT32_MAX if that was given to the constructor
michael@0 2868 uint32_t GetOriginalLength() {
michael@0 2869 NS_ASSERTION(mLength != INT32_MAX, "Length not known");
michael@0 2870 return mLength;
michael@0 2871 }
michael@0 2872 const nsTextFragment* GetFragment() { return mFrag; }
michael@0 2873
michael@0 2874 gfxFontGroup* GetFontGroup() {
michael@0 2875 if (!mFontGroup)
michael@0 2876 InitFontGroupAndFontMetrics();
michael@0 2877 return mFontGroup;
michael@0 2878 }
michael@0 2879
michael@0 2880 nsFontMetrics* GetFontMetrics() {
michael@0 2881 if (!mFontMetrics)
michael@0 2882 InitFontGroupAndFontMetrics();
michael@0 2883 return mFontMetrics;
michael@0 2884 }
michael@0 2885
michael@0 2886 void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength);
michael@0 2887
michael@0 2888 const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
michael@0 2889
michael@0 2890 protected:
michael@0 2891 void SetupJustificationSpacing(bool aPostReflow);
michael@0 2892
michael@0 2893 void InitFontGroupAndFontMetrics() {
michael@0 2894 float inflation = (mWhichTextRun == nsTextFrame::eInflated)
michael@0 2895 ? mFrame->GetFontSizeInflation() : 1.0f;
michael@0 2896 mFontGroup = GetFontGroupForFrame(mFrame, inflation,
michael@0 2897 getter_AddRefs(mFontMetrics));
michael@0 2898 }
michael@0 2899
michael@0 2900 gfxTextRun* mTextRun;
michael@0 2901 gfxFontGroup* mFontGroup;
michael@0 2902 nsRefPtr<nsFontMetrics> mFontMetrics;
michael@0 2903 const nsStyleText* mTextStyle;
michael@0 2904 const nsTextFragment* mFrag;
michael@0 2905 nsIFrame* mLineContainer;
michael@0 2906 nsTextFrame* mFrame;
michael@0 2907 gfxSkipCharsIterator mStart; // Offset in original and transformed string
michael@0 2908 gfxSkipCharsIterator mTempIterator;
michael@0 2909
michael@0 2910 // Either null, or pointing to the frame's TabWidthProperty.
michael@0 2911 TabWidthStore* mTabWidths;
michael@0 2912 // How far we've done tab-width calculation; this is ONLY valid when
michael@0 2913 // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
michael@0 2914 // It's a DOM offset relative to the current frame's offset.
michael@0 2915 uint32_t mTabWidthsAnalyzedLimit;
michael@0 2916
michael@0 2917 int32_t mLength; // DOM string length, may be INT32_MAX
michael@0 2918 gfxFloat mWordSpacing; // space for each whitespace char
michael@0 2919 gfxFloat mLetterSpacing; // space for each letter
michael@0 2920 gfxFloat mJustificationSpacing;
michael@0 2921 gfxFloat mHyphenWidth;
michael@0 2922 gfxFloat mOffsetFromBlockOriginForTabs;
michael@0 2923 bool mReflowing;
michael@0 2924 nsTextFrame::TextRunType mWhichTextRun;
michael@0 2925 };
michael@0 2926
michael@0 2927 uint32_t
michael@0 2928 PropertyProvider::ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength)
michael@0 2929 {
michael@0 2930 // Scan non-skipped characters and count justifiable chars.
michael@0 2931 nsSkipCharsRunIterator
michael@0 2932 run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
michael@0 2933 run.SetOriginalOffset(aOffset);
michael@0 2934 uint32_t justifiableChars = 0;
michael@0 2935 bool isCJK = IsChineseOrJapanese(mFrame);
michael@0 2936 while (run.NextRun()) {
michael@0 2937 for (int32_t i = 0; i < run.GetRunLength(); ++i) {
michael@0 2938 justifiableChars +=
michael@0 2939 IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK);
michael@0 2940 }
michael@0 2941 }
michael@0 2942 return justifiableChars;
michael@0 2943 }
michael@0 2944
michael@0 2945 /**
michael@0 2946 * Finds the offset of the first character of the cluster containing aPos
michael@0 2947 */
michael@0 2948 static void FindClusterStart(gfxTextRun* aTextRun, int32_t aOriginalStart,
michael@0 2949 gfxSkipCharsIterator* aPos)
michael@0 2950 {
michael@0 2951 while (aPos->GetOriginalOffset() > aOriginalStart) {
michael@0 2952 if (aPos->IsOriginalCharSkipped() ||
michael@0 2953 aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
michael@0 2954 break;
michael@0 2955 }
michael@0 2956 aPos->AdvanceOriginal(-1);
michael@0 2957 }
michael@0 2958 }
michael@0 2959
michael@0 2960 /**
michael@0 2961 * Finds the offset of the last character of the cluster containing aPos
michael@0 2962 */
michael@0 2963 static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd,
michael@0 2964 gfxSkipCharsIterator* aPos)
michael@0 2965 {
michael@0 2966 NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd,
michael@0 2967 "character outside string");
michael@0 2968 aPos->AdvanceOriginal(1);
michael@0 2969 while (aPos->GetOriginalOffset() < aOriginalEnd) {
michael@0 2970 if (aPos->IsOriginalCharSkipped() ||
michael@0 2971 aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
michael@0 2972 break;
michael@0 2973 }
michael@0 2974 aPos->AdvanceOriginal(1);
michael@0 2975 }
michael@0 2976 aPos->AdvanceOriginal(-1);
michael@0 2977 }
michael@0 2978
michael@0 2979 // aStart, aLength in transformed string offsets
michael@0 2980 void
michael@0 2981 PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength,
michael@0 2982 Spacing* aSpacing)
michael@0 2983 {
michael@0 2984 GetSpacingInternal(aStart, aLength, aSpacing,
michael@0 2985 (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
michael@0 2986 }
michael@0 2987
michael@0 2988 static bool
michael@0 2989 CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset)
michael@0 2990 {
michael@0 2991 if (aOffset + 1 >= aTextRun->GetLength())
michael@0 2992 return true;
michael@0 2993 return aTextRun->IsClusterStart(aOffset + 1) &&
michael@0 2994 aTextRun->IsLigatureGroupStart(aOffset + 1);
michael@0 2995 }
michael@0 2996
michael@0 2997 void
michael@0 2998 PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength,
michael@0 2999 Spacing* aSpacing, bool aIgnoreTabs)
michael@0 3000 {
michael@0 3001 NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
michael@0 3002
michael@0 3003 uint32_t index;
michael@0 3004 for (index = 0; index < aLength; ++index) {
michael@0 3005 aSpacing[index].mBefore = 0.0;
michael@0 3006 aSpacing[index].mAfter = 0.0;
michael@0 3007 }
michael@0 3008
michael@0 3009 // Find our offset into the original+transformed string
michael@0 3010 gfxSkipCharsIterator start(mStart);
michael@0 3011 start.SetSkippedOffset(aStart);
michael@0 3012
michael@0 3013 // First, compute the word and letter spacing
michael@0 3014 if (mWordSpacing || mLetterSpacing) {
michael@0 3015 // Iterate over non-skipped characters
michael@0 3016 nsSkipCharsRunIterator
michael@0 3017 run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
michael@0 3018 while (run.NextRun()) {
michael@0 3019 uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
michael@0 3020 gfxSkipCharsIterator iter = run.GetPos();
michael@0 3021 for (int32_t i = 0; i < run.GetRunLength(); ++i) {
michael@0 3022 if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
michael@0 3023 // End of a cluster, not in a ligature: put letter-spacing after it
michael@0 3024 aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
michael@0 3025 }
michael@0 3026 if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
michael@0 3027 mTextStyle)) {
michael@0 3028 // It kinda sucks, but space characters can be part of clusters,
michael@0 3029 // and even still be whitespace (I think!)
michael@0 3030 iter.SetSkippedOffset(run.GetSkippedOffset() + i);
michael@0 3031 FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
michael@0 3032 &iter);
michael@0 3033 aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
michael@0 3034 }
michael@0 3035 }
michael@0 3036 }
michael@0 3037 }
michael@0 3038
michael@0 3039 // Ignore tab spacing rather than computing it, if the tab size is 0
michael@0 3040 if (!aIgnoreTabs)
michael@0 3041 aIgnoreTabs = mFrame->StyleText()->mTabSize == 0;
michael@0 3042
michael@0 3043 // Now add tab spacing, if there is any
michael@0 3044 if (!aIgnoreTabs) {
michael@0 3045 CalcTabWidths(aStart, aLength);
michael@0 3046 if (mTabWidths) {
michael@0 3047 mTabWidths->ApplySpacing(aSpacing,
michael@0 3048 aStart - mStart.GetSkippedOffset(), aLength);
michael@0 3049 }
michael@0 3050 }
michael@0 3051
michael@0 3052 // Now add in justification spacing
michael@0 3053 if (mJustificationSpacing) {
michael@0 3054 gfxFloat halfJustificationSpace = mJustificationSpacing/2;
michael@0 3055 // Scan non-skipped characters and adjust justifiable chars, adding
michael@0 3056 // justification space on either side of the cluster
michael@0 3057 bool isCJK = IsChineseOrJapanese(mFrame);
michael@0 3058 gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart);
michael@0 3059 FindJustificationRange(&justificationStart, &justificationEnd);
michael@0 3060
michael@0 3061 nsSkipCharsRunIterator
michael@0 3062 run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
michael@0 3063 while (run.NextRun()) {
michael@0 3064 gfxSkipCharsIterator iter = run.GetPos();
michael@0 3065 int32_t runOriginalOffset = run.GetOriginalOffset();
michael@0 3066 for (int32_t i = 0; i < run.GetRunLength(); ++i) {
michael@0 3067 int32_t iterOriginalOffset = runOriginalOffset + i;
michael@0 3068 if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) {
michael@0 3069 iter.SetOriginalOffset(iterOriginalOffset);
michael@0 3070 FindClusterStart(mTextRun, runOriginalOffset, &iter);
michael@0 3071 uint32_t clusterFirstChar = iter.GetSkippedOffset();
michael@0 3072 FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter);
michael@0 3073 uint32_t clusterLastChar = iter.GetSkippedOffset();
michael@0 3074 // Only apply justification to characters before justificationEnd
michael@0 3075 if (clusterFirstChar >= justificationStart.GetSkippedOffset() &&
michael@0 3076 clusterLastChar < justificationEnd.GetSkippedOffset()) {
michael@0 3077 aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace;
michael@0 3078 aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace;
michael@0 3079 }
michael@0 3080 }
michael@0 3081 }
michael@0 3082 }
michael@0 3083 }
michael@0 3084 }
michael@0 3085
michael@0 3086 static gfxFloat
michael@0 3087 ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
michael@0 3088 {
michael@0 3089 // Get the number of spaces from CSS -moz-tab-size
michael@0 3090 const nsStyleText* textStyle = aFrame->StyleText();
michael@0 3091
michael@0 3092 // Round the space width when converting to appunits the same way
michael@0 3093 // textruns do
michael@0 3094 gfxFloat spaceWidthAppUnits =
michael@0 3095 NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup()).spaceWidth *
michael@0 3096 aTextRun->GetAppUnitsPerDevUnit());
michael@0 3097 return textStyle->mTabSize * spaceWidthAppUnits;
michael@0 3098 }
michael@0 3099
michael@0 3100 // aX and the result are in whole appunits.
michael@0 3101 static gfxFloat
michael@0 3102 AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
michael@0 3103 gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
michael@0 3104 {
michael@0 3105 if (*aCachedTabWidth < 0) {
michael@0 3106 *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun);
michael@0 3107 }
michael@0 3108
michael@0 3109 // Advance aX to the next multiple of *aCachedTabWidth. We must advance
michael@0 3110 // by at least 1 appunit.
michael@0 3111 // XXX should we make this 1 CSS pixel?
michael@0 3112 return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth);
michael@0 3113 }
michael@0 3114
michael@0 3115 void
michael@0 3116 PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength)
michael@0 3117 {
michael@0 3118 if (!mTabWidths) {
michael@0 3119 if (mReflowing && !mLineContainer) {
michael@0 3120 // Intrinsic width computation does its own tab processing. We
michael@0 3121 // just don't do anything here.
michael@0 3122 return;
michael@0 3123 }
michael@0 3124 if (!mReflowing) {
michael@0 3125 mTabWidths = static_cast<TabWidthStore*>
michael@0 3126 (mFrame->Properties().Get(TabWidthProperty()));
michael@0 3127 #ifdef DEBUG
michael@0 3128 // If we're not reflowing, we should have already computed the
michael@0 3129 // tab widths; check that they're available as far as the last
michael@0 3130 // tab character present (if any)
michael@0 3131 for (uint32_t i = aStart + aLength; i > aStart; --i) {
michael@0 3132 if (mTextRun->CharIsTab(i - 1)) {
michael@0 3133 uint32_t startOffset = mStart.GetSkippedOffset();
michael@0 3134 NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
michael@0 3135 "Precomputed tab widths are missing!");
michael@0 3136 break;
michael@0 3137 }
michael@0 3138 }
michael@0 3139 #endif
michael@0 3140 return;
michael@0 3141 }
michael@0 3142 }
michael@0 3143
michael@0 3144 uint32_t startOffset = mStart.GetSkippedOffset();
michael@0 3145 MOZ_ASSERT(aStart >= startOffset, "wrong start offset");
michael@0 3146 MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end");
michael@0 3147 uint32_t tabsEnd =
michael@0 3148 (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
michael@0 3149 if (tabsEnd < aStart + aLength) {
michael@0 3150 NS_ASSERTION(mReflowing,
michael@0 3151 "We need precomputed tab widths, but don't have enough.");
michael@0 3152
michael@0 3153 gfxFloat tabWidth = -1;
michael@0 3154 for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) {
michael@0 3155 Spacing spacing;
michael@0 3156 GetSpacingInternal(i, 1, &spacing, true);
michael@0 3157 mOffsetFromBlockOriginForTabs += spacing.mBefore;
michael@0 3158
michael@0 3159 if (!mTextRun->CharIsTab(i)) {
michael@0 3160 if (mTextRun->IsClusterStart(i)) {
michael@0 3161 uint32_t clusterEnd = i + 1;
michael@0 3162 while (clusterEnd < mTextRun->GetLength() &&
michael@0 3163 !mTextRun->IsClusterStart(clusterEnd)) {
michael@0 3164 ++clusterEnd;
michael@0 3165 }
michael@0 3166 mOffsetFromBlockOriginForTabs +=
michael@0 3167 mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr);
michael@0 3168 }
michael@0 3169 } else {
michael@0 3170 if (!mTabWidths) {
michael@0 3171 mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
michael@0 3172 mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
michael@0 3173 }
michael@0 3174 double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
michael@0 3175 mFrame, mTextRun, &tabWidth);
michael@0 3176 mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
michael@0 3177 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
michael@0 3178 mOffsetFromBlockOriginForTabs = nextTab;
michael@0 3179 }
michael@0 3180
michael@0 3181 mOffsetFromBlockOriginForTabs += spacing.mAfter;
michael@0 3182 }
michael@0 3183
michael@0 3184 if (mTabWidths) {
michael@0 3185 mTabWidths->mLimit = aStart + aLength - startOffset;
michael@0 3186 }
michael@0 3187 }
michael@0 3188
michael@0 3189 if (!mTabWidths) {
michael@0 3190 // Delete any stale property that may be left on the frame
michael@0 3191 mFrame->Properties().Delete(TabWidthProperty());
michael@0 3192 mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
michael@0 3193 aStart + aLength - startOffset);
michael@0 3194 }
michael@0 3195 }
michael@0 3196
michael@0 3197 gfxFloat
michael@0 3198 PropertyProvider::GetHyphenWidth()
michael@0 3199 {
michael@0 3200 if (mHyphenWidth < 0) {
michael@0 3201 mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
michael@0 3202 }
michael@0 3203 return mHyphenWidth + mLetterSpacing;
michael@0 3204 }
michael@0 3205
michael@0 3206 void
michael@0 3207 PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
michael@0 3208 bool* aBreakBefore)
michael@0 3209 {
michael@0 3210 NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
michael@0 3211 NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
michael@0 3212
michael@0 3213 if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
michael@0 3214 mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE)
michael@0 3215 {
michael@0 3216 memset(aBreakBefore, false, aLength*sizeof(bool));
michael@0 3217 return;
michael@0 3218 }
michael@0 3219
michael@0 3220 // Iterate through the original-string character runs
michael@0 3221 nsSkipCharsRunIterator
michael@0 3222 run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
michael@0 3223 run.SetSkippedOffset(aStart);
michael@0 3224 // We need to visit skipped characters so that we can detect SHY
michael@0 3225 run.SetVisitSkipped();
michael@0 3226
michael@0 3227 int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
michael@0 3228 bool allowHyphenBreakBeforeNextChar =
michael@0 3229 prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
michael@0 3230 prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
michael@0 3231 mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
michael@0 3232
michael@0 3233 while (run.NextRun()) {
michael@0 3234 NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
michael@0 3235 if (run.IsSkipped()) {
michael@0 3236 // Check if there's a soft hyphen which would let us hyphenate before
michael@0 3237 // the next non-skipped character. Don't look at soft hyphens followed
michael@0 3238 // by other skipped characters, we won't use them.
michael@0 3239 allowHyphenBreakBeforeNextChar =
michael@0 3240 mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
michael@0 3241 } else {
michael@0 3242 int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
michael@0 3243 memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
michael@0 3244 // Don't allow hyphen breaks at the start of the line
michael@0 3245 aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
michael@0 3246 (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
michael@0 3247 run.GetSkippedOffset() > mStart.GetSkippedOffset());
michael@0 3248 allowHyphenBreakBeforeNextChar = false;
michael@0 3249 }
michael@0 3250 }
michael@0 3251
michael@0 3252 if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
michael@0 3253 for (uint32_t i = 0; i < aLength; ++i) {
michael@0 3254 if (mTextRun->CanHyphenateBefore(aStart + i)) {
michael@0 3255 aBreakBefore[i] = true;
michael@0 3256 }
michael@0 3257 }
michael@0 3258 }
michael@0 3259 }
michael@0 3260
michael@0 3261 void
michael@0 3262 PropertyProvider::InitializeForDisplay(bool aTrimAfter)
michael@0 3263 {
michael@0 3264 nsTextFrame::TrimmedOffsets trimmed =
michael@0 3265 mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
michael@0 3266 mStart.SetOriginalOffset(trimmed.mStart);
michael@0 3267 mLength = trimmed.mLength;
michael@0 3268 SetupJustificationSpacing(true);
michael@0 3269 }
michael@0 3270
michael@0 3271 void
michael@0 3272 PropertyProvider::InitializeForMeasure()
michael@0 3273 {
michael@0 3274 nsTextFrame::TrimmedOffsets trimmed =
michael@0 3275 mFrame->GetTrimmedOffsets(mFrag, true, false);
michael@0 3276 mStart.SetOriginalOffset(trimmed.mStart);
michael@0 3277 mLength = trimmed.mLength;
michael@0 3278 SetupJustificationSpacing(false);
michael@0 3279 }
michael@0 3280
michael@0 3281
michael@0 3282 static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart,
michael@0 3283 const gfxSkipCharsIterator& aEnd)
michael@0 3284 {
michael@0 3285 return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
michael@0 3286 }
michael@0 3287
michael@0 3288 void
michael@0 3289 PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart,
michael@0 3290 gfxSkipCharsIterator* aEnd)
michael@0 3291 {
michael@0 3292 NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
michael@0 3293 NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null");
michael@0 3294
michael@0 3295 aStart->SetOriginalOffset(mStart.GetOriginalOffset());
michael@0 3296 aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength);
michael@0 3297
michael@0 3298 // Ignore first cluster at start of line for justification purposes
michael@0 3299 if (mFrame->GetStateBits() & TEXT_START_OF_LINE) {
michael@0 3300 while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) {
michael@0 3301 aStart->AdvanceOriginal(1);
michael@0 3302 if (!aStart->IsOriginalCharSkipped() &&
michael@0 3303 mTextRun->IsClusterStart(aStart->GetSkippedOffset()))
michael@0 3304 break;
michael@0 3305 }
michael@0 3306 }
michael@0 3307
michael@0 3308 // Ignore trailing cluster at end of line for justification purposes
michael@0 3309 if (mFrame->GetStateBits() & TEXT_END_OF_LINE) {
michael@0 3310 while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) {
michael@0 3311 aEnd->AdvanceOriginal(-1);
michael@0 3312 if (!aEnd->IsOriginalCharSkipped() &&
michael@0 3313 mTextRun->IsClusterStart(aEnd->GetSkippedOffset()))
michael@0 3314 break;
michael@0 3315 }
michael@0 3316 }
michael@0 3317 }
michael@0 3318
michael@0 3319 void
michael@0 3320 PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
michael@0 3321 {
michael@0 3322 NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
michael@0 3323
michael@0 3324 if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
michael@0 3325 return;
michael@0 3326
michael@0 3327 gfxSkipCharsIterator start(mStart), end(mStart);
michael@0 3328 // We can't just use our mLength here; when InitializeForDisplay is
michael@0 3329 // called with false for aTrimAfter, we still shouldn't be assigning
michael@0 3330 // justification space to any trailing whitespace.
michael@0 3331 nsTextFrame::TrimmedOffsets trimmed =
michael@0 3332 mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
michael@0 3333 end.AdvanceOriginal(trimmed.mLength);
michael@0 3334 gfxSkipCharsIterator realEnd(end);
michael@0 3335 FindJustificationRange(&start, &end);
michael@0 3336
michael@0 3337 int32_t justifiableCharacters =
michael@0 3338 ComputeJustifiableCharacters(start.GetOriginalOffset(),
michael@0 3339 end.GetOriginalOffset() - start.GetOriginalOffset());
michael@0 3340 if (justifiableCharacters == 0) {
michael@0 3341 // Nothing to do, nothing is justifiable and we shouldn't have any
michael@0 3342 // justification space assigned
michael@0 3343 return;
michael@0 3344 }
michael@0 3345
michael@0 3346 gfxFloat naturalWidth =
michael@0 3347 mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
michael@0 3348 GetSkippedDistance(mStart, realEnd), this);
michael@0 3349 if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
michael@0 3350 naturalWidth += GetHyphenWidth();
michael@0 3351 }
michael@0 3352 gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth;
michael@0 3353 if (totalJustificationSpace <= 0) {
michael@0 3354 // No space available
michael@0 3355 return;
michael@0 3356 }
michael@0 3357
michael@0 3358 mJustificationSpacing = totalJustificationSpace/justifiableCharacters;
michael@0 3359 }
michael@0 3360
michael@0 3361 //----------------------------------------------------------------------
michael@0 3362
michael@0 3363 static nscolor
michael@0 3364 EnsureDifferentColors(nscolor colorA, nscolor colorB)
michael@0 3365 {
michael@0 3366 if (colorA == colorB) {
michael@0 3367 nscolor res;
michael@0 3368 res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
michael@0 3369 NS_GET_G(colorA) ^ 0xff,
michael@0 3370 NS_GET_B(colorA) ^ 0xff);
michael@0 3371 return res;
michael@0 3372 }
michael@0 3373 return colorA;
michael@0 3374 }
michael@0 3375
michael@0 3376 //-----------------------------------------------------------------------------
michael@0 3377
michael@0 3378 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
michael@0 3379 : mFrame(aFrame),
michael@0 3380 mPresContext(aFrame->PresContext()),
michael@0 3381 mInitCommonColors(false),
michael@0 3382 mInitSelectionColorsAndShadow(false),
michael@0 3383 mResolveColors(true),
michael@0 3384 mHasSelectionShadow(false)
michael@0 3385 {
michael@0 3386 for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
michael@0 3387 mSelectionStyle[i].mInit = false;
michael@0 3388 }
michael@0 3389
michael@0 3390 bool
michael@0 3391 nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
michael@0 3392 {
michael@0 3393 InitCommonColors();
michael@0 3394
michael@0 3395 // If the combination of selection background color and frame background color
michael@0 3396 // is sufficient contrast, don't exchange the selection colors.
michael@0 3397 int32_t backLuminosityDifference =
michael@0 3398 NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
michael@0 3399 if (backLuminosityDifference >= mSufficientContrast)
michael@0 3400 return false;
michael@0 3401
michael@0 3402 // Otherwise, we should use the higher-contrast color for the selection
michael@0 3403 // background color.
michael@0 3404 int32_t foreLuminosityDifference =
michael@0 3405 NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
michael@0 3406 if (backLuminosityDifference < foreLuminosityDifference) {
michael@0 3407 nscolor tmpColor = *aForeColor;
michael@0 3408 *aForeColor = *aBackColor;
michael@0 3409 *aBackColor = tmpColor;
michael@0 3410 return true;
michael@0 3411 }
michael@0 3412 return false;
michael@0 3413 }
michael@0 3414
michael@0 3415 nscolor
michael@0 3416 nsTextPaintStyle::GetTextColor()
michael@0 3417 {
michael@0 3418 if (mFrame->IsSVGText()) {
michael@0 3419 if (!mResolveColors)
michael@0 3420 return NS_SAME_AS_FOREGROUND_COLOR;
michael@0 3421
michael@0 3422 const nsStyleSVG* style = mFrame->StyleSVG();
michael@0 3423 switch (style->mFill.mType) {
michael@0 3424 case eStyleSVGPaintType_None:
michael@0 3425 return NS_RGBA(0, 0, 0, 0);
michael@0 3426 case eStyleSVGPaintType_Color:
michael@0 3427 return nsLayoutUtils::GetColor(mFrame, eCSSProperty_fill);
michael@0 3428 default:
michael@0 3429 NS_ERROR("cannot resolve SVG paint to nscolor");
michael@0 3430 return NS_RGBA(0, 0, 0, 255);
michael@0 3431 }
michael@0 3432 }
michael@0 3433 return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
michael@0 3434 }
michael@0 3435
michael@0 3436 bool
michael@0 3437 nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
michael@0 3438 nscolor* aBackColor)
michael@0 3439 {
michael@0 3440 NS_ASSERTION(aForeColor, "aForeColor is null");
michael@0 3441 NS_ASSERTION(aBackColor, "aBackColor is null");
michael@0 3442
michael@0 3443 if (!InitSelectionColorsAndShadow())
michael@0 3444 return false;
michael@0 3445
michael@0 3446 *aForeColor = mSelectionTextColor;
michael@0 3447 *aBackColor = mSelectionBGColor;
michael@0 3448 return true;
michael@0 3449 }
michael@0 3450
michael@0 3451 void
michael@0 3452 nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
michael@0 3453 nscolor* aBackColor)
michael@0 3454 {
michael@0 3455 NS_ASSERTION(aForeColor, "aForeColor is null");
michael@0 3456 NS_ASSERTION(aBackColor, "aBackColor is null");
michael@0 3457
michael@0 3458 nscolor backColor =
michael@0 3459 LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
michael@0 3460 nscolor foreColor =
michael@0 3461 LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
michael@0 3462 EnsureSufficientContrast(&foreColor, &backColor);
michael@0 3463 *aForeColor = foreColor;
michael@0 3464 *aBackColor = backColor;
michael@0 3465 }
michael@0 3466
michael@0 3467 void
michael@0 3468 nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
michael@0 3469 {
michael@0 3470 NS_ASSERTION(aForeColor, "aForeColor is null");
michael@0 3471
michael@0 3472 nscolor textColor = GetTextColor();
michael@0 3473 textColor = NS_RGBA(NS_GET_R(textColor),
michael@0 3474 NS_GET_G(textColor),
michael@0 3475 NS_GET_B(textColor),
michael@0 3476 (uint8_t)(255 * 0.5f));
michael@0 3477 // Don't use true alpha color for readability.
michael@0 3478 InitCommonColors();
michael@0 3479 *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
michael@0 3480 }
michael@0 3481
michael@0 3482 void
michael@0 3483 nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex,
michael@0 3484 nscolor* aForeColor,
michael@0 3485 nscolor* aBackColor)
michael@0 3486 {
michael@0 3487 NS_ASSERTION(aForeColor, "aForeColor is null");
michael@0 3488 NS_ASSERTION(aBackColor, "aBackColor is null");
michael@0 3489 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
michael@0 3490
michael@0 3491 nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
michael@0 3492 *aForeColor = selectionStyle->mTextColor;
michael@0 3493 *aBackColor = selectionStyle->mBGColor;
michael@0 3494 }
michael@0 3495
michael@0 3496 bool
michael@0 3497 nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex,
michael@0 3498 nscolor* aLineColor,
michael@0 3499 float* aRelativeSize,
michael@0 3500 uint8_t* aStyle)
michael@0 3501 {
michael@0 3502 NS_ASSERTION(aLineColor, "aLineColor is null");
michael@0 3503 NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
michael@0 3504 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
michael@0 3505
michael@0 3506 nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
michael@0 3507 if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE ||
michael@0 3508 selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
michael@0 3509 selectionStyle->mUnderlineRelativeSize <= 0.0f)
michael@0 3510 return false;
michael@0 3511
michael@0 3512 *aLineColor = selectionStyle->mUnderlineColor;
michael@0 3513 *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
michael@0 3514 *aStyle = selectionStyle->mUnderlineStyle;
michael@0 3515 return true;
michael@0 3516 }
michael@0 3517
michael@0 3518 void
michael@0 3519 nsTextPaintStyle::InitCommonColors()
michael@0 3520 {
michael@0 3521 if (mInitCommonColors)
michael@0 3522 return;
michael@0 3523
michael@0 3524 nsIFrame* bgFrame =
michael@0 3525 nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
michael@0 3526 NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
michael@0 3527 nscolor bgColor =
michael@0 3528 bgFrame->GetVisitedDependentColor(eCSSProperty_background_color);
michael@0 3529
michael@0 3530 nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
michael@0 3531 mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
michael@0 3532
michael@0 3533 if (bgFrame->IsThemed()) {
michael@0 3534 // Assume a native widget has sufficient contrast always
michael@0 3535 mSufficientContrast = 0;
michael@0 3536 mInitCommonColors = true;
michael@0 3537 return;
michael@0 3538 }
michael@0 3539
michael@0 3540 NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
michael@0 3541 "default background color is not opaque");
michael@0 3542
michael@0 3543 nscolor defaultWindowBackgroundColor =
michael@0 3544 LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
michael@0 3545 nscolor selectionTextColor =
michael@0 3546 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
michael@0 3547 nscolor selectionBGColor =
michael@0 3548 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
michael@0 3549
michael@0 3550 mSufficientContrast =
michael@0 3551 std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
michael@0 3552 NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
michael@0 3553 selectionBGColor)),
michael@0 3554 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
michael@0 3555 selectionBGColor));
michael@0 3556
michael@0 3557 mInitCommonColors = true;
michael@0 3558 }
michael@0 3559
michael@0 3560 static Element*
michael@0 3561 FindElementAncestorForMozSelection(nsIContent* aContent)
michael@0 3562 {
michael@0 3563 NS_ENSURE_TRUE(aContent, nullptr);
michael@0 3564 while (aContent && aContent->IsInNativeAnonymousSubtree()) {
michael@0 3565 aContent = aContent->GetBindingParent();
michael@0 3566 }
michael@0 3567 NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
michael@0 3568 while (aContent && !aContent->IsElement()) {
michael@0 3569 aContent = aContent->GetParent();
michael@0 3570 }
michael@0 3571 return aContent ? aContent->AsElement() : nullptr;
michael@0 3572 }
michael@0 3573
michael@0 3574 bool
michael@0 3575 nsTextPaintStyle::InitSelectionColorsAndShadow()
michael@0 3576 {
michael@0 3577 if (mInitSelectionColorsAndShadow)
michael@0 3578 return true;
michael@0 3579
michael@0 3580 int16_t selectionFlags;
michael@0 3581 int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
michael@0 3582 if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
michael@0 3583 selectionStatus < nsISelectionController::SELECTION_ON) {
michael@0 3584 // Not displaying the normal selection.
michael@0 3585 // We're not caching this fact, so every call to GetSelectionColors
michael@0 3586 // will come through here. We could avoid this, but it's not really worth it.
michael@0 3587 return false;
michael@0 3588 }
michael@0 3589
michael@0 3590 mInitSelectionColorsAndShadow = true;
michael@0 3591
michael@0 3592 nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame);
michael@0 3593 Element* selectionElement =
michael@0 3594 FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent());
michael@0 3595
michael@0 3596 if (selectionElement &&
michael@0 3597 selectionStatus == nsISelectionController::SELECTION_ON) {
michael@0 3598 nsRefPtr<nsStyleContext> sc = nullptr;
michael@0 3599 sc = mPresContext->StyleSet()->
michael@0 3600 ProbePseudoElementStyle(selectionElement,
michael@0 3601 nsCSSPseudoElements::ePseudo_mozSelection,
michael@0 3602 mFrame->StyleContext());
michael@0 3603 // Use -moz-selection pseudo class.
michael@0 3604 if (sc) {
michael@0 3605 mSelectionBGColor =
michael@0 3606 sc->GetVisitedDependentColor(eCSSProperty_background_color);
michael@0 3607 mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color);
michael@0 3608 mHasSelectionShadow =
michael@0 3609 nsRuleNode::HasAuthorSpecifiedRules(sc,
michael@0 3610 NS_AUTHOR_SPECIFIED_TEXT_SHADOW,
michael@0 3611 true);
michael@0 3612 if (mHasSelectionShadow) {
michael@0 3613 mSelectionShadow = sc->StyleText()->mTextShadow;
michael@0 3614 }
michael@0 3615 return true;
michael@0 3616 }
michael@0 3617 }
michael@0 3618
michael@0 3619 nscolor selectionBGColor =
michael@0 3620 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
michael@0 3621
michael@0 3622 if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
michael@0 3623 mSelectionBGColor =
michael@0 3624 LookAndFeel::GetColor(
michael@0 3625 LookAndFeel::eColorID_TextSelectBackgroundAttention);
michael@0 3626 mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
michael@0 3627 selectionBGColor);
michael@0 3628 } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
michael@0 3629 mSelectionBGColor =
michael@0 3630 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
michael@0 3631 mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
michael@0 3632 selectionBGColor);
michael@0 3633 } else {
michael@0 3634 mSelectionBGColor = selectionBGColor;
michael@0 3635 }
michael@0 3636
michael@0 3637 mSelectionTextColor =
michael@0 3638 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
michael@0 3639
michael@0 3640 if (mResolveColors) {
michael@0 3641 // On MacOS X, we don't exchange text color and BG color.
michael@0 3642 if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
michael@0 3643 nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill :
michael@0 3644 eCSSProperty_color;
michael@0 3645 nscoord frameColor = mFrame->GetVisitedDependentColor(property);
michael@0 3646 mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
michael@0 3647 } else {
michael@0 3648 EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
michael@0 3649 }
michael@0 3650 } else {
michael@0 3651 if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
michael@0 3652 mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR;
michael@0 3653 }
michael@0 3654 }
michael@0 3655 return true;
michael@0 3656 }
michael@0 3657
michael@0 3658 nsTextPaintStyle::nsSelectionStyle*
michael@0 3659 nsTextPaintStyle::GetSelectionStyle(int32_t aIndex)
michael@0 3660 {
michael@0 3661 InitSelectionStyle(aIndex);
michael@0 3662 return &mSelectionStyle[aIndex];
michael@0 3663 }
michael@0 3664
michael@0 3665 struct StyleIDs {
michael@0 3666 LookAndFeel::ColorID mForeground, mBackground, mLine;
michael@0 3667 LookAndFeel::IntID mLineStyle;
michael@0 3668 LookAndFeel::FloatID mLineRelativeSize;
michael@0 3669 };
michael@0 3670 static StyleIDs SelectionStyleIDs[] = {
michael@0 3671 { LookAndFeel::eColorID_IMERawInputForeground,
michael@0 3672 LookAndFeel::eColorID_IMERawInputBackground,
michael@0 3673 LookAndFeel::eColorID_IMERawInputUnderline,
michael@0 3674 LookAndFeel::eIntID_IMERawInputUnderlineStyle,
michael@0 3675 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
michael@0 3676 { LookAndFeel::eColorID_IMESelectedRawTextForeground,
michael@0 3677 LookAndFeel::eColorID_IMESelectedRawTextBackground,
michael@0 3678 LookAndFeel::eColorID_IMESelectedRawTextUnderline,
michael@0 3679 LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
michael@0 3680 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
michael@0 3681 { LookAndFeel::eColorID_IMEConvertedTextForeground,
michael@0 3682 LookAndFeel::eColorID_IMEConvertedTextBackground,
michael@0 3683 LookAndFeel::eColorID_IMEConvertedTextUnderline,
michael@0 3684 LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
michael@0 3685 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
michael@0 3686 { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
michael@0 3687 LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
michael@0 3688 LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
michael@0 3689 LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
michael@0 3690 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
michael@0 3691 { LookAndFeel::eColorID_LAST_COLOR,
michael@0 3692 LookAndFeel::eColorID_LAST_COLOR,
michael@0 3693 LookAndFeel::eColorID_SpellCheckerUnderline,
michael@0 3694 LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
michael@0 3695 LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
michael@0 3696 };
michael@0 3697
michael@0 3698 void
michael@0 3699 nsTextPaintStyle::InitSelectionStyle(int32_t aIndex)
michael@0 3700 {
michael@0 3701 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
michael@0 3702 nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
michael@0 3703 if (selectionStyle->mInit)
michael@0 3704 return;
michael@0 3705
michael@0 3706 StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
michael@0 3707
michael@0 3708 nscolor foreColor, backColor;
michael@0 3709 if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
michael@0 3710 foreColor = NS_SAME_AS_FOREGROUND_COLOR;
michael@0 3711 } else {
michael@0 3712 foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
michael@0 3713 }
michael@0 3714 if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
michael@0 3715 backColor = NS_TRANSPARENT;
michael@0 3716 } else {
michael@0 3717 backColor = LookAndFeel::GetColor(styleIDs->mBackground);
michael@0 3718 }
michael@0 3719
michael@0 3720 // Convert special color to actual color
michael@0 3721 NS_ASSERTION(foreColor != NS_TRANSPARENT,
michael@0 3722 "foreColor cannot be NS_TRANSPARENT");
michael@0 3723 NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
michael@0 3724 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
michael@0 3725 NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
michael@0 3726 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
michael@0 3727
michael@0 3728 if (mResolveColors) {
michael@0 3729 foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
michael@0 3730
michael@0 3731 if (NS_GET_A(backColor) > 0)
michael@0 3732 EnsureSufficientContrast(&foreColor, &backColor);
michael@0 3733 }
michael@0 3734
michael@0 3735 nscolor lineColor;
michael@0 3736 float relativeSize;
michael@0 3737 uint8_t lineStyle;
michael@0 3738 GetSelectionUnderline(mPresContext, aIndex,
michael@0 3739 &lineColor, &relativeSize, &lineStyle);
michael@0 3740
michael@0 3741 if (mResolveColors)
michael@0 3742 lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
michael@0 3743
michael@0 3744 selectionStyle->mTextColor = foreColor;
michael@0 3745 selectionStyle->mBGColor = backColor;
michael@0 3746 selectionStyle->mUnderlineColor = lineColor;
michael@0 3747 selectionStyle->mUnderlineStyle = lineStyle;
michael@0 3748 selectionStyle->mUnderlineRelativeSize = relativeSize;
michael@0 3749 selectionStyle->mInit = true;
michael@0 3750 }
michael@0 3751
michael@0 3752 /* static */ bool
michael@0 3753 nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
michael@0 3754 int32_t aIndex,
michael@0 3755 nscolor* aLineColor,
michael@0 3756 float* aRelativeSize,
michael@0 3757 uint8_t* aStyle)
michael@0 3758 {
michael@0 3759 NS_ASSERTION(aPresContext, "aPresContext is null");
michael@0 3760 NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
michael@0 3761 NS_ASSERTION(aStyle, "aStyle is null");
michael@0 3762 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
michael@0 3763
michael@0 3764 StyleIDs& styleID = SelectionStyleIDs[aIndex];
michael@0 3765
michael@0 3766 nscolor color = LookAndFeel::GetColor(styleID.mLine);
michael@0 3767 int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
michael@0 3768 if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
michael@0 3769 NS_ERROR("Invalid underline style value is specified");
michael@0 3770 style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
michael@0 3771 }
michael@0 3772 float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
michael@0 3773
michael@0 3774 NS_ASSERTION(size, "selection underline relative size must be larger than 0");
michael@0 3775
michael@0 3776 if (aLineColor) {
michael@0 3777 *aLineColor = color;
michael@0 3778 }
michael@0 3779 *aRelativeSize = size;
michael@0 3780 *aStyle = style;
michael@0 3781
michael@0 3782 return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
michael@0 3783 color != NS_TRANSPARENT &&
michael@0 3784 size > 0.0f;
michael@0 3785 }
michael@0 3786
michael@0 3787 bool
michael@0 3788 nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow)
michael@0 3789 {
michael@0 3790 if (!InitSelectionColorsAndShadow()) {
michael@0 3791 return false;
michael@0 3792 }
michael@0 3793
michael@0 3794 if (mHasSelectionShadow) {
michael@0 3795 *aShadow = mSelectionShadow;
michael@0 3796 return true;
michael@0 3797 }
michael@0 3798
michael@0 3799 return false;
michael@0 3800 }
michael@0 3801
michael@0 3802 inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
michael@0 3803 {
michael@0 3804 nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
michael@0 3805 NS_GET_G(aForeColor),
michael@0 3806 NS_GET_B(aForeColor),
michael@0 3807 (uint8_t)(255 * 0.4f));
michael@0 3808 // Don't use true alpha color for readability.
michael@0 3809 return NS_ComposeColors(aBackColor, foreColor);
michael@0 3810 }
michael@0 3811
michael@0 3812 nscolor
michael@0 3813 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
michael@0 3814 nscolor aDefaultForeColor,
michael@0 3815 nscolor aBackColor)
michael@0 3816 {
michael@0 3817 if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
michael@0 3818 return aDefaultForeColor;
michael@0 3819
michael@0 3820 if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
michael@0 3821 return aColor;
michael@0 3822
michael@0 3823 // Get actual background color
michael@0 3824 nscolor actualBGColor = aBackColor;
michael@0 3825 if (actualBGColor == NS_TRANSPARENT) {
michael@0 3826 InitCommonColors();
michael@0 3827 actualBGColor = mFrameBackgroundColor;
michael@0 3828 }
michael@0 3829 return Get40PercentColor(aDefaultForeColor, actualBGColor);
michael@0 3830 }
michael@0 3831
michael@0 3832 //-----------------------------------------------------------------------------
michael@0 3833
michael@0 3834 #ifdef ACCESSIBILITY
michael@0 3835 a11y::AccType
michael@0 3836 nsTextFrame::AccessibleType()
michael@0 3837 {
michael@0 3838 if (IsEmpty()) {
michael@0 3839 nsAutoString renderedWhitespace;
michael@0 3840 GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1);
michael@0 3841 if (renderedWhitespace.IsEmpty()) {
michael@0 3842 return a11y::eNoType;
michael@0 3843 }
michael@0 3844 }
michael@0 3845
michael@0 3846 return a11y::eTextLeafType;
michael@0 3847 }
michael@0 3848 #endif
michael@0 3849
michael@0 3850
michael@0 3851 //-----------------------------------------------------------------------------
michael@0 3852 void
michael@0 3853 nsTextFrame::Init(nsIContent* aContent,
michael@0 3854 nsIFrame* aParent,
michael@0 3855 nsIFrame* aPrevInFlow)
michael@0 3856 {
michael@0 3857 NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
michael@0 3858 NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
michael@0 3859 "Bogus content!");
michael@0 3860
michael@0 3861 // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
michael@0 3862 // might be invalid if the content was modified while there was no frame
michael@0 3863 aContent->DeleteProperty(nsGkAtoms::newline);
michael@0 3864 if (PresContext()->BidiEnabled()) {
michael@0 3865 aContent->DeleteProperty(nsGkAtoms::flowlength);
michael@0 3866 }
michael@0 3867
michael@0 3868 // Since our content has a frame now, this flag is no longer needed.
michael@0 3869 aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
michael@0 3870
michael@0 3871 // We're not a continuing frame.
michael@0 3872 // mContentOffset = 0; not necessary since we get zeroed out at init
michael@0 3873 nsFrame::Init(aContent, aParent, aPrevInFlow);
michael@0 3874 }
michael@0 3875
michael@0 3876 void
michael@0 3877 nsTextFrame::ClearFrameOffsetCache()
michael@0 3878 {
michael@0 3879 // See if we need to remove ourselves from the offset cache
michael@0 3880 if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
michael@0 3881 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
michael@0 3882 if (primaryFrame) {
michael@0 3883 // The primary frame might be null here. For example, nsLineBox::DeleteLineList
michael@0 3884 // just destroys the frames in order, which means that the primary frame is already
michael@0 3885 // dead if we're a continuing text frame, in which case, all of its properties are
michael@0 3886 // gone, and we don't need to worry about deleting this property here.
michael@0 3887 primaryFrame->Properties().Delete(OffsetToFrameProperty());
michael@0 3888 }
michael@0 3889 RemoveStateBits(TEXT_IN_OFFSET_CACHE);
michael@0 3890 }
michael@0 3891 }
michael@0 3892
michael@0 3893 void
michael@0 3894 nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
michael@0 3895 {
michael@0 3896 ClearFrameOffsetCache();
michael@0 3897
michael@0 3898 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
michael@0 3899 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
michael@0 3900 // type might be changing. Not clear whether it's worth it.
michael@0 3901 ClearTextRuns();
michael@0 3902 if (mNextContinuation) {
michael@0 3903 mNextContinuation->SetPrevInFlow(nullptr);
michael@0 3904 }
michael@0 3905 // Let the base class destroy the frame
michael@0 3906 nsFrame::DestroyFrom(aDestructRoot);
michael@0 3907 }
michael@0 3908
michael@0 3909 class nsContinuingTextFrame : public nsTextFrame {
michael@0 3910 public:
michael@0 3911 NS_DECL_FRAMEARENA_HELPERS
michael@0 3912
michael@0 3913 friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
michael@0 3914
michael@0 3915 virtual void Init(nsIContent* aContent,
michael@0 3916 nsIFrame* aParent,
michael@0 3917 nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
michael@0 3918
michael@0 3919 virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
michael@0 3920
michael@0 3921 virtual nsIFrame* GetPrevContinuation() const MOZ_OVERRIDE {
michael@0 3922 return mPrevContinuation;
michael@0 3923 }
michael@0 3924 virtual void SetPrevContinuation(nsIFrame* aPrevContinuation) MOZ_OVERRIDE {
michael@0 3925 NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(),
michael@0 3926 "setting a prev continuation with incorrect type!");
michael@0 3927 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
michael@0 3928 "creating a loop in continuation chain!");
michael@0 3929 mPrevContinuation = aPrevContinuation;
michael@0 3930 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
michael@0 3931 }
michael@0 3932 virtual nsIFrame* GetPrevInFlowVirtual() const MOZ_OVERRIDE {
michael@0 3933 return GetPrevInFlow();
michael@0 3934 }
michael@0 3935 nsIFrame* GetPrevInFlow() const {
michael@0 3936 return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr;
michael@0 3937 }
michael@0 3938 virtual void SetPrevInFlow(nsIFrame* aPrevInFlow) MOZ_OVERRIDE {
michael@0 3939 NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(),
michael@0 3940 "setting a prev in flow with incorrect type!");
michael@0 3941 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
michael@0 3942 "creating a loop in continuation chain!");
michael@0 3943 mPrevContinuation = aPrevInFlow;
michael@0 3944 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
michael@0 3945 }
michael@0 3946 virtual nsIFrame* FirstInFlow() const MOZ_OVERRIDE;
michael@0 3947 virtual nsIFrame* FirstContinuation() const MOZ_OVERRIDE;
michael@0 3948
michael@0 3949 virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext,
michael@0 3950 InlineMinWidthData *aData) MOZ_OVERRIDE;
michael@0 3951 virtual void AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
michael@0 3952 InlinePrefWidthData *aData) MOZ_OVERRIDE;
michael@0 3953
michael@0 3954 virtual nsresult GetRenderedText(nsAString* aString = nullptr,
michael@0 3955 gfxSkipChars* aSkipChars = nullptr,
michael@0 3956 gfxSkipCharsIterator* aSkipIter = nullptr,
michael@0 3957 uint32_t aSkippedStartOffset = 0,
michael@0 3958 uint32_t aSkippedMaxLength = UINT32_MAX) MOZ_OVERRIDE
michael@0 3959 { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
michael@0 3960
michael@0 3961 protected:
michael@0 3962 nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
michael@0 3963 nsIFrame* mPrevContinuation;
michael@0 3964 };
michael@0 3965
michael@0 3966 void
michael@0 3967 nsContinuingTextFrame::Init(nsIContent* aContent,
michael@0 3968 nsIFrame* aParent,
michael@0 3969 nsIFrame* aPrevInFlow)
michael@0 3970 {
michael@0 3971 NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
michael@0 3972 // NOTE: bypassing nsTextFrame::Init!!!
michael@0 3973 nsFrame::Init(aContent, aParent, aPrevInFlow);
michael@0 3974
michael@0 3975 nsTextFrame* nextContinuation =
michael@0 3976 static_cast<nsTextFrame*>(aPrevInFlow->GetNextContinuation());
michael@0 3977 // Hook the frame into the flow
michael@0 3978 SetPrevInFlow(aPrevInFlow);
michael@0 3979 aPrevInFlow->SetNextInFlow(this);
michael@0 3980 nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
michael@0 3981 mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
michael@0 3982 NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
michael@0 3983 "Creating ContinuingTextFrame, but there is no more content");
michael@0 3984 if (prev->StyleContext() != StyleContext()) {
michael@0 3985 // We're taking part of prev's text, and its style may be different
michael@0 3986 // so clear its textrun which may no longer be valid (and don't set ours)
michael@0 3987 prev->ClearTextRuns();
michael@0 3988 } else {
michael@0 3989 float inflation = prev->GetFontSizeInflation();
michael@0 3990 SetFontSizeInflation(inflation);
michael@0 3991 mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
michael@0 3992 if (inflation != 1.0f) {
michael@0 3993 gfxTextRun *uninflatedTextRun =
michael@0 3994 prev->GetTextRun(nsTextFrame::eNotInflated);
michael@0 3995 if (uninflatedTextRun) {
michael@0 3996 SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
michael@0 3997 }
michael@0 3998 }
michael@0 3999 }
michael@0 4000 if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
michael@0 4001 FramePropertyTable *propTable = PresContext()->PropertyTable();
michael@0 4002 // Get all the properties from the prev-in-flow first to take
michael@0 4003 // advantage of the propTable's cache and simplify the assertion below
michael@0 4004 void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty());
michael@0 4005 void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty());
michael@0 4006 void* paragraphDepth = propTable->Get(aPrevInFlow, ParagraphDepthProperty());
michael@0 4007 propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel);
michael@0 4008 propTable->Set(this, BaseLevelProperty(), baseLevel);
michael@0 4009 propTable->Set(this, ParagraphDepthProperty(), paragraphDepth);
michael@0 4010
michael@0 4011 if (nextContinuation) {
michael@0 4012 SetNextContinuation(nextContinuation);
michael@0 4013 nextContinuation->SetPrevContinuation(this);
michael@0 4014 // Adjust next-continuations' content offset as needed.
michael@0 4015 while (nextContinuation &&
michael@0 4016 nextContinuation->GetContentOffset() < mContentOffset) {
michael@0 4017 NS_ASSERTION(
michael@0 4018 embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) &&
michael@0 4019 baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()) &&
michael@0 4020 paragraphDepth == propTable->Get(nextContinuation, ParagraphDepthProperty()),
michael@0 4021 "stealing text from different type of BIDI continuation");
michael@0 4022 nextContinuation->mContentOffset = mContentOffset;
michael@0 4023 nextContinuation = static_cast<nsTextFrame*>(nextContinuation->GetNextContinuation());
michael@0 4024 }
michael@0 4025 }
michael@0 4026 mState |= NS_FRAME_IS_BIDI;
michael@0 4027 } // prev frame is bidi
michael@0 4028 }
michael@0 4029
michael@0 4030 void
michael@0 4031 nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
michael@0 4032 {
michael@0 4033 ClearFrameOffsetCache();
michael@0 4034
michael@0 4035 // The text associated with this frame will become associated with our
michael@0 4036 // prev-continuation. If that means the text has changed style, then
michael@0 4037 // we need to wipe out the text run for the text.
michael@0 4038 // Note that mPrevContinuation can be null if we're destroying the whole
michael@0 4039 // frame chain from the start to the end.
michael@0 4040 // If this frame is mentioned in the userData for a textrun (say
michael@0 4041 // because there's a direction change at the start of this frame), then
michael@0 4042 // we have to clear the textrun because we're going away and the
michael@0 4043 // textrun had better not keep a dangling reference to us.
michael@0 4044 if (IsInTextRunUserData() ||
michael@0 4045 (mPrevContinuation &&
michael@0 4046 mPrevContinuation->StyleContext() != StyleContext())) {
michael@0 4047 ClearTextRuns();
michael@0 4048 // Clear the previous continuation's text run also, so that it can rebuild
michael@0 4049 // the text run to include our text.
michael@0 4050 if (mPrevContinuation) {
michael@0 4051 nsTextFrame *prevContinuationText =
michael@0 4052 static_cast<nsTextFrame*>(mPrevContinuation);
michael@0 4053 prevContinuationText->ClearTextRuns();
michael@0 4054 }
michael@0 4055 }
michael@0 4056 nsSplittableFrame::RemoveFromFlow(this);
michael@0 4057 // Let the base class destroy the frame
michael@0 4058 nsFrame::DestroyFrom(aDestructRoot);
michael@0 4059 }
michael@0 4060
michael@0 4061 nsIFrame*
michael@0 4062 nsContinuingTextFrame::FirstInFlow() const
michael@0 4063 {
michael@0 4064 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
michael@0 4065 nsIFrame *firstInFlow,
michael@0 4066 *previous = const_cast<nsIFrame*>
michael@0 4067 (static_cast<const nsIFrame*>(this));
michael@0 4068 do {
michael@0 4069 firstInFlow = previous;
michael@0 4070 previous = firstInFlow->GetPrevInFlow();
michael@0 4071 } while (previous);
michael@0 4072 MOZ_ASSERT(firstInFlow, "post-condition failed");
michael@0 4073 return firstInFlow;
michael@0 4074 }
michael@0 4075
michael@0 4076 nsIFrame*
michael@0 4077 nsContinuingTextFrame::FirstContinuation() const
michael@0 4078 {
michael@0 4079 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
michael@0 4080 nsIFrame *firstContinuation,
michael@0 4081 *previous = const_cast<nsIFrame*>
michael@0 4082 (static_cast<const nsIFrame*>(mPrevContinuation));
michael@0 4083
michael@0 4084 NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
michael@0 4085
michael@0 4086 do {
michael@0 4087 firstContinuation = previous;
michael@0 4088 previous = firstContinuation->GetPrevContinuation();
michael@0 4089 } while (previous);
michael@0 4090 MOZ_ASSERT(firstContinuation, "post-condition failed");
michael@0 4091 return firstContinuation;
michael@0 4092 }
michael@0 4093
michael@0 4094 // XXX Do we want to do all the work for the first-in-flow or do the
michael@0 4095 // work for each part? (Be careful of first-letter / first-line, though,
michael@0 4096 // especially first-line!) Doing all the work on the first-in-flow has
michael@0 4097 // the advantage of avoiding the potential for incremental reflow bugs,
michael@0 4098 // but depends on our maintining the frame tree in reasonable ways even
michael@0 4099 // for edge cases (block-within-inline splits, nextBidi, etc.)
michael@0 4100
michael@0 4101 // XXX We really need to make :first-letter happen during frame
michael@0 4102 // construction.
michael@0 4103
michael@0 4104 // Needed for text frames in XUL.
michael@0 4105 /* virtual */ nscoord
michael@0 4106 nsTextFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
michael@0 4107 {
michael@0 4108 return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext);
michael@0 4109 }
michael@0 4110
michael@0 4111 // Needed for text frames in XUL.
michael@0 4112 /* virtual */ nscoord
michael@0 4113 nsTextFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
michael@0 4114 {
michael@0 4115 return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext);
michael@0 4116 }
michael@0 4117
michael@0 4118 /* virtual */ void
michael@0 4119 nsContinuingTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
michael@0 4120 InlineMinWidthData *aData)
michael@0 4121 {
michael@0 4122 // Do nothing, since the first-in-flow accounts for everything.
michael@0 4123 return;
michael@0 4124 }
michael@0 4125
michael@0 4126 /* virtual */ void
michael@0 4127 nsContinuingTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
michael@0 4128 InlinePrefWidthData *aData)
michael@0 4129 {
michael@0 4130 // Do nothing, since the first-in-flow accounts for everything.
michael@0 4131 return;
michael@0 4132 }
michael@0 4133
michael@0 4134 static void
michael@0 4135 DestroySelectionDetails(SelectionDetails* aDetails)
michael@0 4136 {
michael@0 4137 while (aDetails) {
michael@0 4138 SelectionDetails* next = aDetails->mNext;
michael@0 4139 delete aDetails;
michael@0 4140 aDetails = next;
michael@0 4141 }
michael@0 4142 }
michael@0 4143
michael@0 4144 //----------------------------------------------------------------------
michael@0 4145
michael@0 4146 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
michael@0 4147 static void
michael@0 4148 VerifyNotDirty(nsFrameState state)
michael@0 4149 {
michael@0 4150 bool isZero = state & NS_FRAME_FIRST_REFLOW;
michael@0 4151 bool isDirty = state & NS_FRAME_IS_DIRTY;
michael@0 4152 if (!isZero && isDirty)
michael@0 4153 NS_WARNING("internal offsets may be out-of-sync");
michael@0 4154 }
michael@0 4155 #define DEBUG_VERIFY_NOT_DIRTY(state) \
michael@0 4156 VerifyNotDirty(state)
michael@0 4157 #else
michael@0 4158 #define DEBUG_VERIFY_NOT_DIRTY(state)
michael@0 4159 #endif
michael@0 4160
michael@0 4161 nsIFrame*
michael@0 4162 NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
michael@0 4163 {
michael@0 4164 return new (aPresShell) nsTextFrame(aContext);
michael@0 4165 }
michael@0 4166
michael@0 4167 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
michael@0 4168
michael@0 4169 nsIFrame*
michael@0 4170 NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
michael@0 4171 {
michael@0 4172 return new (aPresShell) nsContinuingTextFrame(aContext);
michael@0 4173 }
michael@0 4174
michael@0 4175 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
michael@0 4176
michael@0 4177 nsTextFrame::~nsTextFrame()
michael@0 4178 {
michael@0 4179 }
michael@0 4180
michael@0 4181 nsresult
michael@0 4182 nsTextFrame::GetCursor(const nsPoint& aPoint,
michael@0 4183 nsIFrame::Cursor& aCursor)
michael@0 4184 {
michael@0 4185 FillCursorInformationFromStyle(StyleUserInterface(), aCursor);
michael@0 4186 if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
michael@0 4187 aCursor.mCursor = NS_STYLE_CURSOR_TEXT;
michael@0 4188 // If this is editable, we should ignore tabindex value.
michael@0 4189 if (mContent->IsEditable()) {
michael@0 4190 return NS_OK;
michael@0 4191 }
michael@0 4192
michael@0 4193 // If tabindex >= 0, use default cursor to indicate it's not selectable
michael@0 4194 nsIFrame *ancestorFrame = this;
michael@0 4195 while ((ancestorFrame = ancestorFrame->GetParent()) != nullptr) {
michael@0 4196 nsIContent *ancestorContent = ancestorFrame->GetContent();
michael@0 4197 if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
michael@0 4198 nsAutoString tabIndexStr;
michael@0 4199 ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
michael@0 4200 if (!tabIndexStr.IsEmpty()) {
michael@0 4201 nsresult rv;
michael@0 4202 int32_t tabIndexVal = tabIndexStr.ToInteger(&rv);
michael@0 4203 if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) {
michael@0 4204 aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
michael@0 4205 break;
michael@0 4206 }
michael@0 4207 }
michael@0 4208 }
michael@0 4209 }
michael@0 4210 }
michael@0 4211
michael@0 4212 return NS_OK;
michael@0 4213 }
michael@0 4214
michael@0 4215 nsIFrame*
michael@0 4216 nsTextFrame::LastInFlow() const
michael@0 4217 {
michael@0 4218 nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
michael@0 4219 while (lastInFlow->GetNextInFlow()) {
michael@0 4220 lastInFlow = static_cast<nsTextFrame*>(lastInFlow->GetNextInFlow());
michael@0 4221 }
michael@0 4222 MOZ_ASSERT(lastInFlow, "post-condition failed");
michael@0 4223 return lastInFlow;
michael@0 4224 }
michael@0 4225
michael@0 4226 nsIFrame*
michael@0 4227 nsTextFrame::LastContinuation() const
michael@0 4228 {
michael@0 4229 nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
michael@0 4230 while (lastContinuation->mNextContinuation) {
michael@0 4231 lastContinuation =
michael@0 4232 static_cast<nsTextFrame*>(lastContinuation->mNextContinuation);
michael@0 4233 }
michael@0 4234 MOZ_ASSERT(lastContinuation, "post-condition failed");
michael@0 4235 return lastContinuation;
michael@0 4236 }
michael@0 4237
michael@0 4238 void
michael@0 4239 nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey)
michael@0 4240 {
michael@0 4241 if (IsSVGText()) {
michael@0 4242 nsIFrame* svgTextFrame =
michael@0 4243 nsLayoutUtils::GetClosestFrameOfType(GetParent(),
michael@0 4244 nsGkAtoms::svgTextFrame);
michael@0 4245 svgTextFrame->InvalidateFrame();
michael@0 4246 return;
michael@0 4247 }
michael@0 4248 nsTextFrameBase::InvalidateFrame(aDisplayItemKey);
michael@0 4249 }
michael@0 4250
michael@0 4251 void
michael@0 4252 nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
michael@0 4253 {
michael@0 4254 if (IsSVGText()) {
michael@0 4255 nsIFrame* svgTextFrame =
michael@0 4256 nsLayoutUtils::GetClosestFrameOfType(GetParent(),
michael@0 4257 nsGkAtoms::svgTextFrame);
michael@0 4258 svgTextFrame->InvalidateFrame();
michael@0 4259 return;
michael@0 4260 }
michael@0 4261 nsTextFrameBase::InvalidateFrameWithRect(aRect, aDisplayItemKey);
michael@0 4262 }
michael@0 4263
michael@0 4264 gfxTextRun*
michael@0 4265 nsTextFrame::GetUninflatedTextRun()
michael@0 4266 {
michael@0 4267 return static_cast<gfxTextRun*>(
michael@0 4268 Properties().Get(UninflatedTextRunProperty()));
michael@0 4269 }
michael@0 4270
michael@0 4271 void
michael@0 4272 nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
michael@0 4273 float aInflation)
michael@0 4274 {
michael@0 4275 NS_ASSERTION(aTextRun, "must have text run");
michael@0 4276
michael@0 4277 // Our inflated text run is always stored in mTextRun. In the cases
michael@0 4278 // where our current inflation is not 1.0, however, we store two text
michael@0 4279 // runs, and the uninflated one goes in a frame property. We never
michael@0 4280 // store a single text run in both.
michael@0 4281 if (aWhichTextRun == eInflated) {
michael@0 4282 if (HasFontSizeInflation() && aInflation == 1.0f) {
michael@0 4283 // FIXME: Probably shouldn't do this within each SetTextRun
michael@0 4284 // method, but it doesn't hurt.
michael@0 4285 ClearTextRun(nullptr, nsTextFrame::eNotInflated);
michael@0 4286 }
michael@0 4287 SetFontSizeInflation(aInflation);
michael@0 4288 } else {
michael@0 4289 NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation");
michael@0 4290 if (HasFontSizeInflation()) {
michael@0 4291 Properties().Set(UninflatedTextRunProperty(), aTextRun);
michael@0 4292 return;
michael@0 4293 }
michael@0 4294 // fall through to setting mTextRun
michael@0 4295 }
michael@0 4296
michael@0 4297 mTextRun = aTextRun;
michael@0 4298
michael@0 4299 // FIXME: Add assertions testing the relationship between
michael@0 4300 // GetFontSizeInflation() and whether we have an uninflated text run
michael@0 4301 // (but be aware that text runs can go away).
michael@0 4302 }
michael@0 4303
michael@0 4304 bool
michael@0 4305 nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
michael@0 4306 {
michael@0 4307 if (aTextRun == mTextRun) {
michael@0 4308 mTextRun = nullptr;
michael@0 4309 return true;
michael@0 4310 }
michael@0 4311 FrameProperties props = Properties();
michael@0 4312 if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
michael@0 4313 props.Get(UninflatedTextRunProperty()) == aTextRun) {
michael@0 4314 props.Delete(UninflatedTextRunProperty());
michael@0 4315 return true;
michael@0 4316 }
michael@0 4317 return false;
michael@0 4318 }
michael@0 4319
michael@0 4320 void
michael@0 4321 nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
michael@0 4322 TextRunType aWhichTextRun)
michael@0 4323 {
michael@0 4324 gfxTextRun* textRun = GetTextRun(aWhichTextRun);
michael@0 4325 if (!textRun) {
michael@0 4326 return;
michael@0 4327 }
michael@0 4328
michael@0 4329 DebugOnly<bool> checkmTextrun = textRun == mTextRun;
michael@0 4330 UnhookTextRunFromFrames(textRun, aStartContinuation);
michael@0 4331 MOZ_ASSERT(checkmTextrun ? !mTextRun
michael@0 4332 : !Properties().Get(UninflatedTextRunProperty()));
michael@0 4333
michael@0 4334 // see comments in BuildTextRunForFrames...
michael@0 4335 // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
michael@0 4336 // NS_ERROR("Shouldn't reach here for now...");
michael@0 4337 // // the textrun's text may be referencing a DOM node that has changed,
michael@0 4338 // // so we'd better kill this textrun now.
michael@0 4339 // if (textRun->GetExpirationState()->IsTracked()) {
michael@0 4340 // gTextRuns->RemoveFromCache(textRun);
michael@0 4341 // }
michael@0 4342 // delete textRun;
michael@0 4343 // return;
michael@0 4344 // }
michael@0 4345
michael@0 4346 if (!textRun->GetUserData()) {
michael@0 4347 // Remove it now because it's not doing anything useful
michael@0 4348 gTextRuns->RemoveFromCache(textRun);
michael@0 4349 delete textRun;
michael@0 4350 }
michael@0 4351 }
michael@0 4352
michael@0 4353 void
michael@0 4354 nsTextFrame::DisconnectTextRuns()
michael@0 4355 {
michael@0 4356 MOZ_ASSERT(!IsInTextRunUserData(),
michael@0 4357 "Textrun mentions this frame in its user data so we can't just disconnect");
michael@0 4358 mTextRun = nullptr;
michael@0 4359 if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
michael@0 4360 Properties().Delete(UninflatedTextRunProperty());
michael@0 4361 }
michael@0 4362 }
michael@0 4363
michael@0 4364 nsresult
michael@0 4365 nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
michael@0 4366 {
michael@0 4367 mContent->DeleteProperty(nsGkAtoms::newline);
michael@0 4368 if (PresContext()->BidiEnabled()) {
michael@0 4369 mContent->DeleteProperty(nsGkAtoms::flowlength);
michael@0 4370 }
michael@0 4371
michael@0 4372 // Find the first frame whose text has changed. Frames that are entirely
michael@0 4373 // before the text change are completely unaffected.
michael@0 4374 nsTextFrame* next;
michael@0 4375 nsTextFrame* textFrame = this;
michael@0 4376 while (true) {
michael@0 4377 next = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
michael@0 4378 if (!next || next->GetContentOffset() > int32_t(aInfo->mChangeStart))
michael@0 4379 break;
michael@0 4380 textFrame = next;
michael@0 4381 }
michael@0 4382
michael@0 4383 int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength;
michael@0 4384 nsTextFrame* lastDirtiedFrame = nullptr;
michael@0 4385
michael@0 4386 nsIPresShell* shell = PresContext()->GetPresShell();
michael@0 4387 do {
michael@0 4388 // textFrame contained deleted text (or the insertion point,
michael@0 4389 // if this was a pure insertion).
michael@0 4390 textFrame->mState &= ~TEXT_WHITESPACE_FLAGS;
michael@0 4391 textFrame->ClearTextRuns();
michael@0 4392 if (!lastDirtiedFrame ||
michael@0 4393 lastDirtiedFrame->GetParent() != textFrame->GetParent()) {
michael@0 4394 // Ask the parent frame to reflow me.
michael@0 4395 shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
michael@0 4396 NS_FRAME_IS_DIRTY);
michael@0 4397 lastDirtiedFrame = textFrame;
michael@0 4398 } else {
michael@0 4399 // if the parent is a block, we're cheating here because we should
michael@0 4400 // be marking our line dirty, but we're not. nsTextFrame::SetLength
michael@0 4401 // will do that when it gets called during reflow.
michael@0 4402 textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
michael@0 4403 }
michael@0 4404 textFrame->InvalidateFrame();
michael@0 4405
michael@0 4406 // Below, frames that start after the deleted text will be adjusted so that
michael@0 4407 // their offsets move with the trailing unchanged text. If this change
michael@0 4408 // deletes more text than it inserts, those frame offsets will decrease.
michael@0 4409 // We need to maintain the invariant that mContentOffset is non-decreasing
michael@0 4410 // along the continuation chain. So we need to ensure that frames that
michael@0 4411 // started in the deleted text are all still starting before the
michael@0 4412 // unchanged text.
michael@0 4413 if (textFrame->mContentOffset > endOfChangedText) {
michael@0 4414 textFrame->mContentOffset = endOfChangedText;
michael@0 4415 }
michael@0 4416
michael@0 4417 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
michael@0 4418 } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo->mChangeEnd));
michael@0 4419
michael@0 4420 // This is how much the length of the string changed by --- i.e.,
michael@0 4421 // how much the trailing unchanged text moved.
michael@0 4422 int32_t sizeChange =
michael@0 4423 aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd;
michael@0 4424
michael@0 4425 if (sizeChange) {
michael@0 4426 // Fix the offsets of the text frames that start in the trailing
michael@0 4427 // unchanged text.
michael@0 4428 while (textFrame) {
michael@0 4429 textFrame->mContentOffset += sizeChange;
michael@0 4430 // XXX we could rescue some text runs by adjusting their user data
michael@0 4431 // to reflect the change in DOM offsets
michael@0 4432 textFrame->ClearTextRuns();
michael@0 4433 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
michael@0 4434 }
michael@0 4435 }
michael@0 4436
michael@0 4437 return NS_OK;
michael@0 4438 }
michael@0 4439
michael@0 4440 /* virtual */ void
michael@0 4441 nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
michael@0 4442 {
michael@0 4443 nsFrame::DidSetStyleContext(aOldStyleContext);
michael@0 4444 }
michael@0 4445
michael@0 4446 class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry
michael@0 4447 {
michael@0 4448 public:
michael@0 4449 nsDisplayTextGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
michael@0 4450 : nsDisplayItemGenericGeometry(aItem, aBuilder)
michael@0 4451 {
michael@0 4452 nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
michael@0 4453 f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations);
michael@0 4454 }
michael@0 4455
michael@0 4456 /**
michael@0 4457 * We store the computed text decorations here since they are
michael@0 4458 * computed using style data from parent frames. Any changes to these
michael@0 4459 * styles will only invalidate the parent frame and not this frame.
michael@0 4460 */
michael@0 4461 nsTextFrame::TextDecorations mDecorations;
michael@0 4462 };
michael@0 4463
michael@0 4464 class nsDisplayText : public nsCharClipDisplayItem {
michael@0 4465 public:
michael@0 4466 nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
michael@0 4467 nsCharClipDisplayItem(aBuilder, aFrame),
michael@0 4468 mDisableSubpixelAA(false) {
michael@0 4469 MOZ_COUNT_CTOR(nsDisplayText);
michael@0 4470 }
michael@0 4471 #ifdef NS_BUILD_REFCNT_LOGGING
michael@0 4472 virtual ~nsDisplayText() {
michael@0 4473 MOZ_COUNT_DTOR(nsDisplayText);
michael@0 4474 }
michael@0 4475 #endif
michael@0 4476
michael@0 4477 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
michael@0 4478 bool* aSnap) MOZ_OVERRIDE {
michael@0 4479 *aSnap = false;
michael@0 4480 nsRect temp = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
michael@0 4481 // Bug 748228
michael@0 4482 temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
michael@0 4483 return temp;
michael@0 4484 }
michael@0 4485 virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
michael@0 4486 HitTestState* aState,
michael@0 4487 nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE {
michael@0 4488 if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
michael@0 4489 aOutFrames->AppendElement(mFrame);
michael@0 4490 }
michael@0 4491 }
michael@0 4492 virtual void Paint(nsDisplayListBuilder* aBuilder,
michael@0 4493 nsRenderingContext* aCtx) MOZ_OVERRIDE;
michael@0 4494 NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
michael@0 4495
michael@0 4496 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
michael@0 4497 {
michael@0 4498 bool snap;
michael@0 4499 return GetBounds(aBuilder, &snap);
michael@0 4500 }
michael@0 4501
michael@0 4502 virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
michael@0 4503 {
michael@0 4504 return new nsDisplayTextGeometry(this, aBuilder);
michael@0 4505 }
michael@0 4506
michael@0 4507 virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
michael@0 4508 const nsDisplayItemGeometry* aGeometry,
michael@0 4509 nsRegion *aInvalidRegion) MOZ_OVERRIDE
michael@0 4510 {
michael@0 4511 const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
michael@0 4512 nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
michael@0 4513
michael@0 4514 nsTextFrame::TextDecorations decorations;
michael@0 4515 f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
michael@0 4516
michael@0 4517 bool snap;
michael@0 4518 nsRect newRect = geometry->mBounds;
michael@0 4519 nsRect oldRect = GetBounds(aBuilder, &snap);
michael@0 4520 if (decorations != geometry->mDecorations ||
michael@0 4521 !oldRect.IsEqualInterior(newRect) ||
michael@0 4522 !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
michael@0 4523 aInvalidRegion->Or(oldRect, newRect);
michael@0 4524 }
michael@0 4525 }
michael@0 4526
michael@0 4527 virtual void DisableComponentAlpha() MOZ_OVERRIDE {
michael@0 4528 mDisableSubpixelAA = true;
michael@0 4529 }
michael@0 4530
michael@0 4531 bool mDisableSubpixelAA;
michael@0 4532 };
michael@0 4533
michael@0 4534 void
michael@0 4535 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
michael@0 4536 nsRenderingContext* aCtx) {
michael@0 4537 PROFILER_LABEL("nsDisplayText", "Paint");
michael@0 4538 // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
michael@0 4539 // antialiased pixels beyond the measured text extents.
michael@0 4540 // This is temporary until we do this in the actual calculation of text extents.
michael@0 4541 nsRect extraVisible = mVisibleRect;
michael@0 4542 nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
michael@0 4543 extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel);
michael@0 4544 nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
michael@0 4545
michael@0 4546 gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
michael@0 4547 mDisableSubpixelAA);
michael@0 4548 NS_ASSERTION(mLeftEdge >= 0, "illegal left edge");
michael@0 4549 NS_ASSERTION(mRightEdge >= 0, "illegal right edge");
michael@0 4550 f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this);
michael@0 4551 }
michael@0 4552
michael@0 4553 void
michael@0 4554 nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
michael@0 4555 const nsRect& aDirtyRect,
michael@0 4556 const nsDisplayListSet& aLists)
michael@0 4557 {
michael@0 4558 if (!IsVisibleForPainting(aBuilder))
michael@0 4559 return;
michael@0 4560
michael@0 4561 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
michael@0 4562
michael@0 4563 aLists.Content()->AppendNewToTop(
michael@0 4564 new (aBuilder) nsDisplayText(aBuilder, this));
michael@0 4565 }
michael@0 4566
michael@0 4567 static nsIFrame*
michael@0 4568 GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
michael@0 4569 {
michael@0 4570 *aIsBefore = false;
michael@0 4571 while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
michael@0 4572 if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) {
michael@0 4573 *aIsBefore = true;
michael@0 4574 }
michael@0 4575 aFrame = aFrame->GetParent();
michael@0 4576 }
michael@0 4577 return aFrame;
michael@0 4578 }
michael@0 4579
michael@0 4580 SelectionDetails*
michael@0 4581 nsTextFrame::GetSelectionDetails()
michael@0 4582 {
michael@0 4583 const nsFrameSelection* frameSelection = GetConstFrameSelection();
michael@0 4584 if (frameSelection->GetTableCellSelection()) {
michael@0 4585 return nullptr;
michael@0 4586 }
michael@0 4587 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
michael@0 4588 SelectionDetails* details =
michael@0 4589 frameSelection->LookUpSelection(mContent, GetContentOffset(),
michael@0 4590 GetContentLength(), false);
michael@0 4591 SelectionDetails* sd;
michael@0 4592 for (sd = details; sd; sd = sd->mNext) {
michael@0 4593 sd->mStart += mContentOffset;
michael@0 4594 sd->mEnd += mContentOffset;
michael@0 4595 }
michael@0 4596 return details;
michael@0 4597 }
michael@0 4598
michael@0 4599 // Check if the beginning or end of the element is selected, depending on
michael@0 4600 // whether we're :before content or :after content.
michael@0 4601 bool isBefore;
michael@0 4602 nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
michael@0 4603 if (!owner || !owner->GetContent())
michael@0 4604 return nullptr;
michael@0 4605
michael@0 4606 SelectionDetails* details =
michael@0 4607 frameSelection->LookUpSelection(owner->GetContent(),
michael@0 4608 isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
michael@0 4609 SelectionDetails* sd;
michael@0 4610 for (sd = details; sd; sd = sd->mNext) {
michael@0 4611 // The entire text is selected!
michael@0 4612 sd->mStart = GetContentOffset();
michael@0 4613 sd->mEnd = GetContentEnd();
michael@0 4614 }
michael@0 4615 return details;
michael@0 4616 }
michael@0 4617
michael@0 4618 static void
michael@0 4619 PaintSelectionBackground(gfxContext* aCtx, nsPresContext* aPresContext,
michael@0 4620 nscolor aColor, const gfxRect& aDirtyRect,
michael@0 4621 const gfxRect& aRect,
michael@0 4622 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 4623 {
michael@0 4624 if (aCallbacks) {
michael@0 4625 aCallbacks->NotifyBeforeSelectionBackground(aColor);
michael@0 4626 }
michael@0 4627
michael@0 4628 gfxRect r = aRect.Intersect(aDirtyRect);
michael@0 4629 // For now, we need to put this in pixel coordinates
michael@0 4630 int32_t app = aPresContext->AppUnitsPerDevPixel();
michael@0 4631 aCtx->NewPath();
michael@0 4632 // pixel-snap
michael@0 4633 aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app,
michael@0 4634 r.Width() / app, r.Height() / app), true);
michael@0 4635
michael@0 4636 if (aCallbacks) {
michael@0 4637 aCallbacks->NotifySelectionBackgroundPathEmitted();
michael@0 4638 } else {
michael@0 4639 aCtx->SetColor(gfxRGBA(aColor));
michael@0 4640 aCtx->Fill();
michael@0 4641 }
michael@0 4642 }
michael@0 4643
michael@0 4644 void
michael@0 4645 nsTextFrame::GetTextDecorations(
michael@0 4646 nsPresContext* aPresContext,
michael@0 4647 nsTextFrame::TextDecorationColorResolution aColorResolution,
michael@0 4648 nsTextFrame::TextDecorations& aDecorations)
michael@0 4649 {
michael@0 4650 const nsCompatibility compatMode = aPresContext->CompatibilityMode();
michael@0 4651
michael@0 4652 bool useOverride = false;
michael@0 4653 nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
michael@0 4654
michael@0 4655 // frameTopOffset represents the offset to f's top from our baseline in our
michael@0 4656 // coordinate space
michael@0 4657 // baselineOffset represents the offset from our baseline to f's baseline or
michael@0 4658 // the nearest block's baseline, in our coordinate space, whichever is closest
michael@0 4659 // during the particular iteration
michael@0 4660 nscoord frameTopOffset = mAscent,
michael@0 4661 baselineOffset = 0;
michael@0 4662
michael@0 4663 bool nearestBlockFound = false;
michael@0 4664
michael@0 4665 for (nsIFrame* f = this, *fChild = nullptr;
michael@0 4666 f;
michael@0 4667 fChild = f,
michael@0 4668 f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
michael@0 4669 {
michael@0 4670 nsStyleContext *const context = f->StyleContext();
michael@0 4671 if (!context->HasTextDecorationLines()) {
michael@0 4672 break;
michael@0 4673 }
michael@0 4674
michael@0 4675 const nsStyleTextReset *const styleText = context->StyleTextReset();
michael@0 4676 const uint8_t textDecorations = styleText->mTextDecorationLine;
michael@0 4677
michael@0 4678 if (!useOverride &&
michael@0 4679 (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
michael@0 4680 // This handles the <a href="blah.html"><font color="green">La
michael@0 4681 // la la</font></a> case. The link underline should be green.
michael@0 4682 useOverride = true;
michael@0 4683 overrideColor =
michael@0 4684 nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
michael@0 4685 }
michael@0 4686
michael@0 4687 const bool firstBlock = !nearestBlockFound && nsLayoutUtils::GetAsBlock(f);
michael@0 4688
michael@0 4689 // Not updating positions once we hit a parent block is equivalent to
michael@0 4690 // the CSS 2.1 spec that blocks should propagate decorations down to their
michael@0 4691 // children (albeit the style should be preserved)
michael@0 4692 // However, if we're vertically aligned within a block, then we need to
michael@0 4693 // recover the right baseline from the line by querying the FrameProperty
michael@0 4694 // that should be set (see nsLineLayout::VerticalAlignLine).
michael@0 4695 if (firstBlock) {
michael@0 4696 // At this point, fChild can't be null since TextFrames can't be blocks
michael@0 4697 if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
michael@0 4698 // Since offset is the offset in the child's coordinate space, we have
michael@0 4699 // to undo the accumulation to bring the transform out of the block's
michael@0 4700 // coordinate space
michael@0 4701 baselineOffset =
michael@0 4702 frameTopOffset - fChild->GetNormalPosition().y
michael@0 4703 - NS_PTR_TO_INT32(
michael@0 4704 fChild->Properties().Get(nsIFrame::LineBaselineOffset()));
michael@0 4705 }
michael@0 4706 }
michael@0 4707 else if (!nearestBlockFound) {
michael@0 4708 baselineOffset = frameTopOffset - f->GetBaseline();
michael@0 4709 }
michael@0 4710
michael@0 4711 nearestBlockFound = nearestBlockFound || firstBlock;
michael@0 4712 frameTopOffset += f->GetNormalPosition().y;
michael@0 4713
michael@0 4714 const uint8_t style = styleText->GetDecorationStyle();
michael@0 4715 if (textDecorations) {
michael@0 4716 nscolor color;
michael@0 4717 if (useOverride) {
michael@0 4718 color = overrideColor;
michael@0 4719 } else if (IsSVGText()) {
michael@0 4720 // XXX We might want to do something with text-decoration-color when
michael@0 4721 // painting SVG text, but it's not clear what we should do. We
michael@0 4722 // at least need SVG text decorations to paint with 'fill' if
michael@0 4723 // text-decoration-color has its initial value currentColor.
michael@0 4724 // We could choose to interpret currentColor as "currentFill"
michael@0 4725 // for SVG text, and have e.g. text-decoration-color:red to
michael@0 4726 // override the fill paint of the decoration.
michael@0 4727 color = aColorResolution == eResolvedColors ?
michael@0 4728 nsLayoutUtils::GetColor(f, eCSSProperty_fill) :
michael@0 4729 NS_SAME_AS_FOREGROUND_COLOR;
michael@0 4730 } else {
michael@0 4731 color = nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
michael@0 4732 }
michael@0 4733
michael@0 4734 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
michael@0 4735 aDecorations.mUnderlines.AppendElement(
michael@0 4736 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
michael@0 4737 }
michael@0 4738 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
michael@0 4739 aDecorations.mOverlines.AppendElement(
michael@0 4740 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
michael@0 4741 }
michael@0 4742 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
michael@0 4743 aDecorations.mStrikes.AppendElement(
michael@0 4744 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
michael@0 4745 }
michael@0 4746 }
michael@0 4747
michael@0 4748 // In all modes, if we're on an inline-block or inline-table (or
michael@0 4749 // inline-stack, inline-box, inline-grid), we're done.
michael@0 4750 uint8_t display = f->GetDisplay();
michael@0 4751 if (display != NS_STYLE_DISPLAY_INLINE &&
michael@0 4752 nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
michael@0 4753 break;
michael@0 4754 }
michael@0 4755
michael@0 4756 if (compatMode == eCompatibility_NavQuirks) {
michael@0 4757 // In quirks mode, if we're on an HTML table element, we're done.
michael@0 4758 if (f->GetContent()->IsHTML(nsGkAtoms::table)) {
michael@0 4759 break;
michael@0 4760 }
michael@0 4761 } else {
michael@0 4762 // In standards/almost-standards mode, if we're on an
michael@0 4763 // absolutely-positioned element or a floating element, we're done.
michael@0 4764 if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
michael@0 4765 break;
michael@0 4766 }
michael@0 4767 }
michael@0 4768 }
michael@0 4769 }
michael@0 4770
michael@0 4771 static float
michael@0 4772 GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
michael@0 4773 {
michael@0 4774 if (aFrame->IsSVGText()) {
michael@0 4775 const nsIFrame* container = aFrame;
michael@0 4776 while (container->GetType() != nsGkAtoms::svgTextFrame) {
michael@0 4777 container = container->GetParent();
michael@0 4778 }
michael@0 4779 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
michael@0 4780 return
michael@0 4781 static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
michael@0 4782 }
michael@0 4783 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
michael@0 4784 }
michael@0 4785
michael@0 4786 void
michael@0 4787 nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
michael@0 4788 const nsHTMLReflowState& aBlockReflowState,
michael@0 4789 PropertyProvider& aProvider,
michael@0 4790 nsRect* aVisualOverflowRect,
michael@0 4791 bool aIncludeTextDecorations)
michael@0 4792 {
michael@0 4793 // Text-shadow overflows
michael@0 4794 nsRect shadowRect =
michael@0 4795 nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
michael@0 4796 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
michael@0 4797
michael@0 4798 if (IsFloatingFirstLetterChild()) {
michael@0 4799 // The underline/overline drawable area must be contained in the overflow
michael@0 4800 // rect when this is in floating first letter frame at *both* modes.
michael@0 4801 nsIFrame* firstLetterFrame = aBlockReflowState.frame;
michael@0 4802 uint8_t decorationStyle = firstLetterFrame->StyleContext()->
michael@0 4803 StyleTextReset()->GetDecorationStyle();
michael@0 4804 // If the style is none, let's include decoration line rect as solid style
michael@0 4805 // since changing the style from none to solid/dotted/dashed doesn't cause
michael@0 4806 // reflow.
michael@0 4807 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 4808 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
michael@0 4809 }
michael@0 4810 nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
michael@0 4811 nscoord underlineOffset, underlineSize;
michael@0 4812 fontMetrics->GetUnderline(underlineOffset, underlineSize);
michael@0 4813 nscoord maxAscent = fontMetrics->MaxAscent();
michael@0 4814
michael@0 4815 gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
michael@0 4816 gfxFloat gfxWidth = aVisualOverflowRect->width / appUnitsPerDevUnit;
michael@0 4817 gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
michael@0 4818 gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
michael@0 4819 gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit;
michael@0 4820 gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit;
michael@0 4821 nsRect underlineRect =
michael@0 4822 nsCSSRendering::GetTextDecorationRect(aPresContext,
michael@0 4823 gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
michael@0 4824 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle);
michael@0 4825 nsRect overlineRect =
michael@0 4826 nsCSSRendering::GetTextDecorationRect(aPresContext,
michael@0 4827 gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
michael@0 4828 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle);
michael@0 4829
michael@0 4830 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
michael@0 4831 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
michael@0 4832
michael@0 4833 // XXX If strikeoutSize is much thicker than the underlineSize, it may
michael@0 4834 // cause overflowing from the overflow rect. However, such case
michael@0 4835 // isn't realistic, we don't need to compute it now.
michael@0 4836 }
michael@0 4837 if (aIncludeTextDecorations) {
michael@0 4838 // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
michael@0 4839 // style and position, they can be drawn at virtually any y-offset, so
michael@0 4840 // maxima and minima are required to reliably generate the rectangle for
michael@0 4841 // them
michael@0 4842 TextDecorations textDecs;
michael@0 4843 GetTextDecorations(aPresContext, eResolvedColors, textDecs);
michael@0 4844 if (textDecs.HasDecorationLines()) {
michael@0 4845 nscoord inflationMinFontSize =
michael@0 4846 nsLayoutUtils::InflationMinFontSizeFor(aBlockReflowState.frame);
michael@0 4847
michael@0 4848 const nscoord width = GetSize().width;
michael@0 4849 const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
michael@0 4850 gfxWidth = width / appUnitsPerDevUnit,
michael@0 4851 ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
michael@0 4852 nscoord top(nscoord_MAX), bottom(nscoord_MIN);
michael@0 4853 // Below we loop through all text decorations and compute the rectangle
michael@0 4854 // containing all of them, in this frame's coordinate space
michael@0 4855 for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
michael@0 4856 const LineDecoration& dec = textDecs.mUnderlines[i];
michael@0 4857 uint8_t decorationStyle = dec.mStyle;
michael@0 4858 // If the style is solid, let's include decoration line rect of solid
michael@0 4859 // style since changing the style from none to solid/dotted/dashed
michael@0 4860 // doesn't cause reflow.
michael@0 4861 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 4862 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
michael@0 4863 }
michael@0 4864
michael@0 4865 float inflation =
michael@0 4866 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 4867 const gfxFont::Metrics metrics =
michael@0 4868 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 4869
michael@0 4870 const nsRect decorationRect =
michael@0 4871 nsCSSRendering::GetTextDecorationRect(aPresContext,
michael@0 4872 gfxSize(gfxWidth, metrics.underlineSize),
michael@0 4873 ascent, metrics.underlineOffset,
michael@0 4874 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle) +
michael@0 4875 nsPoint(0, -dec.mBaselineOffset);
michael@0 4876
michael@0 4877 top = std::min(decorationRect.y, top);
michael@0 4878 bottom = std::max(decorationRect.YMost(), bottom);
michael@0 4879 }
michael@0 4880 for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
michael@0 4881 const LineDecoration& dec = textDecs.mOverlines[i];
michael@0 4882 uint8_t decorationStyle = dec.mStyle;
michael@0 4883 // If the style is solid, let's include decoration line rect of solid
michael@0 4884 // style since changing the style from none to solid/dotted/dashed
michael@0 4885 // doesn't cause reflow.
michael@0 4886 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 4887 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
michael@0 4888 }
michael@0 4889
michael@0 4890 float inflation =
michael@0 4891 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 4892 const gfxFont::Metrics metrics =
michael@0 4893 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 4894
michael@0 4895 const nsRect decorationRect =
michael@0 4896 nsCSSRendering::GetTextDecorationRect(aPresContext,
michael@0 4897 gfxSize(gfxWidth, metrics.underlineSize),
michael@0 4898 ascent, metrics.maxAscent,
michael@0 4899 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle) +
michael@0 4900 nsPoint(0, -dec.mBaselineOffset);
michael@0 4901
michael@0 4902 top = std::min(decorationRect.y, top);
michael@0 4903 bottom = std::max(decorationRect.YMost(), bottom);
michael@0 4904 }
michael@0 4905 for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
michael@0 4906 const LineDecoration& dec = textDecs.mStrikes[i];
michael@0 4907 uint8_t decorationStyle = dec.mStyle;
michael@0 4908 // If the style is solid, let's include decoration line rect of solid
michael@0 4909 // style since changing the style from none to solid/dotted/dashed
michael@0 4910 // doesn't cause reflow.
michael@0 4911 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 4912 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
michael@0 4913 }
michael@0 4914
michael@0 4915 float inflation =
michael@0 4916 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 4917 const gfxFont::Metrics metrics =
michael@0 4918 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 4919
michael@0 4920 const nsRect decorationRect =
michael@0 4921 nsCSSRendering::GetTextDecorationRect(aPresContext,
michael@0 4922 gfxSize(gfxWidth, metrics.strikeoutSize),
michael@0 4923 ascent, metrics.strikeoutOffset,
michael@0 4924 NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle) +
michael@0 4925 nsPoint(0, -dec.mBaselineOffset);
michael@0 4926 top = std::min(decorationRect.y, top);
michael@0 4927 bottom = std::max(decorationRect.YMost(), bottom);
michael@0 4928 }
michael@0 4929
michael@0 4930 aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
michael@0 4931 nsRect(0, top, width, bottom - top));
michael@0 4932 }
michael@0 4933 }
michael@0 4934 // When this frame is not selected, the text-decoration area must be in
michael@0 4935 // frame bounds.
michael@0 4936 if (!IsSelected() ||
michael@0 4937 !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
michael@0 4938 return;
michael@0 4939 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
michael@0 4940 }
michael@0 4941
michael@0 4942 static gfxFloat
michael@0 4943 ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext,
michael@0 4944 nsTextFrame* aFrame,
michael@0 4945 const gfxFont::Metrics& aFontMetrics)
michael@0 4946 {
michael@0 4947 gfxFloat app = aPresContext->AppUnitsPerDevPixel();
michael@0 4948 nscoord lineHeightApp =
michael@0 4949 nsHTMLReflowState::CalcLineHeight(aFrame->GetContent(),
michael@0 4950 aFrame->StyleContext(), NS_AUTOHEIGHT,
michael@0 4951 aFrame->GetFontSizeInflation());
michael@0 4952 gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
michael@0 4953 if (lineHeight <= aFontMetrics.maxHeight) {
michael@0 4954 return aFontMetrics.maxDescent;
michael@0 4955 }
michael@0 4956 return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
michael@0 4957 }
michael@0 4958
michael@0 4959
michael@0 4960 // Make sure this stays in sync with DrawSelectionDecorations below
michael@0 4961 static const SelectionType SelectionTypesWithDecorations =
michael@0 4962 nsISelectionController::SELECTION_SPELLCHECK |
michael@0 4963 nsISelectionController::SELECTION_IME_RAWINPUT |
michael@0 4964 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT |
michael@0 4965 nsISelectionController::SELECTION_IME_CONVERTEDTEXT |
michael@0 4966 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;
michael@0 4967
michael@0 4968 static gfxFloat
michael@0 4969 ComputeSelectionUnderlineHeight(nsPresContext* aPresContext,
michael@0 4970 const gfxFont::Metrics& aFontMetrics,
michael@0 4971 SelectionType aSelectionType)
michael@0 4972 {
michael@0 4973 switch (aSelectionType) {
michael@0 4974 case nsISelectionController::SELECTION_IME_RAWINPUT:
michael@0 4975 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
michael@0 4976 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
michael@0 4977 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
michael@0 4978 return aFontMetrics.underlineSize;
michael@0 4979 case nsISelectionController::SELECTION_SPELLCHECK: {
michael@0 4980 // The thickness of the spellchecker underline shouldn't honor the font
michael@0 4981 // metrics. It should be constant pixels value which is decided from the
michael@0 4982 // default font size. Note that if the actual font size is smaller than
michael@0 4983 // the default font size, we should use the actual font size because the
michael@0 4984 // computed value from the default font size can be too thick for the
michael@0 4985 // current font size.
michael@0 4986 int32_t defaultFontSize =
michael@0 4987 aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size);
michael@0 4988 gfxFloat fontSize = std::min(gfxFloat(defaultFontSize),
michael@0 4989 aFontMetrics.emHeight);
michael@0 4990 fontSize = std::max(fontSize, 1.0);
michael@0 4991 return ceil(fontSize / 20);
michael@0 4992 }
michael@0 4993 default:
michael@0 4994 NS_WARNING("Requested underline style is not valid");
michael@0 4995 return aFontMetrics.underlineSize;
michael@0 4996 }
michael@0 4997 }
michael@0 4998
michael@0 4999 enum DecorationType {
michael@0 5000 eNormalDecoration,
michael@0 5001 eSelectionDecoration
michael@0 5002 };
michael@0 5003
michael@0 5004 static void
michael@0 5005 PaintDecorationLine(nsIFrame* aFrame,
michael@0 5006 gfxContext* const aCtx,
michael@0 5007 const gfxRect& aDirtyRect,
michael@0 5008 nscolor aColor,
michael@0 5009 const nscolor* aOverrideColor,
michael@0 5010 const gfxPoint& aPt,
michael@0 5011 gfxFloat aXInFrame,
michael@0 5012 const gfxSize& aLineSize,
michael@0 5013 gfxFloat aAscent,
michael@0 5014 gfxFloat aOffset,
michael@0 5015 uint8_t aDecoration,
michael@0 5016 uint8_t aStyle,
michael@0 5017 DecorationType aDecorationType,
michael@0 5018 nsTextFrame::DrawPathCallbacks* aCallbacks,
michael@0 5019 gfxFloat aDescentLimit = -1.0)
michael@0 5020 {
michael@0 5021 nscolor lineColor = aOverrideColor ? *aOverrideColor : aColor;
michael@0 5022 if (aCallbacks) {
michael@0 5023 if (aDecorationType == eNormalDecoration) {
michael@0 5024 aCallbacks->NotifyBeforeDecorationLine(lineColor);
michael@0 5025 } else {
michael@0 5026 aCallbacks->NotifyBeforeSelectionDecorationLine(lineColor);
michael@0 5027 }
michael@0 5028 nsCSSRendering::DecorationLineToPath(aFrame, aCtx, aDirtyRect, lineColor,
michael@0 5029 aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle,
michael@0 5030 aDescentLimit);
michael@0 5031 if (aDecorationType == eNormalDecoration) {
michael@0 5032 aCallbacks->NotifyDecorationLinePathEmitted();
michael@0 5033 } else {
michael@0 5034 aCallbacks->NotifySelectionDecorationLinePathEmitted();
michael@0 5035 }
michael@0 5036 } else {
michael@0 5037 nsCSSRendering::PaintDecorationLine(aFrame, aCtx, aDirtyRect, lineColor,
michael@0 5038 aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle,
michael@0 5039 aDescentLimit);
michael@0 5040 }
michael@0 5041 }
michael@0 5042
michael@0 5043 /**
michael@0 5044 * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
michael@0 5045 * drawing text decoration for selections.
michael@0 5046 */
michael@0 5047 static void DrawSelectionDecorations(gfxContext* aContext,
michael@0 5048 const gfxRect& aDirtyRect,
michael@0 5049 SelectionType aType,
michael@0 5050 nsTextFrame* aFrame,
michael@0 5051 nsTextPaintStyle& aTextPaintStyle,
michael@0 5052 const TextRangeStyle &aRangeStyle,
michael@0 5053 const gfxPoint& aPt, gfxFloat aXInFrame, gfxFloat aWidth,
michael@0 5054 gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics,
michael@0 5055 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5056 {
michael@0 5057 gfxPoint pt(aPt);
michael@0 5058 gfxSize size(aWidth,
michael@0 5059 ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
michael@0 5060 aFontMetrics, aType));
michael@0 5061 gfxFloat descentLimit =
michael@0 5062 ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
michael@0 5063 aFrame, aFontMetrics);
michael@0 5064
michael@0 5065 float relativeSize;
michael@0 5066 uint8_t style;
michael@0 5067 nscolor color;
michael@0 5068 int32_t index =
michael@0 5069 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType);
michael@0 5070 bool weDefineSelectionUnderline =
michael@0 5071 aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color,
michael@0 5072 &relativeSize, &style);
michael@0 5073
michael@0 5074 switch (aType) {
michael@0 5075 case nsISelectionController::SELECTION_IME_RAWINPUT:
michael@0 5076 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
michael@0 5077 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
michael@0 5078 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: {
michael@0 5079 // IME decoration lines should not be drawn on the both ends, i.e., we
michael@0 5080 // need to cut both edges of the decoration lines. Because same style
michael@0 5081 // IME selections can adjoin, but the users need to be able to know
michael@0 5082 // where are the boundaries of the selections.
michael@0 5083 //
michael@0 5084 // X: underline
michael@0 5085 //
michael@0 5086 // IME selection #1 IME selection #2 IME selection #3
michael@0 5087 // | | |
michael@0 5088 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
michael@0 5089 // +---------------------+----------------------+--------------------
michael@0 5090 // ^ ^ ^ ^ ^
michael@0 5091 // gap gap gap
michael@0 5092 pt.x += 1.0;
michael@0 5093 size.width -= 2.0;
michael@0 5094 if (aRangeStyle.IsDefined()) {
michael@0 5095 // If IME defines the style, that should override our definition.
michael@0 5096 if (aRangeStyle.IsLineStyleDefined()) {
michael@0 5097 if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
michael@0 5098 return;
michael@0 5099 }
michael@0 5100 style = aRangeStyle.mLineStyle;
michael@0 5101 relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
michael@0 5102 } else if (!weDefineSelectionUnderline) {
michael@0 5103 // There is no underline style definition.
michael@0 5104 return;
michael@0 5105 }
michael@0 5106 if (aRangeStyle.IsUnderlineColorDefined()) {
michael@0 5107 color = aRangeStyle.mUnderlineColor;
michael@0 5108 } else if (aRangeStyle.IsForegroundColorDefined()) {
michael@0 5109 color = aRangeStyle.mForegroundColor;
michael@0 5110 } else {
michael@0 5111 NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(),
michael@0 5112 "Only the background color is defined");
michael@0 5113 color = aTextPaintStyle.GetTextColor();
michael@0 5114 }
michael@0 5115 } else if (!weDefineSelectionUnderline) {
michael@0 5116 // IME doesn't specify the selection style and we don't define selection
michael@0 5117 // underline.
michael@0 5118 return;
michael@0 5119 }
michael@0 5120 break;
michael@0 5121 }
michael@0 5122 case nsISelectionController::SELECTION_SPELLCHECK:
michael@0 5123 if (!weDefineSelectionUnderline)
michael@0 5124 return;
michael@0 5125 break;
michael@0 5126 default:
michael@0 5127 NS_WARNING("Requested selection decorations when there aren't any");
michael@0 5128 return;
michael@0 5129 }
michael@0 5130 size.height *= relativeSize;
michael@0 5131 PaintDecorationLine(aFrame, aContext, aDirtyRect, color, nullptr, pt,
michael@0 5132 pt.x - aPt.x + aXInFrame, size, aAscent, aFontMetrics.underlineOffset,
michael@0 5133 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, eSelectionDecoration,
michael@0 5134 aCallbacks, descentLimit);
michael@0 5135 }
michael@0 5136
michael@0 5137 /**
michael@0 5138 * This function encapsulates all knowledge of how selections affect foreground
michael@0 5139 * and background colors.
michael@0 5140 * @return true if the selection affects colors, false otherwise
michael@0 5141 * @param aForeground the foreground color to use
michael@0 5142 * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
michael@0 5143 * background should be painted
michael@0 5144 */
michael@0 5145 static bool GetSelectionTextColors(SelectionType aType,
michael@0 5146 nsTextPaintStyle& aTextPaintStyle,
michael@0 5147 const TextRangeStyle &aRangeStyle,
michael@0 5148 nscolor* aForeground, nscolor* aBackground)
michael@0 5149 {
michael@0 5150 switch (aType) {
michael@0 5151 case nsISelectionController::SELECTION_NORMAL:
michael@0 5152 return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
michael@0 5153 case nsISelectionController::SELECTION_FIND:
michael@0 5154 aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
michael@0 5155 return true;
michael@0 5156 case nsISelectionController::SELECTION_URLSECONDARY:
michael@0 5157 aTextPaintStyle.GetURLSecondaryColor(aForeground);
michael@0 5158 *aBackground = NS_RGBA(0,0,0,0);
michael@0 5159 return true;
michael@0 5160 case nsISelectionController::SELECTION_IME_RAWINPUT:
michael@0 5161 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
michael@0 5162 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
michael@0 5163 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
michael@0 5164 if (aRangeStyle.IsDefined()) {
michael@0 5165 *aForeground = aTextPaintStyle.GetTextColor();
michael@0 5166 *aBackground = NS_RGBA(0,0,0,0);
michael@0 5167 if (!aRangeStyle.IsForegroundColorDefined() &&
michael@0 5168 !aRangeStyle.IsBackgroundColorDefined()) {
michael@0 5169 return false;
michael@0 5170 }
michael@0 5171 if (aRangeStyle.IsForegroundColorDefined()) {
michael@0 5172 *aForeground = aRangeStyle.mForegroundColor;
michael@0 5173 }
michael@0 5174 if (aRangeStyle.IsBackgroundColorDefined()) {
michael@0 5175 *aBackground = aRangeStyle.mBackgroundColor;
michael@0 5176 }
michael@0 5177 return true;
michael@0 5178 }
michael@0 5179 aTextPaintStyle.GetIMESelectionColors(
michael@0 5180 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType),
michael@0 5181 aForeground, aBackground);
michael@0 5182 return true;
michael@0 5183 default:
michael@0 5184 *aForeground = aTextPaintStyle.GetTextColor();
michael@0 5185 *aBackground = NS_RGBA(0,0,0,0);
michael@0 5186 return false;
michael@0 5187 }
michael@0 5188 }
michael@0 5189
michael@0 5190 /**
michael@0 5191 * This sets *aShadow to the appropriate shadow, if any, for the given
michael@0 5192 * type of selection. Returns true if *aShadow was set.
michael@0 5193 * If text-shadow was not specified, *aShadow is left untouched
michael@0 5194 * (NOT reset to null), and the function returns false.
michael@0 5195 */
michael@0 5196 static bool GetSelectionTextShadow(nsIFrame* aFrame,
michael@0 5197 SelectionType aType,
michael@0 5198 nsTextPaintStyle& aTextPaintStyle,
michael@0 5199 nsCSSShadowArray** aShadow)
michael@0 5200 {
michael@0 5201 switch (aType) {
michael@0 5202 case nsISelectionController::SELECTION_NORMAL:
michael@0 5203 return aTextPaintStyle.GetSelectionShadow(aShadow);
michael@0 5204 default:
michael@0 5205 return false;
michael@0 5206 }
michael@0 5207 }
michael@0 5208
michael@0 5209 /**
michael@0 5210 * This class lets us iterate over chunks of text in a uniform selection state,
michael@0 5211 * observing cluster boundaries, in content order, maintaining the current
michael@0 5212 * x-offset as we go, and telling whether the text chunk has a hyphen after
michael@0 5213 * it or not. The caller is responsible for actually computing the advance
michael@0 5214 * width of each chunk.
michael@0 5215 */
michael@0 5216 class SelectionIterator {
michael@0 5217 public:
michael@0 5218 /**
michael@0 5219 * aStart and aLength are in the original string. aSelectionDetails is
michael@0 5220 * according to the original string.
michael@0 5221 * @param aXOffset the offset from the origin of the frame to the start
michael@0 5222 * of the text (the left baseline origin for LTR, the right baseline origin
michael@0 5223 * for RTL)
michael@0 5224 */
michael@0 5225 SelectionIterator(SelectionDetails** aSelectionDetails,
michael@0 5226 int32_t aStart, int32_t aLength,
michael@0 5227 PropertyProvider& aProvider, gfxTextRun* aTextRun,
michael@0 5228 gfxFloat aXOffset);
michael@0 5229
michael@0 5230 /**
michael@0 5231 * Returns the next segment of uniformly selected (or not) text.
michael@0 5232 * @param aXOffset the offset from the origin of the frame to the start
michael@0 5233 * of the text (the left baseline origin for LTR, the right baseline origin
michael@0 5234 * for RTL)
michael@0 5235 * @param aOffset the transformed string offset of the text for this segment
michael@0 5236 * @param aLength the transformed string length of the text for this segment
michael@0 5237 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
michael@0 5238 * width of the hyphen, otherwise zero
michael@0 5239 * @param aType the selection type for this segment
michael@0 5240 * @param aStyle the selection style for this segment
michael@0 5241 * @return false if there are no more segments
michael@0 5242 */
michael@0 5243 bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength,
michael@0 5244 gfxFloat* aHyphenWidth, SelectionType* aType,
michael@0 5245 TextRangeStyle* aStyle);
michael@0 5246 void UpdateWithAdvance(gfxFloat aAdvance) {
michael@0 5247 mXOffset += aAdvance*mTextRun->GetDirection();
michael@0 5248 }
michael@0 5249
michael@0 5250 private:
michael@0 5251 SelectionDetails** mSelectionDetails;
michael@0 5252 PropertyProvider& mProvider;
michael@0 5253 gfxTextRun* mTextRun;
michael@0 5254 gfxSkipCharsIterator mIterator;
michael@0 5255 int32_t mOriginalStart;
michael@0 5256 int32_t mOriginalEnd;
michael@0 5257 gfxFloat mXOffset;
michael@0 5258 };
michael@0 5259
michael@0 5260 SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
michael@0 5261 int32_t aStart, int32_t aLength, PropertyProvider& aProvider,
michael@0 5262 gfxTextRun* aTextRun, gfxFloat aXOffset)
michael@0 5263 : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
michael@0 5264 mTextRun(aTextRun), mIterator(aProvider.GetStart()),
michael@0 5265 mOriginalStart(aStart), mOriginalEnd(aStart + aLength),
michael@0 5266 mXOffset(aXOffset)
michael@0 5267 {
michael@0 5268 mIterator.SetOriginalOffset(aStart);
michael@0 5269 }
michael@0 5270
michael@0 5271 bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
michael@0 5272 uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth,
michael@0 5273 SelectionType* aType, TextRangeStyle* aStyle)
michael@0 5274 {
michael@0 5275 if (mIterator.GetOriginalOffset() >= mOriginalEnd)
michael@0 5276 return false;
michael@0 5277
michael@0 5278 // save offset into transformed string now
michael@0 5279 uint32_t runOffset = mIterator.GetSkippedOffset();
michael@0 5280
michael@0 5281 int32_t index = mIterator.GetOriginalOffset() - mOriginalStart;
michael@0 5282 SelectionDetails* sdptr = mSelectionDetails[index];
michael@0 5283 SelectionType type =
michael@0 5284 sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE;
michael@0 5285 TextRangeStyle style;
michael@0 5286 if (sdptr) {
michael@0 5287 style = sdptr->mTextRangeStyle;
michael@0 5288 }
michael@0 5289 for (++index; mOriginalStart + index < mOriginalEnd; ++index) {
michael@0 5290 if (sdptr != mSelectionDetails[index])
michael@0 5291 break;
michael@0 5292 }
michael@0 5293 mIterator.SetOriginalOffset(index + mOriginalStart);
michael@0 5294
michael@0 5295 // Advance to the next cluster boundary
michael@0 5296 while (mIterator.GetOriginalOffset() < mOriginalEnd &&
michael@0 5297 !mIterator.IsOriginalCharSkipped() &&
michael@0 5298 !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
michael@0 5299 mIterator.AdvanceOriginal(1);
michael@0 5300 }
michael@0 5301
michael@0 5302 bool haveHyphenBreak =
michael@0 5303 (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
michael@0 5304 *aOffset = runOffset;
michael@0 5305 *aLength = mIterator.GetSkippedOffset() - runOffset;
michael@0 5306 *aXOffset = mXOffset;
michael@0 5307 *aHyphenWidth = 0;
michael@0 5308 if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) {
michael@0 5309 *aHyphenWidth = mProvider.GetHyphenWidth();
michael@0 5310 }
michael@0 5311 *aType = type;
michael@0 5312 *aStyle = style;
michael@0 5313 return true;
michael@0 5314 }
michael@0 5315
michael@0 5316 static void
michael@0 5317 AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun,
michael@0 5318 gfxTextRun::Metrics* aMetrics,
michael@0 5319 gfxFont::BoundingBoxType aBoundingBoxType,
michael@0 5320 gfxContext* aContext)
michael@0 5321 {
michael@0 5322 // Fix up metrics to include hyphen
michael@0 5323 nsAutoPtr<gfxTextRun> hyphenTextRun(
michael@0 5324 GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame));
michael@0 5325 if (!hyphenTextRun.get())
michael@0 5326 return;
michael@0 5327
michael@0 5328 gfxTextRun::Metrics hyphenMetrics =
michael@0 5329 hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(),
michael@0 5330 aBoundingBoxType, aContext, nullptr);
michael@0 5331 aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
michael@0 5332 }
michael@0 5333
michael@0 5334 void
michael@0 5335 nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength,
michael@0 5336 nsCSSShadowItem* aShadowDetails,
michael@0 5337 PropertyProvider* aProvider, const nsRect& aDirtyRect,
michael@0 5338 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
michael@0 5339 gfxContext* aCtx, const nscolor& aForegroundColor,
michael@0 5340 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
michael@0 5341 nscoord aLeftSideOffset, gfxRect& aBoundingBox)
michael@0 5342 {
michael@0 5343 PROFILER_LABEL("nsTextFrame", "PaintOneShadow");
michael@0 5344 gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
michael@0 5345 nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
michael@0 5346
michael@0 5347 // This rect is the box which is equivalent to where the shadow will be painted.
michael@0 5348 // The origin of aBoundingBox is the text baseline left, so we must translate it by
michael@0 5349 // that much in order to make the origin the top-left corner of the text bounding box.
michael@0 5350 gfxRect shadowGfxRect = aBoundingBox +
michael@0 5351 gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset;
michael@0 5352 nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
michael@0 5353 NSToCoordRound(shadowGfxRect.Y()),
michael@0 5354 NSToCoordRound(shadowGfxRect.Width()),
michael@0 5355 NSToCoordRound(shadowGfxRect.Height()));
michael@0 5356
michael@0 5357 nsContextBoxBlur contextBoxBlur;
michael@0 5358 gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
michael@0 5359 PresContext()->AppUnitsPerDevPixel(),
michael@0 5360 aCtx, aDirtyRect, nullptr);
michael@0 5361 if (!shadowContext)
michael@0 5362 return;
michael@0 5363
michael@0 5364 nscolor shadowColor;
michael@0 5365 const nscolor* decorationOverrideColor;
michael@0 5366 if (aShadowDetails->mHasColor) {
michael@0 5367 shadowColor = aShadowDetails->mColor;
michael@0 5368 decorationOverrideColor = &shadowColor;
michael@0 5369 } else {
michael@0 5370 shadowColor = aForegroundColor;
michael@0 5371 decorationOverrideColor = nullptr;
michael@0 5372 }
michael@0 5373
michael@0 5374 aCtx->Save();
michael@0 5375 aCtx->NewPath();
michael@0 5376 aCtx->SetColor(gfxRGBA(shadowColor));
michael@0 5377
michael@0 5378 // Draw the text onto our alpha-only surface to capture the alpha values.
michael@0 5379 // Remember that the box blur context has a device offset on it, so we don't need to
michael@0 5380 // translate any coordinates to fit on the surface.
michael@0 5381 gfxFloat advanceWidth;
michael@0 5382 gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
michael@0 5383 aDirtyRect.width, aDirtyRect.height);
michael@0 5384 DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset,
michael@0 5385 aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
michael@0 5386 nsTextPaintStyle(this),
michael@0 5387 aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges,
michael@0 5388 advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
michael@0 5389 decorationOverrideColor);
michael@0 5390
michael@0 5391 contextBoxBlur.DoPaint();
michael@0 5392 aCtx->Restore();
michael@0 5393 }
michael@0 5394
michael@0 5395 // Paints selection backgrounds and text in the correct colors. Also computes
michael@0 5396 // aAllTypes, the union of all selection types that are applying to this text.
michael@0 5397 bool
michael@0 5398 nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
michael@0 5399 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
michael@0 5400 const gfxRect& aDirtyRect,
michael@0 5401 PropertyProvider& aProvider,
michael@0 5402 uint32_t aContentOffset, uint32_t aContentLength,
michael@0 5403 nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
michael@0 5404 SelectionType* aAllTypes,
michael@0 5405 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
michael@0 5406 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5407 {
michael@0 5408 // Figure out which selections control the colors to use for each character.
michael@0 5409 AutoFallibleTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
michael@0 5410 SelectionDetails** prevailingSelections =
michael@0 5411 prevailingSelectionsBuffer.AppendElements(aContentLength);
michael@0 5412 if (!prevailingSelections) {
michael@0 5413 return false;
michael@0 5414 }
michael@0 5415
michael@0 5416 SelectionType allTypes = 0;
michael@0 5417 for (uint32_t i = 0; i < aContentLength; ++i) {
michael@0 5418 prevailingSelections[i] = nullptr;
michael@0 5419 }
michael@0 5420
michael@0 5421 SelectionDetails *sdptr = aDetails;
michael@0 5422 bool anyBackgrounds = false;
michael@0 5423 while (sdptr) {
michael@0 5424 int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
michael@0 5425 int32_t end = std::min(int32_t(aContentLength),
michael@0 5426 sdptr->mEnd - int32_t(aContentOffset));
michael@0 5427 SelectionType type = sdptr->mType;
michael@0 5428 if (start < end) {
michael@0 5429 allTypes |= type;
michael@0 5430 // Ignore selections that don't set colors
michael@0 5431 nscolor foreground, background;
michael@0 5432 if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle,
michael@0 5433 &foreground, &background)) {
michael@0 5434 if (NS_GET_A(background) > 0) {
michael@0 5435 anyBackgrounds = true;
michael@0 5436 }
michael@0 5437 for (int32_t i = start; i < end; ++i) {
michael@0 5438 // Favour normal selection over IME selections
michael@0 5439 if (!prevailingSelections[i] ||
michael@0 5440 type < prevailingSelections[i]->mType) {
michael@0 5441 prevailingSelections[i] = sdptr;
michael@0 5442 }
michael@0 5443 }
michael@0 5444 }
michael@0 5445 }
michael@0 5446 sdptr = sdptr->mNext;
michael@0 5447 }
michael@0 5448 *aAllTypes = allTypes;
michael@0 5449
michael@0 5450 if (!allTypes) {
michael@0 5451 // Nothing is selected in the given text range. XXX can this still occur?
michael@0 5452 return false;
michael@0 5453 }
michael@0 5454
michael@0 5455 const gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
michael@0 5456 gfxFloat xOffset, hyphenWidth;
michael@0 5457 uint32_t offset, length; // in transformed string
michael@0 5458 SelectionType type;
michael@0 5459 TextRangeStyle rangeStyle;
michael@0 5460 // Draw background colors
michael@0 5461 if (anyBackgrounds) {
michael@0 5462 SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
michael@0 5463 aProvider, mTextRun, startXOffset);
michael@0 5464 while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
michael@0 5465 &type, &rangeStyle)) {
michael@0 5466 nscolor foreground, background;
michael@0 5467 GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
michael@0 5468 &foreground, &background);
michael@0 5469 // Draw background color
michael@0 5470 gfxFloat advance = hyphenWidth +
michael@0 5471 mTextRun->GetAdvanceWidth(offset, length, &aProvider);
michael@0 5472 if (NS_GET_A(background) > 0) {
michael@0 5473 gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0);
michael@0 5474 PaintSelectionBackground(aCtx, aTextPaintStyle.PresContext(),
michael@0 5475 background, aDirtyRect,
michael@0 5476 gfxRect(aFramePt.x + x, aFramePt.y, advance,
michael@0 5477 GetSize().height), aCallbacks);
michael@0 5478 }
michael@0 5479 iterator.UpdateWithAdvance(advance);
michael@0 5480 }
michael@0 5481 }
michael@0 5482
michael@0 5483 // Draw text
michael@0 5484 const nsStyleText* textStyle = StyleText();
michael@0 5485 nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
michael@0 5486 aDirtyRect.width, aDirtyRect.height);
michael@0 5487 SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
michael@0 5488 aProvider, mTextRun, startXOffset);
michael@0 5489 while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
michael@0 5490 &type, &rangeStyle)) {
michael@0 5491 nscolor foreground, background;
michael@0 5492 GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
michael@0 5493 &foreground, &background);
michael@0 5494 gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y);
michael@0 5495
michael@0 5496 // Determine what shadow, if any, to draw - either from textStyle
michael@0 5497 // or from the ::-moz-selection pseudo-class if specified there
michael@0 5498 nsCSSShadowArray* shadow = textStyle->GetTextShadow();
michael@0 5499 GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow);
michael@0 5500
michael@0 5501 // Draw shadows, if any
michael@0 5502 if (shadow) {
michael@0 5503 gfxTextRun::Metrics shadowMetrics =
michael@0 5504 mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
michael@0 5505 nullptr, &aProvider);
michael@0 5506 if (GetStateBits() & TEXT_HYPHEN_BREAK) {
michael@0 5507 AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
michael@0 5508 gfxFont::LOOSE_INK_EXTENTS, aCtx);
michael@0 5509 }
michael@0 5510 for (uint32_t i = shadow->Length(); i > 0; --i) {
michael@0 5511 PaintOneShadow(offset, length,
michael@0 5512 shadow->ShadowAt(i - 1), &aProvider,
michael@0 5513 dirtyRect, aFramePt, textBaselinePt, aCtx,
michael@0 5514 foreground, aClipEdges,
michael@0 5515 xOffset - (mTextRun->IsRightToLeft() ?
michael@0 5516 shadowMetrics.mBoundingBox.width : 0),
michael@0 5517 shadowMetrics.mBoundingBox);
michael@0 5518 }
michael@0 5519 }
michael@0 5520
michael@0 5521 // Draw text segment
michael@0 5522 gfxFloat advance;
michael@0 5523
michael@0 5524 DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt,
michael@0 5525 offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges,
michael@0 5526 advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks);
michael@0 5527 if (hyphenWidth) {
michael@0 5528 advance += hyphenWidth;
michael@0 5529 }
michael@0 5530 iterator.UpdateWithAdvance(advance);
michael@0 5531 }
michael@0 5532 return true;
michael@0 5533 }
michael@0 5534
michael@0 5535 void
michael@0 5536 nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
michael@0 5537 const gfxPoint& aFramePt,
michael@0 5538 const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
michael@0 5539 PropertyProvider& aProvider,
michael@0 5540 uint32_t aContentOffset, uint32_t aContentLength,
michael@0 5541 nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
michael@0 5542 SelectionType aSelectionType,
michael@0 5543 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5544 {
michael@0 5545 // Hide text decorations if we're currently hiding @font-face fallback text
michael@0 5546 if (aProvider.GetFontGroup()->ShouldSkipDrawing())
michael@0 5547 return;
michael@0 5548
michael@0 5549 // Figure out which characters will be decorated for this selection.
michael@0 5550 AutoFallibleTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
michael@0 5551 SelectionDetails** selectedChars =
michael@0 5552 selectedCharsBuffer.AppendElements(aContentLength);
michael@0 5553 if (!selectedChars) {
michael@0 5554 return;
michael@0 5555 }
michael@0 5556 for (uint32_t i = 0; i < aContentLength; ++i) {
michael@0 5557 selectedChars[i] = nullptr;
michael@0 5558 }
michael@0 5559
michael@0 5560 SelectionDetails *sdptr = aDetails;
michael@0 5561 while (sdptr) {
michael@0 5562 if (sdptr->mType == aSelectionType) {
michael@0 5563 int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
michael@0 5564 int32_t end = std::min(int32_t(aContentLength),
michael@0 5565 sdptr->mEnd - int32_t(aContentOffset));
michael@0 5566 for (int32_t i = start; i < end; ++i) {
michael@0 5567 selectedChars[i] = sdptr;
michael@0 5568 }
michael@0 5569 }
michael@0 5570 sdptr = sdptr->mNext;
michael@0 5571 }
michael@0 5572
michael@0 5573 gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0);
michael@0 5574 if (!firstFont)
michael@0 5575 return; // OOM
michael@0 5576 gfxFont::Metrics decorationMetrics(firstFont->GetMetrics());
michael@0 5577 decorationMetrics.underlineOffset =
michael@0 5578 aProvider.GetFontGroup()->GetUnderlineOffset();
michael@0 5579
michael@0 5580 gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
michael@0 5581 SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
michael@0 5582 aProvider, mTextRun, startXOffset);
michael@0 5583 gfxFloat xOffset, hyphenWidth;
michael@0 5584 uint32_t offset, length;
michael@0 5585 int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
michael@0 5586 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
michael@0 5587 gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app);
michael@0 5588 gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
michael@0 5589 aDirtyRect.width / app, aDirtyRect.height / app);
michael@0 5590 SelectionType type;
michael@0 5591 TextRangeStyle selectedStyle;
michael@0 5592 while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
michael@0 5593 &type, &selectedStyle)) {
michael@0 5594 gfxFloat advance = hyphenWidth +
michael@0 5595 mTextRun->GetAdvanceWidth(offset, length, &aProvider);
michael@0 5596 if (type == aSelectionType) {
michael@0 5597 pt.x = (aFramePt.x + xOffset -
michael@0 5598 (mTextRun->IsRightToLeft() ? advance : 0)) / app;
michael@0 5599 gfxFloat width = Abs(advance) / app;
michael@0 5600 gfxFloat xInFrame = pt.x - (aFramePt.x / app);
michael@0 5601 DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
michael@0 5602 aTextPaintStyle, selectedStyle, pt, xInFrame,
michael@0 5603 width, mAscent / app, decorationMetrics,
michael@0 5604 aCallbacks);
michael@0 5605 }
michael@0 5606 iterator.UpdateWithAdvance(advance);
michael@0 5607 }
michael@0 5608 }
michael@0 5609
michael@0 5610 bool
michael@0 5611 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
michael@0 5612 const gfxPoint& aFramePt,
michael@0 5613 const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
michael@0 5614 PropertyProvider& aProvider,
michael@0 5615 uint32_t aContentOffset, uint32_t aContentLength,
michael@0 5616 nsTextPaintStyle& aTextPaintStyle,
michael@0 5617 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
michael@0 5618 gfxTextContextPaint* aContextPaint,
michael@0 5619 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5620 {
michael@0 5621 NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
michael@0 5622
michael@0 5623 SelectionDetails* details = GetSelectionDetails();
michael@0 5624 if (!details) {
michael@0 5625 return false;
michael@0 5626 }
michael@0 5627
michael@0 5628 SelectionType allTypes;
michael@0 5629 if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
michael@0 5630 aProvider, aContentOffset, aContentLength,
michael@0 5631 aTextPaintStyle, details, &allTypes,
michael@0 5632 aClipEdges, aCallbacks)) {
michael@0 5633 DestroySelectionDetails(details);
michael@0 5634 return false;
michael@0 5635 }
michael@0 5636 // Iterate through just the selection types that paint decorations and
michael@0 5637 // paint decorations for any that actually occur in this frame. Paint
michael@0 5638 // higher-numbered selection types below lower-numered ones on the
michael@0 5639 // general principal that lower-numbered selections are higher priority.
michael@0 5640 allTypes &= SelectionTypesWithDecorations;
michael@0 5641 for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1;
michael@0 5642 i >= 1; --i) {
michael@0 5643 SelectionType type = 1 << (i - 1);
michael@0 5644 if (allTypes & type) {
michael@0 5645 // There is some selection of this type. Try to paint its decorations
michael@0 5646 // (there might not be any for this type but that's OK,
michael@0 5647 // PaintTextSelectionDecorations will exit early).
michael@0 5648 PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
michael@0 5649 aProvider, aContentOffset, aContentLength,
michael@0 5650 aTextPaintStyle, details, type,
michael@0 5651 aCallbacks);
michael@0 5652 }
michael@0 5653 }
michael@0 5654
michael@0 5655 DestroySelectionDetails(details);
michael@0 5656 return true;
michael@0 5657 }
michael@0 5658
michael@0 5659 nscolor
michael@0 5660 nsTextFrame::GetCaretColorAt(int32_t aOffset)
michael@0 5661 {
michael@0 5662 NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
michael@0 5663
michael@0 5664 nscolor result = nsFrame::GetCaretColorAt(aOffset);
michael@0 5665 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 5666 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
michael@0 5667 int32_t contentOffset = provider.GetStart().GetOriginalOffset();
michael@0 5668 int32_t contentLength = provider.GetOriginalLength();
michael@0 5669 NS_PRECONDITION(aOffset >= contentOffset &&
michael@0 5670 aOffset <= contentOffset + contentLength,
michael@0 5671 "aOffset must be in the frame's range");
michael@0 5672 int32_t offsetInFrame = aOffset - contentOffset;
michael@0 5673 if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
michael@0 5674 return result;
michael@0 5675 }
michael@0 5676
michael@0 5677 bool isSolidTextColor = true;
michael@0 5678 if (IsSVGText()) {
michael@0 5679 const nsStyleSVG* style = StyleSVG();
michael@0 5680 if (style->mFill.mType != eStyleSVGPaintType_None &&
michael@0 5681 style->mFill.mType != eStyleSVGPaintType_Color) {
michael@0 5682 isSolidTextColor = false;
michael@0 5683 }
michael@0 5684 }
michael@0 5685
michael@0 5686 nsTextPaintStyle textPaintStyle(this);
michael@0 5687 textPaintStyle.SetResolveColors(isSolidTextColor);
michael@0 5688 SelectionDetails* details = GetSelectionDetails();
michael@0 5689 SelectionDetails* sdptr = details;
michael@0 5690 SelectionType type = 0;
michael@0 5691 while (sdptr) {
michael@0 5692 int32_t start = std::max(0, sdptr->mStart - contentOffset);
michael@0 5693 int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
michael@0 5694 if (start <= offsetInFrame && offsetInFrame < end &&
michael@0 5695 (type == 0 || sdptr->mType < type)) {
michael@0 5696 nscolor foreground, background;
michael@0 5697 if (GetSelectionTextColors(sdptr->mType, textPaintStyle,
michael@0 5698 sdptr->mTextRangeStyle,
michael@0 5699 &foreground, &background)) {
michael@0 5700 if (!isSolidTextColor &&
michael@0 5701 NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
michael@0 5702 result = NS_RGBA(0, 0, 0, 255);
michael@0 5703 } else {
michael@0 5704 result = foreground;
michael@0 5705 }
michael@0 5706 type = sdptr->mType;
michael@0 5707 }
michael@0 5708 }
michael@0 5709 sdptr = sdptr->mNext;
michael@0 5710 }
michael@0 5711
michael@0 5712 DestroySelectionDetails(details);
michael@0 5713 return result;
michael@0 5714 }
michael@0 5715
michael@0 5716 static uint32_t
michael@0 5717 ComputeTransformedLength(PropertyProvider& aProvider)
michael@0 5718 {
michael@0 5719 gfxSkipCharsIterator iter(aProvider.GetStart());
michael@0 5720 uint32_t start = iter.GetSkippedOffset();
michael@0 5721 iter.AdvanceOriginal(aProvider.GetOriginalLength());
michael@0 5722 return iter.GetSkippedOffset() - start;
michael@0 5723 }
michael@0 5724
michael@0 5725 bool
michael@0 5726 nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge,
michael@0 5727 nscoord* aSnappedLeftEdge,
michael@0 5728 nscoord* aSnappedRightEdge)
michael@0 5729 {
michael@0 5730 // We need a *reference* rendering context (not one that might have a
michael@0 5731 // transform), so we don't have a rendering context argument.
michael@0 5732 // XXX get the block and line passed to us somehow! This is slow!
michael@0 5733 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 5734 if (!mTextRun)
michael@0 5735 return false;
michael@0 5736
michael@0 5737 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
michael@0 5738 // Trim trailing whitespace
michael@0 5739 provider.InitializeForDisplay(true);
michael@0 5740
michael@0 5741 uint32_t startOffset = provider.GetStart().GetSkippedOffset();
michael@0 5742 uint32_t maxLength = ComputeTransformedLength(provider);
michael@0 5743 return MeasureCharClippedText(provider, aLeftEdge, aRightEdge,
michael@0 5744 &startOffset, &maxLength,
michael@0 5745 aSnappedLeftEdge, aSnappedRightEdge);
michael@0 5746 }
michael@0 5747
michael@0 5748 static uint32_t GetClusterLength(gfxTextRun* aTextRun,
michael@0 5749 uint32_t aStartOffset,
michael@0 5750 uint32_t aMaxLength,
michael@0 5751 bool aIsRTL)
michael@0 5752 {
michael@0 5753 uint32_t clusterLength = aIsRTL ? 0 : 1;
michael@0 5754 while (clusterLength < aMaxLength) {
michael@0 5755 if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
michael@0 5756 if (aIsRTL) {
michael@0 5757 ++clusterLength;
michael@0 5758 }
michael@0 5759 break;
michael@0 5760 }
michael@0 5761 ++clusterLength;
michael@0 5762 }
michael@0 5763 return clusterLength;
michael@0 5764 }
michael@0 5765
michael@0 5766 bool
michael@0 5767 nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
michael@0 5768 nscoord aLeftEdge, nscoord aRightEdge,
michael@0 5769 uint32_t* aStartOffset,
michael@0 5770 uint32_t* aMaxLength,
michael@0 5771 nscoord* aSnappedLeftEdge,
michael@0 5772 nscoord* aSnappedRightEdge)
michael@0 5773 {
michael@0 5774 *aSnappedLeftEdge = 0;
michael@0 5775 *aSnappedRightEdge = 0;
michael@0 5776 if (aLeftEdge <= 0 && aRightEdge <= 0) {
michael@0 5777 return true;
michael@0 5778 }
michael@0 5779
michael@0 5780 uint32_t offset = *aStartOffset;
michael@0 5781 uint32_t maxLength = *aMaxLength;
michael@0 5782 const nscoord frameWidth = GetSize().width;
michael@0 5783 const bool rtl = mTextRun->IsRightToLeft();
michael@0 5784 gfxFloat advanceWidth = 0;
michael@0 5785 const nscoord startEdge = rtl ? aRightEdge : aLeftEdge;
michael@0 5786 if (startEdge > 0) {
michael@0 5787 const gfxFloat maxAdvance = gfxFloat(startEdge);
michael@0 5788 while (maxLength > 0) {
michael@0 5789 uint32_t clusterLength =
michael@0 5790 GetClusterLength(mTextRun, offset, maxLength, rtl);
michael@0 5791 advanceWidth +=
michael@0 5792 mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
michael@0 5793 maxLength -= clusterLength;
michael@0 5794 offset += clusterLength;
michael@0 5795 if (advanceWidth >= maxAdvance) {
michael@0 5796 break;
michael@0 5797 }
michael@0 5798 }
michael@0 5799 nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge;
michael@0 5800 *snappedStartEdge = NSToCoordFloor(advanceWidth);
michael@0 5801 *aStartOffset = offset;
michael@0 5802 }
michael@0 5803
michael@0 5804 const nscoord endEdge = rtl ? aLeftEdge : aRightEdge;
michael@0 5805 if (endEdge > 0) {
michael@0 5806 const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge);
michael@0 5807 while (maxLength > 0) {
michael@0 5808 uint32_t clusterLength =
michael@0 5809 GetClusterLength(mTextRun, offset, maxLength, rtl);
michael@0 5810 gfxFloat nextAdvance = advanceWidth +
michael@0 5811 mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
michael@0 5812 if (nextAdvance > maxAdvance) {
michael@0 5813 break;
michael@0 5814 }
michael@0 5815 // This cluster fits, include it.
michael@0 5816 advanceWidth = nextAdvance;
michael@0 5817 maxLength -= clusterLength;
michael@0 5818 offset += clusterLength;
michael@0 5819 }
michael@0 5820 maxLength = offset - *aStartOffset;
michael@0 5821 nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge;
michael@0 5822 *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth);
michael@0 5823 }
michael@0 5824 *aMaxLength = maxLength;
michael@0 5825 return maxLength != 0;
michael@0 5826 }
michael@0 5827
michael@0 5828 void
michael@0 5829 nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
michael@0 5830 const nsRect& aDirtyRect,
michael@0 5831 const nsCharClipDisplayItem& aItem,
michael@0 5832 gfxTextContextPaint* aContextPaint,
michael@0 5833 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5834 {
michael@0 5835 // Don't pass in aRenderingContext here, because we need a *reference*
michael@0 5836 // context and aRenderingContext might have some transform in it
michael@0 5837 // XXX get the block and line passed to us somehow! This is slow!
michael@0 5838 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 5839 if (!mTextRun)
michael@0 5840 return;
michael@0 5841
michael@0 5842 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
michael@0 5843 // Trim trailing whitespace
michael@0 5844 provider.InitializeForDisplay(true);
michael@0 5845
michael@0 5846 gfxContext* ctx = aRenderingContext->ThebesContext();
michael@0 5847 const bool rtl = mTextRun->IsRightToLeft();
michael@0 5848 const nscoord frameWidth = GetSize().width;
michael@0 5849 gfxPoint framePt(aPt.x, aPt.y);
michael@0 5850 gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
michael@0 5851 nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
michael@0 5852 uint32_t startOffset = provider.GetStart().GetSkippedOffset();
michael@0 5853 uint32_t maxLength = ComputeTransformedLength(provider);
michael@0 5854 nscoord snappedLeftEdge, snappedRightEdge;
michael@0 5855 if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
michael@0 5856 &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
michael@0 5857 return;
michael@0 5858 }
michael@0 5859 textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
michael@0 5860 nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
michael@0 5861 snappedRightEdge);
michael@0 5862 nsTextPaintStyle textPaintStyle(this);
michael@0 5863 textPaintStyle.SetResolveColors(!aCallbacks);
michael@0 5864
michael@0 5865 gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
michael@0 5866 aDirtyRect.width, aDirtyRect.height);
michael@0 5867 // Fork off to the (slower) paint-with-selection path if necessary.
michael@0 5868 if (IsSelected()) {
michael@0 5869 gfxSkipCharsIterator tmp(provider.GetStart());
michael@0 5870 int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
michael@0 5871 int32_t contentLength =
michael@0 5872 tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset;
michael@0 5873 if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect,
michael@0 5874 provider, contentOffset, contentLength,
michael@0 5875 textPaintStyle, clipEdges, aContextPaint,
michael@0 5876 aCallbacks)) {
michael@0 5877 return;
michael@0 5878 }
michael@0 5879 }
michael@0 5880
michael@0 5881 nscolor foregroundColor = textPaintStyle.GetTextColor();
michael@0 5882 if (!aCallbacks) {
michael@0 5883 const nsStyleText* textStyle = StyleText();
michael@0 5884 if (textStyle->HasTextShadow()) {
michael@0 5885 // Text shadow happens with the last value being painted at the back,
michael@0 5886 // ie. it is painted first.
michael@0 5887 gfxTextRun::Metrics shadowMetrics =
michael@0 5888 mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS,
michael@0 5889 nullptr, &provider);
michael@0 5890 for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
michael@0 5891 PaintOneShadow(startOffset, maxLength,
michael@0 5892 textStyle->mTextShadow->ShadowAt(i - 1), &provider,
michael@0 5893 aDirtyRect, framePt, textBaselinePt, ctx,
michael@0 5894 foregroundColor, clipEdges,
michael@0 5895 snappedLeftEdge, shadowMetrics.mBoundingBox);
michael@0 5896 }
michael@0 5897 }
michael@0 5898 }
michael@0 5899
michael@0 5900 gfxFloat advanceWidth;
michael@0 5901 DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider,
michael@0 5902 textPaintStyle, foregroundColor, clipEdges, advanceWidth,
michael@0 5903 (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
michael@0 5904 nullptr, aContextPaint, aCallbacks);
michael@0 5905 }
michael@0 5906
michael@0 5907 static void
michael@0 5908 DrawTextRun(gfxTextRun* aTextRun,
michael@0 5909 gfxContext* const aCtx,
michael@0 5910 const gfxPoint& aTextBaselinePt,
michael@0 5911 uint32_t aOffset, uint32_t aLength,
michael@0 5912 PropertyProvider* aProvider,
michael@0 5913 nscolor aTextColor,
michael@0 5914 gfxFloat* aAdvanceWidth,
michael@0 5915 gfxTextContextPaint* aContextPaint,
michael@0 5916 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5917 {
michael@0 5918 DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH :
michael@0 5919 DrawMode::GLYPH_FILL;
michael@0 5920 if (aCallbacks) {
michael@0 5921 aCallbacks->NotifyBeforeText(aTextColor);
michael@0 5922 aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
michael@0 5923 aProvider, aAdvanceWidth, aContextPaint, aCallbacks);
michael@0 5924 aCallbacks->NotifyAfterText();
michael@0 5925 } else {
michael@0 5926 aCtx->SetColor(gfxRGBA(aTextColor));
michael@0 5927 aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
michael@0 5928 aProvider, aAdvanceWidth, aContextPaint);
michael@0 5929 }
michael@0 5930 }
michael@0 5931
michael@0 5932 void
michael@0 5933 nsTextFrame::DrawTextRun(gfxContext* const aCtx,
michael@0 5934 const gfxPoint& aTextBaselinePt,
michael@0 5935 uint32_t aOffset, uint32_t aLength,
michael@0 5936 PropertyProvider& aProvider,
michael@0 5937 nscolor aTextColor,
michael@0 5938 gfxFloat& aAdvanceWidth,
michael@0 5939 bool aDrawSoftHyphen,
michael@0 5940 gfxTextContextPaint* aContextPaint,
michael@0 5941 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5942 {
michael@0 5943 ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider,
michael@0 5944 aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks);
michael@0 5945
michael@0 5946 if (aDrawSoftHyphen) {
michael@0 5947 // Don't use ctx as the context, because we need a reference context here,
michael@0 5948 // ctx may be transformed.
michael@0 5949 nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this));
michael@0 5950 if (hyphenTextRun.get()) {
michael@0 5951 // For right-to-left text runs, the soft-hyphen is positioned at the left
michael@0 5952 // of the text, minus its own width
michael@0 5953 gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
michael@0 5954 (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nullptr) : 0);
michael@0 5955 ::DrawTextRun(hyphenTextRun.get(), aCtx,
michael@0 5956 gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
michael@0 5957 0, hyphenTextRun->GetLength(),
michael@0 5958 nullptr, aTextColor, nullptr, aContextPaint, aCallbacks);
michael@0 5959 }
michael@0 5960 }
michael@0 5961 }
michael@0 5962
michael@0 5963 void
michael@0 5964 nsTextFrame::DrawTextRunAndDecorations(
michael@0 5965 gfxContext* const aCtx, const gfxRect& aDirtyRect,
michael@0 5966 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
michael@0 5967 uint32_t aOffset, uint32_t aLength,
michael@0 5968 PropertyProvider& aProvider,
michael@0 5969 const nsTextPaintStyle& aTextStyle,
michael@0 5970 nscolor aTextColor,
michael@0 5971 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
michael@0 5972 gfxFloat& aAdvanceWidth,
michael@0 5973 bool aDrawSoftHyphen,
michael@0 5974 const TextDecorations& aDecorations,
michael@0 5975 const nscolor* const aDecorationOverrideColor,
michael@0 5976 gfxTextContextPaint* aContextPaint,
michael@0 5977 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 5978 {
michael@0 5979 const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
michael@0 5980
michael@0 5981 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
michael@0 5982 nscoord x = NSToCoordRound(aFramePt.x);
michael@0 5983 nscoord width = GetRect().width;
michael@0 5984 aClipEdges.Intersect(&x, &width);
michael@0 5985
michael@0 5986 gfxPoint decPt(x / app, 0);
michael@0 5987 gfxSize decSize(width / app, 0);
michael@0 5988 const gfxFloat ascent = gfxFloat(mAscent) / app;
michael@0 5989 const gfxFloat frameTop = aFramePt.y;
michael@0 5990
michael@0 5991 gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
michael@0 5992 aDirtyRect.Width() / app, aDirtyRect.Height() / app);
michael@0 5993
michael@0 5994 nscoord inflationMinFontSize =
michael@0 5995 nsLayoutUtils::InflationMinFontSizeFor(this);
michael@0 5996
michael@0 5997 // Underlines
michael@0 5998 for (uint32_t i = aDecorations.mUnderlines.Length(); i-- > 0; ) {
michael@0 5999 const LineDecoration& dec = aDecorations.mUnderlines[i];
michael@0 6000 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 6001 continue;
michael@0 6002 }
michael@0 6003
michael@0 6004 float inflation =
michael@0 6005 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 6006 const gfxFont::Metrics metrics =
michael@0 6007 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 6008
michael@0 6009 decSize.height = metrics.underlineSize;
michael@0 6010 decPt.y = (frameTop - dec.mBaselineOffset) / app;
michael@0 6011
michael@0 6012 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
michael@0 6013 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
michael@0 6014 metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
michael@0 6015 dec.mStyle, eNormalDecoration, aCallbacks);
michael@0 6016 }
michael@0 6017 // Overlines
michael@0 6018 for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
michael@0 6019 const LineDecoration& dec = aDecorations.mOverlines[i];
michael@0 6020 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 6021 continue;
michael@0 6022 }
michael@0 6023
michael@0 6024 float inflation =
michael@0 6025 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 6026 const gfxFont::Metrics metrics =
michael@0 6027 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 6028
michael@0 6029 decSize.height = metrics.underlineSize;
michael@0 6030 decPt.y = (frameTop - dec.mBaselineOffset) / app;
michael@0 6031
michael@0 6032 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
michael@0 6033 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
michael@0 6034 metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
michael@0 6035 eNormalDecoration, aCallbacks);
michael@0 6036 }
michael@0 6037
michael@0 6038 // CSS 2.1 mandates that text be painted after over/underlines, and *then*
michael@0 6039 // line-throughs
michael@0 6040 DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
michael@0 6041 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
michael@0 6042
michael@0 6043 // Line-throughs
michael@0 6044 for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) {
michael@0 6045 const LineDecoration& dec = aDecorations.mStrikes[i];
michael@0 6046 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
michael@0 6047 continue;
michael@0 6048 }
michael@0 6049
michael@0 6050 float inflation =
michael@0 6051 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
michael@0 6052 const gfxFont::Metrics metrics =
michael@0 6053 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
michael@0 6054
michael@0 6055 decSize.height = metrics.strikeoutSize;
michael@0 6056 decPt.y = (frameTop - dec.mBaselineOffset) / app;
michael@0 6057
michael@0 6058 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
michael@0 6059 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
michael@0 6060 metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
michael@0 6061 dec.mStyle, eNormalDecoration, aCallbacks);
michael@0 6062 }
michael@0 6063 }
michael@0 6064
michael@0 6065 void
michael@0 6066 nsTextFrame::DrawText(
michael@0 6067 gfxContext* const aCtx, const gfxRect& aDirtyRect,
michael@0 6068 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
michael@0 6069 uint32_t aOffset, uint32_t aLength,
michael@0 6070 PropertyProvider& aProvider,
michael@0 6071 const nsTextPaintStyle& aTextStyle,
michael@0 6072 nscolor aTextColor,
michael@0 6073 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
michael@0 6074 gfxFloat& aAdvanceWidth,
michael@0 6075 bool aDrawSoftHyphen,
michael@0 6076 const nscolor* const aDecorationOverrideColor,
michael@0 6077 gfxTextContextPaint* aContextPaint,
michael@0 6078 nsTextFrame::DrawPathCallbacks* aCallbacks)
michael@0 6079 {
michael@0 6080 TextDecorations decorations;
michael@0 6081 GetTextDecorations(aTextStyle.PresContext(),
michael@0 6082 aCallbacks ? eUnresolvedColors : eResolvedColors,
michael@0 6083 decorations);
michael@0 6084
michael@0 6085 // Hide text decorations if we're currently hiding @font-face fallback text
michael@0 6086 const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
michael@0 6087 decorations.HasDecorationLines();
michael@0 6088 if (drawDecorations) {
michael@0 6089 DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
michael@0 6090 aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth,
michael@0 6091 aDrawSoftHyphen, decorations,
michael@0 6092 aDecorationOverrideColor, aContextPaint, aCallbacks);
michael@0 6093 } else {
michael@0 6094 DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
michael@0 6095 aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
michael@0 6096 }
michael@0 6097 }
michael@0 6098
michael@0 6099 int16_t
michael@0 6100 nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
michael@0 6101 {
michael@0 6102 // get the selection controller
michael@0 6103 nsCOMPtr<nsISelectionController> selectionController;
michael@0 6104 nsresult rv = GetSelectionController(PresContext(),
michael@0 6105 getter_AddRefs(selectionController));
michael@0 6106 if (NS_FAILED(rv) || !selectionController)
michael@0 6107 return nsISelectionController::SELECTION_OFF;
michael@0 6108
michael@0 6109 selectionController->GetSelectionFlags(aSelectionFlags);
michael@0 6110
michael@0 6111 int16_t selectionValue;
michael@0 6112 selectionController->GetDisplaySelection(&selectionValue);
michael@0 6113
michael@0 6114 return selectionValue;
michael@0 6115 }
michael@0 6116
michael@0 6117 bool
michael@0 6118 nsTextFrame::IsVisibleInSelection(nsISelection* aSelection)
michael@0 6119 {
michael@0 6120 // Check the quick way first
michael@0 6121 if (!GetContent()->IsSelectionDescendant())
michael@0 6122 return false;
michael@0 6123
michael@0 6124 SelectionDetails* details = GetSelectionDetails();
michael@0 6125 bool found = false;
michael@0 6126
michael@0 6127 // where are the selection points "really"
michael@0 6128 SelectionDetails *sdptr = details;
michael@0 6129 while (sdptr) {
michael@0 6130 if (sdptr->mEnd > GetContentOffset() &&
michael@0 6131 sdptr->mStart < GetContentEnd() &&
michael@0 6132 sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
michael@0 6133 found = true;
michael@0 6134 break;
michael@0 6135 }
michael@0 6136 sdptr = sdptr->mNext;
michael@0 6137 }
michael@0 6138 DestroySelectionDetails(details);
michael@0 6139
michael@0 6140 return found;
michael@0 6141 }
michael@0 6142
michael@0 6143 /**
michael@0 6144 * Compute the longest prefix of text whose width is <= aWidth. Return
michael@0 6145 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
michael@0 6146 */
michael@0 6147 static uint32_t
michael@0 6148 CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength,
michael@0 6149 gfxFloat aWidth, PropertyProvider* aProvider,
michael@0 6150 gfxFloat* aFitWidth)
michael@0 6151 {
michael@0 6152 uint32_t last = 0;
michael@0 6153 gfxFloat width = 0;
michael@0 6154 for (uint32_t i = 1; i <= aLength; ++i) {
michael@0 6155 if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
michael@0 6156 gfxFloat nextWidth = width +
michael@0 6157 aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
michael@0 6158 if (nextWidth > aWidth)
michael@0 6159 break;
michael@0 6160 last = i;
michael@0 6161 width = nextWidth;
michael@0 6162 }
michael@0 6163 }
michael@0 6164 *aFitWidth = width;
michael@0 6165 return last;
michael@0 6166 }
michael@0 6167
michael@0 6168 nsIFrame::ContentOffsets
michael@0 6169 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint)
michael@0 6170 {
michael@0 6171 return GetCharacterOffsetAtFramePointInternal(aPoint, true);
michael@0 6172 }
michael@0 6173
michael@0 6174 nsIFrame::ContentOffsets
michael@0 6175 nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
michael@0 6176 {
michael@0 6177 return GetCharacterOffsetAtFramePointInternal(aPoint, false);
michael@0 6178 }
michael@0 6179
michael@0 6180 nsIFrame::ContentOffsets
michael@0 6181 nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint,
michael@0 6182 bool aForInsertionPoint)
michael@0 6183 {
michael@0 6184 ContentOffsets offsets;
michael@0 6185
michael@0 6186 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 6187 if (!mTextRun)
michael@0 6188 return offsets;
michael@0 6189
michael@0 6190 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
michael@0 6191 // Trim leading but not trailing whitespace if possible
michael@0 6192 provider.InitializeForDisplay(false);
michael@0 6193 gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x;
michael@0 6194 gfxFloat fitWidth;
michael@0 6195 uint32_t skippedLength = ComputeTransformedLength(provider);
michael@0 6196
michael@0 6197 uint32_t charsFit = CountCharsFit(mTextRun,
michael@0 6198 provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
michael@0 6199
michael@0 6200 int32_t selectedOffset;
michael@0 6201 if (charsFit < skippedLength) {
michael@0 6202 // charsFit characters fitted, but no more could fit. See if we're
michael@0 6203 // more than halfway through the cluster.. If we are, choose the next
michael@0 6204 // cluster.
michael@0 6205 gfxSkipCharsIterator extraCluster(provider.GetStart());
michael@0 6206 extraCluster.AdvanceSkipped(charsFit);
michael@0 6207 gfxSkipCharsIterator extraClusterLastChar(extraCluster);
michael@0 6208 FindClusterEnd(mTextRun,
michael@0 6209 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
michael@0 6210 &extraClusterLastChar);
michael@0 6211 gfxFloat charWidth =
michael@0 6212 mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
michael@0 6213 GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
michael@0 6214 &provider);
michael@0 6215 selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2
michael@0 6216 ? extraCluster.GetOriginalOffset()
michael@0 6217 : extraClusterLastChar.GetOriginalOffset() + 1;
michael@0 6218 } else {
michael@0 6219 // All characters fitted, we're at (or beyond) the end of the text.
michael@0 6220 // XXX This could be some pathological situation where negative spacing
michael@0 6221 // caused characters to move backwards. We can't really handle that
michael@0 6222 // in the current frame system because frames can't have negative
michael@0 6223 // intrinsic widths.
michael@0 6224 selectedOffset =
michael@0 6225 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
michael@0 6226 // If we're at the end of a preformatted line which has a terminating
michael@0 6227 // linefeed, we want to reduce the offset by one to make sure that the
michael@0 6228 // selection is placed before the linefeed character.
michael@0 6229 if (HasSignificantTerminalNewline()) {
michael@0 6230 --selectedOffset;
michael@0 6231 }
michael@0 6232 }
michael@0 6233
michael@0 6234 offsets.content = GetContent();
michael@0 6235 offsets.offset = offsets.secondaryOffset = selectedOffset;
michael@0 6236 offsets.associateWithNext = mContentOffset == offsets.offset;
michael@0 6237 return offsets;
michael@0 6238 }
michael@0 6239
michael@0 6240 bool
michael@0 6241 nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
michael@0 6242 nsRect& aRect)
michael@0 6243 {
michael@0 6244 if (aRect.IsEmpty())
michael@0 6245 return false;
michael@0 6246
michael@0 6247 nsRect givenRect = aRect;
michael@0 6248
michael@0 6249 nsRefPtr<nsFontMetrics> fm;
michael@0 6250 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
michael@0 6251 GetFontSizeInflation());
michael@0 6252 gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
michael@0 6253 gfxFont* firstFont = fontGroup->GetFontAt(0);
michael@0 6254 if (!firstFont)
michael@0 6255 return false; // OOM
michael@0 6256 const gfxFont::Metrics& metrics = firstFont->GetMetrics();
michael@0 6257 gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
michael@0 6258 gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
michael@0 6259 gfxFloat descentLimit =
michael@0 6260 ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
michael@0 6261
michael@0 6262 SelectionDetails *details = GetSelectionDetails();
michael@0 6263 for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
michael@0 6264 if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
michael@0 6265 continue;
michael@0 6266
michael@0 6267 uint8_t style;
michael@0 6268 float relativeSize;
michael@0 6269 int32_t index =
michael@0 6270 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType);
michael@0 6271 if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) {
michael@0 6272 if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr,
michael@0 6273 &relativeSize, &style)) {
michael@0 6274 continue;
michael@0 6275 }
michael@0 6276 } else {
michael@0 6277 // IME selections
michael@0 6278 TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
michael@0 6279 if (rangeStyle.IsDefined()) {
michael@0 6280 if (!rangeStyle.IsLineStyleDefined() ||
michael@0 6281 rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
michael@0 6282 continue;
michael@0 6283 }
michael@0 6284 style = rangeStyle.mLineStyle;
michael@0 6285 relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
michael@0 6286 } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
michael@0 6287 nullptr, &relativeSize,
michael@0 6288 &style)) {
michael@0 6289 continue;
michael@0 6290 }
michael@0 6291 }
michael@0 6292 nsRect decorationArea;
michael@0 6293 gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
michael@0 6294 ComputeSelectionUnderlineHeight(aPresContext,
michael@0 6295 metrics, sd->mType));
michael@0 6296 relativeSize = std::max(relativeSize, 1.0f);
michael@0 6297 size.height *= relativeSize;
michael@0 6298 decorationArea =
michael@0 6299 nsCSSRendering::GetTextDecorationRect(aPresContext, size,
michael@0 6300 ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
michael@0 6301 style, descentLimit);
michael@0 6302 aRect.UnionRect(aRect, decorationArea);
michael@0 6303 }
michael@0 6304 DestroySelectionDetails(details);
michael@0 6305
michael@0 6306 return !aRect.IsEmpty() && !givenRect.Contains(aRect);
michael@0 6307 }
michael@0 6308
michael@0 6309 bool
michael@0 6310 nsTextFrame::IsFrameSelected() const
michael@0 6311 {
michael@0 6312 NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
michael@0 6313 "use the public IsSelected() instead");
michael@0 6314 return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
michael@0 6315 GetContentEnd());
michael@0 6316 }
michael@0 6317
michael@0 6318 void
michael@0 6319 nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected,
michael@0 6320 SelectionType aType)
michael@0 6321 {
michael@0 6322 NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
michael@0 6323 DEBUG_VERIFY_NOT_DIRTY(mState);
michael@0 6324
michael@0 6325 // Selection is collapsed, which can't affect text frame rendering
michael@0 6326 if (aStart == aEnd)
michael@0 6327 return;
michael@0 6328
michael@0 6329 nsTextFrame* f = this;
michael@0 6330 while (f && f->GetContentEnd() <= int32_t(aStart)) {
michael@0 6331 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
michael@0 6332 }
michael@0 6333
michael@0 6334 nsPresContext* presContext = PresContext();
michael@0 6335 while (f && f->GetContentOffset() < int32_t(aEnd)) {
michael@0 6336 // We may need to reflow to recompute the overflow area for
michael@0 6337 // spellchecking or IME underline if their underline is thicker than
michael@0 6338 // the normal decoration line.
michael@0 6339 if (aType & SelectionTypesWithDecorations) {
michael@0 6340 bool didHaveOverflowingSelection =
michael@0 6341 (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
michael@0 6342 nsRect r(nsPoint(0, 0), GetSize());
michael@0 6343 bool willHaveOverflowingSelection =
michael@0 6344 aSelected && f->CombineSelectionUnderlineRect(presContext, r);
michael@0 6345 if (didHaveOverflowingSelection || willHaveOverflowingSelection) {
michael@0 6346 presContext->PresShell()->FrameNeedsReflow(f,
michael@0 6347 nsIPresShell::eStyleChange,
michael@0 6348 NS_FRAME_IS_DIRTY);
michael@0 6349 }
michael@0 6350 }
michael@0 6351 // Selection might change anything. Invalidate the overflow area.
michael@0 6352 f->InvalidateFrame();
michael@0 6353
michael@0 6354 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
michael@0 6355 }
michael@0 6356 }
michael@0 6357
michael@0 6358 nsresult
michael@0 6359 nsTextFrame::GetPointFromOffset(int32_t inOffset,
michael@0 6360 nsPoint* outPoint)
michael@0 6361 {
michael@0 6362 if (!outPoint)
michael@0 6363 return NS_ERROR_NULL_POINTER;
michael@0 6364
michael@0 6365 outPoint->x = 0;
michael@0 6366 outPoint->y = 0;
michael@0 6367
michael@0 6368 DEBUG_VERIFY_NOT_DIRTY(mState);
michael@0 6369 if (mState & NS_FRAME_IS_DIRTY)
michael@0 6370 return NS_ERROR_UNEXPECTED;
michael@0 6371
michael@0 6372 if (GetContentLength() <= 0) {
michael@0 6373 return NS_OK;
michael@0 6374 }
michael@0 6375
michael@0 6376 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 6377 if (!mTextRun)
michael@0 6378 return NS_ERROR_FAILURE;
michael@0 6379
michael@0 6380 PropertyProvider properties(this, iter, nsTextFrame::eInflated);
michael@0 6381 // Don't trim trailing whitespace, we want the caret to appear in the right
michael@0 6382 // place if it's positioned there
michael@0 6383 properties.InitializeForDisplay(false);
michael@0 6384
michael@0 6385 if (inOffset < GetContentOffset()){
michael@0 6386 NS_WARNING("offset before this frame's content");
michael@0 6387 inOffset = GetContentOffset();
michael@0 6388 } else if (inOffset > GetContentEnd()) {
michael@0 6389 NS_WARNING("offset after this frame's content");
michael@0 6390 inOffset = GetContentEnd();
michael@0 6391 }
michael@0 6392 int32_t trimmedOffset = properties.GetStart().GetOriginalOffset();
michael@0 6393 int32_t trimmedEnd = trimmedOffset + properties.GetOriginalLength();
michael@0 6394 inOffset = std::max(inOffset, trimmedOffset);
michael@0 6395 inOffset = std::min(inOffset, trimmedEnd);
michael@0 6396
michael@0 6397 iter.SetOriginalOffset(inOffset);
michael@0 6398
michael@0 6399 if (inOffset < trimmedEnd &&
michael@0 6400 !iter.IsOriginalCharSkipped() &&
michael@0 6401 !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
michael@0 6402 NS_WARNING("GetPointFromOffset called for non-cluster boundary");
michael@0 6403 FindClusterStart(mTextRun, trimmedOffset, &iter);
michael@0 6404 }
michael@0 6405
michael@0 6406 gfxFloat advanceWidth =
michael@0 6407 mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
michael@0 6408 GetSkippedDistance(properties.GetStart(), iter),
michael@0 6409 &properties);
michael@0 6410 nscoord width = NSToCoordCeilClamped(advanceWidth);
michael@0 6411
michael@0 6412 if (mTextRun->IsRightToLeft()) {
michael@0 6413 outPoint->x = mRect.width - width;
michael@0 6414 } else {
michael@0 6415 outPoint->x = width;
michael@0 6416 }
michael@0 6417 outPoint->y = 0;
michael@0 6418
michael@0 6419 return NS_OK;
michael@0 6420 }
michael@0 6421
michael@0 6422 nsresult
michael@0 6423 nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
michael@0 6424 bool aHint,
michael@0 6425 int32_t* aOutOffset,
michael@0 6426 nsIFrame**aOutFrame)
michael@0 6427 {
michael@0 6428 DEBUG_VERIFY_NOT_DIRTY(mState);
michael@0 6429 #if 0 //XXXrbs disable due to bug 310227
michael@0 6430 if (mState & NS_FRAME_IS_DIRTY)
michael@0 6431 return NS_ERROR_UNEXPECTED;
michael@0 6432 #endif
michael@0 6433
michael@0 6434 NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
michael@0 6435 NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
michael@0 6436 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
michael@0 6437 if (this != primaryFrame) {
michael@0 6438 // This call needs to happen on the primary frame
michael@0 6439 return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
michael@0 6440 aOutOffset, aOutFrame);
michael@0 6441 }
michael@0 6442
michael@0 6443 nsTextFrame* f = this;
michael@0 6444 int32_t offset = mContentOffset;
michael@0 6445
michael@0 6446 // Try to look up the offset to frame property
michael@0 6447 nsTextFrame* cachedFrame = static_cast<nsTextFrame*>
michael@0 6448 (Properties().Get(OffsetToFrameProperty()));
michael@0 6449
michael@0 6450 if (cachedFrame) {
michael@0 6451 f = cachedFrame;
michael@0 6452 offset = f->GetContentOffset();
michael@0 6453
michael@0 6454 f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
michael@0 6455 }
michael@0 6456
michael@0 6457 if ((aContentOffset >= offset) &&
michael@0 6458 (aHint || aContentOffset != offset)) {
michael@0 6459 while (true) {
michael@0 6460 nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextContinuation());
michael@0 6461 if (!next || aContentOffset < next->GetContentOffset())
michael@0 6462 break;
michael@0 6463 if (aContentOffset == next->GetContentOffset()) {
michael@0 6464 if (aHint) {
michael@0 6465 f = next;
michael@0 6466 if (f->GetContentLength() == 0) {
michael@0 6467 continue; // use the last of the empty frames with this offset
michael@0 6468 }
michael@0 6469 }
michael@0 6470 break;
michael@0 6471 }
michael@0 6472 f = next;
michael@0 6473 }
michael@0 6474 } else {
michael@0 6475 while (true) {
michael@0 6476 nsTextFrame* prev = static_cast<nsTextFrame*>(f->GetPrevContinuation());
michael@0 6477 if (!prev || aContentOffset > f->GetContentOffset())
michael@0 6478 break;
michael@0 6479 if (aContentOffset == f->GetContentOffset()) {
michael@0 6480 if (!aHint) {
michael@0 6481 f = prev;
michael@0 6482 if (f->GetContentLength() == 0) {
michael@0 6483 continue; // use the first of the empty frames with this offset
michael@0 6484 }
michael@0 6485 }
michael@0 6486 break;
michael@0 6487 }
michael@0 6488 f = prev;
michael@0 6489 }
michael@0 6490 }
michael@0 6491
michael@0 6492 *aOutOffset = aContentOffset - f->GetContentOffset();
michael@0 6493 *aOutFrame = f;
michael@0 6494
michael@0 6495 // cache the frame we found
michael@0 6496 Properties().Set(OffsetToFrameProperty(), f);
michael@0 6497 f->AddStateBits(TEXT_IN_OFFSET_CACHE);
michael@0 6498
michael@0 6499 return NS_OK;
michael@0 6500 }
michael@0 6501
michael@0 6502 nsIFrame::FrameSearchResult
michael@0 6503 nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset)
michael@0 6504 {
michael@0 6505 NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
michael@0 6506
michael@0 6507 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 6508 if (!mTextRun)
michael@0 6509 return CONTINUE_EMPTY;
michael@0 6510
michael@0 6511 TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
michael@0 6512 // Check whether there are nonskipped characters in the trimmmed range
michael@0 6513 return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
michael@0 6514 iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE;
michael@0 6515 }
michael@0 6516
michael@0 6517 /**
michael@0 6518 * This class iterates through the clusters before or after the given
michael@0 6519 * aPosition (which is a content offset). You can test each cluster
michael@0 6520 * to see if it's whitespace (as far as selection/caret movement is concerned),
michael@0 6521 * or punctuation, or if there is a word break before the cluster. ("Before"
michael@0 6522 * is interpreted according to aDirection, so if aDirection is -1, "before"
michael@0 6523 * means actually *after* the cluster content.)
michael@0 6524 */
michael@0 6525 class MOZ_STACK_CLASS ClusterIterator {
michael@0 6526 public:
michael@0 6527 ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection,
michael@0 6528 nsString& aContext);
michael@0 6529
michael@0 6530 bool NextCluster();
michael@0 6531 bool IsWhitespace();
michael@0 6532 bool IsPunctuation();
michael@0 6533 bool HaveWordBreakBefore() { return mHaveWordBreak; }
michael@0 6534 int32_t GetAfterOffset();
michael@0 6535 int32_t GetBeforeOffset();
michael@0 6536
michael@0 6537 private:
michael@0 6538 gfxSkipCharsIterator mIterator;
michael@0 6539 const nsTextFragment* mFrag;
michael@0 6540 nsTextFrame* mTextFrame;
michael@0 6541 int32_t mDirection;
michael@0 6542 int32_t mCharIndex;
michael@0 6543 nsTextFrame::TrimmedOffsets mTrimmed;
michael@0 6544 nsTArray<bool> mWordBreaks;
michael@0 6545 bool mHaveWordBreak;
michael@0 6546 };
michael@0 6547
michael@0 6548 static bool
michael@0 6549 IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
michael@0 6550 bool aRespectClusters,
michael@0 6551 gfxTextRun* aTextRun,
michael@0 6552 nsIFrame* aFrame)
michael@0 6553 {
michael@0 6554 if (aIter.IsOriginalCharSkipped())
michael@0 6555 return false;
michael@0 6556 uint32_t index = aIter.GetSkippedOffset();
michael@0 6557 if (aRespectClusters && !aTextRun->IsClusterStart(index))
michael@0 6558 return false;
michael@0 6559 if (index > 0) {
michael@0 6560 // Check whether the proposed position is in between the two halves of a
michael@0 6561 // surrogate pair; if so, this is not a valid character boundary.
michael@0 6562 // (In the case where we are respecting clusters, we won't actually get
michael@0 6563 // this far because the low surrogate is also marked as non-clusterStart
michael@0 6564 // so we'll return FALSE above.)
michael@0 6565 if (aTextRun->CharIsLowSurrogate(index)) {
michael@0 6566 return false;
michael@0 6567 }
michael@0 6568 }
michael@0 6569 return true;
michael@0 6570 }
michael@0 6571
michael@0 6572 nsIFrame::FrameSearchResult
michael@0 6573 nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset,
michael@0 6574 bool aRespectClusters)
michael@0 6575 {
michael@0 6576 int32_t contentLength = GetContentLength();
michael@0 6577 NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
michael@0 6578
michael@0 6579 bool selectable;
michael@0 6580 uint8_t selectStyle;
michael@0 6581 IsSelectable(&selectable, &selectStyle);
michael@0 6582 if (selectStyle == NS_STYLE_USER_SELECT_ALL)
michael@0 6583 return CONTINUE_UNSELECTABLE;
michael@0 6584
michael@0 6585 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 6586 if (!mTextRun)
michael@0 6587 return CONTINUE_EMPTY;
michael@0 6588
michael@0 6589 TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
michael@0 6590
michael@0 6591 // A negative offset means "end of frame".
michael@0 6592 int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
michael@0 6593
michael@0 6594 if (!aForward) {
michael@0 6595 // If at the beginning of the line, look at the previous continuation
michael@0 6596 for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
michael@0 6597 i >= trimmed.mStart; --i) {
michael@0 6598 iter.SetOriginalOffset(i);
michael@0 6599 if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
michael@0 6600 *aOffset = i - mContentOffset;
michael@0 6601 return FOUND;
michael@0 6602 }
michael@0 6603 }
michael@0 6604 *aOffset = 0;
michael@0 6605 } else {
michael@0 6606 // If we're at the end of a line, look at the next continuation
michael@0 6607 iter.SetOriginalOffset(startOffset);
michael@0 6608 if (startOffset <= trimmed.GetEnd() &&
michael@0 6609 !(startOffset < trimmed.GetEnd() &&
michael@0 6610 StyleText()->NewlineIsSignificant() &&
michael@0 6611 iter.GetSkippedOffset() < mTextRun->GetLength() &&
michael@0 6612 mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
michael@0 6613 for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
michael@0 6614 iter.SetOriginalOffset(i);
michael@0 6615 if (i == trimmed.GetEnd() ||
michael@0 6616 IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
michael@0 6617 *aOffset = i - mContentOffset;
michael@0 6618 return FOUND;
michael@0 6619 }
michael@0 6620 }
michael@0 6621 }
michael@0 6622 *aOffset = contentLength;
michael@0 6623 }
michael@0 6624
michael@0 6625 return CONTINUE;
michael@0 6626 }
michael@0 6627
michael@0 6628 bool
michael@0 6629 ClusterIterator::IsWhitespace()
michael@0 6630 {
michael@0 6631 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
michael@0 6632 return IsSelectionSpace(mFrag, mCharIndex);
michael@0 6633 }
michael@0 6634
michael@0 6635 bool
michael@0 6636 ClusterIterator::IsPunctuation()
michael@0 6637 {
michael@0 6638 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
michael@0 6639 nsIUGenCategory::nsUGenCategory c =
michael@0 6640 mozilla::unicode::GetGenCategory(mFrag->CharAt(mCharIndex));
michael@0 6641 return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol;
michael@0 6642 }
michael@0 6643
michael@0 6644 int32_t
michael@0 6645 ClusterIterator::GetBeforeOffset()
michael@0 6646 {
michael@0 6647 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
michael@0 6648 return mCharIndex + (mDirection > 0 ? 0 : 1);
michael@0 6649 }
michael@0 6650
michael@0 6651 int32_t
michael@0 6652 ClusterIterator::GetAfterOffset()
michael@0 6653 {
michael@0 6654 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
michael@0 6655 return mCharIndex + (mDirection > 0 ? 1 : 0);
michael@0 6656 }
michael@0 6657
michael@0 6658 bool
michael@0 6659 ClusterIterator::NextCluster()
michael@0 6660 {
michael@0 6661 if (!mDirection)
michael@0 6662 return false;
michael@0 6663 gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
michael@0 6664
michael@0 6665 mHaveWordBreak = false;
michael@0 6666 while (true) {
michael@0 6667 bool keepGoing = false;
michael@0 6668 if (mDirection > 0) {
michael@0 6669 if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
michael@0 6670 return false;
michael@0 6671 keepGoing = mIterator.IsOriginalCharSkipped() ||
michael@0 6672 mIterator.GetOriginalOffset() < mTrimmed.mStart ||
michael@0 6673 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
michael@0 6674 mCharIndex = mIterator.GetOriginalOffset();
michael@0 6675 mIterator.AdvanceOriginal(1);
michael@0 6676 } else {
michael@0 6677 if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
michael@0 6678 return false;
michael@0 6679 mIterator.AdvanceOriginal(-1);
michael@0 6680 keepGoing = mIterator.IsOriginalCharSkipped() ||
michael@0 6681 mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
michael@0 6682 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
michael@0 6683 mCharIndex = mIterator.GetOriginalOffset();
michael@0 6684 }
michael@0 6685
michael@0 6686 if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
michael@0 6687 mHaveWordBreak = true;
michael@0 6688 }
michael@0 6689 if (!keepGoing)
michael@0 6690 return true;
michael@0 6691 }
michael@0 6692 }
michael@0 6693
michael@0 6694 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
michael@0 6695 int32_t aDirection, nsString& aContext)
michael@0 6696 : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
michael@0 6697 {
michael@0 6698 mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
michael@0 6699 if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
michael@0 6700 mDirection = 0; // signal failure
michael@0 6701 return;
michael@0 6702 }
michael@0 6703 mIterator.SetOriginalOffset(aPosition);
michael@0 6704
michael@0 6705 mFrag = aTextFrame->GetContent()->GetText();
michael@0 6706 mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
michael@0 6707
michael@0 6708 int32_t textOffset = aTextFrame->GetContentOffset();
michael@0 6709 int32_t textLen = aTextFrame->GetContentLength();
michael@0 6710 if (!mWordBreaks.AppendElements(textLen + 1)) {
michael@0 6711 mDirection = 0; // signal failure
michael@0 6712 return;
michael@0 6713 }
michael@0 6714 memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
michael@0 6715 int32_t textStart;
michael@0 6716 if (aDirection > 0) {
michael@0 6717 if (aContext.IsEmpty()) {
michael@0 6718 // No previous context, so it must be the start of a line or text run
michael@0 6719 mWordBreaks[0] = true;
michael@0 6720 }
michael@0 6721 textStart = aContext.Length();
michael@0 6722 mFrag->AppendTo(aContext, textOffset, textLen);
michael@0 6723 } else {
michael@0 6724 if (aContext.IsEmpty()) {
michael@0 6725 // No following context, so it must be the end of a line or text run
michael@0 6726 mWordBreaks[textLen] = true;
michael@0 6727 }
michael@0 6728 textStart = 0;
michael@0 6729 nsAutoString str;
michael@0 6730 mFrag->AppendTo(str, textOffset, textLen);
michael@0 6731 aContext.Insert(str, 0);
michael@0 6732 }
michael@0 6733 nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
michael@0 6734 for (int32_t i = 0; i <= textLen; ++i) {
michael@0 6735 int32_t indexInText = i + textStart;
michael@0 6736 mWordBreaks[i] |=
michael@0 6737 wordBreaker->BreakInBetween(aContext.get(), indexInText,
michael@0 6738 aContext.get() + indexInText,
michael@0 6739 aContext.Length() - indexInText);
michael@0 6740 }
michael@0 6741 }
michael@0 6742
michael@0 6743 nsIFrame::FrameSearchResult
michael@0 6744 nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
michael@0 6745 int32_t* aOffset, PeekWordState* aState)
michael@0 6746 {
michael@0 6747 int32_t contentLength = GetContentLength();
michael@0 6748 NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
michael@0 6749
michael@0 6750 bool selectable;
michael@0 6751 uint8_t selectStyle;
michael@0 6752 IsSelectable(&selectable, &selectStyle);
michael@0 6753 if (selectStyle == NS_STYLE_USER_SELECT_ALL)
michael@0 6754 return CONTINUE_UNSELECTABLE;
michael@0 6755
michael@0 6756 int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
michael@0 6757 ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
michael@0 6758
michael@0 6759 if (!cIter.NextCluster())
michael@0 6760 return CONTINUE_EMPTY;
michael@0 6761
michael@0 6762 do {
michael@0 6763 bool isPunctuation = cIter.IsPunctuation();
michael@0 6764 bool isWhitespace = cIter.IsWhitespace();
michael@0 6765 bool isWordBreakBefore = cIter.HaveWordBreakBefore();
michael@0 6766 if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
michael@0 6767 aState->SetSawBeforeType();
michael@0 6768 aState->Update(isPunctuation, isWhitespace);
michael@0 6769 continue;
michael@0 6770 }
michael@0 6771 // See if we can break before the current cluster
michael@0 6772 if (!aState->mAtStart) {
michael@0 6773 bool canBreak;
michael@0 6774 if (isPunctuation != aState->mLastCharWasPunctuation) {
michael@0 6775 canBreak = BreakWordBetweenPunctuation(aState, aForward,
michael@0 6776 isPunctuation, isWhitespace, aIsKeyboardSelect);
michael@0 6777 } else if (!aState->mLastCharWasWhitespace &&
michael@0 6778 !isWhitespace && !isPunctuation && isWordBreakBefore) {
michael@0 6779 // if both the previous and the current character are not white
michael@0 6780 // space but this can be word break before, we don't need to eat
michael@0 6781 // a white space in this case. This case happens in some languages
michael@0 6782 // that their words are not separated by white spaces. E.g.,
michael@0 6783 // Japanese and Chinese.
michael@0 6784 canBreak = true;
michael@0 6785 } else {
michael@0 6786 canBreak = isWordBreakBefore && aState->mSawBeforeType &&
michael@0 6787 (aWordSelectEatSpace != isWhitespace);
michael@0 6788 }
michael@0 6789 if (canBreak) {
michael@0 6790 *aOffset = cIter.GetBeforeOffset() - mContentOffset;
michael@0 6791 return FOUND;
michael@0 6792 }
michael@0 6793 }
michael@0 6794 aState->Update(isPunctuation, isWhitespace);
michael@0 6795 } while (cIter.NextCluster());
michael@0 6796
michael@0 6797 *aOffset = cIter.GetAfterOffset() - mContentOffset;
michael@0 6798 return CONTINUE;
michael@0 6799 }
michael@0 6800
michael@0 6801 // TODO this needs to be deCOMtaminated with the interface fixed in
michael@0 6802 // nsIFrame.h, but we won't do that until the old textframe is gone.
michael@0 6803 nsresult
michael@0 6804 nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
michael@0 6805 int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
michael@0 6806 {
michael@0 6807 if (!aRetval)
michael@0 6808 return NS_ERROR_NULL_POINTER;
michael@0 6809
michael@0 6810 // Text in the range is visible if there is at least one character in the range
michael@0 6811 // that is not skipped and is mapped by this frame (which is the primary frame)
michael@0 6812 // or one of its continuations.
michael@0 6813 for (nsTextFrame* f = this; f;
michael@0 6814 f = static_cast<nsTextFrame*>(GetNextContinuation())) {
michael@0 6815 int32_t dummyOffset = 0;
michael@0 6816 if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
michael@0 6817 *aRetval = true;
michael@0 6818 return NS_OK;
michael@0 6819 }
michael@0 6820 }
michael@0 6821
michael@0 6822 *aRetval = false;
michael@0 6823 return NS_OK;
michael@0 6824 }
michael@0 6825
michael@0 6826 nsresult
michael@0 6827 nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const
michael@0 6828 {
michael@0 6829 start = GetContentOffset();
michael@0 6830 end = GetContentEnd();
michael@0 6831 return NS_OK;
michael@0 6832 }
michael@0 6833
michael@0 6834 static int32_t
michael@0 6835 FindEndOfPunctuationRun(const nsTextFragment* aFrag,
michael@0 6836 gfxTextRun* aTextRun,
michael@0 6837 gfxSkipCharsIterator* aIter,
michael@0 6838 int32_t aOffset,
michael@0 6839 int32_t aStart,
michael@0 6840 int32_t aEnd)
michael@0 6841 {
michael@0 6842 int32_t i;
michael@0 6843
michael@0 6844 for (i = aStart; i < aEnd - aOffset; ++i) {
michael@0 6845 if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
michael@0 6846 aIter->SetOriginalOffset(aOffset + i);
michael@0 6847 FindClusterEnd(aTextRun, aEnd, aIter);
michael@0 6848 i = aIter->GetOriginalOffset() - aOffset;
michael@0 6849 } else {
michael@0 6850 break;
michael@0 6851 }
michael@0 6852 }
michael@0 6853 return i;
michael@0 6854 }
michael@0 6855
michael@0 6856 /**
michael@0 6857 * Returns true if this text frame completes the first-letter, false
michael@0 6858 * if it does not contain a true "letter".
michael@0 6859 * If returns true, then it also updates aLength to cover just the first-letter
michael@0 6860 * text.
michael@0 6861 *
michael@0 6862 * XXX :first-letter should be handled during frame construction
michael@0 6863 * (and it has a good bit in common with nextBidi)
michael@0 6864 *
michael@0 6865 * @param aLength an in/out parameter: on entry contains the maximum length to
michael@0 6866 * return, on exit returns length of the first-letter fragment (which may
michael@0 6867 * include leading and trailing punctuation, for example)
michael@0 6868 */
michael@0 6869 static bool
michael@0 6870 FindFirstLetterRange(const nsTextFragment* aFrag,
michael@0 6871 gfxTextRun* aTextRun,
michael@0 6872 int32_t aOffset, const gfxSkipCharsIterator& aIter,
michael@0 6873 int32_t* aLength)
michael@0 6874 {
michael@0 6875 int32_t i;
michael@0 6876 int32_t length = *aLength;
michael@0 6877 int32_t endOffset = aOffset + length;
michael@0 6878 gfxSkipCharsIterator iter(aIter);
michael@0 6879
michael@0 6880 // skip leading whitespace, then consume clusters that start with punctuation
michael@0 6881 i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset,
michael@0 6882 GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
michael@0 6883 endOffset);
michael@0 6884 if (i == length)
michael@0 6885 return false;
michael@0 6886
michael@0 6887 // If the next character is not a letter or number, there is no first-letter.
michael@0 6888 // Return true so that we don't go on looking, but set aLength to 0.
michael@0 6889 if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
michael@0 6890 *aLength = 0;
michael@0 6891 return true;
michael@0 6892 }
michael@0 6893
michael@0 6894 // consume another cluster (the actual first letter)
michael@0 6895 iter.SetOriginalOffset(aOffset + i);
michael@0 6896 FindClusterEnd(aTextRun, endOffset, &iter);
michael@0 6897 i = iter.GetOriginalOffset() - aOffset;
michael@0 6898 if (i + 1 == length)
michael@0 6899 return true;
michael@0 6900
michael@0 6901 // consume clusters that start with punctuation
michael@0 6902 i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
michael@0 6903 if (i < length)
michael@0 6904 *aLength = i;
michael@0 6905 return true;
michael@0 6906 }
michael@0 6907
michael@0 6908 static uint32_t
michael@0 6909 FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
michael@0 6910 nsIFrame::InlineIntrinsicWidthData* aData,
michael@0 6911 const nsStyleText* aTextStyle,
michael@0 6912 gfxSkipCharsIterator* aIterator,
michael@0 6913 uint32_t aFlowEndInTextRun)
michael@0 6914 {
michael@0 6915 if (aData->skipWhitespace) {
michael@0 6916 while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
michael@0 6917 IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
michael@0 6918 aIterator->AdvanceOriginal(1);
michael@0 6919 }
michael@0 6920 }
michael@0 6921 return aIterator->GetSkippedOffset();
michael@0 6922 }
michael@0 6923
michael@0 6924 union VoidPtrOrFloat {
michael@0 6925 VoidPtrOrFloat() : p(nullptr) {}
michael@0 6926
michael@0 6927 void *p;
michael@0 6928 float f;
michael@0 6929 };
michael@0 6930
michael@0 6931 float
michael@0 6932 nsTextFrame::GetFontSizeInflation() const
michael@0 6933 {
michael@0 6934 if (!HasFontSizeInflation()) {
michael@0 6935 return 1.0f;
michael@0 6936 }
michael@0 6937 VoidPtrOrFloat u;
michael@0 6938 u.p = Properties().Get(FontSizeInflationProperty());
michael@0 6939 return u.f;
michael@0 6940 }
michael@0 6941
michael@0 6942 void
michael@0 6943 nsTextFrame::SetFontSizeInflation(float aInflation)
michael@0 6944 {
michael@0 6945 if (aInflation == 1.0f) {
michael@0 6946 if (HasFontSizeInflation()) {
michael@0 6947 RemoveStateBits(TEXT_HAS_FONT_INFLATION);
michael@0 6948 Properties().Delete(FontSizeInflationProperty());
michael@0 6949 }
michael@0 6950 return;
michael@0 6951 }
michael@0 6952
michael@0 6953 AddStateBits(TEXT_HAS_FONT_INFLATION);
michael@0 6954 VoidPtrOrFloat u;
michael@0 6955 u.f = aInflation;
michael@0 6956 Properties().Set(FontSizeInflationProperty(), u.p);
michael@0 6957 }
michael@0 6958
michael@0 6959 /* virtual */
michael@0 6960 void nsTextFrame::MarkIntrinsicWidthsDirty()
michael@0 6961 {
michael@0 6962 ClearTextRuns();
michael@0 6963 nsFrame::MarkIntrinsicWidthsDirty();
michael@0 6964 }
michael@0 6965
michael@0 6966 // XXX this doesn't handle characters shaped by line endings. We need to
michael@0 6967 // temporarily override the "current line ending" settings.
michael@0 6968 void
michael@0 6969 nsTextFrame::AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext,
michael@0 6970 nsIFrame::InlineMinWidthData *aData,
michael@0 6971 TextRunType aTextRunType)
michael@0 6972 {
michael@0 6973 uint32_t flowEndInTextRun;
michael@0 6974 gfxContext* ctx = aRenderingContext->ThebesContext();
michael@0 6975 gfxSkipCharsIterator iter =
michael@0 6976 EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
michael@0 6977 aData->line, &flowEndInTextRun);
michael@0 6978 gfxTextRun *textRun = GetTextRun(aTextRunType);
michael@0 6979 if (!textRun)
michael@0 6980 return;
michael@0 6981
michael@0 6982 // Pass null for the line container. This will disable tab spacing, but that's
michael@0 6983 // OK since we can't really handle tabs for intrinsic sizing anyway.
michael@0 6984 const nsStyleText* textStyle = StyleText();
michael@0 6985 const nsTextFragment* frag = mContent->GetText();
michael@0 6986
michael@0 6987 // If we're hyphenating, the PropertyProvider needs the actual length;
michael@0 6988 // otherwise we can just pass INT32_MAX to mean "all the text"
michael@0 6989 int32_t len = INT32_MAX;
michael@0 6990 bool hyphenating = frag->GetLength() > 0 &&
michael@0 6991 (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO ||
michael@0 6992 (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL &&
michael@0 6993 (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
michael@0 6994 if (hyphenating) {
michael@0 6995 gfxSkipCharsIterator tmp(iter);
michael@0 6996 len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
michael@0 6997 tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
michael@0 6998 }
michael@0 6999 PropertyProvider provider(textRun, textStyle, frag, this,
michael@0 7000 iter, len, nullptr, 0, aTextRunType);
michael@0 7001
michael@0 7002 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
michael@0 7003 bool preformatNewlines = textStyle->NewlineIsSignificant();
michael@0 7004 bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
michael@0 7005 gfxFloat tabWidth = -1;
michael@0 7006 uint32_t start =
michael@0 7007 FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
michael@0 7008
michael@0 7009 AutoFallibleTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
michael@0 7010 bool *hyphBreakBefore = nullptr;
michael@0 7011 if (hyphenating) {
michael@0 7012 hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start);
michael@0 7013 if (hyphBreakBefore) {
michael@0 7014 provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
michael@0 7015 hyphBreakBefore);
michael@0 7016 }
michael@0 7017 }
michael@0 7018
michael@0 7019 for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
michael@0 7020 bool preformattedNewline = false;
michael@0 7021 bool preformattedTab = false;
michael@0 7022 if (i < flowEndInTextRun) {
michael@0 7023 // XXXldb Shouldn't we be including the newline as part of the
michael@0 7024 // segment that it ends rather than part of the segment that it
michael@0 7025 // starts?
michael@0 7026 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
michael@0 7027 preformattedTab = preformatTabs && textRun->CharIsTab(i);
michael@0 7028 if (!textRun->CanBreakLineBefore(i) &&
michael@0 7029 !preformattedNewline &&
michael@0 7030 !preformattedTab &&
michael@0 7031 (!hyphBreakBefore || !hyphBreakBefore[i - start]))
michael@0 7032 {
michael@0 7033 // we can't break here (and it's not the end of the flow)
michael@0 7034 continue;
michael@0 7035 }
michael@0 7036 }
michael@0 7037
michael@0 7038 if (i > wordStart) {
michael@0 7039 nscoord width =
michael@0 7040 NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider));
michael@0 7041 aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
michael@0 7042 aData->atStartOfLine = false;
michael@0 7043
michael@0 7044 if (collapseWhitespace) {
michael@0 7045 uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
michael@0 7046 if (trimStart == start) {
michael@0 7047 // This is *all* trimmable whitespace, so whatever trailingWhitespace
michael@0 7048 // we saw previously is still trailing...
michael@0 7049 aData->trailingWhitespace += width;
michael@0 7050 } else {
michael@0 7051 // Some non-whitespace so the old trailingWhitespace is no longer trailing
michael@0 7052 aData->trailingWhitespace =
michael@0 7053 NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
michael@0 7054 }
michael@0 7055 } else {
michael@0 7056 aData->trailingWhitespace = 0;
michael@0 7057 }
michael@0 7058 }
michael@0 7059
michael@0 7060 if (preformattedTab) {
michael@0 7061 PropertyProvider::Spacing spacing;
michael@0 7062 provider.GetSpacing(i, 1, &spacing);
michael@0 7063 aData->currentLine += nscoord(spacing.mBefore);
michael@0 7064 gfxFloat afterTab =
michael@0 7065 AdvanceToNextTab(aData->currentLine, this,
michael@0 7066 textRun, &tabWidth);
michael@0 7067 aData->currentLine = nscoord(afterTab + spacing.mAfter);
michael@0 7068 wordStart = i + 1;
michael@0 7069 } else if (i < flowEndInTextRun ||
michael@0 7070 (i == textRun->GetLength() &&
michael@0 7071 (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
michael@0 7072 if (preformattedNewline) {
michael@0 7073 aData->ForceBreak(aRenderingContext);
michael@0 7074 } else if (i < flowEndInTextRun && hyphBreakBefore &&
michael@0 7075 hyphBreakBefore[i - start])
michael@0 7076 {
michael@0 7077 aData->OptionallyBreak(aRenderingContext,
michael@0 7078 NSToCoordRound(provider.GetHyphenWidth()));
michael@0 7079 } else {
michael@0 7080 aData->OptionallyBreak(aRenderingContext);
michael@0 7081 }
michael@0 7082 wordStart = i;
michael@0 7083 }
michael@0 7084 }
michael@0 7085
michael@0 7086 if (start < flowEndInTextRun) {
michael@0 7087 // Check if we have collapsible whitespace at the end
michael@0 7088 aData->skipWhitespace =
michael@0 7089 IsTrimmableSpace(provider.GetFragment(),
michael@0 7090 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
michael@0 7091 textStyle);
michael@0 7092 }
michael@0 7093 }
michael@0 7094
michael@0 7095 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
michael@0 7096 return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
michael@0 7097 }
michael@0 7098
michael@0 7099 // XXX Need to do something here to avoid incremental reflow bugs due to
michael@0 7100 // first-line and first-letter changing min-width
michael@0 7101 /* virtual */ void
michael@0 7102 nsTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
michael@0 7103 nsIFrame::InlineMinWidthData *aData)
michael@0 7104 {
michael@0 7105 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
michael@0 7106 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
michael@0 7107
michael@0 7108 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
michael@0 7109 // FIXME: Ideally, if we already have a text run, we'd move it to be
michael@0 7110 // the uninflated text run.
michael@0 7111 ClearTextRun(nullptr, nsTextFrame::eInflated);
michael@0 7112 }
michael@0 7113
michael@0 7114 nsTextFrame* f;
michael@0 7115 gfxTextRun* lastTextRun = nullptr;
michael@0 7116 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
michael@0 7117 // in the flow are handled right here.
michael@0 7118 for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
michael@0 7119 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
michael@0 7120 // haven't set up textruns yet for f. Except in OOM situations,
michael@0 7121 // lastTextRun will only be null for the first text frame.
michael@0 7122 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
michael@0 7123 nsIFrame* lc;
michael@0 7124 if (aData->lineContainer &&
michael@0 7125 aData->lineContainer != (lc = FindLineContainer(f))) {
michael@0 7126 NS_ASSERTION(f != this, "wrong InlineMinWidthData container"
michael@0 7127 " for first continuation");
michael@0 7128 aData->line = nullptr;
michael@0 7129 aData->lineContainer = lc;
michael@0 7130 }
michael@0 7131
michael@0 7132 // This will process all the text frames that share the same textrun as f.
michael@0 7133 f->AddInlineMinWidthForFlow(aRenderingContext, aData, trtype);
michael@0 7134 lastTextRun = f->GetTextRun(trtype);
michael@0 7135 }
michael@0 7136 }
michael@0 7137 }
michael@0 7138
michael@0 7139 // XXX this doesn't handle characters shaped by line endings. We need to
michael@0 7140 // temporarily override the "current line ending" settings.
michael@0 7141 void
michael@0 7142 nsTextFrame::AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext,
michael@0 7143 nsIFrame::InlinePrefWidthData *aData,
michael@0 7144 TextRunType aTextRunType)
michael@0 7145 {
michael@0 7146 uint32_t flowEndInTextRun;
michael@0 7147 gfxContext* ctx = aRenderingContext->ThebesContext();
michael@0 7148 gfxSkipCharsIterator iter =
michael@0 7149 EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
michael@0 7150 aData->line, &flowEndInTextRun);
michael@0 7151 gfxTextRun *textRun = GetTextRun(aTextRunType);
michael@0 7152 if (!textRun)
michael@0 7153 return;
michael@0 7154
michael@0 7155 // Pass null for the line container. This will disable tab spacing, but that's
michael@0 7156 // OK since we can't really handle tabs for intrinsic sizing anyway.
michael@0 7157
michael@0 7158 const nsStyleText* textStyle = StyleText();
michael@0 7159 const nsTextFragment* frag = mContent->GetText();
michael@0 7160 PropertyProvider provider(textRun, textStyle, frag, this,
michael@0 7161 iter, INT32_MAX, nullptr, 0, aTextRunType);
michael@0 7162
michael@0 7163 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
michael@0 7164 bool preformatNewlines = textStyle->NewlineIsSignificant();
michael@0 7165 bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
michael@0 7166 gfxFloat tabWidth = -1;
michael@0 7167 uint32_t start =
michael@0 7168 FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
michael@0 7169
michael@0 7170 // XXX Should we consider hyphenation here?
michael@0 7171 // If newlines and tabs aren't preformatted, nothing to do inside
michael@0 7172 // the loop so make i skip to the end
michael@0 7173 uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
michael@0 7174 for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
michael@0 7175 bool preformattedNewline = false;
michael@0 7176 bool preformattedTab = false;
michael@0 7177 if (i < flowEndInTextRun) {
michael@0 7178 // XXXldb Shouldn't we be including the newline as part of the
michael@0 7179 // segment that it ends rather than part of the segment that it
michael@0 7180 // starts?
michael@0 7181 NS_ASSERTION(preformatNewlines || textStyle->NewlineIsDiscarded(),
michael@0 7182 "We can't be here unless newlines are hard breaks or are discarded");
michael@0 7183 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
michael@0 7184 preformattedTab = preformatTabs && textRun->CharIsTab(i);
michael@0 7185 if (!preformattedNewline && !preformattedTab) {
michael@0 7186 // we needn't break here (and it's not the end of the flow)
michael@0 7187 continue;
michael@0 7188 }
michael@0 7189 }
michael@0 7190
michael@0 7191 if (i > lineStart) {
michael@0 7192 nscoord width =
michael@0 7193 NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider));
michael@0 7194 aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
michael@0 7195
michael@0 7196 if (collapseWhitespace) {
michael@0 7197 uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
michael@0 7198 if (trimStart == start) {
michael@0 7199 // This is *all* trimmable whitespace, so whatever trailingWhitespace
michael@0 7200 // we saw previously is still trailing...
michael@0 7201 aData->trailingWhitespace += width;
michael@0 7202 } else {
michael@0 7203 // Some non-whitespace so the old trailingWhitespace is no longer trailing
michael@0 7204 aData->trailingWhitespace =
michael@0 7205 NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
michael@0 7206 }
michael@0 7207 } else {
michael@0 7208 aData->trailingWhitespace = 0;
michael@0 7209 }
michael@0 7210 }
michael@0 7211
michael@0 7212 if (preformattedTab) {
michael@0 7213 PropertyProvider::Spacing spacing;
michael@0 7214 provider.GetSpacing(i, 1, &spacing);
michael@0 7215 aData->currentLine += nscoord(spacing.mBefore);
michael@0 7216 gfxFloat afterTab =
michael@0 7217 AdvanceToNextTab(aData->currentLine, this,
michael@0 7218 textRun, &tabWidth);
michael@0 7219 aData->currentLine = nscoord(afterTab + spacing.mAfter);
michael@0 7220 lineStart = i + 1;
michael@0 7221 } else if (preformattedNewline) {
michael@0 7222 aData->ForceBreak(aRenderingContext);
michael@0 7223 lineStart = i;
michael@0 7224 }
michael@0 7225 }
michael@0 7226
michael@0 7227 // Check if we have collapsible whitespace at the end
michael@0 7228 if (start < flowEndInTextRun) {
michael@0 7229 aData->skipWhitespace =
michael@0 7230 IsTrimmableSpace(provider.GetFragment(),
michael@0 7231 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
michael@0 7232 textStyle);
michael@0 7233 }
michael@0 7234 }
michael@0 7235
michael@0 7236 // XXX Need to do something here to avoid incremental reflow bugs due to
michael@0 7237 // first-line and first-letter changing pref-width
michael@0 7238 /* virtual */ void
michael@0 7239 nsTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
michael@0 7240 nsIFrame::InlinePrefWidthData *aData)
michael@0 7241 {
michael@0 7242 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
michael@0 7243 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
michael@0 7244
michael@0 7245 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
michael@0 7246 // FIXME: Ideally, if we already have a text run, we'd move it to be
michael@0 7247 // the uninflated text run.
michael@0 7248 ClearTextRun(nullptr, nsTextFrame::eInflated);
michael@0 7249 }
michael@0 7250
michael@0 7251 nsTextFrame* f;
michael@0 7252 gfxTextRun* lastTextRun = nullptr;
michael@0 7253 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
michael@0 7254 // in the flow are handled right here.
michael@0 7255 for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
michael@0 7256 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
michael@0 7257 // haven't set up textruns yet for f. Except in OOM situations,
michael@0 7258 // lastTextRun will only be null for the first text frame.
michael@0 7259 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
michael@0 7260 nsIFrame* lc;
michael@0 7261 if (aData->lineContainer &&
michael@0 7262 aData->lineContainer != (lc = FindLineContainer(f))) {
michael@0 7263 NS_ASSERTION(f != this, "wrong InlinePrefWidthData container"
michael@0 7264 " for first continuation");
michael@0 7265 aData->line = nullptr;
michael@0 7266 aData->lineContainer = lc;
michael@0 7267 }
michael@0 7268
michael@0 7269 // This will process all the text frames that share the same textrun as f.
michael@0 7270 f->AddInlinePrefWidthForFlow(aRenderingContext, aData, trtype);
michael@0 7271 lastTextRun = f->GetTextRun(trtype);
michael@0 7272 }
michael@0 7273 }
michael@0 7274 }
michael@0 7275
michael@0 7276 /* virtual */ nsSize
michael@0 7277 nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext,
michael@0 7278 nsSize aCBSize, nscoord aAvailableWidth,
michael@0 7279 nsSize aMargin, nsSize aBorder, nsSize aPadding,
michael@0 7280 uint32_t aFlags)
michael@0 7281 {
michael@0 7282 // Inlines and text don't compute size before reflow.
michael@0 7283 return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
michael@0 7284 }
michael@0 7285
michael@0 7286 static nsRect
michael@0 7287 RoundOut(const gfxRect& aRect)
michael@0 7288 {
michael@0 7289 nsRect r;
michael@0 7290 r.x = NSToCoordFloor(aRect.X());
michael@0 7291 r.y = NSToCoordFloor(aRect.Y());
michael@0 7292 r.width = NSToCoordCeil(aRect.XMost()) - r.x;
michael@0 7293 r.height = NSToCoordCeil(aRect.YMost()) - r.y;
michael@0 7294 return r;
michael@0 7295 }
michael@0 7296
michael@0 7297 nsRect
michael@0 7298 nsTextFrame::ComputeTightBounds(gfxContext* aContext) const
michael@0 7299 {
michael@0 7300 if (StyleContext()->HasTextDecorationLines() ||
michael@0 7301 (GetStateBits() & TEXT_HYPHEN_BREAK)) {
michael@0 7302 // This is conservative, but OK.
michael@0 7303 return GetVisualOverflowRect();
michael@0 7304 }
michael@0 7305
michael@0 7306 gfxSkipCharsIterator iter =
michael@0 7307 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
michael@0 7308 if (!mTextRun)
michael@0 7309 return nsRect(0, 0, 0, 0);
michael@0 7310
michael@0 7311 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
michael@0 7312 nsTextFrame::eInflated);
michael@0 7313 // Trim trailing whitespace
michael@0 7314 provider.InitializeForDisplay(true);
michael@0 7315
michael@0 7316 gfxTextRun::Metrics metrics =
michael@0 7317 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
michael@0 7318 ComputeTransformedLength(provider),
michael@0 7319 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
michael@0 7320 aContext, &provider);
michael@0 7321 // mAscent should be the same as metrics.mAscent, but it's what we use to
michael@0 7322 // paint so that's the one we'll use.
michael@0 7323 return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
michael@0 7324 }
michael@0 7325
michael@0 7326 /* virtual */ nsresult
michael@0 7327 nsTextFrame::GetPrefWidthTightBounds(nsRenderingContext* aContext,
michael@0 7328 nscoord* aX,
michael@0 7329 nscoord* aXMost)
michael@0 7330 {
michael@0 7331 gfxSkipCharsIterator iter =
michael@0 7332 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
michael@0 7333 if (!mTextRun)
michael@0 7334 return NS_ERROR_FAILURE;
michael@0 7335
michael@0 7336 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
michael@0 7337 nsTextFrame::eInflated);
michael@0 7338 provider.InitializeForMeasure();
michael@0 7339
michael@0 7340 gfxTextRun::Metrics metrics =
michael@0 7341 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
michael@0 7342 ComputeTransformedLength(provider),
michael@0 7343 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
michael@0 7344 aContext->ThebesContext(), &provider);
michael@0 7345 // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
michael@0 7346 *aX = NSToCoordFloor(metrics.mBoundingBox.x);
michael@0 7347 *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
michael@0 7348
michael@0 7349 return NS_OK;
michael@0 7350 }
michael@0 7351
michael@0 7352 static bool
michael@0 7353 HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun,
michael@0 7354 int32_t aStartOffset, const gfxSkipCharsIterator& aIter)
michael@0 7355 {
michael@0 7356 if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
michael@0 7357 aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
michael@0 7358 return true;
michael@0 7359 }
michael@0 7360 if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY))
michael@0 7361 return false;
michael@0 7362 gfxSkipCharsIterator iter = aIter;
michael@0 7363 while (iter.GetOriginalOffset() > aStartOffset) {
michael@0 7364 iter.AdvanceOriginal(-1);
michael@0 7365 if (!iter.IsOriginalCharSkipped())
michael@0 7366 break;
michael@0 7367 if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
michael@0 7368 return true;
michael@0 7369 }
michael@0 7370 return false;
michael@0 7371 }
michael@0 7372
michael@0 7373 /**
michael@0 7374 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
michael@0 7375 * because their text has all been taken and reflowed by earlier frames.
michael@0 7376 */
michael@0 7377 static void
michael@0 7378 RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove)
michael@0 7379 {
michael@0 7380 NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly");
michael@0 7381 // We have to be careful here, because some RemoveFrame implementations
michael@0 7382 // remove and destroy not only the passed-in frame but also all its following
michael@0 7383 // in-flows (and sometimes all its following continuations in general). So
michael@0 7384 // we remove |f| and everything up to but not including firstToNotRemove from
michael@0 7385 // the flow first, to make sure that only the things we want destroyed are
michael@0 7386 // destroyed.
michael@0 7387
michael@0 7388 // This sadly duplicates some of the logic from
michael@0 7389 // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
michael@0 7390 // all of it, because we know that the prev-continuation links of
michael@0 7391 // firstToNotRemove and f are fluid, and non-null.
michael@0 7392 NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
michael@0 7393 aFirstToNotRemove->GetPrevInFlow() &&
michael@0 7394 aFirstToNotRemove->GetPrevInFlow() != nullptr,
michael@0 7395 "aFirstToNotRemove should have a fluid prev continuation");
michael@0 7396 NS_ASSERTION(aFrame->GetPrevContinuation() ==
michael@0 7397 aFrame->GetPrevInFlow() &&
michael@0 7398 aFrame->GetPrevInFlow() != nullptr,
michael@0 7399 "aFrame should have a fluid prev continuation");
michael@0 7400
michael@0 7401 nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
michael@0 7402 nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
michael@0 7403
michael@0 7404 for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
michael@0 7405 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
michael@0 7406 // f is going to be destroyed soon, after it is unlinked from the
michael@0 7407 // continuation chain. If its textrun is going to be destroyed we need to
michael@0 7408 // do it now, before we unlink the frames to remove from the flow,
michael@0 7409 // because DestroyFrom calls ClearTextRuns() and that will start at the
michael@0 7410 // first frame with the text run and walk the continuations.
michael@0 7411 if (f->IsInTextRunUserData()) {
michael@0 7412 f->ClearTextRuns();
michael@0 7413 } else {
michael@0 7414 f->DisconnectTextRuns();
michael@0 7415 }
michael@0 7416 }
michael@0 7417
michael@0 7418 prevContinuation->SetNextInFlow(aFirstToNotRemove);
michael@0 7419 aFirstToNotRemove->SetPrevInFlow(prevContinuation);
michael@0 7420
michael@0 7421 aFrame->SetPrevInFlow(nullptr);
michael@0 7422 lastRemoved->SetNextInFlow(nullptr);
michael@0 7423
michael@0 7424 nsIFrame* parent = aFrame->GetParent();
michael@0 7425 nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent);
michael@0 7426 if (parentBlock) {
michael@0 7427 // Manually call DoRemoveFrame so we can tell it that we're
michael@0 7428 // removing empty frames; this will keep it from blowing away
michael@0 7429 // text runs.
michael@0 7430 parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
michael@0 7431 } else {
michael@0 7432 // Just remove it normally; use kNoReflowPrincipalList to avoid posting
michael@0 7433 // new reflows.
michael@0 7434 parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
michael@0 7435 }
michael@0 7436 }
michael@0 7437
michael@0 7438 void
michael@0 7439 nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
michael@0 7440 uint32_t aSetLengthFlags)
michael@0 7441 {
michael@0 7442 mContentLengthHint = aLength;
michael@0 7443 int32_t end = GetContentOffset() + aLength;
michael@0 7444 nsTextFrame* f = static_cast<nsTextFrame*>(GetNextInFlow());
michael@0 7445 if (!f)
michael@0 7446 return;
michael@0 7447
michael@0 7448 // If our end offset is moving, then even if frames are not being pushed or
michael@0 7449 // pulled, content is moving to or from the next line and the next line
michael@0 7450 // must be reflowed.
michael@0 7451 // If the next-continuation is dirty, then we should dirty the next line now
michael@0 7452 // because we may have skipped doing it if we dirtied it in
michael@0 7453 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
michael@0 7454 // and ChildIsDirty to handle a range of frames would be worse.
michael@0 7455 if (aLineLayout &&
michael@0 7456 (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
michael@0 7457 aLineLayout->SetDirtyNextLine();
michael@0 7458 }
michael@0 7459
michael@0 7460 if (end < f->mContentOffset) {
michael@0 7461 // Our frame is shrinking. Give the text to our next in flow.
michael@0 7462 if (aLineLayout &&
michael@0 7463 HasSignificantTerminalNewline() &&
michael@0 7464 GetParent()->GetType() != nsGkAtoms::letterFrame &&
michael@0 7465 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
michael@0 7466 // Whatever text we hand to our next-in-flow will end up in a frame all of
michael@0 7467 // its own, since it ends in a forced linebreak. Might as well just put
michael@0 7468 // it in a separate frame now. This is important to prevent text run
michael@0 7469 // churn; if we did not do that, then we'd likely end up rebuilding
michael@0 7470 // textruns for all our following continuations.
michael@0 7471 // We skip this optimization when the parent is a first-letter frame
michael@0 7472 // because it doesn't deal well with more than one child frame.
michael@0 7473 // We also skip this optimization if we were called during bidi
michael@0 7474 // resolution, so as not to create a new frame which doesn't appear in
michael@0 7475 // the bidi resolver's list of frames
michael@0 7476 nsPresContext* presContext = PresContext();
michael@0 7477 nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()->
michael@0 7478 CreateContinuingFrame(presContext, this, GetParent());
michael@0 7479 nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
michael@0 7480 nsFrameList temp(next, next);
michael@0 7481 GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
michael@0 7482 f = next;
michael@0 7483 }
michael@0 7484
michael@0 7485 f->mContentOffset = end;
michael@0 7486 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
michael@0 7487 ClearTextRuns();
michael@0 7488 f->ClearTextRuns();
michael@0 7489 }
michael@0 7490 return;
michael@0 7491 }
michael@0 7492 // Our frame is growing. Take text from our in-flow(s).
michael@0 7493 // We can take text from frames in lines beyond just the next line.
michael@0 7494 // We don't dirty those lines. That's OK, because when we reflow
michael@0 7495 // our empty next-in-flow, it will take text from its next-in-flow and
michael@0 7496 // dirty that line.
michael@0 7497
michael@0 7498 // Note that in the process we may end up removing some frames from
michael@0 7499 // the flow if they end up empty.
michael@0 7500 nsTextFrame* framesToRemove = nullptr;
michael@0 7501 while (f && f->mContentOffset < end) {
michael@0 7502 f->mContentOffset = end;
michael@0 7503 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
michael@0 7504 ClearTextRuns();
michael@0 7505 f->ClearTextRuns();
michael@0 7506 }
michael@0 7507 nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextInFlow());
michael@0 7508 // Note: the "f->GetNextSibling() == next" check below is to restrict
michael@0 7509 // this optimization to the case where they are on the same child list.
michael@0 7510 // Otherwise we might remove the only child of a nsFirstLetterFrame
michael@0 7511 // for example and it can't handle that. See bug 597627 for details.
michael@0 7512 if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
michael@0 7513 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
michael@0 7514 // |f| is now empty. We may as well remove it, instead of copying all
michael@0 7515 // the text from |next| into it instead; the latter leads to use
michael@0 7516 // rebuilding textruns for all following continuations.
michael@0 7517 // We skip this optimization if we were called during bidi resolution,
michael@0 7518 // since the bidi resolver may try to handle the destroyed frame later
michael@0 7519 // and crash
michael@0 7520 if (!framesToRemove) {
michael@0 7521 // Remember that we have to remove this frame.
michael@0 7522 framesToRemove = f;
michael@0 7523 }
michael@0 7524 } else if (framesToRemove) {
michael@0 7525 RemoveEmptyInFlows(framesToRemove, f);
michael@0 7526 framesToRemove = nullptr;
michael@0 7527 }
michael@0 7528 f = next;
michael@0 7529 }
michael@0 7530 NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end),
michael@0 7531 "How did we exit the loop if we null out framesToRemove if "
michael@0 7532 "!next || next->mContentOffset > end ?");
michael@0 7533 if (framesToRemove) {
michael@0 7534 // We are guaranteed that we exited the loop with f not null, per the
michael@0 7535 // postcondition above
michael@0 7536 RemoveEmptyInFlows(framesToRemove, f);
michael@0 7537 }
michael@0 7538
michael@0 7539 #ifdef DEBUG
michael@0 7540 f = this;
michael@0 7541 int32_t iterations = 0;
michael@0 7542 while (f && iterations < 10) {
michael@0 7543 f->GetContentLength(); // Assert if negative length
michael@0 7544 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
michael@0 7545 ++iterations;
michael@0 7546 }
michael@0 7547 f = this;
michael@0 7548 iterations = 0;
michael@0 7549 while (f && iterations < 10) {
michael@0 7550 f->GetContentLength(); // Assert if negative length
michael@0 7551 f = static_cast<nsTextFrame*>(f->GetPrevContinuation());
michael@0 7552 ++iterations;
michael@0 7553 }
michael@0 7554 #endif
michael@0 7555 }
michael@0 7556
michael@0 7557 bool
michael@0 7558 nsTextFrame::IsFloatingFirstLetterChild() const
michael@0 7559 {
michael@0 7560 nsIFrame* frame = GetParent();
michael@0 7561 return frame && frame->IsFloating() &&
michael@0 7562 frame->GetType() == nsGkAtoms::letterFrame;
michael@0 7563 }
michael@0 7564
michael@0 7565 struct NewlineProperty {
michael@0 7566 int32_t mStartOffset;
michael@0 7567 // The offset of the first \n after mStartOffset, or -1 if there is none
michael@0 7568 int32_t mNewlineOffset;
michael@0 7569 };
michael@0 7570
michael@0 7571 nsresult
michael@0 7572 nsTextFrame::Reflow(nsPresContext* aPresContext,
michael@0 7573 nsHTMLReflowMetrics& aMetrics,
michael@0 7574 const nsHTMLReflowState& aReflowState,
michael@0 7575 nsReflowStatus& aStatus)
michael@0 7576 {
michael@0 7577 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
michael@0 7578 DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
michael@0 7579
michael@0 7580 // XXX If there's no line layout, we shouldn't even have created this
michael@0 7581 // frame. This may happen if, for example, this is text inside a table
michael@0 7582 // but not inside a cell. For now, just don't reflow.
michael@0 7583 if (!aReflowState.mLineLayout) {
michael@0 7584 ClearMetrics(aMetrics);
michael@0 7585 aStatus = NS_FRAME_COMPLETE;
michael@0 7586 return NS_OK;
michael@0 7587 }
michael@0 7588
michael@0 7589 ReflowText(*aReflowState.mLineLayout, aReflowState.AvailableWidth(),
michael@0 7590 aReflowState.rendContext, aMetrics, aStatus);
michael@0 7591
michael@0 7592 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
michael@0 7593 return NS_OK;
michael@0 7594 }
michael@0 7595
michael@0 7596 #ifdef ACCESSIBILITY
michael@0 7597 /**
michael@0 7598 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
michael@0 7599 */
michael@0 7600 class MOZ_STACK_CLASS ReflowTextA11yNotifier
michael@0 7601 {
michael@0 7602 public:
michael@0 7603 ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
michael@0 7604 mContent(aContent), mPresContext(aPresContext)
michael@0 7605 {
michael@0 7606 }
michael@0 7607 ~ReflowTextA11yNotifier()
michael@0 7608 {
michael@0 7609 nsAccessibilityService* accService = nsIPresShell::AccService();
michael@0 7610 if (accService) {
michael@0 7611 accService->UpdateText(mPresContext->PresShell(), mContent);
michael@0 7612 }
michael@0 7613 }
michael@0 7614 private:
michael@0 7615 ReflowTextA11yNotifier();
michael@0 7616 ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
michael@0 7617 ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
michael@0 7618
michael@0 7619 nsIContent* mContent;
michael@0 7620 nsPresContext* mPresContext;
michael@0 7621 };
michael@0 7622 #endif
michael@0 7623
michael@0 7624 void
michael@0 7625 nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
michael@0 7626 nsRenderingContext* aRenderingContext,
michael@0 7627 nsHTMLReflowMetrics& aMetrics,
michael@0 7628 nsReflowStatus& aStatus)
michael@0 7629 {
michael@0 7630 #ifdef NOISY_REFLOW
michael@0 7631 ListTag(stdout);
michael@0 7632 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
michael@0 7633 #endif
michael@0 7634
michael@0 7635 nsPresContext* presContext = PresContext();
michael@0 7636
michael@0 7637 #ifdef ACCESSIBILITY
michael@0 7638 // Schedule the update of accessible tree since rendered text might be changed.
michael@0 7639 ReflowTextA11yNotifier(presContext, mContent);
michael@0 7640 #endif
michael@0 7641
michael@0 7642 /////////////////////////////////////////////////////////////////////
michael@0 7643 // Set up flags and clear out state
michael@0 7644 /////////////////////////////////////////////////////////////////////
michael@0 7645
michael@0 7646 // Clear out the reflow state flags in mState. We also clear the whitespace
michael@0 7647 // flags because this can change whether the frame maps whitespace-only text
michael@0 7648 // or not.
michael@0 7649 RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
michael@0 7650
michael@0 7651 // Temporarily map all possible content while we construct our new textrun.
michael@0 7652 // so that when doing reflow our styles prevail over any part of the
michael@0 7653 // textrun we look at. Note that next-in-flows may be mapping the same
michael@0 7654 // content; gfxTextRun construction logic will ensure that we take priority.
michael@0 7655 int32_t maxContentLength = GetInFlowContentLength();
michael@0 7656
michael@0 7657 // We don't need to reflow if there is no content.
michael@0 7658 if (!maxContentLength) {
michael@0 7659 ClearMetrics(aMetrics);
michael@0 7660 aStatus = NS_FRAME_COMPLETE;
michael@0 7661 return;
michael@0 7662 }
michael@0 7663
michael@0 7664 #ifdef NOISY_BIDI
michael@0 7665 printf("Reflowed textframe\n");
michael@0 7666 #endif
michael@0 7667
michael@0 7668 const nsStyleText* textStyle = StyleText();
michael@0 7669
michael@0 7670 bool atStartOfLine = aLineLayout.LineAtStart();
michael@0 7671 if (atStartOfLine) {
michael@0 7672 AddStateBits(TEXT_START_OF_LINE);
michael@0 7673 }
michael@0 7674
michael@0 7675 uint32_t flowEndInTextRun;
michael@0 7676 nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
michael@0 7677 gfxContext* ctx = aRenderingContext->ThebesContext();
michael@0 7678 const nsTextFragment* frag = mContent->GetText();
michael@0 7679
michael@0 7680 // DOM offsets of the text range we need to measure, after trimming
michael@0 7681 // whitespace, restricting to first-letter, and restricting preformatted text
michael@0 7682 // to nearest newline
michael@0 7683 int32_t length = maxContentLength;
michael@0 7684 int32_t offset = GetContentOffset();
michael@0 7685
michael@0 7686 // Restrict preformatted text to the nearest newline
michael@0 7687 int32_t newLineOffset = -1; // this will be -1 or a content offset
michael@0 7688 int32_t contentNewLineOffset = -1;
michael@0 7689 // Pointer to the nsGkAtoms::newline set on this frame's element
michael@0 7690 NewlineProperty* cachedNewlineOffset = nullptr;
michael@0 7691 if (textStyle->NewlineIsSignificant()) {
michael@0 7692 cachedNewlineOffset =
michael@0 7693 static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline));
michael@0 7694 if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
michael@0 7695 (cachedNewlineOffset->mNewlineOffset == -1 ||
michael@0 7696 cachedNewlineOffset->mNewlineOffset >= offset)) {
michael@0 7697 contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
michael@0 7698 } else {
michael@0 7699 contentNewLineOffset = FindChar(frag, offset,
michael@0 7700 mContent->TextLength() - offset, '\n');
michael@0 7701 }
michael@0 7702 if (contentNewLineOffset < offset + length) {
michael@0 7703 /*
michael@0 7704 The new line offset could be outside this frame if the frame has been
michael@0 7705 split by bidi resolution. In that case we won't use it in this reflow
michael@0 7706 (newLineOffset will remain -1), but we will still cache it in mContent
michael@0 7707 */
michael@0 7708 newLineOffset = contentNewLineOffset;
michael@0 7709 }
michael@0 7710 if (newLineOffset >= 0) {
michael@0 7711 length = newLineOffset + 1 - offset;
michael@0 7712 }
michael@0 7713 }
michael@0 7714 if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
michael@0 7715 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
michael@0 7716 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
michael@0 7717 // newline if there is one.
michael@0 7718 int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
michael@0 7719 int32_t whitespaceCount =
michael@0 7720 GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
michael@0 7721 if (whitespaceCount) {
michael@0 7722 offset += whitespaceCount;
michael@0 7723 length -= whitespaceCount;
michael@0 7724 // Make sure this frame maps the trimmable whitespace.
michael@0 7725 if (MOZ_UNLIKELY(offset > GetContentEnd())) {
michael@0 7726 SetLength(offset - GetContentOffset(), &aLineLayout,
michael@0 7727 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
michael@0 7728 }
michael@0 7729 }
michael@0 7730 }
michael@0 7731
michael@0 7732 bool completedFirstLetter = false;
michael@0 7733 // Layout dependent styles are a problem because we need to reconstruct
michael@0 7734 // the gfxTextRun based on our layout.
michael@0 7735 if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
michael@0 7736 SetLength(maxContentLength, &aLineLayout,
michael@0 7737 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
michael@0 7738
michael@0 7739 if (aLineLayout.GetInFirstLetter()) {
michael@0 7740 // floating first-letter boundaries are significant in textrun
michael@0 7741 // construction, so clear the textrun out every time we hit a first-letter
michael@0 7742 // and have changed our length (which controls the first-letter boundary)
michael@0 7743 ClearTextRuns();
michael@0 7744 // Find the length of the first-letter. We need a textrun for this.
michael@0 7745 // REVIEW: maybe-bogus inflation should be ok (fixed below)
michael@0 7746 gfxSkipCharsIterator iter =
michael@0 7747 EnsureTextRun(nsTextFrame::eInflated, ctx,
michael@0 7748 lineContainer, aLineLayout.GetLine(),
michael@0 7749 &flowEndInTextRun);
michael@0 7750
michael@0 7751 if (mTextRun) {
michael@0 7752 int32_t firstLetterLength = length;
michael@0 7753 if (aLineLayout.GetFirstLetterStyleOK()) {
michael@0 7754 completedFirstLetter =
michael@0 7755 FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
michael@0 7756 if (newLineOffset >= 0) {
michael@0 7757 // Don't allow a preformatted newline to be part of a first-letter.
michael@0 7758 firstLetterLength = std::min(firstLetterLength, length - 1);
michael@0 7759 if (length == 1) {
michael@0 7760 // There is no text to be consumed by the first-letter before the
michael@0 7761 // preformatted newline. Note that the first letter is therefore
michael@0 7762 // complete (FindFirstLetterRange will have returned false).
michael@0 7763 completedFirstLetter = true;
michael@0 7764 }
michael@0 7765 }
michael@0 7766 } else {
michael@0 7767 // We're in a first-letter frame's first in flow, so if there
michael@0 7768 // was a first-letter, we'd be it. However, for one reason
michael@0 7769 // or another (e.g., preformatted line break before this text),
michael@0 7770 // we're not actually supposed to have first-letter style. So
michael@0 7771 // just make a zero-length first-letter.
michael@0 7772 firstLetterLength = 0;
michael@0 7773 completedFirstLetter = true;
michael@0 7774 }
michael@0 7775 length = firstLetterLength;
michael@0 7776 if (length) {
michael@0 7777 AddStateBits(TEXT_FIRST_LETTER);
michael@0 7778 }
michael@0 7779 // Change this frame's length to the first-letter length right now
michael@0 7780 // so that when we rebuild the textrun it will be built with the
michael@0 7781 // right first-letter boundary
michael@0 7782 SetLength(offset + length - GetContentOffset(), &aLineLayout,
michael@0 7783 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
michael@0 7784 // Ensure that the textrun will be rebuilt
michael@0 7785 ClearTextRuns();
michael@0 7786 }
michael@0 7787 }
michael@0 7788 }
michael@0 7789
michael@0 7790 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
michael@0 7791
michael@0 7792 if (!IsCurrentFontInflation(fontSizeInflation)) {
michael@0 7793 // FIXME: Ideally, if we already have a text run, we'd move it to be
michael@0 7794 // the uninflated text run.
michael@0 7795 ClearTextRun(nullptr, nsTextFrame::eInflated);
michael@0 7796 }
michael@0 7797
michael@0 7798 gfxSkipCharsIterator iter =
michael@0 7799 EnsureTextRun(nsTextFrame::eInflated, ctx,
michael@0 7800 lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
michael@0 7801
michael@0 7802 NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
michael@0 7803 "EnsureTextRun should have set font size inflation");
michael@0 7804
michael@0 7805 if (mTextRun && iter.GetOriginalEnd() < offset + length) {
michael@0 7806 // The textrun does not map enough text for this frame. This can happen
michael@0 7807 // when the textrun was ended in the middle of a text node because a
michael@0 7808 // preformatted newline was encountered, and prev-in-flow frames have
michael@0 7809 // consumed all the text of the textrun. We need a new textrun.
michael@0 7810 ClearTextRuns();
michael@0 7811 iter = EnsureTextRun(nsTextFrame::eInflated, ctx,
michael@0 7812 lineContainer, aLineLayout.GetLine(),
michael@0 7813 &flowEndInTextRun);
michael@0 7814 }
michael@0 7815
michael@0 7816 if (!mTextRun) {
michael@0 7817 ClearMetrics(aMetrics);
michael@0 7818 aStatus = NS_FRAME_COMPLETE;
michael@0 7819 return;
michael@0 7820 }
michael@0 7821
michael@0 7822 NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
michael@0 7823 <= mTextRun->GetLength(),
michael@0 7824 "Text run does not map enough text for our reflow");
michael@0 7825
michael@0 7826 /////////////////////////////////////////////////////////////////////
michael@0 7827 // See how much text should belong to this text frame, and measure it
michael@0 7828 /////////////////////////////////////////////////////////////////////
michael@0 7829
michael@0 7830 iter.SetOriginalOffset(offset);
michael@0 7831 nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ?
michael@0 7832 (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
michael@0 7833 lineContainer->GetUsedBorderAndPadding().left)
michael@0 7834 : -1;
michael@0 7835 PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
michael@0 7836 lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
michael@0 7837
michael@0 7838 uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
michael@0 7839
michael@0 7840 // The metrics for the text go in here
michael@0 7841 gfxTextRun::Metrics textMetrics;
michael@0 7842 gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ?
michael@0 7843 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS :
michael@0 7844 gfxFont::LOOSE_INK_EXTENTS;
michael@0 7845 NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
michael@0 7846 "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
michael@0 7847
michael@0 7848 int32_t limitLength = length;
michael@0 7849 int32_t forceBreak = aLineLayout.GetForcedBreakPosition(mContent);
michael@0 7850 bool forceBreakAfter = false;
michael@0 7851 if (forceBreak >= offset + length) {
michael@0 7852 forceBreakAfter = forceBreak == offset + length;
michael@0 7853 // The break is not within the text considered for this textframe.
michael@0 7854 forceBreak = -1;
michael@0 7855 }
michael@0 7856 if (forceBreak >= 0) {
michael@0 7857 limitLength = forceBreak - offset;
michael@0 7858 NS_ASSERTION(limitLength >= 0, "Weird break found!");
michael@0 7859 }
michael@0 7860 // This is the heart of text reflow right here! We don't know where
michael@0 7861 // to break, so we need to see how much text fits in the available width.
michael@0 7862 uint32_t transformedLength;
michael@0 7863 if (offset + limitLength >= int32_t(frag->GetLength())) {
michael@0 7864 NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
michael@0 7865 "Content offset/length out of bounds");
michael@0 7866 NS_ASSERTION(flowEndInTextRun >= transformedOffset,
michael@0 7867 "Negative flow length?");
michael@0 7868 transformedLength = flowEndInTextRun - transformedOffset;
michael@0 7869 } else {
michael@0 7870 // we're not looking at all the content, so we need to compute the
michael@0 7871 // length of the transformed substring we're looking at
michael@0 7872 gfxSkipCharsIterator iter(provider.GetStart());
michael@0 7873 iter.SetOriginalOffset(offset + limitLength);
michael@0 7874 transformedLength = iter.GetSkippedOffset() - transformedOffset;
michael@0 7875 }
michael@0 7876 uint32_t transformedLastBreak = 0;
michael@0 7877 bool usedHyphenation;
michael@0 7878 gfxFloat trimmedWidth = 0;
michael@0 7879 gfxFloat availWidth = aAvailableWidth;
michael@0 7880 bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
michael@0 7881 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
michael@0 7882 int32_t unusedOffset;
michael@0 7883 gfxBreakPriority breakPriority;
michael@0 7884 aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
michael@0 7885 uint32_t transformedCharsFit =
michael@0 7886 mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
michael@0 7887 (GetStateBits() & TEXT_START_OF_LINE) != 0,
michael@0 7888 availWidth,
michael@0 7889 &provider, !aLineLayout.LineIsBreakable(),
michael@0 7890 canTrimTrailingWhitespace ? &trimmedWidth : nullptr,
michael@0 7891 &textMetrics, boundingBoxType, ctx,
michael@0 7892 &usedHyphenation, &transformedLastBreak,
michael@0 7893 textStyle->WordCanWrap(this), &breakPriority);
michael@0 7894 if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
michael@0 7895 // If we're measuring a zero-length piece of text, update
michael@0 7896 // the height manually.
michael@0 7897 nsFontMetrics* fm = provider.GetFontMetrics();
michael@0 7898 if (fm) {
michael@0 7899 textMetrics.mAscent = gfxFloat(fm->MaxAscent());
michael@0 7900 textMetrics.mDescent = gfxFloat(fm->MaxDescent());
michael@0 7901 }
michael@0 7902 }
michael@0 7903 // The "end" iterator points to the first character after the string mapped
michael@0 7904 // by this frame. Basically, its original-string offset is offset+charsFit
michael@0 7905 // after we've computed charsFit.
michael@0 7906 gfxSkipCharsIterator end(provider.GetEndHint());
michael@0 7907 end.SetSkippedOffset(transformedOffset + transformedCharsFit);
michael@0 7908 int32_t charsFit = end.GetOriginalOffset() - offset;
michael@0 7909 if (offset + charsFit == newLineOffset) {
michael@0 7910 // We broke before a trailing preformatted '\n'. The newline should
michael@0 7911 // be assigned to this frame. Note that newLineOffset will be -1 if
michael@0 7912 // there was no preformatted newline, so we wouldn't get here in that
michael@0 7913 // case.
michael@0 7914 ++charsFit;
michael@0 7915 }
michael@0 7916 // That might have taken us beyond our assigned content range (because
michael@0 7917 // we might have advanced over some skipped chars that extend outside
michael@0 7918 // this frame), so get back in.
michael@0 7919 int32_t lastBreak = -1;
michael@0 7920 if (charsFit >= limitLength) {
michael@0 7921 charsFit = limitLength;
michael@0 7922 if (transformedLastBreak != UINT32_MAX) {
michael@0 7923 // lastBreak is needed.
michael@0 7924 // This may set lastBreak greater than 'length', but that's OK
michael@0 7925 lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
michael@0 7926 }
michael@0 7927 end.SetOriginalOffset(offset + charsFit);
michael@0 7928 // If we were forced to fit, and the break position is after a soft hyphen,
michael@0 7929 // note that this is a hyphenation break.
michael@0 7930 if ((forceBreak >= 0 || forceBreakAfter) &&
michael@0 7931 HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
michael@0 7932 usedHyphenation = true;
michael@0 7933 }
michael@0 7934 }
michael@0 7935 if (usedHyphenation) {
michael@0 7936 // Fix up metrics to include hyphen
michael@0 7937 AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx);
michael@0 7938 AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
michael@0 7939 }
michael@0 7940
michael@0 7941 gfxFloat trimmableWidth = 0;
michael@0 7942 bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
michael@0 7943 if (canTrimTrailingWhitespace) {
michael@0 7944 // Optimization: if we trimmed trailing whitespace, and we can be sure
michael@0 7945 // this frame will be at the end of the line, then leave it trimmed off.
michael@0 7946 // Otherwise we have to undo the trimming, in case we're not at the end of
michael@0 7947 // the line. (If we actually do end up at the end of the line, we'll have
michael@0 7948 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
michael@0 7949 // having to re-do it.)
michael@0 7950 if (brokeText ||
michael@0 7951 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
michael@0 7952 // We're definitely going to break so our trailing whitespace should
michael@0 7953 // definitely be trimmed. Record that we've already done it.
michael@0 7954 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
michael@0 7955 } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
michael@0 7956 // We might not be at the end of the line. (Note that even if this frame
michael@0 7957 // ends in breakable whitespace, it might not be at the end of the line
michael@0 7958 // because it might be followed by breakable, but preformatted, whitespace.)
michael@0 7959 // Undo the trimming.
michael@0 7960 textMetrics.mAdvanceWidth += trimmedWidth;
michael@0 7961 trimmableWidth = trimmedWidth;
michael@0 7962 if (mTextRun->IsRightToLeft()) {
michael@0 7963 // Space comes before text, so the bounding box is moved to the
michael@0 7964 // right by trimmdWidth
michael@0 7965 textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
michael@0 7966 }
michael@0 7967 }
michael@0 7968 }
michael@0 7969
michael@0 7970 if (!brokeText && lastBreak >= 0) {
michael@0 7971 // Since everything fit and no break was forced,
michael@0 7972 // record the last break opportunity
michael@0 7973 NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth,
michael@0 7974 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
michael@0 7975 aLineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, true, breakPriority);
michael@0 7976 }
michael@0 7977
michael@0 7978 int32_t contentLength = offset + charsFit - GetContentOffset();
michael@0 7979
michael@0 7980 /////////////////////////////////////////////////////////////////////
michael@0 7981 // Compute output metrics
michael@0 7982 /////////////////////////////////////////////////////////////////////
michael@0 7983
michael@0 7984 // first-letter frames should use the tight bounding box metrics for ascent/descent
michael@0 7985 // for good drop-cap effects
michael@0 7986 if (GetStateBits() & TEXT_FIRST_LETTER) {
michael@0 7987 textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
michael@0 7988 textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
michael@0 7989 }
michael@0 7990
michael@0 7991 // Setup metrics for caller
michael@0 7992 // Disallow negative widths
michael@0 7993 aMetrics.Width() = NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
michael@0 7994
michael@0 7995 if (transformedCharsFit == 0 && !usedHyphenation) {
michael@0 7996 aMetrics.SetTopAscent(0);
michael@0 7997 aMetrics.Height() = 0;
michael@0 7998 } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
michael@0 7999 // Use actual text metrics for floating first letter frame.
michael@0 8000 aMetrics.SetTopAscent(NSToCoordCeil(textMetrics.mAscent));
michael@0 8001 aMetrics.Height() = aMetrics.TopAscent() + NSToCoordCeil(textMetrics.mDescent);
michael@0 8002 } else {
michael@0 8003 // Otherwise, ascent should contain the overline drawable area.
michael@0 8004 // And also descent should contain the underline drawable area.
michael@0 8005 // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
michael@0 8006 nsFontMetrics* fm = provider.GetFontMetrics();
michael@0 8007 nscoord fontAscent = fm->MaxAscent();
michael@0 8008 nscoord fontDescent = fm->MaxDescent();
michael@0 8009 aMetrics.SetTopAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
michael@0 8010 nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
michael@0 8011 aMetrics.Height() = aMetrics.TopAscent() + descent;
michael@0 8012 }
michael@0 8013
michael@0 8014 NS_ASSERTION(aMetrics.TopAscent() >= 0, "Negative ascent???");
michael@0 8015 NS_ASSERTION(aMetrics.Height() - aMetrics.TopAscent() >= 0, "Negative descent???");
michael@0 8016
michael@0 8017 mAscent = aMetrics.TopAscent();
michael@0 8018
michael@0 8019 // Handle text that runs outside its normal bounds.
michael@0 8020 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
michael@0 8021 aMetrics.SetOverflowAreasToDesiredBounds();
michael@0 8022 aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
michael@0 8023
michael@0 8024 // When we have text decorations, we don't need to compute their overflow now
michael@0 8025 // because we're guaranteed to do it later
michael@0 8026 // (see nsLineLayout::RelativePositionFrames)
michael@0 8027 UnionAdditionalOverflow(presContext, *aLineLayout.LineContainerRS(),
michael@0 8028 provider, &aMetrics.VisualOverflow(), false);
michael@0 8029
michael@0 8030 /////////////////////////////////////////////////////////////////////
michael@0 8031 // Clean up, update state
michael@0 8032 /////////////////////////////////////////////////////////////////////
michael@0 8033
michael@0 8034 // If all our characters are discarded or collapsed, then trimmable width
michael@0 8035 // from the last textframe should be preserved. Otherwise the trimmable width
michael@0 8036 // from this textframe overrides. (Currently in CSS trimmable width can be
michael@0 8037 // at most one space so there's no way for trimmable width from a previous
michael@0 8038 // frame to accumulate with trimmable width from this frame.)
michael@0 8039 if (transformedCharsFit > 0) {
michael@0 8040 aLineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
michael@0 8041 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
michael@0 8042 }
michael@0 8043 if (charsFit > 0 && charsFit == length &&
michael@0 8044 textStyle->mHyphens != NS_STYLE_HYPHENS_NONE &&
michael@0 8045 HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
michael@0 8046 // Record a potential break after final soft hyphen
michael@0 8047 aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
michael@0 8048 textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth,
michael@0 8049 gfxBreakPriority::eNormalBreak);
michael@0 8050 }
michael@0 8051 bool breakAfter = forceBreakAfter;
michael@0 8052 // length == 0 means either the text is empty or it's all collapsed away
michael@0 8053 bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
michael@0 8054 if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
michael@0 8055 transformedOffset + transformedLength == mTextRun->GetLength() &&
michael@0 8056 (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) {
michael@0 8057 // We placed all the text in the textrun and we have a break opportunity at
michael@0 8058 // the end of the textrun. We need to record it because the following
michael@0 8059 // content may not care about nsLineBreaker.
michael@0 8060
michael@0 8061 // Note that because we didn't break, we can be sure that (thanks to the
michael@0 8062 // code up above) textMetrics.mAdvanceWidth includes the width of any
michael@0 8063 // trailing whitespace. So we need to subtract trimmableWidth here
michael@0 8064 // because if we did break at this point, that much width would be trimmed.
michael@0 8065 if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
michael@0 8066 breakAfter = true;
michael@0 8067 } else {
michael@0 8068 aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
michael@0 8069 true, gfxBreakPriority::eNormalBreak);
michael@0 8070 }
michael@0 8071 }
michael@0 8072
michael@0 8073 // Compute reflow status
michael@0 8074 aStatus = contentLength == maxContentLength
michael@0 8075 ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE;
michael@0 8076
michael@0 8077 if (charsFit == 0 && length > 0 && !usedHyphenation) {
michael@0 8078 // Couldn't place any text
michael@0 8079 aStatus = NS_INLINE_LINE_BREAK_BEFORE();
michael@0 8080 } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
michael@0 8081 // Ends in \n
michael@0 8082 aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
michael@0 8083 aLineLayout.SetLineEndsInBR(true);
michael@0 8084 } else if (breakAfter) {
michael@0 8085 aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
michael@0 8086 }
michael@0 8087 if (completedFirstLetter) {
michael@0 8088 aLineLayout.SetFirstLetterStyleOK(false);
michael@0 8089 aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE;
michael@0 8090 }
michael@0 8091
michael@0 8092 // Updated the cached NewlineProperty, or delete it.
michael@0 8093 if (contentLength < maxContentLength &&
michael@0 8094 textStyle->NewlineIsSignificant() &&
michael@0 8095 (contentNewLineOffset < 0 ||
michael@0 8096 mContentOffset + contentLength <= contentNewLineOffset)) {
michael@0 8097 if (!cachedNewlineOffset) {
michael@0 8098 cachedNewlineOffset = new NewlineProperty;
michael@0 8099 if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
michael@0 8100 nsINode::DeleteProperty<NewlineProperty>))) {
michael@0 8101 delete cachedNewlineOffset;
michael@0 8102 cachedNewlineOffset = nullptr;
michael@0 8103 }
michael@0 8104 }
michael@0 8105 if (cachedNewlineOffset) {
michael@0 8106 cachedNewlineOffset->mStartOffset = offset;
michael@0 8107 cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
michael@0 8108 }
michael@0 8109 } else if (cachedNewlineOffset) {
michael@0 8110 mContent->DeleteProperty(nsGkAtoms::newline);
michael@0 8111 }
michael@0 8112
michael@0 8113 // Compute space and letter counts for justification, if required
michael@0 8114 if (!textStyle->WhiteSpaceIsSignificant() &&
michael@0 8115 (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
michael@0 8116 lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) &&
michael@0 8117 !lineContainer->IsSVGText()) {
michael@0 8118 AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present.
michael@0 8119 // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
michael@0 8120 int32_t numJustifiableCharacters =
michael@0 8121 provider.ComputeJustifiableCharacters(offset, charsFit);
michael@0 8122
michael@0 8123 NS_ASSERTION(numJustifiableCharacters <= charsFit,
michael@0 8124 "Bad justifiable character count");
michael@0 8125 aLineLayout.SetTextJustificationWeights(numJustifiableCharacters,
michael@0 8126 charsFit - numJustifiableCharacters);
michael@0 8127 }
michael@0 8128
michael@0 8129 SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
michael@0 8130
michael@0 8131 InvalidateFrame();
michael@0 8132
michael@0 8133 #ifdef NOISY_REFLOW
michael@0 8134 ListTag(stdout);
michael@0 8135 printf(": desiredSize=%d,%d(b=%d) status=%x\n",
michael@0 8136 aMetrics.Width(), aMetrics.Height(), aMetrics.TopAscent(),
michael@0 8137 aStatus);
michael@0 8138 #endif
michael@0 8139 }
michael@0 8140
michael@0 8141 /* virtual */ bool
michael@0 8142 nsTextFrame::CanContinueTextRun() const
michael@0 8143 {
michael@0 8144 // We can continue a text run through a text frame
michael@0 8145 return true;
michael@0 8146 }
michael@0 8147
michael@0 8148 nsTextFrame::TrimOutput
michael@0 8149 nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC)
michael@0 8150 {
michael@0 8151 TrimOutput result;
michael@0 8152 result.mChanged = false;
michael@0 8153 result.mLastCharIsJustifiable = false;
michael@0 8154 result.mDeltaWidth = 0;
michael@0 8155
michael@0 8156 AddStateBits(TEXT_END_OF_LINE);
michael@0 8157
michael@0 8158 int32_t contentLength = GetContentLength();
michael@0 8159 if (!contentLength)
michael@0 8160 return result;
michael@0 8161
michael@0 8162 gfxContext* ctx = aRC->ThebesContext();
michael@0 8163 gfxSkipCharsIterator start =
michael@0 8164 EnsureTextRun(nsTextFrame::eInflated, ctx);
michael@0 8165 NS_ENSURE_TRUE(mTextRun, result);
michael@0 8166
michael@0 8167 uint32_t trimmedStart = start.GetSkippedOffset();
michael@0 8168
michael@0 8169 const nsTextFragment* frag = mContent->GetText();
michael@0 8170 TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
michael@0 8171 gfxSkipCharsIterator trimmedEndIter = start;
michael@0 8172 const nsStyleText* textStyle = StyleText();
michael@0 8173 gfxFloat delta = 0;
michael@0 8174 uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
michael@0 8175
michael@0 8176 if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
michael@0 8177 // We pre-trimmed this frame, so the last character is justifiable
michael@0 8178 result.mLastCharIsJustifiable = true;
michael@0 8179 } else if (trimmed.GetEnd() < GetContentEnd()) {
michael@0 8180 gfxSkipCharsIterator end = trimmedEndIter;
michael@0 8181 uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
michael@0 8182 if (trimmedEnd < endOffset) {
michael@0 8183 // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
michael@0 8184 // OK to pass null for the line container.
michael@0 8185 PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
michael@0 8186 nullptr, 0, nsTextFrame::eInflated);
michael@0 8187 delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
michael@0 8188 // non-compressed whitespace being skipped at end of line -> justifiable
michael@0 8189 // XXX should we actually *count* justifiable characters that should be
michael@0 8190 // removed from the overall count? I think so...
michael@0 8191 result.mLastCharIsJustifiable = true;
michael@0 8192 result.mChanged = true;
michael@0 8193 }
michael@0 8194 }
michael@0 8195
michael@0 8196 if (!result.mLastCharIsJustifiable &&
michael@0 8197 (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
michael@0 8198 // Check if any character in the last cluster is justifiable
michael@0 8199 PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
michael@0 8200 nullptr, 0, nsTextFrame::eInflated);
michael@0 8201 bool isCJK = IsChineseOrJapanese(this);
michael@0 8202 gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter);
michael@0 8203 provider.FindJustificationRange(&justificationStart, &justificationEnd);
michael@0 8204
michael@0 8205 for (int32_t i = justificationEnd.GetOriginalOffset();
michael@0 8206 i < trimmed.GetEnd(); ++i) {
michael@0 8207 if (IsJustifiableCharacter(frag, i, isCJK)) {
michael@0 8208 result.mLastCharIsJustifiable = true;
michael@0 8209 }
michael@0 8210 }
michael@0 8211 }
michael@0 8212
michael@0 8213 gfxFloat advanceDelta;
michael@0 8214 mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
michael@0 8215 (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
michael@0 8216 &advanceDelta, ctx);
michael@0 8217 if (advanceDelta != 0) {
michael@0 8218 result.mChanged = true;
michael@0 8219 }
michael@0 8220
michael@0 8221 // aDeltaWidth is *subtracted* from our width.
michael@0 8222 // If advanceDelta is positive then setting the line break made us longer,
michael@0 8223 // so aDeltaWidth could go negative.
michael@0 8224 result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
michael@0 8225 // If aDeltaWidth goes negative, that means this frame might not actually fit
michael@0 8226 // anymore!!! We need higher level line layout to recover somehow.
michael@0 8227 // If it's because the frame has a soft hyphen that is now being displayed,
michael@0 8228 // this should actually be OK, because our reflow recorded the break
michael@0 8229 // opportunity that allowed the soft hyphen to be used, and we wouldn't
michael@0 8230 // have recorded the opportunity unless the hyphen fit (or was the first
michael@0 8231 // opportunity on the line).
michael@0 8232 // Otherwise this can/ really only happen when we have glyphs with special
michael@0 8233 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
michael@0 8234 // do it because that would mean we broke inside this textrun, and
michael@0 8235 // BreakAndMeasureText should make sure the resulting shaped substring fits.
michael@0 8236 // Maybe if we passed a maxTextLength? But that only happens at direction
michael@0 8237 // changes (so we wouldn't kern across the boundary) or for first-letter
michael@0 8238 // (which always fits because it starts the line!).
michael@0 8239 NS_WARN_IF_FALSE(result.mDeltaWidth >= 0,
michael@0 8240 "Negative deltawidth, something odd is happening");
michael@0 8241
michael@0 8242 #ifdef NOISY_TRIM
michael@0 8243 ListTag(stdout);
michael@0 8244 printf(": trim => %d\n", result.mDeltaWidth);
michael@0 8245 #endif
michael@0 8246 return result;
michael@0 8247 }
michael@0 8248
michael@0 8249 nsOverflowAreas
michael@0 8250 nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState)
michael@0 8251 {
michael@0 8252 nsRect bounds(nsPoint(0, 0), GetSize());
michael@0 8253 nsOverflowAreas result(bounds, bounds);
michael@0 8254
michael@0 8255 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 8256 if (!mTextRun)
michael@0 8257 return result;
michael@0 8258
michael@0 8259 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
michael@0 8260 provider.InitializeForDisplay(true);
michael@0 8261
michael@0 8262 gfxTextRun::Metrics textMetrics =
michael@0 8263 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
michael@0 8264 ComputeTransformedLength(provider),
michael@0 8265 gfxFont::LOOSE_INK_EXTENTS, nullptr,
michael@0 8266 &provider);
michael@0 8267 nsRect &vis = result.VisualOverflow();
michael@0 8268 vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent));
michael@0 8269 UnionAdditionalOverflow(PresContext(), aBlockReflowState, provider,
michael@0 8270 &vis, true);
michael@0 8271 return result;
michael@0 8272 }
michael@0 8273 static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
michael@0 8274 uint32_t aSkippedOffset, char16_t aChar)
michael@0 8275 {
michael@0 8276 if (aChar == '\n') {
michael@0 8277 return aStyle->NewlineIsSignificant() || aStyle->NewlineIsDiscarded() ?
michael@0 8278 aChar : ' ';
michael@0 8279 }
michael@0 8280 switch (aStyle->mTextTransform) {
michael@0 8281 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
michael@0 8282 aChar = ToLowerCase(aChar);
michael@0 8283 break;
michael@0 8284 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
michael@0 8285 aChar = ToUpperCase(aChar);
michael@0 8286 break;
michael@0 8287 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
michael@0 8288 if (aTextRun->CanBreakLineBefore(aSkippedOffset)) {
michael@0 8289 aChar = ToTitleCase(aChar);
michael@0 8290 }
michael@0 8291 break;
michael@0 8292 }
michael@0 8293
michael@0 8294 return aChar;
michael@0 8295 }
michael@0 8296
michael@0 8297 nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString,
michael@0 8298 gfxSkipChars* aSkipChars,
michael@0 8299 gfxSkipCharsIterator* aSkipIter,
michael@0 8300 uint32_t aSkippedStartOffset,
michael@0 8301 uint32_t aSkippedMaxLength)
michael@0 8302 {
michael@0 8303 // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
michael@0 8304 gfxSkipChars skipChars;
michael@0 8305 nsTextFrame* textFrame;
michael@0 8306 const nsTextFragment* textFrag = mContent->GetText();
michael@0 8307 uint32_t keptCharsLength = 0;
michael@0 8308 uint32_t validCharsLength = 0;
michael@0 8309
michael@0 8310 // Build skipChars and copy text, for each text frame in this continuation block
michael@0 8311 for (textFrame = this; textFrame;
michael@0 8312 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
michael@0 8313 // For each text frame continuation in this block ...
michael@0 8314
michael@0 8315 if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
michael@0 8316 // We don't trust dirty frames, expecially when computing rendered text.
michael@0 8317 break;
michael@0 8318 }
michael@0 8319
michael@0 8320 // Ensure the text run and grab the gfxSkipCharsIterator for it
michael@0 8321 gfxSkipCharsIterator iter =
michael@0 8322 textFrame->EnsureTextRun(nsTextFrame::eInflated);
michael@0 8323 if (!textFrame->mTextRun)
michael@0 8324 return NS_ERROR_FAILURE;
michael@0 8325
michael@0 8326 // Skip to the start of the text run, past ignored chars at start of line
michael@0 8327 // XXX In the future we may decide to trim extra spaces before a hard line
michael@0 8328 // break, in which case we need to accurately detect those sitations and
michael@0 8329 // call GetTrimmedOffsets() with true to trim whitespace at the line's end
michael@0 8330 TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false);
michael@0 8331 int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset;
michael@0 8332 if (startOfLineSkipChars > 0) {
michael@0 8333 skipChars.SkipChars(startOfLineSkipChars);
michael@0 8334 iter.SetOriginalOffset(trimmedContentOffsets.mStart);
michael@0 8335 }
michael@0 8336
michael@0 8337 // Keep and copy the appropriate chars withing the caller's requested range
michael@0 8338 const nsStyleText* textStyle = textFrame->StyleText();
michael@0 8339 while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() &&
michael@0 8340 keptCharsLength < aSkippedMaxLength) {
michael@0 8341 // For each original char from content text
michael@0 8342 if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) {
michael@0 8343 skipChars.SkipChar();
michael@0 8344 } else {
michael@0 8345 ++keptCharsLength;
michael@0 8346 skipChars.KeepChar();
michael@0 8347 if (aAppendToString) {
michael@0 8348 aAppendToString->Append(
michael@0 8349 TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(),
michael@0 8350 textFrag->CharAt(iter.GetOriginalOffset())));
michael@0 8351 }
michael@0 8352 }
michael@0 8353 iter.AdvanceOriginal(1);
michael@0 8354 }
michael@0 8355 if (keptCharsLength >= aSkippedMaxLength) {
michael@0 8356 break; // Already past the end, don't build string or gfxSkipCharsIter anymore
michael@0 8357 }
michael@0 8358 }
michael@0 8359
michael@0 8360 if (aSkipChars) {
michael@0 8361 aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars
michael@0 8362 if (aSkipIter) {
michael@0 8363 // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
michael@0 8364 // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars.
michael@0 8365 *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
michael@0 8366 }
michael@0 8367 }
michael@0 8368
michael@0 8369 return NS_OK;
michael@0 8370 }
michael@0 8371
michael@0 8372 nsIAtom*
michael@0 8373 nsTextFrame::GetType() const
michael@0 8374 {
michael@0 8375 return nsGkAtoms::textFrame;
michael@0 8376 }
michael@0 8377
michael@0 8378 /* virtual */ bool
michael@0 8379 nsTextFrame::IsEmpty()
michael@0 8380 {
michael@0 8381 NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
michael@0 8382 !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
michael@0 8383 "Invalid state");
michael@0 8384
michael@0 8385 // XXXldb Should this check compatibility mode as well???
michael@0 8386 const nsStyleText* textStyle = StyleText();
michael@0 8387 if (textStyle->WhiteSpaceIsSignificant() &&
michael@0 8388 textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES) {
michael@0 8389 // XXX shouldn't we return true if the length is zero?
michael@0 8390 return false;
michael@0 8391 }
michael@0 8392
michael@0 8393 if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
michael@0 8394 return false;
michael@0 8395 }
michael@0 8396
michael@0 8397 if (mState & TEXT_IS_ONLY_WHITESPACE) {
michael@0 8398 return true;
michael@0 8399 }
michael@0 8400
michael@0 8401 bool isEmpty =
michael@0 8402 textStyle->mWhiteSpace == NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES ?
michael@0 8403 IsAllNewlines(mContent->GetText()) :
michael@0 8404 IsAllWhitespace(mContent->GetText(),
michael@0 8405 textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE);
michael@0 8406 mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
michael@0 8407 return isEmpty;
michael@0 8408 }
michael@0 8409
michael@0 8410 #ifdef DEBUG_FRAME_DUMP
michael@0 8411 // Translate the mapped content into a string that's printable
michael@0 8412 void
michael@0 8413 nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const
michael@0 8414 {
michael@0 8415 // Get the frames text content
michael@0 8416 const nsTextFragment* frag = mContent->GetText();
michael@0 8417 if (!frag) {
michael@0 8418 return;
michael@0 8419 }
michael@0 8420
michael@0 8421 // Compute the total length of the text content.
michael@0 8422 *aTotalContentLength = frag->GetLength();
michael@0 8423
michael@0 8424 int32_t contentLength = GetContentLength();
michael@0 8425 // Set current fragment and current fragment offset
michael@0 8426 if (0 == contentLength) {
michael@0 8427 return;
michael@0 8428 }
michael@0 8429 int32_t fragOffset = GetContentOffset();
michael@0 8430 int32_t n = fragOffset + contentLength;
michael@0 8431 while (fragOffset < n) {
michael@0 8432 char16_t ch = frag->CharAt(fragOffset++);
michael@0 8433 if (ch == '\r') {
michael@0 8434 aBuf.AppendLiteral("\\r");
michael@0 8435 } else if (ch == '\n') {
michael@0 8436 aBuf.AppendLiteral("\\n");
michael@0 8437 } else if (ch == '\t') {
michael@0 8438 aBuf.AppendLiteral("\\t");
michael@0 8439 } else if ((ch < ' ') || (ch >= 127)) {
michael@0 8440 aBuf.Append(nsPrintfCString("\\u%04x", ch));
michael@0 8441 } else {
michael@0 8442 aBuf.Append(ch);
michael@0 8443 }
michael@0 8444 }
michael@0 8445 }
michael@0 8446
michael@0 8447 nsresult
michael@0 8448 nsTextFrame::GetFrameName(nsAString& aResult) const
michael@0 8449 {
michael@0 8450 MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
michael@0 8451 int32_t totalContentLength;
michael@0 8452 nsAutoCString tmp;
michael@0 8453 ToCString(tmp, &totalContentLength);
michael@0 8454 tmp.SetLength(std::min(tmp.Length(), 50u));
michael@0 8455 aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
michael@0 8456 return NS_OK;
michael@0 8457 }
michael@0 8458
michael@0 8459 void
michael@0 8460 nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
michael@0 8461 {
michael@0 8462 nsCString str;
michael@0 8463 ListGeneric(str, aPrefix, aFlags);
michael@0 8464
michael@0 8465 str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
michael@0 8466
michael@0 8467 // Output the first/last content offset and prev/next in flow info
michael@0 8468 bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
michael@0 8469 str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
michael@0 8470 isComplete ? 'T':'F');
michael@0 8471
michael@0 8472 if (IsSelected()) {
michael@0 8473 str += " SELECTED";
michael@0 8474 }
michael@0 8475 fprintf_stderr(out, "%s\n", str.get());
michael@0 8476 }
michael@0 8477 #endif
michael@0 8478
michael@0 8479 #ifdef DEBUG
michael@0 8480 nsFrameState
michael@0 8481 nsTextFrame::GetDebugStateBits() const
michael@0 8482 {
michael@0 8483 // mask out our emptystate flags; those are just caches
michael@0 8484 return nsFrame::GetDebugStateBits() &
michael@0 8485 ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS);
michael@0 8486 }
michael@0 8487 #endif
michael@0 8488
michael@0 8489 void
michael@0 8490 nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
michael@0 8491 {
michael@0 8492 AddStateBits(NS_FRAME_IS_BIDI);
michael@0 8493 mContent->DeleteProperty(nsGkAtoms::flowlength);
michael@0 8494
michael@0 8495 /*
michael@0 8496 * After Bidi resolution we may need to reassign text runs.
michael@0 8497 * This is called during bidi resolution from the block container, so we
michael@0 8498 * shouldn't be holding a local reference to a textrun anywhere.
michael@0 8499 */
michael@0 8500 ClearTextRuns();
michael@0 8501
michael@0 8502 nsTextFrame* prev = static_cast<nsTextFrame*>(GetPrevContinuation());
michael@0 8503 if (prev) {
michael@0 8504 // the bidi resolver can be very evil when columns/pages are involved. Don't
michael@0 8505 // let it violate our invariants.
michael@0 8506 int32_t prevOffset = prev->GetContentOffset();
michael@0 8507 aStart = std::max(aStart, prevOffset);
michael@0 8508 aEnd = std::max(aEnd, prevOffset);
michael@0 8509 prev->ClearTextRuns();
michael@0 8510 }
michael@0 8511
michael@0 8512 mContentOffset = aStart;
michael@0 8513 SetLength(aEnd - aStart, nullptr, 0);
michael@0 8514
michael@0 8515 /**
michael@0 8516 * After inserting text the caret Bidi level must be set to the level of the
michael@0 8517 * inserted text.This is difficult, because we cannot know what the level is
michael@0 8518 * until after the Bidi algorithm is applied to the whole paragraph.
michael@0 8519 *
michael@0 8520 * So we set the caret Bidi level to UNDEFINED here, and the caret code will
michael@0 8521 * set it correctly later
michael@0 8522 */
michael@0 8523 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
michael@0 8524 if (frameSelection) {
michael@0 8525 frameSelection->UndefineCaretBidiLevel();
michael@0 8526 }
michael@0 8527 }
michael@0 8528
michael@0 8529 /**
michael@0 8530 * @return true if this text frame ends with a newline character. It should return
michael@0 8531 * false if it is not a text frame.
michael@0 8532 */
michael@0 8533 bool
michael@0 8534 nsTextFrame::HasSignificantTerminalNewline() const
michael@0 8535 {
michael@0 8536 return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant();
michael@0 8537 }
michael@0 8538
michael@0 8539 bool
michael@0 8540 nsTextFrame::IsAtEndOfLine() const
michael@0 8541 {
michael@0 8542 return (GetStateBits() & TEXT_END_OF_LINE) != 0;
michael@0 8543 }
michael@0 8544
michael@0 8545 nscoord
michael@0 8546 nsTextFrame::GetBaseline() const
michael@0 8547 {
michael@0 8548 return mAscent;
michael@0 8549 }
michael@0 8550
michael@0 8551 bool
michael@0 8552 nsTextFrame::HasAnyNoncollapsedCharacters()
michael@0 8553 {
michael@0 8554 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
michael@0 8555 int32_t offset = GetContentOffset(),
michael@0 8556 offsetEnd = GetContentEnd();
michael@0 8557 int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
michael@0 8558 int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
michael@0 8559 return skippedOffset != skippedOffsetEnd;
michael@0 8560 }

mercurial