1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/generic/nsTextFrame.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,8560 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +/* rendering object for textual content of elements */ 1.10 + 1.11 +#include "nsTextFrame.h" 1.12 + 1.13 +#include "mozilla/Attributes.h" 1.14 +#include "mozilla/DebugOnly.h" 1.15 +#include "mozilla/Likely.h" 1.16 +#include "mozilla/MathAlgorithms.h" 1.17 +#include "mozilla/TextEvents.h" 1.18 + 1.19 +#include "nsCOMPtr.h" 1.20 +#include "nsBlockFrame.h" 1.21 +#include "nsCRT.h" 1.22 +#include "nsSplittableFrame.h" 1.23 +#include "nsLineLayout.h" 1.24 +#include "nsString.h" 1.25 +#include "nsUnicharUtils.h" 1.26 +#include "nsPresContext.h" 1.27 +#include "nsIContent.h" 1.28 +#include "nsStyleConsts.h" 1.29 +#include "nsStyleContext.h" 1.30 +#include "nsStyleStruct.h" 1.31 +#include "nsStyleStructInlines.h" 1.32 +#include "SVGTextFrame.h" 1.33 +#include "nsCoord.h" 1.34 +#include "nsRenderingContext.h" 1.35 +#include "nsIPresShell.h" 1.36 +#include "nsTArray.h" 1.37 +#include "nsCSSPseudoElements.h" 1.38 +#include "nsCSSFrameConstructor.h" 1.39 +#include "nsCompatibility.h" 1.40 +#include "nsCSSColorUtils.h" 1.41 +#include "nsLayoutUtils.h" 1.42 +#include "nsDisplayList.h" 1.43 +#include "nsFrame.h" 1.44 +#include "nsIMathMLFrame.h" 1.45 +#include "nsPlaceholderFrame.h" 1.46 +#include "nsTextFrameUtils.h" 1.47 +#include "nsTextRunTransformations.h" 1.48 +#include "MathMLTextRunFactory.h" 1.49 +#include "nsExpirationTracker.h" 1.50 +#include "nsUnicodeProperties.h" 1.51 + 1.52 +#include "nsTextFragment.h" 1.53 +#include "nsGkAtoms.h" 1.54 +#include "nsFrameSelection.h" 1.55 +#include "nsRange.h" 1.56 +#include "nsCSSRendering.h" 1.57 +#include "nsContentUtils.h" 1.58 +#include "nsLineBreaker.h" 1.59 +#include "nsIWordBreaker.h" 1.60 +#include "nsGenericDOMDataNode.h" 1.61 +#include "nsIFrameInlines.h" 1.62 + 1.63 +#include <algorithm> 1.64 +#ifdef ACCESSIBILITY 1.65 +#include "nsAccessibilityService.h" 1.66 +#endif 1.67 +#include "nsAutoPtr.h" 1.68 + 1.69 +#include "nsPrintfCString.h" 1.70 + 1.71 +#include "gfxFont.h" 1.72 +#include "gfxContext.h" 1.73 + 1.74 +#include "mozilla/dom/Element.h" 1.75 +#include "mozilla/LookAndFeel.h" 1.76 + 1.77 +#include "GeckoProfiler.h" 1.78 + 1.79 +#ifdef DEBUG 1.80 +#undef NOISY_REFLOW 1.81 +#undef NOISY_TRIM 1.82 +#else 1.83 +#undef NOISY_REFLOW 1.84 +#undef NOISY_TRIM 1.85 +#endif 1.86 + 1.87 +#ifdef DrawText 1.88 +#undef DrawText 1.89 +#endif 1.90 + 1.91 +using namespace mozilla; 1.92 +using namespace mozilla::dom; 1.93 + 1.94 +struct TabWidth { 1.95 + TabWidth(uint32_t aOffset, uint32_t aWidth) 1.96 + : mOffset(aOffset), mWidth(float(aWidth)) 1.97 + { } 1.98 + 1.99 + uint32_t mOffset; // DOM offset relative to the current frame's offset. 1.100 + float mWidth; // extra space to be added at this position (in app units) 1.101 +}; 1.102 + 1.103 +struct TabWidthStore { 1.104 + TabWidthStore(int32_t aValidForContentOffset) 1.105 + : mLimit(0) 1.106 + , mValidForContentOffset(aValidForContentOffset) 1.107 + { } 1.108 + 1.109 + // Apply tab widths to the aSpacing array, which corresponds to characters 1.110 + // beginning at aOffset and has length aLength. (Width records outside this 1.111 + // range will be ignored.) 1.112 + void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing, 1.113 + uint32_t aOffset, uint32_t aLength); 1.114 + 1.115 + // Offset up to which tabs have been measured; positions beyond this have not 1.116 + // been calculated yet but may be appended if needed later. It's a DOM 1.117 + // offset relative to the current frame's offset. 1.118 + uint32_t mLimit; 1.119 + 1.120 + // Need to recalc tab offsets if frame content offset differs from this. 1.121 + int32_t mValidForContentOffset; 1.122 + 1.123 + // A TabWidth record for each tab character measured so far. 1.124 + nsTArray<TabWidth> mWidths; 1.125 +}; 1.126 + 1.127 +void 1.128 +TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing, 1.129 + uint32_t aOffset, uint32_t aLength) 1.130 +{ 1.131 + uint32_t i = 0, len = mWidths.Length(); 1.132 + 1.133 + // If aOffset is non-zero, do a binary search to find where to start 1.134 + // processing the tab widths, in case the list is really long. (See bug 1.135 + // 953247.) 1.136 + // We need to start from the first entry where mOffset >= aOffset. 1.137 + if (aOffset > 0) { 1.138 + uint32_t lo = 0, hi = len; 1.139 + while (lo < hi) { 1.140 + i = (lo + hi) / 2; 1.141 + const TabWidth& tw = mWidths[i]; 1.142 + if (tw.mOffset < aOffset) { 1.143 + // mWidths[i] precedes the target range; new search range 1.144 + // will be [i+1, hi) 1.145 + lo = ++i; 1.146 + continue; 1.147 + } 1.148 + if (tw.mOffset > aOffset) { 1.149 + // mWidths[i] is within (or beyond) the target range; 1.150 + // new search range is [lo, i). If it turns out that 1.151 + // mWidths[i] was the first entry within the range, 1.152 + // we'll never move hi any further, and end up exiting 1.153 + // when i == lo == this value of hi. 1.154 + hi = i; 1.155 + continue; 1.156 + } 1.157 + // Found an exact match for aOffset, so end search now 1.158 + break; 1.159 + } 1.160 + } 1.161 + 1.162 + uint32_t limit = aOffset + aLength; 1.163 + while (i < len) { 1.164 + const TabWidth& tw = mWidths[i]; 1.165 + if (tw.mOffset >= limit) { 1.166 + break; 1.167 + } 1.168 + aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth; 1.169 + i++; 1.170 + } 1.171 +} 1.172 + 1.173 +static void DestroyTabWidth(void* aPropertyValue) 1.174 +{ 1.175 + delete static_cast<TabWidthStore*>(aPropertyValue); 1.176 +} 1.177 + 1.178 +NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth) 1.179 + 1.180 +NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr) 1.181 + 1.182 +// text runs are destroyed by the text run cache 1.183 +NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr) 1.184 + 1.185 +NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr) 1.186 + 1.187 +class GlyphObserver : public gfxFont::GlyphChangeObserver { 1.188 +public: 1.189 + GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame) 1.190 + : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {} 1.191 + virtual void NotifyGlyphsChanged() MOZ_OVERRIDE; 1.192 +private: 1.193 + nsTextFrame* mFrame; 1.194 +}; 1.195 + 1.196 +static void DestroyGlyphObserverList(void* aPropertyValue) 1.197 +{ 1.198 + delete static_cast<nsTArray<nsAutoPtr<GlyphObserver> >*>(aPropertyValue); 1.199 +} 1.200 + 1.201 +/** 1.202 + * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that 1.203 + * have potentially-animated glyphs. 1.204 + * The only reason this list is in a property is to automatically destroy the 1.205 + * list when the frame is deleted, unregistering the observers. 1.206 + */ 1.207 +NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList); 1.208 + 1.209 +#define TEXT_REFLOW_FLAGS \ 1.210 + (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \ 1.211 + TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \ 1.212 + TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED) 1.213 + 1.214 +#define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \ 1.215 + TEXT_ISNOT_ONLY_WHITESPACE) 1.216 + 1.217 +/* 1.218 + * Some general notes 1.219 + * 1.220 + * Text frames delegate work to gfxTextRun objects. The gfxTextRun object 1.221 + * transforms text to positioned glyphs. It can report the geometry of the 1.222 + * glyphs and paint them. Text frames configure gfxTextRuns by providing text, 1.223 + * spacing, language, and other information. 1.224 + * 1.225 + * A gfxTextRun can cover more than one DOM text node. This is necessary to 1.226 + * get kerning, ligatures and shaping for text that spans multiple text nodes 1.227 + * but is all the same font. The userdata for a gfxTextRun object is a 1.228 + * TextRunUserData* or an nsIFrame*. 1.229 + * 1.230 + * We go to considerable effort to make sure things work even if in-flow 1.231 + * siblings have different style contexts (i.e., first-letter and first-line). 1.232 + * 1.233 + * Our convention is that unsigned integer character offsets are offsets into 1.234 + * the transformed string. Signed integer character offsets are offsets into 1.235 + * the DOM string. 1.236 + * 1.237 + * XXX currently we don't handle hyphenated breaks between text frames where the 1.238 + * hyphen occurs at the end of the first text frame, e.g. 1.239 + * <b>Kit­</b>ty 1.240 + */ 1.241 + 1.242 +/** 1.243 + * We use an array of these objects to record which text frames 1.244 + * are associated with the textrun. mStartFrame is the start of a list of 1.245 + * text frames. Some sequence of its continuations are covered by the textrun. 1.246 + * A content textnode can have at most one TextRunMappedFlow associated with it 1.247 + * for a given textrun. 1.248 + * 1.249 + * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain 1.250 + * the offset into the before-transformation text of the textrun. It can be 1.251 + * positive (when a text node starts in the middle of a text run) or 1.252 + * negative (when a text run starts in the middle of a text node). Of course 1.253 + * it can also be zero. 1.254 + */ 1.255 +struct TextRunMappedFlow { 1.256 + nsTextFrame* mStartFrame; 1.257 + int32_t mDOMOffsetToBeforeTransformOffset; 1.258 + // The text mapped starts at mStartFrame->GetContentOffset() and is this long 1.259 + uint32_t mContentLength; 1.260 +}; 1.261 + 1.262 +/** 1.263 + * This is our user data for the textrun, when textRun->GetFlags() does not 1.264 + * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is 1.265 + * just one flow, the textrun's user data pointer is a pointer to mStartFrame 1.266 + * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength 1.267 + * is the length of the text node. 1.268 + */ 1.269 +struct TextRunUserData { 1.270 + TextRunMappedFlow* mMappedFlows; 1.271 + uint32_t mMappedFlowCount; 1.272 + uint32_t mLastFlowIndex; 1.273 +}; 1.274 + 1.275 +/** 1.276 + * This helper object computes colors used for painting, and also IME 1.277 + * underline information. The data is computed lazily and cached as necessary. 1.278 + * These live for just the duration of one paint operation. 1.279 + */ 1.280 +class nsTextPaintStyle { 1.281 +public: 1.282 + nsTextPaintStyle(nsTextFrame* aFrame); 1.283 + 1.284 + void SetResolveColors(bool aResolveColors) { 1.285 + NS_ASSERTION(mFrame->IsSVGText() || aResolveColors, 1.286 + "must resolve colors is frame is not for SVG text"); 1.287 + mResolveColors = aResolveColors; 1.288 + } 1.289 + 1.290 + nscolor GetTextColor(); 1.291 + /** 1.292 + * Compute the colors for normally-selected text. Returns false if 1.293 + * the normal selection is not being displayed. 1.294 + */ 1.295 + bool GetSelectionColors(nscolor* aForeColor, 1.296 + nscolor* aBackColor); 1.297 + void GetHighlightColors(nscolor* aForeColor, 1.298 + nscolor* aBackColor); 1.299 + void GetURLSecondaryColor(nscolor* aForeColor); 1.300 + void GetIMESelectionColors(int32_t aIndex, 1.301 + nscolor* aForeColor, 1.302 + nscolor* aBackColor); 1.303 + // if this returns false, we don't need to draw underline. 1.304 + bool GetSelectionUnderlineForPaint(int32_t aIndex, 1.305 + nscolor* aLineColor, 1.306 + float* aRelativeSize, 1.307 + uint8_t* aStyle); 1.308 + 1.309 + // if this returns false, we don't need to draw underline. 1.310 + static bool GetSelectionUnderline(nsPresContext* aPresContext, 1.311 + int32_t aIndex, 1.312 + nscolor* aLineColor, 1.313 + float* aRelativeSize, 1.314 + uint8_t* aStyle); 1.315 + 1.316 + // if this returns false, no text-shadow was specified for the selection 1.317 + // and the *aShadow parameter was not modified. 1.318 + bool GetSelectionShadow(nsCSSShadowArray** aShadow); 1.319 + 1.320 + nsPresContext* PresContext() const { return mPresContext; } 1.321 + 1.322 + enum { 1.323 + eIndexRawInput = 0, 1.324 + eIndexSelRawText, 1.325 + eIndexConvText, 1.326 + eIndexSelConvText, 1.327 + eIndexSpellChecker 1.328 + }; 1.329 + 1.330 + static int32_t GetUnderlineStyleIndexForSelectionType(int32_t aSelectionType) 1.331 + { 1.332 + switch (aSelectionType) { 1.333 + case nsISelectionController::SELECTION_IME_RAWINPUT: 1.334 + return eIndexRawInput; 1.335 + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: 1.336 + return eIndexSelRawText; 1.337 + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: 1.338 + return eIndexConvText; 1.339 + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: 1.340 + return eIndexSelConvText; 1.341 + case nsISelectionController::SELECTION_SPELLCHECK: 1.342 + return eIndexSpellChecker; 1.343 + default: 1.344 + NS_WARNING("non-IME selection type"); 1.345 + return eIndexRawInput; 1.346 + } 1.347 + } 1.348 + 1.349 +protected: 1.350 + nsTextFrame* mFrame; 1.351 + nsPresContext* mPresContext; 1.352 + bool mInitCommonColors; 1.353 + bool mInitSelectionColorsAndShadow; 1.354 + bool mResolveColors; 1.355 + 1.356 + // Selection data 1.357 + 1.358 + int16_t mSelectionStatus; // see nsIDocument.h SetDisplaySelection() 1.359 + nscolor mSelectionTextColor; 1.360 + nscolor mSelectionBGColor; 1.361 + nsRefPtr<nsCSSShadowArray> mSelectionShadow; 1.362 + bool mHasSelectionShadow; 1.363 + 1.364 + // Common data 1.365 + 1.366 + int32_t mSufficientContrast; 1.367 + nscolor mFrameBackgroundColor; 1.368 + 1.369 + // selection colors and underline info, the colors are resolved colors if 1.370 + // mResolveColors is true (which is the default), i.e., the foreground color 1.371 + // and background color are swapped if it's needed. And also line color will 1.372 + // be resolved from them. 1.373 + struct nsSelectionStyle { 1.374 + bool mInit; 1.375 + nscolor mTextColor; 1.376 + nscolor mBGColor; 1.377 + nscolor mUnderlineColor; 1.378 + uint8_t mUnderlineStyle; 1.379 + float mUnderlineRelativeSize; 1.380 + }; 1.381 + nsSelectionStyle mSelectionStyle[5]; 1.382 + 1.383 + // Color initializations 1.384 + void InitCommonColors(); 1.385 + bool InitSelectionColorsAndShadow(); 1.386 + 1.387 + nsSelectionStyle* GetSelectionStyle(int32_t aIndex); 1.388 + void InitSelectionStyle(int32_t aIndex); 1.389 + 1.390 + bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor); 1.391 + 1.392 + nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, 1.393 + nscolor aBackColor); 1.394 +}; 1.395 + 1.396 +static void 1.397 +DestroyUserData(void* aUserData) 1.398 +{ 1.399 + TextRunUserData* userData = static_cast<TextRunUserData*>(aUserData); 1.400 + if (userData) { 1.401 + nsMemory::Free(userData); 1.402 + } 1.403 +} 1.404 + 1.405 +/** 1.406 + * Remove |aTextRun| from the frame continuation chain starting at 1.407 + * |aStartContinuation| if non-null, otherwise starting at |aFrame|. 1.408 + * Unmark |aFrame| as a text run owner if it's the frame we start at. 1.409 + * Return true if |aStartContinuation| is non-null and was found 1.410 + * in the next-continuation chain of |aFrame|. 1.411 + */ 1.412 +static bool 1.413 +ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun, 1.414 + nsTextFrame* aStartContinuation, 1.415 + nsFrameState aWhichTextRunState) 1.416 +{ 1.417 + NS_PRECONDITION(aFrame, ""); 1.418 + NS_PRECONDITION(!aStartContinuation || 1.419 + (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || 1.420 + aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || 1.421 + (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || 1.422 + aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun), 1.423 + "wrong aStartContinuation for this text run"); 1.424 + 1.425 + if (!aStartContinuation || aStartContinuation == aFrame) { 1.426 + aFrame->RemoveStateBits(aWhichTextRunState); 1.427 + } else { 1.428 + do { 1.429 + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame"); 1.430 + aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation()); 1.431 + } while (aFrame && aFrame != aStartContinuation); 1.432 + } 1.433 + bool found = aStartContinuation == aFrame; 1.434 + while (aFrame) { 1.435 + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame"); 1.436 + if (!aFrame->RemoveTextRun(aTextRun)) { 1.437 + break; 1.438 + } 1.439 + aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation()); 1.440 + } 1.441 + NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?"); 1.442 + return found; 1.443 +} 1.444 + 1.445 +/** 1.446 + * Kill all references to |aTextRun| starting at |aStartContinuation|. 1.447 + * It could be referenced by any of its owners, and all their in-flows. 1.448 + * If |aStartContinuation| is null then process all userdata frames 1.449 + * and their continuations. 1.450 + * @note the caller is expected to take care of possibly destroying the 1.451 + * text run if all userdata frames were reset (userdata is deallocated 1.452 + * by this function though). The caller can detect this has occured by 1.453 + * checking |aTextRun->GetUserData() == nullptr|. 1.454 + */ 1.455 +static void 1.456 +UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation) 1.457 +{ 1.458 + if (!aTextRun->GetUserData()) 1.459 + return; 1.460 + 1.461 + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { 1.462 + nsTextFrame* userDataFrame = static_cast<nsTextFrame*>( 1.463 + static_cast<nsIFrame*>(aTextRun->GetUserData())); 1.464 + nsFrameState whichTextRunState = 1.465 + userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 1.466 + ? TEXT_IN_TEXTRUN_USER_DATA 1.467 + : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 1.468 + DebugOnly<bool> found = 1.469 + ClearAllTextRunReferences(userDataFrame, aTextRun, 1.470 + aStartContinuation, whichTextRunState); 1.471 + NS_ASSERTION(!aStartContinuation || found, 1.472 + "aStartContinuation wasn't found in simple flow text run"); 1.473 + if (!(userDataFrame->GetStateBits() & whichTextRunState)) { 1.474 + aTextRun->SetUserData(nullptr); 1.475 + } 1.476 + } else { 1.477 + TextRunUserData* userData = 1.478 + static_cast<TextRunUserData*>(aTextRun->GetUserData()); 1.479 + int32_t destroyFromIndex = aStartContinuation ? -1 : 0; 1.480 + for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { 1.481 + nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame; 1.482 + nsFrameState whichTextRunState = 1.483 + userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 1.484 + ? TEXT_IN_TEXTRUN_USER_DATA 1.485 + : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 1.486 + bool found = 1.487 + ClearAllTextRunReferences(userDataFrame, aTextRun, 1.488 + aStartContinuation, whichTextRunState); 1.489 + if (found) { 1.490 + if (userDataFrame->GetStateBits() & whichTextRunState) { 1.491 + destroyFromIndex = i + 1; 1.492 + } 1.493 + else { 1.494 + destroyFromIndex = i; 1.495 + } 1.496 + aStartContinuation = nullptr; 1.497 + } 1.498 + } 1.499 + NS_ASSERTION(destroyFromIndex >= 0, 1.500 + "aStartContinuation wasn't found in multi flow text run"); 1.501 + if (destroyFromIndex == 0) { 1.502 + DestroyUserData(userData); 1.503 + aTextRun->SetUserData(nullptr); 1.504 + } 1.505 + else { 1.506 + userData->mMappedFlowCount = uint32_t(destroyFromIndex); 1.507 + if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) { 1.508 + userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1; 1.509 + } 1.510 + } 1.511 + } 1.512 +} 1.513 + 1.514 +void 1.515 +GlyphObserver::NotifyGlyphsChanged() 1.516 +{ 1.517 + nsIPresShell* shell = mFrame->PresContext()->PresShell(); 1.518 + for (nsIFrame* f = mFrame; f; 1.519 + f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { 1.520 + if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) { 1.521 + // f will have its own GlyphObserver (if needed) so we can stop here. 1.522 + break; 1.523 + } 1.524 + f->InvalidateFrame(); 1.525 + // Theoretically we could just update overflow areas, perhaps using 1.526 + // OverflowChangedTracker, but that would do a bunch of work eagerly that 1.527 + // we should probably do lazily here since there could be a lot 1.528 + // of text frames affected and we'd like to coalesce the work. So that's 1.529 + // not easy to do well. 1.530 + shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); 1.531 + } 1.532 +} 1.533 + 1.534 +class FrameTextRunCache; 1.535 + 1.536 +static FrameTextRunCache *gTextRuns = nullptr; 1.537 + 1.538 +/* 1.539 + * Cache textruns and expire them after 3*10 seconds of no use. 1.540 + */ 1.541 +class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker<gfxTextRun,3> { 1.542 +public: 1.543 + enum { TIMEOUT_SECONDS = 10 }; 1.544 + FrameTextRunCache() 1.545 + : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {} 1.546 + ~FrameTextRunCache() { 1.547 + AgeAllGenerations(); 1.548 + } 1.549 + 1.550 + void RemoveFromCache(gfxTextRun* aTextRun) { 1.551 + if (aTextRun->GetExpirationState()->IsTracked()) { 1.552 + RemoveObject(aTextRun); 1.553 + } 1.554 + } 1.555 + 1.556 + // This gets called when the timeout has expired on a gfxTextRun 1.557 + virtual void NotifyExpired(gfxTextRun* aTextRun) { 1.558 + UnhookTextRunFromFrames(aTextRun, nullptr); 1.559 + RemoveFromCache(aTextRun); 1.560 + delete aTextRun; 1.561 + } 1.562 +}; 1.563 + 1.564 +// Helper to create a textrun and remember it in the textframe cache, 1.565 +// for either 8-bit or 16-bit text strings 1.566 +template<typename T> 1.567 +gfxTextRun * 1.568 +MakeTextRun(const T *aText, uint32_t aLength, 1.569 + gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, 1.570 + uint32_t aFlags) 1.571 +{ 1.572 + nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength, 1.573 + aParams, aFlags)); 1.574 + if (!textRun) { 1.575 + return nullptr; 1.576 + } 1.577 + nsresult rv = gTextRuns->AddObject(textRun); 1.578 + if (NS_FAILED(rv)) { 1.579 + gTextRuns->RemoveFromCache(textRun); 1.580 + return nullptr; 1.581 + } 1.582 +#ifdef NOISY_BIDI 1.583 + printf("Created textrun\n"); 1.584 +#endif 1.585 + return textRun.forget(); 1.586 +} 1.587 + 1.588 +void 1.589 +nsTextFrameTextRunCache::Init() { 1.590 + gTextRuns = new FrameTextRunCache(); 1.591 +} 1.592 + 1.593 +void 1.594 +nsTextFrameTextRunCache::Shutdown() { 1.595 + delete gTextRuns; 1.596 + gTextRuns = nullptr; 1.597 +} 1.598 + 1.599 +int32_t nsTextFrame::GetContentEnd() const { 1.600 + nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation()); 1.601 + return next ? next->GetContentOffset() : mContent->GetText()->GetLength(); 1.602 +} 1.603 + 1.604 +struct FlowLengthProperty { 1.605 + int32_t mStartOffset; 1.606 + // The offset of the next fixed continuation after mStartOffset, or 1.607 + // of the end of the text if there is none 1.608 + int32_t mEndFlowOffset; 1.609 +}; 1.610 + 1.611 +int32_t nsTextFrame::GetInFlowContentLength() { 1.612 + if (!(mState & NS_FRAME_IS_BIDI)) { 1.613 + return mContent->TextLength() - mContentOffset; 1.614 + } 1.615 + 1.616 + FlowLengthProperty* flowLength = 1.617 + static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength)); 1.618 + 1.619 + /** 1.620 + * This frame must start inside the cached flow. If the flow starts at 1.621 + * mContentOffset but this frame is empty, logically it might be before the 1.622 + * start of the cached flow. 1.623 + */ 1.624 + if (flowLength && 1.625 + (flowLength->mStartOffset < mContentOffset || 1.626 + (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) && 1.627 + flowLength->mEndFlowOffset > mContentOffset) { 1.628 +#ifdef DEBUG 1.629 + NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(), 1.630 + "frame crosses fixed continuation boundary"); 1.631 +#endif 1.632 + return flowLength->mEndFlowOffset - mContentOffset; 1.633 + } 1.634 + 1.635 + nsTextFrame* nextBidi = static_cast<nsTextFrame*>(LastInFlow()->GetNextContinuation()); 1.636 + int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength(); 1.637 + 1.638 + if (!flowLength) { 1.639 + flowLength = new FlowLengthProperty; 1.640 + if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength, 1.641 + nsINode::DeleteProperty<FlowLengthProperty>))) { 1.642 + delete flowLength; 1.643 + flowLength = nullptr; 1.644 + } 1.645 + } 1.646 + if (flowLength) { 1.647 + flowLength->mStartOffset = mContentOffset; 1.648 + flowLength->mEndFlowOffset = endFlow; 1.649 + } 1.650 + 1.651 + return endFlow - mContentOffset; 1.652 +} 1.653 + 1.654 +// Smarter versions of dom::IsSpaceCharacter. 1.655 +// Unicode is really annoying; sometimes a space character isn't whitespace --- 1.656 +// when it combines with another character 1.657 +// So we have several versions of IsSpace for use in different contexts. 1.658 + 1.659 +static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos) 1.660 +{ 1.661 + NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset"); 1.662 + if (!aFrag->Is2b()) 1.663 + return false; 1.664 + return nsTextFrameUtils::IsSpaceCombiningSequenceTail( 1.665 + aFrag->Get2b() + aPos, aFrag->GetLength() - aPos); 1.666 +} 1.667 + 1.668 +// Check whether aPos is a space for CSS 'word-spacing' purposes 1.669 +static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, 1.670 + uint32_t aPos, const nsStyleText* aStyleText) 1.671 +{ 1.672 + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); 1.673 + 1.674 + char16_t ch = aFrag->CharAt(aPos); 1.675 + switch (ch) { 1.676 + case ' ': 1.677 + case CH_NBSP: 1.678 + return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); 1.679 + case '\r': 1.680 + case '\t': return !aStyleText->WhiteSpaceIsSignificant(); 1.681 + case '\n': return !aStyleText->NewlineIsSignificant() && 1.682 + !aStyleText->NewlineIsDiscarded(); 1.683 + default: return false; 1.684 + } 1.685 +} 1.686 + 1.687 +// Check whether the string aChars/aLength starts with space that's 1.688 +// trimmable according to CSS 'white-space:normal/nowrap'. 1.689 +static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) 1.690 +{ 1.691 + NS_ASSERTION(aLength > 0, "No text for IsSpace!"); 1.692 + 1.693 + char16_t ch = *aChars; 1.694 + if (ch == ' ') 1.695 + return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1); 1.696 + return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r'; 1.697 +} 1.698 + 1.699 +// Check whether the character aCh is trimmable according to CSS 1.700 +// 'white-space:normal/nowrap' 1.701 +static bool IsTrimmableSpace(char aCh) 1.702 +{ 1.703 + return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r'; 1.704 +} 1.705 + 1.706 +static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos, 1.707 + const nsStyleText* aStyleText) 1.708 +{ 1.709 + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); 1.710 + 1.711 + switch (aFrag->CharAt(aPos)) { 1.712 + case ' ': return !aStyleText->WhiteSpaceIsSignificant() && 1.713 + !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); 1.714 + case '\n': return !aStyleText->NewlineIsSignificant() && 1.715 + !aStyleText->NewlineIsDiscarded(); 1.716 + case '\t': 1.717 + case '\r': 1.718 + case '\f': return !aStyleText->WhiteSpaceIsSignificant(); 1.719 + default: return false; 1.720 + } 1.721 +} 1.722 + 1.723 +static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos) 1.724 +{ 1.725 + NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); 1.726 + char16_t ch = aFrag->CharAt(aPos); 1.727 + if (ch == ' ' || ch == CH_NBSP) 1.728 + return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); 1.729 + return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r'; 1.730 +} 1.731 + 1.732 +// Count the amount of trimmable whitespace (as per CSS 1.733 +// 'white-space:normal/nowrap') in a text fragment. The first 1.734 +// character is at offset aStartOffset; the maximum number of characters 1.735 +// to check is aLength. aDirection is -1 or 1 depending on whether we should 1.736 +// progress backwards or forwards. 1.737 +static uint32_t 1.738 +GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, 1.739 + int32_t aStartOffset, int32_t aLength, 1.740 + int32_t aDirection) 1.741 +{ 1.742 + int32_t count = 0; 1.743 + if (aFrag->Is2b()) { 1.744 + const char16_t* str = aFrag->Get2b() + aStartOffset; 1.745 + int32_t fragLen = aFrag->GetLength() - aStartOffset; 1.746 + for (; count < aLength; ++count) { 1.747 + if (!IsTrimmableSpace(str, fragLen)) 1.748 + break; 1.749 + str += aDirection; 1.750 + fragLen -= aDirection; 1.751 + } 1.752 + } else { 1.753 + const char* str = aFrag->Get1b() + aStartOffset; 1.754 + for (; count < aLength; ++count) { 1.755 + if (!IsTrimmableSpace(*str)) 1.756 + break; 1.757 + str += aDirection; 1.758 + } 1.759 + } 1.760 + return count; 1.761 +} 1.762 + 1.763 +static bool 1.764 +IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) 1.765 +{ 1.766 + if (aFrag->Is2b()) 1.767 + return false; 1.768 + int32_t len = aFrag->GetLength(); 1.769 + const char* str = aFrag->Get1b(); 1.770 + for (int32_t i = 0; i < len; ++i) { 1.771 + char ch = str[i]; 1.772 + if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline)) 1.773 + continue; 1.774 + return false; 1.775 + } 1.776 + return true; 1.777 +} 1.778 + 1.779 +static bool 1.780 +IsAllNewlines(const nsTextFragment* aFrag) 1.781 +{ 1.782 + if (aFrag->Is2b()) 1.783 + return false; 1.784 + int32_t len = aFrag->GetLength(); 1.785 + const char* str = aFrag->Get1b(); 1.786 + for (int32_t i = 0; i < len; ++i) { 1.787 + char ch = str[i]; 1.788 + if (ch != '\n') 1.789 + return false; 1.790 + } 1.791 + return true; 1.792 +} 1.793 + 1.794 +static void 1.795 +CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray<gfxFont*>& aFonts) 1.796 +{ 1.797 + if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) { 1.798 + // Maybe the textrun was created for uninflated text. 1.799 + return; 1.800 + } 1.801 + 1.802 + nsTArray<nsAutoPtr<GlyphObserver> >* observers = 1.803 + new nsTArray<nsAutoPtr<GlyphObserver> >(); 1.804 + for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) { 1.805 + observers->AppendElement(new GlyphObserver(aFonts[i], aFrame)); 1.806 + } 1.807 + aFrame->Properties().Set(TextFrameGlyphObservers(), observers); 1.808 + // We are lazy and don't try to remove a property value that might be 1.809 + // obsolete due to style changes or font selection changes. That is 1.810 + // likely to be rarely needed, and we don't want to eat the overhead of 1.811 + // doing it for the overwhelmingly common case of no property existing. 1.812 + // (And we're out of state bits to conveniently use for a fast property 1.813 + // existence check.) The only downside is that in some rare cases we might 1.814 + // keep fonts alive for longer than necessary, or unnecessarily invalidate 1.815 + // frames. 1.816 +} 1.817 + 1.818 +static void 1.819 +CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) 1.820 +{ 1.821 + if (!aTextRun->GetUserData()) { 1.822 + return; 1.823 + } 1.824 + nsTArray<gfxFont*> fontsWithAnimatedGlyphs; 1.825 + uint32_t numGlyphRuns; 1.826 + const gfxTextRun::GlyphRun* glyphRuns = 1.827 + aTextRun->GetGlyphRuns(&numGlyphRuns); 1.828 + for (uint32_t i = 0; i < numGlyphRuns; ++i) { 1.829 + gfxFont* font = glyphRuns[i].mFont; 1.830 + if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) { 1.831 + fontsWithAnimatedGlyphs.AppendElement(font); 1.832 + } 1.833 + } 1.834 + if (fontsWithAnimatedGlyphs.IsEmpty()) { 1.835 + return; 1.836 + } 1.837 + 1.838 + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { 1.839 + CreateObserverForAnimatedGlyphs(static_cast<nsTextFrame*>( 1.840 + static_cast<nsIFrame*>(aTextRun->GetUserData())), fontsWithAnimatedGlyphs); 1.841 + } else { 1.842 + TextRunUserData* userData = 1.843 + static_cast<TextRunUserData*>(aTextRun->GetUserData()); 1.844 + for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { 1.845 + CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame, 1.846 + fontsWithAnimatedGlyphs); 1.847 + } 1.848 + } 1.849 +} 1.850 + 1.851 +/** 1.852 + * This class accumulates state as we scan a paragraph of text. It detects 1.853 + * textrun boundaries (changes from text to non-text, hard 1.854 + * line breaks, and font changes) and builds a gfxTextRun at each boundary. 1.855 + * It also detects linebreaker run boundaries (changes from text to non-text, 1.856 + * and hard line breaks) and at each boundary runs the linebreaker to compute 1.857 + * potential line breaks. It also records actual line breaks to store them in 1.858 + * the textruns. 1.859 + */ 1.860 +class BuildTextRunsScanner { 1.861 +public: 1.862 + BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext, 1.863 + nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) : 1.864 + mCurrentFramesAllSameTextRun(nullptr), 1.865 + mContext(aContext), 1.866 + mLineContainer(aLineContainer), 1.867 + mBidiEnabled(aPresContext->BidiEnabled()), 1.868 + mSkipIncompleteTextRuns(false), 1.869 + mWhichTextRun(aWhichTextRun), 1.870 + mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE), 1.871 + mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) { 1.872 + ResetRunInfo(); 1.873 + } 1.874 + ~BuildTextRunsScanner() { 1.875 + NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared"); 1.876 + NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared"); 1.877 + NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared"); 1.878 + NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared"); 1.879 + } 1.880 + 1.881 + void SetAtStartOfLine() { 1.882 + mStartOfLine = true; 1.883 + mCanStopOnThisLine = false; 1.884 + } 1.885 + void SetSkipIncompleteTextRuns(bool aSkip) { 1.886 + mSkipIncompleteTextRuns = aSkip; 1.887 + } 1.888 + void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { 1.889 + mCommonAncestorWithLastFrame = aFrame; 1.890 + } 1.891 + bool CanStopOnThisLine() { 1.892 + return mCanStopOnThisLine; 1.893 + } 1.894 + nsIFrame* GetCommonAncestorWithLastFrame() { 1.895 + return mCommonAncestorWithLastFrame; 1.896 + } 1.897 + void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { 1.898 + if (mCommonAncestorWithLastFrame && 1.899 + mCommonAncestorWithLastFrame->GetParent() == aFrame) { 1.900 + mCommonAncestorWithLastFrame = aFrame; 1.901 + } 1.902 + } 1.903 + void ScanFrame(nsIFrame* aFrame); 1.904 + bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun); 1.905 + void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak); 1.906 + void FlushLineBreaks(gfxTextRun* aTrailingTextRun); 1.907 + void ResetRunInfo() { 1.908 + mLastFrame = nullptr; 1.909 + mMappedFlows.Clear(); 1.910 + mLineBreakBeforeFrames.Clear(); 1.911 + mMaxTextLength = 0; 1.912 + mDoubleByteText = false; 1.913 + } 1.914 + void AccumulateRunInfo(nsTextFrame* aFrame); 1.915 + /** 1.916 + * @return null to indicate either textrun construction failed or 1.917 + * we constructed just a partial textrun to set up linebreaker and other 1.918 + * state for following textruns. 1.919 + */ 1.920 + gfxTextRun* BuildTextRunForFrames(void* aTextBuffer); 1.921 + bool SetupLineBreakerContext(gfxTextRun *aTextRun); 1.922 + void AssignTextRun(gfxTextRun* aTextRun, float aInflation); 1.923 + nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex); 1.924 + enum SetupBreakSinksFlags { 1.925 + SBS_DOUBLE_BYTE = (1 << 0), 1.926 + SBS_EXISTING_TEXTRUN = (1 << 1), 1.927 + SBS_SUPPRESS_SINK = (1 << 2) 1.928 + }; 1.929 + void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, 1.930 + const void* aTextPtr, 1.931 + uint32_t aFlags); 1.932 + struct FindBoundaryState { 1.933 + nsIFrame* mStopAtFrame; 1.934 + nsTextFrame* mFirstTextFrame; 1.935 + nsTextFrame* mLastTextFrame; 1.936 + bool mSeenTextRunBoundaryOnLaterLine; 1.937 + bool mSeenTextRunBoundaryOnThisLine; 1.938 + bool mSeenSpaceForLineBreakingOnThisLine; 1.939 + }; 1.940 + enum FindBoundaryResult { 1.941 + FB_CONTINUE, 1.942 + FB_STOPPED_AT_STOP_FRAME, 1.943 + FB_FOUND_VALID_TEXTRUN_BOUNDARY 1.944 + }; 1.945 + FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState); 1.946 + 1.947 + bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); 1.948 + 1.949 + // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame 1.950 + // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then 1.951 + // continuations starting from mStartFrame are a sequence of in-flow frames). 1.952 + struct MappedFlow { 1.953 + nsTextFrame* mStartFrame; 1.954 + nsTextFrame* mEndFrame; 1.955 + // When we consider breaking between elements, the nearest common 1.956 + // ancestor of the elements containing the characters is the one whose 1.957 + // CSS 'white-space' property governs. So this records the nearest common 1.958 + // ancestor of mStartFrame and the previous text frame, or null if there 1.959 + // was no previous text frame on this line. 1.960 + nsIFrame* mAncestorControllingInitialBreak; 1.961 + 1.962 + int32_t GetContentEnd() { 1.963 + return mEndFrame ? mEndFrame->GetContentOffset() 1.964 + : mStartFrame->GetContent()->GetText()->GetLength(); 1.965 + } 1.966 + }; 1.967 + 1.968 + class BreakSink MOZ_FINAL : public nsILineBreakSink { 1.969 + public: 1.970 + BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, uint32_t aOffsetIntoTextRun, 1.971 + bool aExistingTextRun) : 1.972 + mTextRun(aTextRun), mContext(aContext), 1.973 + mOffsetIntoTextRun(aOffsetIntoTextRun), 1.974 + mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {} 1.975 + 1.976 + virtual void SetBreaks(uint32_t aOffset, uint32_t aLength, 1.977 + uint8_t* aBreakBefore) MOZ_OVERRIDE { 1.978 + if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength, 1.979 + aBreakBefore, mContext)) { 1.980 + mChangedBreaks = true; 1.981 + // Be conservative and assume that some breaks have been set 1.982 + mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS); 1.983 + } 1.984 + } 1.985 + 1.986 + virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength, 1.987 + bool* aCapitalize) MOZ_OVERRIDE { 1.988 + NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED, 1.989 + "Text run should be transformed!"); 1.990 + nsTransformedTextRun* transformedTextRun = 1.991 + static_cast<nsTransformedTextRun*>(mTextRun); 1.992 + transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength, 1.993 + aCapitalize, mContext); 1.994 + } 1.995 + 1.996 + void Finish() { 1.997 + NS_ASSERTION(!(mTextRun->GetFlags() & 1.998 + (gfxTextRunFactory::TEXT_UNUSED_FLAGS | 1.999 + nsTextFrameUtils::TEXT_UNUSED_FLAG)), 1.1000 + "Flag set that should never be set! (memory safety error?)"); 1.1001 + if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) { 1.1002 + nsTransformedTextRun* transformedTextRun = 1.1003 + static_cast<nsTransformedTextRun*>(mTextRun); 1.1004 + transformedTextRun->FinishSettingProperties(mContext); 1.1005 + } 1.1006 + // The way nsTransformedTextRun is implemented, its glyph runs aren't 1.1007 + // available until after nsTransformedTextRun::FinishSettingProperties() 1.1008 + // is called. So that's why we defer checking for animated glyphs to here. 1.1009 + CreateObserversForAnimatedGlyphs(mTextRun); 1.1010 + } 1.1011 + 1.1012 + gfxTextRun* mTextRun; 1.1013 + gfxContext* mContext; 1.1014 + uint32_t mOffsetIntoTextRun; 1.1015 + bool mChangedBreaks; 1.1016 + bool mExistingTextRun; 1.1017 + }; 1.1018 + 1.1019 +private: 1.1020 + nsAutoTArray<MappedFlow,10> mMappedFlows; 1.1021 + nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames; 1.1022 + nsAutoTArray<nsAutoPtr<BreakSink>,10> mBreakSinks; 1.1023 + nsAutoTArray<gfxTextRun*,5> mTextRunsToDelete; 1.1024 + nsLineBreaker mLineBreaker; 1.1025 + gfxTextRun* mCurrentFramesAllSameTextRun; 1.1026 + gfxContext* mContext; 1.1027 + nsIFrame* mLineContainer; 1.1028 + nsTextFrame* mLastFrame; 1.1029 + // The common ancestor of the current frame and the previous leaf frame 1.1030 + // on the line, or null if there was no previous leaf frame. 1.1031 + nsIFrame* mCommonAncestorWithLastFrame; 1.1032 + // mMaxTextLength is an upper bound on the size of the text in all mapped frames 1.1033 + // The value UINT32_MAX represents overflow; text will be discarded 1.1034 + uint32_t mMaxTextLength; 1.1035 + bool mDoubleByteText; 1.1036 + bool mBidiEnabled; 1.1037 + bool mStartOfLine; 1.1038 + bool mSkipIncompleteTextRuns; 1.1039 + bool mCanStopOnThisLine; 1.1040 + nsTextFrame::TextRunType mWhichTextRun; 1.1041 + uint8_t mNextRunContextInfo; 1.1042 + uint8_t mCurrentRunContextInfo; 1.1043 +}; 1.1044 + 1.1045 +static nsIFrame* 1.1046 +FindLineContainer(nsIFrame* aFrame) 1.1047 +{ 1.1048 + while (aFrame && aFrame->CanContinueTextRun()) { 1.1049 + aFrame = aFrame->GetParent(); 1.1050 + } 1.1051 + return aFrame; 1.1052 +} 1.1053 + 1.1054 +static bool 1.1055 +IsLineBreakingWhiteSpace(char16_t aChar) 1.1056 +{ 1.1057 + // 0x0A (\n) is not handled as white-space by the line breaker, since 1.1058 + // we break before it, if it isn't transformed to a normal space. 1.1059 + // (If we treat it as normal white-space then we'd only break after it.) 1.1060 + // However, it does induce a line break or is converted to a regular 1.1061 + // space, and either way it can be used to bound the region of text 1.1062 + // that needs to be analyzed for line breaking. 1.1063 + return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A; 1.1064 +} 1.1065 + 1.1066 +static bool 1.1067 +TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength, 1.1068 + bool aIsDoubleByte) 1.1069 +{ 1.1070 + if (aIsDoubleByte) { 1.1071 + const char16_t* chars = static_cast<const char16_t*>(aText); 1.1072 + for (uint32_t i = 0; i < aLength; ++i) { 1.1073 + if (IsLineBreakingWhiteSpace(chars[i])) 1.1074 + return true; 1.1075 + } 1.1076 + return false; 1.1077 + } else { 1.1078 + const uint8_t* chars = static_cast<const uint8_t*>(aText); 1.1079 + for (uint32_t i = 0; i < aLength; ++i) { 1.1080 + if (IsLineBreakingWhiteSpace(chars[i])) 1.1081 + return true; 1.1082 + } 1.1083 + return false; 1.1084 + } 1.1085 +} 1.1086 + 1.1087 +struct FrameTextTraversal { 1.1088 + // These fields identify which frames should be recursively scanned 1.1089 + // The first normal frame to scan (or null, if no such frame should be scanned) 1.1090 + nsIFrame* mFrameToScan; 1.1091 + // The first overflow frame to scan (or null, if no such frame should be scanned) 1.1092 + nsIFrame* mOverflowFrameToScan; 1.1093 + // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto 1.1094 + bool mScanSiblings; 1.1095 + 1.1096 + // These identify the boundaries of the context required for 1.1097 + // line breaking or textrun construction 1.1098 + bool mLineBreakerCanCrossFrameBoundary; 1.1099 + bool mTextRunCanCrossFrameBoundary; 1.1100 + 1.1101 + nsIFrame* NextFrameToScan() { 1.1102 + nsIFrame* f; 1.1103 + if (mFrameToScan) { 1.1104 + f = mFrameToScan; 1.1105 + mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; 1.1106 + } else if (mOverflowFrameToScan) { 1.1107 + f = mOverflowFrameToScan; 1.1108 + mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; 1.1109 + } else { 1.1110 + f = nullptr; 1.1111 + } 1.1112 + return f; 1.1113 + } 1.1114 +}; 1.1115 + 1.1116 +static FrameTextTraversal 1.1117 +CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType) 1.1118 +{ 1.1119 + NS_ASSERTION(aType == aFrame->GetType(), "Wrong type"); 1.1120 + 1.1121 + FrameTextTraversal result; 1.1122 + 1.1123 + bool continuesTextRun = aFrame->CanContinueTextRun(); 1.1124 + if (aType == nsGkAtoms::placeholderFrame) { 1.1125 + // placeholders are "invisible", so a text run should be able to span 1.1126 + // across one. But don't descend into the out-of-flow. 1.1127 + result.mLineBreakerCanCrossFrameBoundary = true; 1.1128 + result.mOverflowFrameToScan = nullptr; 1.1129 + if (continuesTextRun) { 1.1130 + // ... Except for first-letter floats, which are really in-flow 1.1131 + // from the point of view of capitalization etc, so we'd better 1.1132 + // descend into them. But we actually need to break the textrun for 1.1133 + // first-letter floats since things look bad if, say, we try to make a 1.1134 + // ligature across the float boundary. 1.1135 + result.mFrameToScan = 1.1136 + (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame(); 1.1137 + result.mScanSiblings = false; 1.1138 + result.mTextRunCanCrossFrameBoundary = false; 1.1139 + } else { 1.1140 + result.mFrameToScan = nullptr; 1.1141 + result.mTextRunCanCrossFrameBoundary = true; 1.1142 + } 1.1143 + } else { 1.1144 + if (continuesTextRun) { 1.1145 + result.mFrameToScan = aFrame->GetFirstPrincipalChild(); 1.1146 + result.mOverflowFrameToScan = 1.1147 + aFrame->GetFirstChild(nsIFrame::kOverflowList); 1.1148 + NS_WARN_IF_FALSE(!result.mOverflowFrameToScan, 1.1149 + "Scanning overflow inline frames is something we should avoid"); 1.1150 + result.mScanSiblings = true; 1.1151 + result.mTextRunCanCrossFrameBoundary = true; 1.1152 + result.mLineBreakerCanCrossFrameBoundary = true; 1.1153 + } else { 1.1154 + result.mFrameToScan = nullptr; 1.1155 + result.mOverflowFrameToScan = nullptr; 1.1156 + result.mTextRunCanCrossFrameBoundary = false; 1.1157 + result.mLineBreakerCanCrossFrameBoundary = false; 1.1158 + } 1.1159 + } 1.1160 + return result; 1.1161 +} 1.1162 + 1.1163 +BuildTextRunsScanner::FindBoundaryResult 1.1164 +BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState) 1.1165 +{ 1.1166 + nsIAtom* frameType = aFrame->GetType(); 1.1167 + nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame 1.1168 + ? static_cast<nsTextFrame*>(aFrame) : nullptr; 1.1169 + if (textFrame) { 1.1170 + if (aState->mLastTextFrame && 1.1171 + textFrame != aState->mLastTextFrame->GetNextInFlow() && 1.1172 + !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) { 1.1173 + aState->mSeenTextRunBoundaryOnThisLine = true; 1.1174 + if (aState->mSeenSpaceForLineBreakingOnThisLine) 1.1175 + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1.1176 + } 1.1177 + if (!aState->mFirstTextFrame) { 1.1178 + aState->mFirstTextFrame = textFrame; 1.1179 + } 1.1180 + aState->mLastTextFrame = textFrame; 1.1181 + } 1.1182 + 1.1183 + if (aFrame == aState->mStopAtFrame) 1.1184 + return FB_STOPPED_AT_STOP_FRAME; 1.1185 + 1.1186 + if (textFrame) { 1.1187 + if (!aState->mSeenSpaceForLineBreakingOnThisLine) { 1.1188 + const nsTextFragment* frag = textFrame->GetContent()->GetText(); 1.1189 + uint32_t start = textFrame->GetContentOffset(); 1.1190 + const void* text = frag->Is2b() 1.1191 + ? static_cast<const void*>(frag->Get2b() + start) 1.1192 + : static_cast<const void*>(frag->Get1b() + start); 1.1193 + if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(), 1.1194 + frag->Is2b())) { 1.1195 + aState->mSeenSpaceForLineBreakingOnThisLine = true; 1.1196 + if (aState->mSeenTextRunBoundaryOnLaterLine) 1.1197 + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1.1198 + } 1.1199 + } 1.1200 + return FB_CONTINUE; 1.1201 + } 1.1202 + 1.1203 + FrameTextTraversal traversal = 1.1204 + CanTextCrossFrameBoundary(aFrame, frameType); 1.1205 + if (!traversal.mTextRunCanCrossFrameBoundary) { 1.1206 + aState->mSeenTextRunBoundaryOnThisLine = true; 1.1207 + if (aState->mSeenSpaceForLineBreakingOnThisLine) 1.1208 + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1.1209 + } 1.1210 + 1.1211 + for (nsIFrame* f = traversal.NextFrameToScan(); f; 1.1212 + f = traversal.NextFrameToScan()) { 1.1213 + FindBoundaryResult result = FindBoundaries(f, aState); 1.1214 + if (result != FB_CONTINUE) 1.1215 + return result; 1.1216 + } 1.1217 + 1.1218 + if (!traversal.mTextRunCanCrossFrameBoundary) { 1.1219 + aState->mSeenTextRunBoundaryOnThisLine = true; 1.1220 + if (aState->mSeenSpaceForLineBreakingOnThisLine) 1.1221 + return FB_FOUND_VALID_TEXTRUN_BOUNDARY; 1.1222 + } 1.1223 + 1.1224 + return FB_CONTINUE; 1.1225 +} 1.1226 + 1.1227 +// build text runs for the 200 lines following aForFrame, and stop after that 1.1228 +// when we get a chance. 1.1229 +#define NUM_LINES_TO_BUILD_TEXT_RUNS 200 1.1230 + 1.1231 +/** 1.1232 + * General routine for building text runs. This is hairy because of the need 1.1233 + * to build text runs that span content nodes. 1.1234 + * 1.1235 + * @param aContext The gfxContext we're using to construct this text run. 1.1236 + * @param aForFrame The nsTextFrame for which we're building this text run. 1.1237 + * @param aLineContainer the line container containing aForFrame; if null, 1.1238 + * we'll walk the ancestors to find it. It's required to be non-null 1.1239 + * when aForFrameLine is non-null. 1.1240 + * @param aForFrameLine the line containing aForFrame; if null, we'll figure 1.1241 + * out the line (slowly) 1.1242 + * @param aWhichTextRun The type of text run we want to build. If font inflation 1.1243 + * is enabled, this will be eInflated, otherwise it's eNotInflated. 1.1244 + */ 1.1245 +static void 1.1246 +BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame, 1.1247 + nsIFrame* aLineContainer, 1.1248 + const nsLineList::iterator* aForFrameLine, 1.1249 + nsTextFrame::TextRunType aWhichTextRun) 1.1250 +{ 1.1251 + NS_ASSERTION(aForFrame || aLineContainer, 1.1252 + "One of aForFrame or aLineContainer must be set!"); 1.1253 + NS_ASSERTION(!aForFrameLine || aLineContainer, 1.1254 + "line but no line container"); 1.1255 + 1.1256 + nsIFrame* lineContainerChild = aForFrame; 1.1257 + if (!aLineContainer) { 1.1258 + if (aForFrame->IsFloatingFirstLetterChild()) { 1.1259 + lineContainerChild = aForFrame->PresContext()->PresShell()-> 1.1260 + GetPlaceholderFrameFor(aForFrame->GetParent()); 1.1261 + } 1.1262 + aLineContainer = FindLineContainer(lineContainerChild); 1.1263 + } else { 1.1264 + NS_ASSERTION(!aForFrame || 1.1265 + (aLineContainer == FindLineContainer(aForFrame) || 1.1266 + (aLineContainer->GetType() == nsGkAtoms::letterFrame && 1.1267 + aLineContainer->IsFloating())), 1.1268 + "Wrong line container hint"); 1.1269 + } 1.1270 + 1.1271 + if (aForFrame) { 1.1272 + if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 1.1273 + aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML); 1.1274 + if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { 1.1275 + aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI); 1.1276 + } 1.1277 + } 1.1278 + if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { 1.1279 + aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT); 1.1280 + } 1.1281 + } 1.1282 + 1.1283 + nsPresContext* presContext = aLineContainer->PresContext(); 1.1284 + BuildTextRunsScanner scanner(presContext, aContext, aLineContainer, 1.1285 + aWhichTextRun); 1.1286 + 1.1287 + nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer); 1.1288 + 1.1289 + if (!block) { 1.1290 + NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), 1.1291 + "Breakable non-block line containers not supported"); 1.1292 + // Just loop through all the children of the linecontainer ... it's really 1.1293 + // just one line 1.1294 + scanner.SetAtStartOfLine(); 1.1295 + scanner.SetCommonAncestorWithLastFrame(nullptr); 1.1296 + nsIFrame* child = aLineContainer->GetFirstPrincipalChild(); 1.1297 + while (child) { 1.1298 + scanner.ScanFrame(child); 1.1299 + child = child->GetNextSibling(); 1.1300 + } 1.1301 + // Set mStartOfLine so FlushFrames knows its textrun ends a line 1.1302 + scanner.SetAtStartOfLine(); 1.1303 + scanner.FlushFrames(true, false); 1.1304 + return; 1.1305 + } 1.1306 + 1.1307 + // Find the line containing 'lineContainerChild'. 1.1308 + 1.1309 + bool isValid = true; 1.1310 + nsBlockInFlowLineIterator backIterator(block, &isValid); 1.1311 + if (aForFrameLine) { 1.1312 + backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine); 1.1313 + } else { 1.1314 + backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid); 1.1315 + NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); 1.1316 + NS_ASSERTION(backIterator.GetContainer() == block, 1.1317 + "Someone lied to us about the block"); 1.1318 + } 1.1319 + nsBlockFrame::line_iterator startLine = backIterator.GetLine(); 1.1320 + 1.1321 + // Find a line where we can start building text runs. We choose the last line 1.1322 + // where: 1.1323 + // -- there is a textrun boundary between the start of the line and the 1.1324 + // start of aForFrame 1.1325 + // -- there is a space between the start of the line and the textrun boundary 1.1326 + // (this is so we can be sure the line breaks will be set properly 1.1327 + // on the textruns we construct). 1.1328 + // The possibly-partial text runs up to and including the first space 1.1329 + // are not reconstructed. We construct partial text runs for that text --- 1.1330 + // for the sake of simplifying the code and feeding the linebreaker --- 1.1331 + // but we discard them instead of assigning them to frames. 1.1332 + // This is a little awkward because we traverse lines in the reverse direction 1.1333 + // but we traverse the frames in each line in the forward direction. 1.1334 + nsBlockInFlowLineIterator forwardIterator = backIterator; 1.1335 + nsIFrame* stopAtFrame = lineContainerChild; 1.1336 + nsTextFrame* nextLineFirstTextFrame = nullptr; 1.1337 + bool seenTextRunBoundaryOnLaterLine = false; 1.1338 + bool mayBeginInTextRun = true; 1.1339 + while (true) { 1.1340 + forwardIterator = backIterator; 1.1341 + nsBlockFrame::line_iterator line = backIterator.GetLine(); 1.1342 + if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { 1.1343 + mayBeginInTextRun = false; 1.1344 + break; 1.1345 + } 1.1346 + 1.1347 + BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr, 1.1348 + bool(seenTextRunBoundaryOnLaterLine), false, false }; 1.1349 + nsIFrame* child = line->mFirstChild; 1.1350 + bool foundBoundary = false; 1.1351 + for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { 1.1352 + BuildTextRunsScanner::FindBoundaryResult result = 1.1353 + scanner.FindBoundaries(child, &state); 1.1354 + if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { 1.1355 + foundBoundary = true; 1.1356 + break; 1.1357 + } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { 1.1358 + break; 1.1359 + } 1.1360 + child = child->GetNextSibling(); 1.1361 + } 1.1362 + if (foundBoundary) 1.1363 + break; 1.1364 + if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && 1.1365 + !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) { 1.1366 + // Found a usable textrun boundary at the end of the line 1.1367 + if (state.mSeenSpaceForLineBreakingOnThisLine) 1.1368 + break; 1.1369 + seenTextRunBoundaryOnLaterLine = true; 1.1370 + } else if (state.mSeenTextRunBoundaryOnThisLine) { 1.1371 + seenTextRunBoundaryOnLaterLine = true; 1.1372 + } 1.1373 + stopAtFrame = nullptr; 1.1374 + if (state.mFirstTextFrame) { 1.1375 + nextLineFirstTextFrame = state.mFirstTextFrame; 1.1376 + } 1.1377 + } 1.1378 + scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); 1.1379 + 1.1380 + // Now iterate over all text frames starting from the current line. First-in-flow 1.1381 + // text frames will be accumulated into textRunFrames as we go. When a 1.1382 + // text run boundary is required we flush textRunFrames ((re)building their 1.1383 + // gfxTextRuns as necessary). 1.1384 + bool seenStartLine = false; 1.1385 + uint32_t linesAfterStartLine = 0; 1.1386 + do { 1.1387 + nsBlockFrame::line_iterator line = forwardIterator.GetLine(); 1.1388 + if (line->IsBlock()) 1.1389 + break; 1.1390 + line->SetInvalidateTextRuns(false); 1.1391 + scanner.SetAtStartOfLine(); 1.1392 + scanner.SetCommonAncestorWithLastFrame(nullptr); 1.1393 + nsIFrame* child = line->mFirstChild; 1.1394 + for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { 1.1395 + scanner.ScanFrame(child); 1.1396 + child = child->GetNextSibling(); 1.1397 + } 1.1398 + if (line.get() == startLine.get()) { 1.1399 + seenStartLine = true; 1.1400 + } 1.1401 + if (seenStartLine) { 1.1402 + ++linesAfterStartLine; 1.1403 + if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) { 1.1404 + // Don't flush frames; we may be in the middle of a textrun 1.1405 + // that we can't end here. That's OK, we just won't build it. 1.1406 + // Note that we must already have finished the textrun for aForFrame, 1.1407 + // because we've seen the end of a textrun in a line after the line 1.1408 + // containing aForFrame. 1.1409 + scanner.FlushLineBreaks(nullptr); 1.1410 + // This flushes out mMappedFlows and mLineBreakBeforeFrames, which 1.1411 + // silences assertions in the scanner destructor. 1.1412 + scanner.ResetRunInfo(); 1.1413 + return; 1.1414 + } 1.1415 + } 1.1416 + } while (forwardIterator.Next()); 1.1417 + 1.1418 + // Set mStartOfLine so FlushFrames knows its textrun ends a line 1.1419 + scanner.SetAtStartOfLine(); 1.1420 + scanner.FlushFrames(true, false); 1.1421 +} 1.1422 + 1.1423 +static char16_t* 1.1424 +ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) 1.1425 +{ 1.1426 + while (aCount) { 1.1427 + *aDest = *aSrc; 1.1428 + ++aDest; 1.1429 + ++aSrc; 1.1430 + --aCount; 1.1431 + } 1.1432 + return aDest; 1.1433 +} 1.1434 + 1.1435 +bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun) 1.1436 +{ 1.1437 + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) 1.1438 + return mMappedFlows.Length() == 1 && 1.1439 + mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) && 1.1440 + mMappedFlows[0].mEndFrame == nullptr; 1.1441 + 1.1442 + TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData()); 1.1443 + if (userData->mMappedFlowCount != mMappedFlows.Length()) 1.1444 + return false; 1.1445 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.1446 + if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || 1.1447 + int32_t(userData->mMappedFlows[i].mContentLength) != 1.1448 + mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset()) 1.1449 + return false; 1.1450 + } 1.1451 + return true; 1.1452 +} 1.1453 + 1.1454 +/** 1.1455 + * This gets called when we need to make a text run for the current list of 1.1456 + * frames. 1.1457 + */ 1.1458 +void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak) 1.1459 +{ 1.1460 + gfxTextRun* textRun = nullptr; 1.1461 + if (!mMappedFlows.IsEmpty()) { 1.1462 + if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && 1.1463 + ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) == 1.1464 + ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) && 1.1465 + ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) == 1.1466 + ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) && 1.1467 + IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { 1.1468 + // Optimization: We do not need to (re)build the textrun. 1.1469 + textRun = mCurrentFramesAllSameTextRun; 1.1470 + 1.1471 + // Feed this run's text into the linebreaker to provide context. 1.1472 + if (!SetupLineBreakerContext(textRun)) { 1.1473 + return; 1.1474 + } 1.1475 + 1.1476 + // Update mNextRunContextInfo appropriately 1.1477 + mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; 1.1478 + if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) { 1.1479 + mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; 1.1480 + } 1.1481 + if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) { 1.1482 + mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; 1.1483 + } 1.1484 + } else { 1.1485 + AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer; 1.1486 + uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1); 1.1487 + if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX || 1.1488 + !buffer.AppendElements(bufferSize)) { 1.1489 + return; 1.1490 + } 1.1491 + textRun = BuildTextRunForFrames(buffer.Elements()); 1.1492 + } 1.1493 + } 1.1494 + 1.1495 + if (aFlushLineBreaks) { 1.1496 + FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun); 1.1497 + } 1.1498 + 1.1499 + mCanStopOnThisLine = true; 1.1500 + ResetRunInfo(); 1.1501 +} 1.1502 + 1.1503 +void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) 1.1504 +{ 1.1505 + bool trailingLineBreak; 1.1506 + nsresult rv = mLineBreaker.Reset(&trailingLineBreak); 1.1507 + // textRun may be null for various reasons, including because we constructed 1.1508 + // a partial textrun just to get the linebreaker and other state set up 1.1509 + // to build the next textrun. 1.1510 + if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) { 1.1511 + aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK); 1.1512 + } 1.1513 + 1.1514 + for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) { 1.1515 + if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) { 1.1516 + // TODO cause frames associated with the textrun to be reflowed, if they 1.1517 + // aren't being reflowed already! 1.1518 + } 1.1519 + mBreakSinks[i]->Finish(); 1.1520 + } 1.1521 + mBreakSinks.Clear(); 1.1522 + 1.1523 + for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) { 1.1524 + gfxTextRun* deleteTextRun = mTextRunsToDelete[i]; 1.1525 + gTextRuns->RemoveFromCache(deleteTextRun); 1.1526 + delete deleteTextRun; 1.1527 + } 1.1528 + mTextRunsToDelete.Clear(); 1.1529 +} 1.1530 + 1.1531 +void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) 1.1532 +{ 1.1533 + if (mMaxTextLength != UINT32_MAX) { 1.1534 + NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow"); 1.1535 + if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) { 1.1536 + mMaxTextLength = UINT32_MAX; 1.1537 + } else { 1.1538 + mMaxTextLength += aFrame->GetContentLength(); 1.1539 + } 1.1540 + } 1.1541 + mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b(); 1.1542 + mLastFrame = aFrame; 1.1543 + mCommonAncestorWithLastFrame = aFrame->GetParent(); 1.1544 + 1.1545 + MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; 1.1546 + NS_ASSERTION(mappedFlow->mStartFrame == aFrame || 1.1547 + mappedFlow->GetContentEnd() == aFrame->GetContentOffset(), 1.1548 + "Overlapping or discontiguous frames => BAD"); 1.1549 + mappedFlow->mEndFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation()); 1.1550 + if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) { 1.1551 + mCurrentFramesAllSameTextRun = nullptr; 1.1552 + } 1.1553 + 1.1554 + if (mStartOfLine) { 1.1555 + mLineBreakBeforeFrames.AppendElement(aFrame); 1.1556 + mStartOfLine = false; 1.1557 + } 1.1558 +} 1.1559 + 1.1560 +static nscoord StyleToCoord(const nsStyleCoord& aCoord) 1.1561 +{ 1.1562 + if (eStyleUnit_Coord == aCoord.GetUnit()) { 1.1563 + return aCoord.GetCoordValue(); 1.1564 + } else { 1.1565 + return 0; 1.1566 + } 1.1567 +} 1.1568 + 1.1569 +static bool 1.1570 +HasTerminalNewline(const nsTextFrame* aFrame) 1.1571 +{ 1.1572 + if (aFrame->GetContentLength() == 0) 1.1573 + return false; 1.1574 + const nsTextFragment* frag = aFrame->GetContent()->GetText(); 1.1575 + return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n'; 1.1576 +} 1.1577 + 1.1578 +static nscoord 1.1579 +LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) 1.1580 +{ 1.1581 + if (aFrame->IsSVGText()) { 1.1582 + return 0; 1.1583 + } 1.1584 + if (!aStyleText) { 1.1585 + aStyleText = aFrame->StyleText(); 1.1586 + } 1.1587 + return StyleToCoord(aStyleText->mLetterSpacing); 1.1588 +} 1.1589 + 1.1590 +static nscoord 1.1591 +WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) 1.1592 +{ 1.1593 + if (aFrame->IsSVGText()) { 1.1594 + return 0; 1.1595 + } 1.1596 + if (!aStyleText) { 1.1597 + aStyleText = aFrame->StyleText(); 1.1598 + } 1.1599 + return aStyleText->mWordSpacing; 1.1600 +} 1.1601 + 1.1602 +bool 1.1603 +BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2) 1.1604 +{ 1.1605 + // We don't need to check font size inflation, since 1.1606 + // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|) 1.1607 + // ensures that text runs never cross block boundaries. This means 1.1608 + // that the font size inflation on all text frames in the text run is 1.1609 + // already guaranteed to be the same as each other (and for the line 1.1610 + // container). 1.1611 + if (mBidiEnabled && 1.1612 + (NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2) || 1.1613 + NS_GET_PARAGRAPH_DEPTH(aFrame1) != NS_GET_PARAGRAPH_DEPTH(aFrame2))) 1.1614 + return false; 1.1615 + 1.1616 + nsStyleContext* sc1 = aFrame1->StyleContext(); 1.1617 + const nsStyleText* textStyle1 = sc1->StyleText(); 1.1618 + // If the first frame ends in a preformatted newline, then we end the textrun 1.1619 + // here. This avoids creating giant textruns for an entire plain text file. 1.1620 + // Note that we create a single text frame for a preformatted text node, 1.1621 + // even if it has newlines in it, so typically we won't see trailing newlines 1.1622 + // until after reflow has broken up the frame into one (or more) frames per 1.1623 + // line. That's OK though. 1.1624 + if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1)) 1.1625 + return false; 1.1626 + 1.1627 + if (aFrame1->GetContent() == aFrame2->GetContent() && 1.1628 + aFrame1->GetNextInFlow() != aFrame2) { 1.1629 + // aFrame2 must be a non-fluid continuation of aFrame1. This can happen 1.1630 + // sometimes when the unicode-bidi property is used; the bidi resolver 1.1631 + // breaks text into different frames even though the text has the same 1.1632 + // direction. We can't allow these two frames to share the same textrun 1.1633 + // because that would violate our invariant that two flows in the same 1.1634 + // textrun have different content elements. 1.1635 + return false; 1.1636 + } 1.1637 + 1.1638 + nsStyleContext* sc2 = aFrame2->StyleContext(); 1.1639 + const nsStyleText* textStyle2 = sc2->StyleText(); 1.1640 + if (sc1 == sc2) 1.1641 + return true; 1.1642 + 1.1643 + const nsStyleFont* fontStyle1 = sc1->StyleFont(); 1.1644 + const nsStyleFont* fontStyle2 = sc2->StyleFont(); 1.1645 + nscoord letterSpacing1 = LetterSpacing(aFrame1); 1.1646 + nscoord letterSpacing2 = LetterSpacing(aFrame2); 1.1647 + return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) && 1.1648 + sc1->StyleFont()->mLanguage == sc2->StyleFont()->mLanguage && 1.1649 + textStyle1->mTextTransform == textStyle2->mTextTransform && 1.1650 + nsLayoutUtils::GetTextRunFlagsForStyle(sc1, fontStyle1, textStyle1, letterSpacing1) == 1.1651 + nsLayoutUtils::GetTextRunFlagsForStyle(sc2, fontStyle2, textStyle2, letterSpacing2); 1.1652 +} 1.1653 + 1.1654 +void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) 1.1655 +{ 1.1656 + // First check if we can extend the current mapped frame block. This is common. 1.1657 + if (mMappedFlows.Length() > 0) { 1.1658 + MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; 1.1659 + if (mappedFlow->mEndFrame == aFrame && 1.1660 + (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) { 1.1661 + NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, 1.1662 + "Flow-sibling of a text frame is not a text frame?"); 1.1663 + 1.1664 + // Don't do this optimization if mLastFrame has a terminal newline... 1.1665 + // it's quite likely preformatted and we might want to end the textrun here. 1.1666 + // This is almost always true: 1.1667 + if (mLastFrame->StyleContext() == aFrame->StyleContext() && 1.1668 + !HasTerminalNewline(mLastFrame)) { 1.1669 + AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame)); 1.1670 + return; 1.1671 + } 1.1672 + } 1.1673 + } 1.1674 + 1.1675 + nsIAtom* frameType = aFrame->GetType(); 1.1676 + // Now see if we can add a new set of frames to the current textrun 1.1677 + if (frameType == nsGkAtoms::textFrame) { 1.1678 + nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame); 1.1679 + 1.1680 + if (mLastFrame) { 1.1681 + if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { 1.1682 + FlushFrames(false, false); 1.1683 + } else { 1.1684 + if (mLastFrame->GetContent() == frame->GetContent()) { 1.1685 + AccumulateRunInfo(frame); 1.1686 + return; 1.1687 + } 1.1688 + } 1.1689 + } 1.1690 + 1.1691 + MappedFlow* mappedFlow = mMappedFlows.AppendElement(); 1.1692 + if (!mappedFlow) 1.1693 + return; 1.1694 + 1.1695 + mappedFlow->mStartFrame = frame; 1.1696 + mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; 1.1697 + 1.1698 + AccumulateRunInfo(frame); 1.1699 + if (mMappedFlows.Length() == 1) { 1.1700 + mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun); 1.1701 + mCurrentRunContextInfo = mNextRunContextInfo; 1.1702 + } 1.1703 + return; 1.1704 + } 1.1705 + 1.1706 + FrameTextTraversal traversal = 1.1707 + CanTextCrossFrameBoundary(aFrame, frameType); 1.1708 + bool isBR = frameType == nsGkAtoms::brFrame; 1.1709 + if (!traversal.mLineBreakerCanCrossFrameBoundary) { 1.1710 + // BR frames are special. We do not need or want to record a break opportunity 1.1711 + // before a BR frame. 1.1712 + FlushFrames(true, isBR); 1.1713 + mCommonAncestorWithLastFrame = aFrame; 1.1714 + mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; 1.1715 + mStartOfLine = false; 1.1716 + } else if (!traversal.mTextRunCanCrossFrameBoundary) { 1.1717 + FlushFrames(false, false); 1.1718 + } 1.1719 + 1.1720 + for (nsIFrame* f = traversal.NextFrameToScan(); f; 1.1721 + f = traversal.NextFrameToScan()) { 1.1722 + ScanFrame(f); 1.1723 + } 1.1724 + 1.1725 + if (!traversal.mLineBreakerCanCrossFrameBoundary) { 1.1726 + // Really if we're a BR frame this is unnecessary since descendInto will be 1.1727 + // false. In fact this whole "if" statement should move into the descendInto. 1.1728 + FlushFrames(true, isBR); 1.1729 + mCommonAncestorWithLastFrame = aFrame; 1.1730 + mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; 1.1731 + } else if (!traversal.mTextRunCanCrossFrameBoundary) { 1.1732 + FlushFrames(false, false); 1.1733 + } 1.1734 + 1.1735 + LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); 1.1736 +} 1.1737 + 1.1738 +nsTextFrame* 1.1739 +BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) 1.1740 +{ 1.1741 + uint32_t index = *aIndex; 1.1742 + if (index >= mLineBreakBeforeFrames.Length()) 1.1743 + return nullptr; 1.1744 + *aIndex = index + 1; 1.1745 + return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index)); 1.1746 +} 1.1747 + 1.1748 +static uint32_t 1.1749 +GetSpacingFlags(nscoord spacing) 1.1750 +{ 1.1751 + return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0; 1.1752 +} 1.1753 + 1.1754 +static gfxFontGroup* 1.1755 +GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation, 1.1756 + nsFontMetrics** aOutFontMetrics = nullptr) 1.1757 +{ 1.1758 + if (aOutFontMetrics) 1.1759 + *aOutFontMetrics = nullptr; 1.1760 + 1.1761 + nsRefPtr<nsFontMetrics> metrics; 1.1762 + nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics), 1.1763 + aFontSizeInflation); 1.1764 + 1.1765 + if (!metrics) 1.1766 + return nullptr; 1.1767 + 1.1768 + if (aOutFontMetrics) { 1.1769 + *aOutFontMetrics = metrics; 1.1770 + NS_ADDREF(*aOutFontMetrics); 1.1771 + } 1.1772 + // XXX this is a bit bogus, we're releasing 'metrics' so the 1.1773 + // returned font-group might actually be torn down, although because 1.1774 + // of the way the device context caches font metrics, this seems to 1.1775 + // not actually happen. But we should fix this. 1.1776 + return metrics->GetThebesFontGroup(); 1.1777 +} 1.1778 + 1.1779 +static already_AddRefed<gfxContext> 1.1780 +CreateReferenceThebesContext(nsTextFrame* aTextFrame) 1.1781 +{ 1.1782 + nsRefPtr<nsRenderingContext> tmp = 1.1783 + aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext(); 1.1784 + 1.1785 + nsRefPtr<gfxContext> ctx = tmp->ThebesContext(); 1.1786 + return ctx.forget(); 1.1787 +} 1.1788 + 1.1789 +/** 1.1790 + * The returned textrun must be deleted when no longer needed. 1.1791 + */ 1.1792 +static gfxTextRun* 1.1793 +GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame) 1.1794 +{ 1.1795 + nsRefPtr<gfxContext> ctx = aContext; 1.1796 + if (!ctx) { 1.1797 + ctx = CreateReferenceThebesContext(aTextFrame); 1.1798 + } 1.1799 + if (!ctx) 1.1800 + return nullptr; 1.1801 + 1.1802 + return aTextRun->GetFontGroup()-> 1.1803 + MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit()); 1.1804 +} 1.1805 + 1.1806 +static gfxFont::Metrics 1.1807 +GetFirstFontMetrics(gfxFontGroup* aFontGroup) 1.1808 +{ 1.1809 + if (!aFontGroup) 1.1810 + return gfxFont::Metrics(); 1.1811 + gfxFont* font = aFontGroup->GetFontAt(0); 1.1812 + if (!font) 1.1813 + return gfxFont::Metrics(); 1.1814 + return font->GetMetrics(); 1.1815 +} 1.1816 + 1.1817 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0); 1.1818 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1); 1.1819 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2); 1.1820 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3); 1.1821 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4); 1.1822 +PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES == 5); 1.1823 + 1.1824 +static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] = 1.1825 +{ 1.1826 + nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal 1.1827 + nsTextFrameUtils::COMPRESS_NONE, // pre 1.1828 + nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap 1.1829 + nsTextFrameUtils::COMPRESS_NONE, // pre-wrap 1.1830 + nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line 1.1831 + nsTextFrameUtils::DISCARD_NEWLINE // -moz-pre-discard-newlines 1.1832 +}; 1.1833 + 1.1834 +gfxTextRun* 1.1835 +BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer) 1.1836 +{ 1.1837 + gfxSkipChars skipChars; 1.1838 + 1.1839 + const void* textPtr = aTextBuffer; 1.1840 + bool anySmallcapsStyle = false; 1.1841 + bool anyTextTransformStyle = false; 1.1842 + bool anyMathMLStyling = false; 1.1843 + uint8_t sstyScriptLevel = 0; 1.1844 + uint32_t textFlags = nsTextFrameUtils::TEXT_NO_BREAKS; 1.1845 + 1.1846 + if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { 1.1847 + textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE; 1.1848 + } 1.1849 + if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { 1.1850 + textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR; 1.1851 + } 1.1852 + 1.1853 + nsAutoTArray<int32_t,50> textBreakPoints; 1.1854 + TextRunUserData dummyData; 1.1855 + TextRunMappedFlow dummyMappedFlow; 1.1856 + 1.1857 + TextRunUserData* userData; 1.1858 + TextRunUserData* userDataToDestroy; 1.1859 + // If the situation is particularly simple (and common) we don't need to 1.1860 + // allocate userData. 1.1861 + if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && 1.1862 + mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { 1.1863 + userData = &dummyData; 1.1864 + userDataToDestroy = nullptr; 1.1865 + dummyData.mMappedFlows = &dummyMappedFlow; 1.1866 + } else { 1.1867 + userData = static_cast<TextRunUserData*> 1.1868 + (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); 1.1869 + userDataToDestroy = userData; 1.1870 + userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1); 1.1871 + } 1.1872 + userData->mMappedFlowCount = mMappedFlows.Length(); 1.1873 + userData->mLastFlowIndex = 0; 1.1874 + 1.1875 + uint32_t currentTransformedTextOffset = 0; 1.1876 + 1.1877 + uint32_t nextBreakIndex = 0; 1.1878 + nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 1.1879 + bool enabledJustification = mLineContainer && 1.1880 + (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY || 1.1881 + mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) && 1.1882 + !mLineContainer->IsSVGText(); 1.1883 + 1.1884 + // for word-break style 1.1885 + switch (mLineContainer->StyleText()->mWordBreak) { 1.1886 + case NS_STYLE_WORDBREAK_BREAK_ALL: 1.1887 + mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_BreakAll); 1.1888 + break; 1.1889 + case NS_STYLE_WORDBREAK_KEEP_ALL: 1.1890 + mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll); 1.1891 + break; 1.1892 + default: 1.1893 + mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal); 1.1894 + break; 1.1895 + } 1.1896 + 1.1897 + const nsStyleText* textStyle = nullptr; 1.1898 + const nsStyleFont* fontStyle = nullptr; 1.1899 + nsStyleContext* lastStyleContext = nullptr; 1.1900 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.1901 + MappedFlow* mappedFlow = &mMappedFlows[i]; 1.1902 + nsTextFrame* f = mappedFlow->mStartFrame; 1.1903 + 1.1904 + lastStyleContext = f->StyleContext(); 1.1905 + // Detect use of text-transform or font-variant anywhere in the run 1.1906 + textStyle = f->StyleText(); 1.1907 + if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) { 1.1908 + anyTextTransformStyle = true; 1.1909 + } 1.1910 + textFlags |= GetSpacingFlags(LetterSpacing(f)); 1.1911 + textFlags |= GetSpacingFlags(WordSpacing(f)); 1.1912 + nsTextFrameUtils::CompressionMode compression = 1.1913 + CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; 1.1914 + if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) { 1.1915 + textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; 1.1916 + } 1.1917 + fontStyle = f->StyleFont(); 1.1918 + if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) { 1.1919 + anySmallcapsStyle = true; 1.1920 + } 1.1921 + if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) { 1.1922 + anyMathMLStyling = true; 1.1923 + } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) { 1.1924 + textFlags |= nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI; 1.1925 + anyMathMLStyling = true; 1.1926 + } 1.1927 + nsIFrame* parent = mLineContainer->GetParent(); 1.1928 + if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { 1.1929 + // All MathML tokens except <mtext> use 'math' script. 1.1930 + if (!(parent && parent->GetContent() && 1.1931 + parent->GetContent()->Tag() == nsGkAtoms::mtext_)) { 1.1932 + textFlags |= gfxTextRunFactory::TEXT_USE_MATH_SCRIPT; 1.1933 + } 1.1934 + } 1.1935 + nsIFrame* child = mLineContainer; 1.1936 + uint8_t oldScriptLevel = 0; 1.1937 + while (parent && 1.1938 + child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { 1.1939 + // Reconstruct the script level ignoring any user overrides. It is 1.1940 + // calculated this way instead of using scriptlevel to ensure the 1.1941 + // correct ssty font feature setting is used even if the user sets a 1.1942 + // different (especially negative) scriptlevel. 1.1943 + nsIMathMLFrame* mathFrame= do_QueryFrame(parent); 1.1944 + if (mathFrame) { 1.1945 + sstyScriptLevel += mathFrame->ScriptIncrement(child); 1.1946 + } 1.1947 + if (sstyScriptLevel < oldScriptLevel) { 1.1948 + // overflow 1.1949 + sstyScriptLevel = UINT8_MAX; 1.1950 + break; 1.1951 + } 1.1952 + child = parent; 1.1953 + parent = parent->GetParent(); 1.1954 + oldScriptLevel = sstyScriptLevel; 1.1955 + } 1.1956 + if (sstyScriptLevel) { 1.1957 + anyMathMLStyling = true; 1.1958 + } 1.1959 + 1.1960 + // Figure out what content is included in this flow. 1.1961 + nsIContent* content = f->GetContent(); 1.1962 + const nsTextFragment* frag = content->GetText(); 1.1963 + int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); 1.1964 + int32_t contentEnd = mappedFlow->GetContentEnd(); 1.1965 + int32_t contentLength = contentEnd - contentStart; 1.1966 + 1.1967 + TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; 1.1968 + newFlow->mStartFrame = mappedFlow->mStartFrame; 1.1969 + newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() - 1.1970 + mappedFlow->mStartFrame->GetContentOffset(); 1.1971 + newFlow->mContentLength = contentLength; 1.1972 + 1.1973 + while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { 1.1974 + textBreakPoints.AppendElement( 1.1975 + nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); 1.1976 + nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 1.1977 + } 1.1978 + 1.1979 + uint32_t analysisFlags; 1.1980 + if (frag->Is2b()) { 1.1981 + NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); 1.1982 + char16_t* bufStart = static_cast<char16_t*>(aTextBuffer); 1.1983 + char16_t* bufEnd = nsTextFrameUtils::TransformText( 1.1984 + frag->Get2b() + contentStart, contentLength, bufStart, 1.1985 + compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.1986 + aTextBuffer = bufEnd; 1.1987 + currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr); 1.1988 + } else { 1.1989 + if (mDoubleByteText) { 1.1990 + // Need to expand the text. First transform it into a temporary buffer, 1.1991 + // then expand. 1.1992 + AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf; 1.1993 + uint8_t* bufStart = tempBuf.AppendElements(contentLength); 1.1994 + if (!bufStart) { 1.1995 + DestroyUserData(userDataToDestroy); 1.1996 + return nullptr; 1.1997 + } 1.1998 + uint8_t* end = nsTextFrameUtils::TransformText( 1.1999 + reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength, 1.2000 + bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.2001 + aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer), 1.2002 + tempBuf.Elements(), end - tempBuf.Elements()); 1.2003 + currentTransformedTextOffset = 1.2004 + static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr); 1.2005 + } else { 1.2006 + uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer); 1.2007 + uint8_t* end = nsTextFrameUtils::TransformText( 1.2008 + reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength, 1.2009 + bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.2010 + aTextBuffer = end; 1.2011 + currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr); 1.2012 + } 1.2013 + } 1.2014 + textFlags |= analysisFlags; 1.2015 + } 1.2016 + 1.2017 + void* finalUserData; 1.2018 + if (userData == &dummyData) { 1.2019 + textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW; 1.2020 + userData = nullptr; 1.2021 + finalUserData = mMappedFlows[0].mStartFrame; 1.2022 + } else { 1.2023 + finalUserData = userData; 1.2024 + } 1.2025 + 1.2026 + uint32_t transformedLength = currentTransformedTextOffset; 1.2027 + 1.2028 + // Now build the textrun 1.2029 + nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; 1.2030 + float fontInflation; 1.2031 + if (mWhichTextRun == nsTextFrame::eNotInflated) { 1.2032 + fontInflation = 1.0f; 1.2033 + } else { 1.2034 + fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame); 1.2035 + } 1.2036 + 1.2037 + gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation); 1.2038 + if (!fontGroup) { 1.2039 + DestroyUserData(userDataToDestroy); 1.2040 + return nullptr; 1.2041 + } 1.2042 + 1.2043 + if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) { 1.2044 + textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; 1.2045 + } 1.2046 + if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) { 1.2047 + textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS; 1.2048 + } 1.2049 + if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) { 1.2050 + textFlags |= gfxTextRunFactory::TEXT_IS_RTL; 1.2051 + } 1.2052 + if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { 1.2053 + textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE; 1.2054 + } 1.2055 + if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { 1.2056 + textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR; 1.2057 + } 1.2058 + // ContinueTextRunAcrossFrames guarantees that it doesn't matter which 1.2059 + // frame's style is used, so we use a mixture of the first frame and 1.2060 + // last frame's style 1.2061 + textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext, 1.2062 + fontStyle, textStyle, LetterSpacing(firstFrame, textStyle)); 1.2063 + // XXX this is a bit of a hack. For performance reasons, if we're favouring 1.2064 + // performance over quality, don't try to get accurate glyph extents. 1.2065 + if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) { 1.2066 + textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX; 1.2067 + } 1.2068 + 1.2069 + // Convert linebreak coordinates to transformed string offsets 1.2070 + NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(), 1.2071 + "Didn't find all the frames to break-before..."); 1.2072 + gfxSkipCharsIterator iter(skipChars); 1.2073 + nsAutoTArray<uint32_t,50> textBreakPointsAfterTransform; 1.2074 + for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) { 1.2075 + nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, 1.2076 + iter.ConvertOriginalToSkipped(textBreakPoints[i])); 1.2077 + } 1.2078 + if (mStartOfLine) { 1.2079 + nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, 1.2080 + transformedLength); 1.2081 + } 1.2082 + 1.2083 + // Setup factory chain 1.2084 + nsAutoPtr<nsTransformingTextRunFactory> transformingFactory; 1.2085 + if (anySmallcapsStyle) { 1.2086 + transformingFactory = new nsFontVariantTextRunFactory(); 1.2087 + } 1.2088 + if (anyTextTransformStyle) { 1.2089 + transformingFactory = 1.2090 + new nsCaseTransformTextRunFactory(transformingFactory.forget()); 1.2091 + } 1.2092 + if (anyMathMLStyling) { 1.2093 + transformingFactory = 1.2094 + new MathMLTextRunFactory(transformingFactory.forget(), sstyScriptLevel); 1.2095 + } 1.2096 + nsTArray<nsStyleContext*> styles; 1.2097 + if (transformingFactory) { 1.2098 + iter.SetOriginalOffset(0); 1.2099 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.2100 + MappedFlow* mappedFlow = &mMappedFlows[i]; 1.2101 + nsTextFrame* f; 1.2102 + for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; 1.2103 + f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 1.2104 + uint32_t offset = iter.GetSkippedOffset(); 1.2105 + iter.AdvanceOriginal(f->GetContentLength()); 1.2106 + uint32_t end = iter.GetSkippedOffset(); 1.2107 + nsStyleContext* sc = f->StyleContext(); 1.2108 + uint32_t j; 1.2109 + for (j = offset; j < end; ++j) { 1.2110 + styles.AppendElement(sc); 1.2111 + } 1.2112 + } 1.2113 + } 1.2114 + textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED; 1.2115 + NS_ASSERTION(iter.GetSkippedOffset() == transformedLength, 1.2116 + "We didn't cover all the characters in the text run!"); 1.2117 + } 1.2118 + 1.2119 + gfxTextRun* textRun; 1.2120 + gfxTextRunFactory::Parameters params = 1.2121 + { mContext, finalUserData, &skipChars, 1.2122 + textBreakPointsAfterTransform.Elements(), 1.2123 + textBreakPointsAfterTransform.Length(), 1.2124 + int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())}; 1.2125 + 1.2126 + if (mDoubleByteText) { 1.2127 + const char16_t* text = static_cast<const char16_t*>(textPtr); 1.2128 + if (transformingFactory) { 1.2129 + textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, 1.2130 + fontGroup, textFlags, styles.Elements()); 1.2131 + if (textRun) { 1.2132 + // ownership of the factory has passed to the textrun 1.2133 + transformingFactory.forget(); 1.2134 + } 1.2135 + } else { 1.2136 + textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); 1.2137 + } 1.2138 + } else { 1.2139 + const uint8_t* text = static_cast<const uint8_t*>(textPtr); 1.2140 + textFlags |= gfxFontGroup::TEXT_IS_8BIT; 1.2141 + if (transformingFactory) { 1.2142 + textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, 1.2143 + fontGroup, textFlags, styles.Elements()); 1.2144 + if (textRun) { 1.2145 + // ownership of the factory has passed to the textrun 1.2146 + transformingFactory.forget(); 1.2147 + } 1.2148 + } else { 1.2149 + textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); 1.2150 + } 1.2151 + } 1.2152 + if (!textRun) { 1.2153 + DestroyUserData(userDataToDestroy); 1.2154 + return nullptr; 1.2155 + } 1.2156 + 1.2157 + // We have to set these up after we've created the textrun, because 1.2158 + // the breaks may be stored in the textrun during this very call. 1.2159 + // This is a bit annoying because it requires another loop over the frames 1.2160 + // making up the textrun, but I don't see a way to avoid this. 1.2161 + uint32_t flags = 0; 1.2162 + if (mDoubleByteText) { 1.2163 + flags |= SBS_DOUBLE_BYTE; 1.2164 + } 1.2165 + if (mSkipIncompleteTextRuns) { 1.2166 + flags |= SBS_SUPPRESS_SINK; 1.2167 + } 1.2168 + SetupBreakSinksForTextRun(textRun, textPtr, flags); 1.2169 + 1.2170 + if (mSkipIncompleteTextRuns) { 1.2171 + mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr, 1.2172 + transformedLength, mDoubleByteText); 1.2173 + // Arrange for this textrun to be deleted the next time the linebreaker 1.2174 + // is flushed out 1.2175 + mTextRunsToDelete.AppendElement(textRun); 1.2176 + // Since we're doing to destroy the user data now, avoid a dangling 1.2177 + // pointer. Strictly speaking we don't need to do this since it should 1.2178 + // not be used (since this textrun will not be used and will be 1.2179 + // itself deleted soon), but it's always better to not have dangling 1.2180 + // pointers around. 1.2181 + textRun->SetUserData(nullptr); 1.2182 + DestroyUserData(userDataToDestroy); 1.2183 + return nullptr; 1.2184 + } 1.2185 + 1.2186 + // Actually wipe out the textruns associated with the mapped frames and associate 1.2187 + // those frames with this text run. 1.2188 + AssignTextRun(textRun, fontInflation); 1.2189 + return textRun; 1.2190 +} 1.2191 + 1.2192 +// This is a cut-down version of BuildTextRunForFrames used to set up 1.2193 +// context for the line-breaker, when the textrun has already been created. 1.2194 +// So it does the same walk over the mMappedFlows, but doesn't actually 1.2195 +// build a new textrun. 1.2196 +bool 1.2197 +BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun) 1.2198 +{ 1.2199 + AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer; 1.2200 + uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1); 1.2201 + if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) { 1.2202 + return false; 1.2203 + } 1.2204 + void *textPtr = buffer.AppendElements(bufferSize); 1.2205 + if (!textPtr) { 1.2206 + return false; 1.2207 + } 1.2208 + 1.2209 + gfxSkipChars skipChars; 1.2210 + 1.2211 + nsAutoTArray<int32_t,50> textBreakPoints; 1.2212 + TextRunUserData dummyData; 1.2213 + TextRunMappedFlow dummyMappedFlow; 1.2214 + 1.2215 + TextRunUserData* userData; 1.2216 + TextRunUserData* userDataToDestroy; 1.2217 + // If the situation is particularly simple (and common) we don't need to 1.2218 + // allocate userData. 1.2219 + if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && 1.2220 + mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { 1.2221 + userData = &dummyData; 1.2222 + userDataToDestroy = nullptr; 1.2223 + dummyData.mMappedFlows = &dummyMappedFlow; 1.2224 + } else { 1.2225 + userData = static_cast<TextRunUserData*> 1.2226 + (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); 1.2227 + userDataToDestroy = userData; 1.2228 + userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1); 1.2229 + } 1.2230 + userData->mMappedFlowCount = mMappedFlows.Length(); 1.2231 + userData->mLastFlowIndex = 0; 1.2232 + 1.2233 + uint32_t nextBreakIndex = 0; 1.2234 + nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 1.2235 + 1.2236 + const nsStyleText* textStyle = nullptr; 1.2237 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.2238 + MappedFlow* mappedFlow = &mMappedFlows[i]; 1.2239 + nsTextFrame* f = mappedFlow->mStartFrame; 1.2240 + 1.2241 + textStyle = f->StyleText(); 1.2242 + nsTextFrameUtils::CompressionMode compression = 1.2243 + CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; 1.2244 + 1.2245 + // Figure out what content is included in this flow. 1.2246 + nsIContent* content = f->GetContent(); 1.2247 + const nsTextFragment* frag = content->GetText(); 1.2248 + int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); 1.2249 + int32_t contentEnd = mappedFlow->GetContentEnd(); 1.2250 + int32_t contentLength = contentEnd - contentStart; 1.2251 + 1.2252 + TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; 1.2253 + newFlow->mStartFrame = mappedFlow->mStartFrame; 1.2254 + newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() - 1.2255 + mappedFlow->mStartFrame->GetContentOffset(); 1.2256 + newFlow->mContentLength = contentLength; 1.2257 + 1.2258 + while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { 1.2259 + textBreakPoints.AppendElement( 1.2260 + nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); 1.2261 + nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); 1.2262 + } 1.2263 + 1.2264 + uint32_t analysisFlags; 1.2265 + if (frag->Is2b()) { 1.2266 + NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); 1.2267 + char16_t* bufStart = static_cast<char16_t*>(textPtr); 1.2268 + char16_t* bufEnd = nsTextFrameUtils::TransformText( 1.2269 + frag->Get2b() + contentStart, contentLength, bufStart, 1.2270 + compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.2271 + textPtr = bufEnd; 1.2272 + } else { 1.2273 + if (mDoubleByteText) { 1.2274 + // Need to expand the text. First transform it into a temporary buffer, 1.2275 + // then expand. 1.2276 + AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf; 1.2277 + uint8_t* bufStart = tempBuf.AppendElements(contentLength); 1.2278 + if (!bufStart) { 1.2279 + DestroyUserData(userDataToDestroy); 1.2280 + return false; 1.2281 + } 1.2282 + uint8_t* end = nsTextFrameUtils::TransformText( 1.2283 + reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength, 1.2284 + bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.2285 + textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr), 1.2286 + tempBuf.Elements(), end - tempBuf.Elements()); 1.2287 + } else { 1.2288 + uint8_t* bufStart = static_cast<uint8_t*>(textPtr); 1.2289 + uint8_t* end = nsTextFrameUtils::TransformText( 1.2290 + reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength, 1.2291 + bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); 1.2292 + textPtr = end; 1.2293 + } 1.2294 + } 1.2295 + } 1.2296 + 1.2297 + // We have to set these up after we've created the textrun, because 1.2298 + // the breaks may be stored in the textrun during this very call. 1.2299 + // This is a bit annoying because it requires another loop over the frames 1.2300 + // making up the textrun, but I don't see a way to avoid this. 1.2301 + uint32_t flags = 0; 1.2302 + if (mDoubleByteText) { 1.2303 + flags |= SBS_DOUBLE_BYTE; 1.2304 + } 1.2305 + if (mSkipIncompleteTextRuns) { 1.2306 + flags |= SBS_SUPPRESS_SINK; 1.2307 + } 1.2308 + SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags); 1.2309 + 1.2310 + DestroyUserData(userDataToDestroy); 1.2311 + 1.2312 + return true; 1.2313 +} 1.2314 + 1.2315 +static bool 1.2316 +HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText, 1.2317 + int32_t aContentEndOffset, 1.2318 + const gfxSkipCharsIterator& aIterator) 1.2319 +{ 1.2320 + if (!aIterator.IsOriginalCharSkipped()) 1.2321 + return false; 1.2322 + 1.2323 + gfxSkipCharsIterator iter = aIterator; 1.2324 + int32_t frameContentOffset = aFrame->GetContentOffset(); 1.2325 + const nsTextFragment* frag = aFrame->GetContent()->GetText(); 1.2326 + while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) { 1.2327 + if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) 1.2328 + return true; 1.2329 + ++frameContentOffset; 1.2330 + iter.AdvanceOriginal(1); 1.2331 + } 1.2332 + return false; 1.2333 +} 1.2334 + 1.2335 +void 1.2336 +BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, 1.2337 + const void* aTextPtr, 1.2338 + uint32_t aFlags) 1.2339 +{ 1.2340 + // textruns have uniform language 1.2341 + const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont(); 1.2342 + // We should only use a language for hyphenation if it was specified 1.2343 + // explicitly. 1.2344 + nsIAtom* hyphenationLanguage = 1.2345 + styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr; 1.2346 + // We keep this pointed at the skip-chars data for the current mappedFlow. 1.2347 + // This lets us cheaply check whether the flow has compressed initial 1.2348 + // whitespace... 1.2349 + gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); 1.2350 + 1.2351 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.2352 + MappedFlow* mappedFlow = &mMappedFlows[i]; 1.2353 + uint32_t offset = iter.GetSkippedOffset(); 1.2354 + gfxSkipCharsIterator iterNext = iter; 1.2355 + iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - 1.2356 + mappedFlow->mStartFrame->GetContentOffset()); 1.2357 + 1.2358 + nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement( 1.2359 + new BreakSink(aTextRun, mContext, offset, 1.2360 + (aFlags & SBS_EXISTING_TEXTRUN) != 0)); 1.2361 + if (!breakSink || !*breakSink) 1.2362 + return; 1.2363 + 1.2364 + uint32_t length = iterNext.GetSkippedOffset() - offset; 1.2365 + uint32_t flags = 0; 1.2366 + nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak; 1.2367 + if (!initialBreakController) { 1.2368 + initialBreakController = mLineContainer; 1.2369 + } 1.2370 + if (!initialBreakController->StyleText()-> 1.2371 + WhiteSpaceCanWrap(initialBreakController)) { 1.2372 + flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; 1.2373 + } 1.2374 + nsTextFrame* startFrame = mappedFlow->mStartFrame; 1.2375 + const nsStyleText* textStyle = startFrame->StyleText(); 1.2376 + if (!textStyle->WhiteSpaceCanWrap(startFrame)) { 1.2377 + flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; 1.2378 + } 1.2379 + if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) { 1.2380 + flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; 1.2381 + } 1.2382 + if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) { 1.2383 + flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; 1.2384 + } 1.2385 + if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) { 1.2386 + flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION; 1.2387 + } 1.2388 + 1.2389 + if (HasCompressedLeadingWhitespace(startFrame, textStyle, 1.2390 + mappedFlow->GetContentEnd(), iter)) { 1.2391 + mLineBreaker.AppendInvisibleWhitespace(flags); 1.2392 + } 1.2393 + 1.2394 + if (length > 0) { 1.2395 + BreakSink* sink = 1.2396 + (aFlags & SBS_SUPPRESS_SINK) ? nullptr : (*breakSink).get(); 1.2397 + if (aFlags & SBS_DOUBLE_BYTE) { 1.2398 + const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr); 1.2399 + mLineBreaker.AppendText(hyphenationLanguage, text + offset, 1.2400 + length, flags, sink); 1.2401 + } else { 1.2402 + const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr); 1.2403 + mLineBreaker.AppendText(hyphenationLanguage, text + offset, 1.2404 + length, flags, sink); 1.2405 + } 1.2406 + } 1.2407 + 1.2408 + iter = iterNext; 1.2409 + } 1.2410 +} 1.2411 + 1.2412 +// Find the flow corresponding to aContent in aUserData 1.2413 +static inline TextRunMappedFlow* 1.2414 +FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent) 1.2415 +{ 1.2416 + // Find the flow that contains us 1.2417 + int32_t i = aUserData->mLastFlowIndex; 1.2418 + int32_t delta = 1; 1.2419 + int32_t sign = 1; 1.2420 + // Search starting at the current position and examine close-by 1.2421 + // positions first, moving further and further away as we go. 1.2422 + while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) { 1.2423 + TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; 1.2424 + if (flow->mStartFrame->GetContent() == aContent) { 1.2425 + return flow; 1.2426 + } 1.2427 + 1.2428 + i += delta; 1.2429 + sign = -sign; 1.2430 + delta = -delta + sign; 1.2431 + } 1.2432 + 1.2433 + // We ran into an array edge. Add |delta| to |i| once more to get 1.2434 + // back to the side where we still need to search, then step in 1.2435 + // the |sign| direction. 1.2436 + i += delta; 1.2437 + if (sign > 0) { 1.2438 + for (; i < int32_t(aUserData->mMappedFlowCount); ++i) { 1.2439 + TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; 1.2440 + if (flow->mStartFrame->GetContent() == aContent) { 1.2441 + return flow; 1.2442 + } 1.2443 + } 1.2444 + } else { 1.2445 + for (; i >= 0; --i) { 1.2446 + TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; 1.2447 + if (flow->mStartFrame->GetContent() == aContent) { 1.2448 + return flow; 1.2449 + } 1.2450 + } 1.2451 + } 1.2452 + 1.2453 + return nullptr; 1.2454 +} 1.2455 + 1.2456 +void 1.2457 +BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation) 1.2458 +{ 1.2459 + for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { 1.2460 + MappedFlow* mappedFlow = &mMappedFlows[i]; 1.2461 + nsTextFrame* startFrame = mappedFlow->mStartFrame; 1.2462 + nsTextFrame* endFrame = mappedFlow->mEndFrame; 1.2463 + nsTextFrame* f; 1.2464 + for (f = startFrame; f != endFrame; 1.2465 + f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 1.2466 +#ifdef DEBUG_roc 1.2467 + if (f->GetTextRun(mWhichTextRun)) { 1.2468 + gfxTextRun* textRun = f->GetTextRun(mWhichTextRun); 1.2469 + if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { 1.2470 + if (mMappedFlows[0].mStartFrame != static_cast<nsTextFrame*>(textRun->GetUserData())) { 1.2471 + NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!"); 1.2472 + } 1.2473 + } else { 1.2474 + TextRunUserData* userData = 1.2475 + static_cast<TextRunUserData*>(textRun->GetUserData()); 1.2476 + 1.2477 + if (userData->mMappedFlowCount >= mMappedFlows.Length() || 1.2478 + userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != 1.2479 + mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) { 1.2480 + NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!"); 1.2481 + } 1.2482 + } 1.2483 + } 1.2484 +#endif 1.2485 + 1.2486 + gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun); 1.2487 + if (oldTextRun) { 1.2488 + nsTextFrame* firstFrame = nullptr; 1.2489 + uint32_t startOffset = 0; 1.2490 + if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { 1.2491 + firstFrame = static_cast<nsTextFrame*>(oldTextRun->GetUserData()); 1.2492 + } 1.2493 + else { 1.2494 + TextRunUserData* userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData()); 1.2495 + firstFrame = userData->mMappedFlows[0].mStartFrame; 1.2496 + if (MOZ_UNLIKELY(f != firstFrame)) { 1.2497 + TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent()); 1.2498 + if (flow) { 1.2499 + startOffset = flow->mDOMOffsetToBeforeTransformOffset; 1.2500 + } 1.2501 + else { 1.2502 + NS_ERROR("Can't find flow containing frame 'f'"); 1.2503 + } 1.2504 + } 1.2505 + } 1.2506 + 1.2507 + // Optimization: if |f| is the first frame in the flow then there are no 1.2508 + // prev-continuations that use |oldTextRun|. 1.2509 + nsTextFrame* clearFrom = nullptr; 1.2510 + if (MOZ_UNLIKELY(f != firstFrame)) { 1.2511 + // If all the frames in the mapped flow starting at |f| (inclusive) 1.2512 + // are empty then we let the prev-continuations keep the old text run. 1.2513 + gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset()); 1.2514 + uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset()); 1.2515 + clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr; 1.2516 + } 1.2517 + f->ClearTextRun(clearFrom, mWhichTextRun); 1.2518 + 1.2519 +#ifdef DEBUG 1.2520 + if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) { 1.2521 + // oldTextRun was destroyed - assert that we don't reference it. 1.2522 + for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) { 1.2523 + NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun, 1.2524 + "destroyed text run is still in use"); 1.2525 + } 1.2526 + } 1.2527 +#endif 1.2528 + } 1.2529 + f->SetTextRun(aTextRun, mWhichTextRun, aInflation); 1.2530 + } 1.2531 + // Set this bit now; we can't set it any earlier because 1.2532 + // f->ClearTextRun() might clear it out. 1.2533 + nsFrameState whichTextRunState = 1.2534 + startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun 1.2535 + ? TEXT_IN_TEXTRUN_USER_DATA 1.2536 + : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; 1.2537 + startFrame->AddStateBits(whichTextRunState); 1.2538 + } 1.2539 +} 1.2540 + 1.2541 +NS_QUERYFRAME_HEAD(nsTextFrame) 1.2542 + NS_QUERYFRAME_ENTRY(nsTextFrame) 1.2543 +NS_QUERYFRAME_TAIL_INHERITING(nsTextFrameBase) 1.2544 + 1.2545 +gfxSkipCharsIterator 1.2546 +nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun, 1.2547 + gfxContext* aReferenceContext, 1.2548 + nsIFrame* aLineContainer, 1.2549 + const nsLineList::iterator* aLine, 1.2550 + uint32_t* aFlowEndInTextRun) 1.2551 +{ 1.2552 + gfxTextRun *textRun = GetTextRun(aWhichTextRun); 1.2553 + if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) { 1.2554 + if (textRun->GetExpirationState()->IsTracked()) { 1.2555 + gTextRuns->MarkUsed(textRun); 1.2556 + } 1.2557 + } else { 1.2558 + nsRefPtr<gfxContext> ctx = aReferenceContext; 1.2559 + if (!ctx) { 1.2560 + ctx = CreateReferenceThebesContext(this); 1.2561 + } 1.2562 + if (ctx) { 1.2563 + BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun); 1.2564 + } 1.2565 + textRun = GetTextRun(aWhichTextRun); 1.2566 + if (!textRun) { 1.2567 + // A text run was not constructed for this frame. This is bad. The caller 1.2568 + // will check mTextRun. 1.2569 + static const gfxSkipChars emptySkipChars; 1.2570 + return gfxSkipCharsIterator(emptySkipChars, 0); 1.2571 + } 1.2572 + TabWidthStore* tabWidths = 1.2573 + static_cast<TabWidthStore*>(Properties().Get(TabWidthProperty())); 1.2574 + if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) { 1.2575 + Properties().Delete(TabWidthProperty()); 1.2576 + } 1.2577 + } 1.2578 + 1.2579 + if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { 1.2580 + if (aFlowEndInTextRun) { 1.2581 + *aFlowEndInTextRun = textRun->GetLength(); 1.2582 + } 1.2583 + return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset); 1.2584 + } 1.2585 + 1.2586 + TextRunUserData* userData = static_cast<TextRunUserData*>(textRun->GetUserData()); 1.2587 + TextRunMappedFlow* flow = FindFlowForContent(userData, mContent); 1.2588 + if (flow) { 1.2589 + // Since textruns can only contain one flow for a given content element, 1.2590 + // this must be our flow. 1.2591 + uint32_t flowIndex = flow - userData->mMappedFlows; 1.2592 + userData->mLastFlowIndex = flowIndex; 1.2593 + gfxSkipCharsIterator iter(textRun->GetSkipChars(), 1.2594 + flow->mDOMOffsetToBeforeTransformOffset, mContentOffset); 1.2595 + if (aFlowEndInTextRun) { 1.2596 + if (flowIndex + 1 < userData->mMappedFlowCount) { 1.2597 + gfxSkipCharsIterator end(textRun->GetSkipChars()); 1.2598 + *aFlowEndInTextRun = end.ConvertOriginalToSkipped( 1.2599 + flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset); 1.2600 + } else { 1.2601 + *aFlowEndInTextRun = textRun->GetLength(); 1.2602 + } 1.2603 + } 1.2604 + return iter; 1.2605 + } 1.2606 + 1.2607 + NS_ERROR("Can't find flow containing this frame???"); 1.2608 + static const gfxSkipChars emptySkipChars; 1.2609 + return gfxSkipCharsIterator(emptySkipChars, 0); 1.2610 +} 1.2611 + 1.2612 +static uint32_t 1.2613 +GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText, 1.2614 + uint32_t aStart, uint32_t aEnd, 1.2615 + gfxSkipCharsIterator* aIterator) 1.2616 +{ 1.2617 + aIterator->SetSkippedOffset(aEnd); 1.2618 + while (aIterator->GetSkippedOffset() > aStart) { 1.2619 + aIterator->AdvanceSkipped(-1); 1.2620 + if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText)) 1.2621 + return aIterator->GetSkippedOffset() + 1; 1.2622 + } 1.2623 + return aStart; 1.2624 +} 1.2625 + 1.2626 +nsTextFrame::TrimmedOffsets 1.2627 +nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, 1.2628 + bool aTrimAfter, bool aPostReflow) 1.2629 +{ 1.2630 + NS_ASSERTION(mTextRun, "Need textrun here"); 1.2631 + if (aPostReflow) { 1.2632 + // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS 1.2633 + // to be set correctly. If our parent wasn't reflowed due to the frame 1.2634 + // tree being too deep then the return value doesn't matter. 1.2635 + NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || 1.2636 + (GetParent()->GetStateBits() & 1.2637 + NS_FRAME_TOO_DEEP_IN_FRAME_TREE), 1.2638 + "Can only call this on frames that have been reflowed"); 1.2639 + NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW), 1.2640 + "Can only call this on frames that are not being reflowed"); 1.2641 + } 1.2642 + 1.2643 + TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() }; 1.2644 + const nsStyleText* textStyle = StyleText(); 1.2645 + // Note that pre-line newlines should still allow us to trim spaces 1.2646 + // for display 1.2647 + if (textStyle->WhiteSpaceIsSignificant()) 1.2648 + return offsets; 1.2649 + 1.2650 + if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) { 1.2651 + int32_t whitespaceCount = 1.2652 + GetTrimmableWhitespaceCount(aFrag, 1.2653 + offsets.mStart, offsets.mLength, 1); 1.2654 + offsets.mStart += whitespaceCount; 1.2655 + offsets.mLength -= whitespaceCount; 1.2656 + } 1.2657 + 1.2658 + if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) { 1.2659 + // This treats a trailing 'pre-line' newline as trimmable. That's fine, 1.2660 + // it's actually what we want since we want whitespace before it to 1.2661 + // be trimmed. 1.2662 + int32_t whitespaceCount = 1.2663 + GetTrimmableWhitespaceCount(aFrag, 1.2664 + offsets.GetEnd() - 1, offsets.mLength, -1); 1.2665 + offsets.mLength -= whitespaceCount; 1.2666 + } 1.2667 + return offsets; 1.2668 +} 1.2669 + 1.2670 +/* 1.2671 + * Currently only Unicode characters below 0x10000 have their spacing modified 1.2672 + * by justification. If characters above 0x10000 turn out to need 1.2673 + * justification spacing, that will require extra work. Currently, 1.2674 + * this function must not include 0xd800 to 0xdbff because these characters 1.2675 + * are surrogates. 1.2676 + */ 1.2677 +static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos, 1.2678 + bool aLangIsCJ) 1.2679 +{ 1.2680 + char16_t ch = aFrag->CharAt(aPos); 1.2681 + if (ch == '\n' || ch == '\t' || ch == '\r') 1.2682 + return true; 1.2683 + if (ch == ' ' || ch == CH_NBSP) { 1.2684 + // Don't justify spaces that are combined with diacriticals 1.2685 + if (!aFrag->Is2b()) 1.2686 + return true; 1.2687 + return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( 1.2688 + aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); 1.2689 + } 1.2690 + if (ch < 0x2150u) 1.2691 + return false; 1.2692 + if (aLangIsCJ && ( 1.2693 + (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators 1.2694 + (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics 1.2695 + (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats 1.2696 + (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, 1.2697 + // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators, 1.2698 + // Miscellaneous Symbols and Arrows 1.2699 + (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement, 1.2700 + // Ideographic Description Characters, CJK Symbols and Punctuation, 1.2701 + // Hiragana, Katakana, Bopomofo 1.2702 + (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, 1.2703 + // Enclosed CJK Letters and Months, CJK Compatibility, 1.2704 + // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, 1.2705 + // CJK Unified Ideographs, Yi Syllables, Yi Radicals 1.2706 + (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs 1.2707 + (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part) 1.2708 + )) 1.2709 + return true; 1.2710 + return false; 1.2711 +} 1.2712 + 1.2713 +void 1.2714 +nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics) 1.2715 +{ 1.2716 + aMetrics.Width() = 0; 1.2717 + aMetrics.Height() = 0; 1.2718 + aMetrics.SetTopAscent(0); 1.2719 + mAscent = 0; 1.2720 +} 1.2721 + 1.2722 +static int32_t FindChar(const nsTextFragment* frag, 1.2723 + int32_t aOffset, int32_t aLength, char16_t ch) 1.2724 +{ 1.2725 + int32_t i = 0; 1.2726 + if (frag->Is2b()) { 1.2727 + const char16_t* str = frag->Get2b() + aOffset; 1.2728 + for (; i < aLength; ++i) { 1.2729 + if (*str == ch) 1.2730 + return i + aOffset; 1.2731 + ++str; 1.2732 + } 1.2733 + } else { 1.2734 + if (uint16_t(ch) <= 0xFF) { 1.2735 + const char* str = frag->Get1b() + aOffset; 1.2736 + const void* p = memchr(str, ch, aLength); 1.2737 + if (p) 1.2738 + return (static_cast<const char*>(p) - str) + aOffset; 1.2739 + } 1.2740 + } 1.2741 + return -1; 1.2742 +} 1.2743 + 1.2744 +static bool IsChineseOrJapanese(nsIFrame* aFrame) 1.2745 +{ 1.2746 + nsIAtom* language = aFrame->StyleFont()->mLanguage; 1.2747 + if (!language) { 1.2748 + return false; 1.2749 + } 1.2750 + const char16_t *lang = language->GetUTF16String(); 1.2751 + return (!nsCRT::strncmp(lang, MOZ_UTF16("ja"), 2) || 1.2752 + !nsCRT::strncmp(lang, MOZ_UTF16("zh"), 2)) && 1.2753 + (language->GetLength() == 2 || lang[2] == '-'); 1.2754 +} 1.2755 + 1.2756 +#ifdef DEBUG 1.2757 +static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength, 1.2758 + uint32_t aOffset, uint32_t aLength) { 1.2759 + if (aStart.GetSkippedOffset() > aOffset) 1.2760 + return false; 1.2761 + if (aContentLength == INT32_MAX) 1.2762 + return true; 1.2763 + gfxSkipCharsIterator iter(aStart); 1.2764 + iter.AdvanceOriginal(aContentLength); 1.2765 + return iter.GetSkippedOffset() >= aOffset + aLength; 1.2766 +} 1.2767 +#endif 1.2768 + 1.2769 +class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider { 1.2770 +public: 1.2771 + /** 1.2772 + * Use this constructor for reflow, when we don't know what text is 1.2773 + * really mapped by the frame and we have a lot of other data around. 1.2774 + * 1.2775 + * @param aLength can be INT32_MAX to indicate we cover all the text 1.2776 + * associated with aFrame up to where its flow chain ends in the given 1.2777 + * textrun. If INT32_MAX is passed, justification and hyphen-related methods 1.2778 + * cannot be called, nor can GetOriginalLength(). 1.2779 + */ 1.2780 + PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle, 1.2781 + const nsTextFragment* aFrag, nsTextFrame* aFrame, 1.2782 + const gfxSkipCharsIterator& aStart, int32_t aLength, 1.2783 + nsIFrame* aLineContainer, 1.2784 + nscoord aOffsetFromBlockOriginForTabs, 1.2785 + nsTextFrame::TextRunType aWhichTextRun) 1.2786 + : mTextRun(aTextRun), mFontGroup(nullptr), 1.2787 + mTextStyle(aTextStyle), mFrag(aFrag), 1.2788 + mLineContainer(aLineContainer), 1.2789 + mFrame(aFrame), mStart(aStart), mTempIterator(aStart), 1.2790 + mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0), 1.2791 + mLength(aLength), 1.2792 + mWordSpacing(WordSpacing(aFrame, aTextStyle)), 1.2793 + mLetterSpacing(LetterSpacing(aFrame, aTextStyle)), 1.2794 + mJustificationSpacing(0), 1.2795 + mHyphenWidth(-1), 1.2796 + mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), 1.2797 + mReflowing(true), 1.2798 + mWhichTextRun(aWhichTextRun) 1.2799 + { 1.2800 + NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?"); 1.2801 + } 1.2802 + 1.2803 + /** 1.2804 + * Use this constructor after the frame has been reflowed and we don't 1.2805 + * have other data around. Gets everything from the frame. EnsureTextRun 1.2806 + * *must* be called before this!!! 1.2807 + */ 1.2808 + PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, 1.2809 + nsTextFrame::TextRunType aWhichTextRun) 1.2810 + : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr), 1.2811 + mTextStyle(aFrame->StyleText()), 1.2812 + mFrag(aFrame->GetContent()->GetText()), 1.2813 + mLineContainer(nullptr), 1.2814 + mFrame(aFrame), mStart(aStart), mTempIterator(aStart), 1.2815 + mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0), 1.2816 + mLength(aFrame->GetContentLength()), 1.2817 + mWordSpacing(WordSpacing(aFrame)), 1.2818 + mLetterSpacing(LetterSpacing(aFrame)), 1.2819 + mJustificationSpacing(0), 1.2820 + mHyphenWidth(-1), 1.2821 + mOffsetFromBlockOriginForTabs(0), 1.2822 + mReflowing(false), 1.2823 + mWhichTextRun(aWhichTextRun) 1.2824 + { 1.2825 + NS_ASSERTION(mTextRun, "Textrun not initialized!"); 1.2826 + } 1.2827 + 1.2828 + // Call this after construction if you're not going to reflow the text 1.2829 + void InitializeForDisplay(bool aTrimAfter); 1.2830 + 1.2831 + void InitializeForMeasure(); 1.2832 + 1.2833 + virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing); 1.2834 + virtual gfxFloat GetHyphenWidth(); 1.2835 + virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, 1.2836 + bool* aBreakBefore); 1.2837 + virtual int8_t GetHyphensOption() { 1.2838 + return mTextStyle->mHyphens; 1.2839 + } 1.2840 + 1.2841 + virtual already_AddRefed<gfxContext> GetContext() { 1.2842 + return CreateReferenceThebesContext(GetFrame()); 1.2843 + } 1.2844 + 1.2845 + virtual uint32_t GetAppUnitsPerDevUnit() { 1.2846 + return mTextRun->GetAppUnitsPerDevUnit(); 1.2847 + } 1.2848 + 1.2849 + void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing, 1.2850 + bool aIgnoreTabs); 1.2851 + 1.2852 + /** 1.2853 + * Count the number of justifiable characters in the given DOM range 1.2854 + */ 1.2855 + uint32_t ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength); 1.2856 + /** 1.2857 + * Find the start and end of the justifiable characters. Does not depend on the 1.2858 + * position of aStart or aEnd, although it's most efficient if they are near the 1.2859 + * start and end of the text frame. 1.2860 + */ 1.2861 + void FindJustificationRange(gfxSkipCharsIterator* aStart, 1.2862 + gfxSkipCharsIterator* aEnd); 1.2863 + 1.2864 + const nsStyleText* StyleText() { return mTextStyle; } 1.2865 + nsTextFrame* GetFrame() { return mFrame; } 1.2866 + // This may not be equal to the frame offset/length in because we may have 1.2867 + // adjusted for whitespace trimming according to the state bits set in the frame 1.2868 + // (for the static provider) 1.2869 + const gfxSkipCharsIterator& GetStart() { return mStart; } 1.2870 + // May return INT32_MAX if that was given to the constructor 1.2871 + uint32_t GetOriginalLength() { 1.2872 + NS_ASSERTION(mLength != INT32_MAX, "Length not known"); 1.2873 + return mLength; 1.2874 + } 1.2875 + const nsTextFragment* GetFragment() { return mFrag; } 1.2876 + 1.2877 + gfxFontGroup* GetFontGroup() { 1.2878 + if (!mFontGroup) 1.2879 + InitFontGroupAndFontMetrics(); 1.2880 + return mFontGroup; 1.2881 + } 1.2882 + 1.2883 + nsFontMetrics* GetFontMetrics() { 1.2884 + if (!mFontMetrics) 1.2885 + InitFontGroupAndFontMetrics(); 1.2886 + return mFontMetrics; 1.2887 + } 1.2888 + 1.2889 + void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength); 1.2890 + 1.2891 + const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; } 1.2892 + 1.2893 +protected: 1.2894 + void SetupJustificationSpacing(bool aPostReflow); 1.2895 + 1.2896 + void InitFontGroupAndFontMetrics() { 1.2897 + float inflation = (mWhichTextRun == nsTextFrame::eInflated) 1.2898 + ? mFrame->GetFontSizeInflation() : 1.0f; 1.2899 + mFontGroup = GetFontGroupForFrame(mFrame, inflation, 1.2900 + getter_AddRefs(mFontMetrics)); 1.2901 + } 1.2902 + 1.2903 + gfxTextRun* mTextRun; 1.2904 + gfxFontGroup* mFontGroup; 1.2905 + nsRefPtr<nsFontMetrics> mFontMetrics; 1.2906 + const nsStyleText* mTextStyle; 1.2907 + const nsTextFragment* mFrag; 1.2908 + nsIFrame* mLineContainer; 1.2909 + nsTextFrame* mFrame; 1.2910 + gfxSkipCharsIterator mStart; // Offset in original and transformed string 1.2911 + gfxSkipCharsIterator mTempIterator; 1.2912 + 1.2913 + // Either null, or pointing to the frame's TabWidthProperty. 1.2914 + TabWidthStore* mTabWidths; 1.2915 + // How far we've done tab-width calculation; this is ONLY valid when 1.2916 + // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead). 1.2917 + // It's a DOM offset relative to the current frame's offset. 1.2918 + uint32_t mTabWidthsAnalyzedLimit; 1.2919 + 1.2920 + int32_t mLength; // DOM string length, may be INT32_MAX 1.2921 + gfxFloat mWordSpacing; // space for each whitespace char 1.2922 + gfxFloat mLetterSpacing; // space for each letter 1.2923 + gfxFloat mJustificationSpacing; 1.2924 + gfxFloat mHyphenWidth; 1.2925 + gfxFloat mOffsetFromBlockOriginForTabs; 1.2926 + bool mReflowing; 1.2927 + nsTextFrame::TextRunType mWhichTextRun; 1.2928 +}; 1.2929 + 1.2930 +uint32_t 1.2931 +PropertyProvider::ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength) 1.2932 +{ 1.2933 + // Scan non-skipped characters and count justifiable chars. 1.2934 + nsSkipCharsRunIterator 1.2935 + run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); 1.2936 + run.SetOriginalOffset(aOffset); 1.2937 + uint32_t justifiableChars = 0; 1.2938 + bool isCJK = IsChineseOrJapanese(mFrame); 1.2939 + while (run.NextRun()) { 1.2940 + for (int32_t i = 0; i < run.GetRunLength(); ++i) { 1.2941 + justifiableChars += 1.2942 + IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK); 1.2943 + } 1.2944 + } 1.2945 + return justifiableChars; 1.2946 +} 1.2947 + 1.2948 +/** 1.2949 + * Finds the offset of the first character of the cluster containing aPos 1.2950 + */ 1.2951 +static void FindClusterStart(gfxTextRun* aTextRun, int32_t aOriginalStart, 1.2952 + gfxSkipCharsIterator* aPos) 1.2953 +{ 1.2954 + while (aPos->GetOriginalOffset() > aOriginalStart) { 1.2955 + if (aPos->IsOriginalCharSkipped() || 1.2956 + aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { 1.2957 + break; 1.2958 + } 1.2959 + aPos->AdvanceOriginal(-1); 1.2960 + } 1.2961 +} 1.2962 + 1.2963 +/** 1.2964 + * Finds the offset of the last character of the cluster containing aPos 1.2965 + */ 1.2966 +static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd, 1.2967 + gfxSkipCharsIterator* aPos) 1.2968 +{ 1.2969 + NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd, 1.2970 + "character outside string"); 1.2971 + aPos->AdvanceOriginal(1); 1.2972 + while (aPos->GetOriginalOffset() < aOriginalEnd) { 1.2973 + if (aPos->IsOriginalCharSkipped() || 1.2974 + aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { 1.2975 + break; 1.2976 + } 1.2977 + aPos->AdvanceOriginal(1); 1.2978 + } 1.2979 + aPos->AdvanceOriginal(-1); 1.2980 +} 1.2981 + 1.2982 +// aStart, aLength in transformed string offsets 1.2983 +void 1.2984 +PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength, 1.2985 + Spacing* aSpacing) 1.2986 +{ 1.2987 + GetSpacingInternal(aStart, aLength, aSpacing, 1.2988 + (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0); 1.2989 +} 1.2990 + 1.2991 +static bool 1.2992 +CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset) 1.2993 +{ 1.2994 + if (aOffset + 1 >= aTextRun->GetLength()) 1.2995 + return true; 1.2996 + return aTextRun->IsClusterStart(aOffset + 1) && 1.2997 + aTextRun->IsLigatureGroupStart(aOffset + 1); 1.2998 +} 1.2999 + 1.3000 +void 1.3001 +PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength, 1.3002 + Spacing* aSpacing, bool aIgnoreTabs) 1.3003 +{ 1.3004 + NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); 1.3005 + 1.3006 + uint32_t index; 1.3007 + for (index = 0; index < aLength; ++index) { 1.3008 + aSpacing[index].mBefore = 0.0; 1.3009 + aSpacing[index].mAfter = 0.0; 1.3010 + } 1.3011 + 1.3012 + // Find our offset into the original+transformed string 1.3013 + gfxSkipCharsIterator start(mStart); 1.3014 + start.SetSkippedOffset(aStart); 1.3015 + 1.3016 + // First, compute the word and letter spacing 1.3017 + if (mWordSpacing || mLetterSpacing) { 1.3018 + // Iterate over non-skipped characters 1.3019 + nsSkipCharsRunIterator 1.3020 + run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); 1.3021 + while (run.NextRun()) { 1.3022 + uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart; 1.3023 + gfxSkipCharsIterator iter = run.GetPos(); 1.3024 + for (int32_t i = 0; i < run.GetRunLength(); ++i) { 1.3025 + if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) { 1.3026 + // End of a cluster, not in a ligature: put letter-spacing after it 1.3027 + aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing; 1.3028 + } 1.3029 + if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), 1.3030 + mTextStyle)) { 1.3031 + // It kinda sucks, but space characters can be part of clusters, 1.3032 + // and even still be whitespace (I think!) 1.3033 + iter.SetSkippedOffset(run.GetSkippedOffset() + i); 1.3034 + FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), 1.3035 + &iter); 1.3036 + aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing; 1.3037 + } 1.3038 + } 1.3039 + } 1.3040 + } 1.3041 + 1.3042 + // Ignore tab spacing rather than computing it, if the tab size is 0 1.3043 + if (!aIgnoreTabs) 1.3044 + aIgnoreTabs = mFrame->StyleText()->mTabSize == 0; 1.3045 + 1.3046 + // Now add tab spacing, if there is any 1.3047 + if (!aIgnoreTabs) { 1.3048 + CalcTabWidths(aStart, aLength); 1.3049 + if (mTabWidths) { 1.3050 + mTabWidths->ApplySpacing(aSpacing, 1.3051 + aStart - mStart.GetSkippedOffset(), aLength); 1.3052 + } 1.3053 + } 1.3054 + 1.3055 + // Now add in justification spacing 1.3056 + if (mJustificationSpacing) { 1.3057 + gfxFloat halfJustificationSpace = mJustificationSpacing/2; 1.3058 + // Scan non-skipped characters and adjust justifiable chars, adding 1.3059 + // justification space on either side of the cluster 1.3060 + bool isCJK = IsChineseOrJapanese(mFrame); 1.3061 + gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart); 1.3062 + FindJustificationRange(&justificationStart, &justificationEnd); 1.3063 + 1.3064 + nsSkipCharsRunIterator 1.3065 + run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); 1.3066 + while (run.NextRun()) { 1.3067 + gfxSkipCharsIterator iter = run.GetPos(); 1.3068 + int32_t runOriginalOffset = run.GetOriginalOffset(); 1.3069 + for (int32_t i = 0; i < run.GetRunLength(); ++i) { 1.3070 + int32_t iterOriginalOffset = runOriginalOffset + i; 1.3071 + if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) { 1.3072 + iter.SetOriginalOffset(iterOriginalOffset); 1.3073 + FindClusterStart(mTextRun, runOriginalOffset, &iter); 1.3074 + uint32_t clusterFirstChar = iter.GetSkippedOffset(); 1.3075 + FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter); 1.3076 + uint32_t clusterLastChar = iter.GetSkippedOffset(); 1.3077 + // Only apply justification to characters before justificationEnd 1.3078 + if (clusterFirstChar >= justificationStart.GetSkippedOffset() && 1.3079 + clusterLastChar < justificationEnd.GetSkippedOffset()) { 1.3080 + aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace; 1.3081 + aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace; 1.3082 + } 1.3083 + } 1.3084 + } 1.3085 + } 1.3086 + } 1.3087 +} 1.3088 + 1.3089 +static gfxFloat 1.3090 +ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun) 1.3091 +{ 1.3092 + // Get the number of spaces from CSS -moz-tab-size 1.3093 + const nsStyleText* textStyle = aFrame->StyleText(); 1.3094 + 1.3095 + // Round the space width when converting to appunits the same way 1.3096 + // textruns do 1.3097 + gfxFloat spaceWidthAppUnits = 1.3098 + NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup()).spaceWidth * 1.3099 + aTextRun->GetAppUnitsPerDevUnit()); 1.3100 + return textStyle->mTabSize * spaceWidthAppUnits; 1.3101 +} 1.3102 + 1.3103 +// aX and the result are in whole appunits. 1.3104 +static gfxFloat 1.3105 +AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame, 1.3106 + gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth) 1.3107 +{ 1.3108 + if (*aCachedTabWidth < 0) { 1.3109 + *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun); 1.3110 + } 1.3111 + 1.3112 + // Advance aX to the next multiple of *aCachedTabWidth. We must advance 1.3113 + // by at least 1 appunit. 1.3114 + // XXX should we make this 1 CSS pixel? 1.3115 + return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth); 1.3116 +} 1.3117 + 1.3118 +void 1.3119 +PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength) 1.3120 +{ 1.3121 + if (!mTabWidths) { 1.3122 + if (mReflowing && !mLineContainer) { 1.3123 + // Intrinsic width computation does its own tab processing. We 1.3124 + // just don't do anything here. 1.3125 + return; 1.3126 + } 1.3127 + if (!mReflowing) { 1.3128 + mTabWidths = static_cast<TabWidthStore*> 1.3129 + (mFrame->Properties().Get(TabWidthProperty())); 1.3130 +#ifdef DEBUG 1.3131 + // If we're not reflowing, we should have already computed the 1.3132 + // tab widths; check that they're available as far as the last 1.3133 + // tab character present (if any) 1.3134 + for (uint32_t i = aStart + aLength; i > aStart; --i) { 1.3135 + if (mTextRun->CharIsTab(i - 1)) { 1.3136 + uint32_t startOffset = mStart.GetSkippedOffset(); 1.3137 + NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i, 1.3138 + "Precomputed tab widths are missing!"); 1.3139 + break; 1.3140 + } 1.3141 + } 1.3142 +#endif 1.3143 + return; 1.3144 + } 1.3145 + } 1.3146 + 1.3147 + uint32_t startOffset = mStart.GetSkippedOffset(); 1.3148 + MOZ_ASSERT(aStart >= startOffset, "wrong start offset"); 1.3149 + MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end"); 1.3150 + uint32_t tabsEnd = 1.3151 + (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset; 1.3152 + if (tabsEnd < aStart + aLength) { 1.3153 + NS_ASSERTION(mReflowing, 1.3154 + "We need precomputed tab widths, but don't have enough."); 1.3155 + 1.3156 + gfxFloat tabWidth = -1; 1.3157 + for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) { 1.3158 + Spacing spacing; 1.3159 + GetSpacingInternal(i, 1, &spacing, true); 1.3160 + mOffsetFromBlockOriginForTabs += spacing.mBefore; 1.3161 + 1.3162 + if (!mTextRun->CharIsTab(i)) { 1.3163 + if (mTextRun->IsClusterStart(i)) { 1.3164 + uint32_t clusterEnd = i + 1; 1.3165 + while (clusterEnd < mTextRun->GetLength() && 1.3166 + !mTextRun->IsClusterStart(clusterEnd)) { 1.3167 + ++clusterEnd; 1.3168 + } 1.3169 + mOffsetFromBlockOriginForTabs += 1.3170 + mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr); 1.3171 + } 1.3172 + } else { 1.3173 + if (!mTabWidths) { 1.3174 + mTabWidths = new TabWidthStore(mFrame->GetContentOffset()); 1.3175 + mFrame->Properties().Set(TabWidthProperty(), mTabWidths); 1.3176 + } 1.3177 + double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, 1.3178 + mFrame, mTextRun, &tabWidth); 1.3179 + mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset, 1.3180 + NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs))); 1.3181 + mOffsetFromBlockOriginForTabs = nextTab; 1.3182 + } 1.3183 + 1.3184 + mOffsetFromBlockOriginForTabs += spacing.mAfter; 1.3185 + } 1.3186 + 1.3187 + if (mTabWidths) { 1.3188 + mTabWidths->mLimit = aStart + aLength - startOffset; 1.3189 + } 1.3190 + } 1.3191 + 1.3192 + if (!mTabWidths) { 1.3193 + // Delete any stale property that may be left on the frame 1.3194 + mFrame->Properties().Delete(TabWidthProperty()); 1.3195 + mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit, 1.3196 + aStart + aLength - startOffset); 1.3197 + } 1.3198 +} 1.3199 + 1.3200 +gfxFloat 1.3201 +PropertyProvider::GetHyphenWidth() 1.3202 +{ 1.3203 + if (mHyphenWidth < 0) { 1.3204 + mHyphenWidth = GetFontGroup()->GetHyphenWidth(this); 1.3205 + } 1.3206 + return mHyphenWidth + mLetterSpacing; 1.3207 +} 1.3208 + 1.3209 +void 1.3210 +PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, 1.3211 + bool* aBreakBefore) 1.3212 +{ 1.3213 + NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); 1.3214 + NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); 1.3215 + 1.3216 + if (!mTextStyle->WhiteSpaceCanWrap(mFrame) || 1.3217 + mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE) 1.3218 + { 1.3219 + memset(aBreakBefore, false, aLength*sizeof(bool)); 1.3220 + return; 1.3221 + } 1.3222 + 1.3223 + // Iterate through the original-string character runs 1.3224 + nsSkipCharsRunIterator 1.3225 + run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); 1.3226 + run.SetSkippedOffset(aStart); 1.3227 + // We need to visit skipped characters so that we can detect SHY 1.3228 + run.SetVisitSkipped(); 1.3229 + 1.3230 + int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; 1.3231 + bool allowHyphenBreakBeforeNextChar = 1.3232 + prevTrailingCharOffset >= mStart.GetOriginalOffset() && 1.3233 + prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && 1.3234 + mFrag->CharAt(prevTrailingCharOffset) == CH_SHY; 1.3235 + 1.3236 + while (run.NextRun()) { 1.3237 + NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs"); 1.3238 + if (run.IsSkipped()) { 1.3239 + // Check if there's a soft hyphen which would let us hyphenate before 1.3240 + // the next non-skipped character. Don't look at soft hyphens followed 1.3241 + // by other skipped characters, we won't use them. 1.3242 + allowHyphenBreakBeforeNextChar = 1.3243 + mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY; 1.3244 + } else { 1.3245 + int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart; 1.3246 + memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool)); 1.3247 + // Don't allow hyphen breaks at the start of the line 1.3248 + aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar && 1.3249 + (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) || 1.3250 + run.GetSkippedOffset() > mStart.GetSkippedOffset()); 1.3251 + allowHyphenBreakBeforeNextChar = false; 1.3252 + } 1.3253 + } 1.3254 + 1.3255 + if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) { 1.3256 + for (uint32_t i = 0; i < aLength; ++i) { 1.3257 + if (mTextRun->CanHyphenateBefore(aStart + i)) { 1.3258 + aBreakBefore[i] = true; 1.3259 + } 1.3260 + } 1.3261 + } 1.3262 +} 1.3263 + 1.3264 +void 1.3265 +PropertyProvider::InitializeForDisplay(bool aTrimAfter) 1.3266 +{ 1.3267 + nsTextFrame::TrimmedOffsets trimmed = 1.3268 + mFrame->GetTrimmedOffsets(mFrag, aTrimAfter); 1.3269 + mStart.SetOriginalOffset(trimmed.mStart); 1.3270 + mLength = trimmed.mLength; 1.3271 + SetupJustificationSpacing(true); 1.3272 +} 1.3273 + 1.3274 +void 1.3275 +PropertyProvider::InitializeForMeasure() 1.3276 +{ 1.3277 + nsTextFrame::TrimmedOffsets trimmed = 1.3278 + mFrame->GetTrimmedOffsets(mFrag, true, false); 1.3279 + mStart.SetOriginalOffset(trimmed.mStart); 1.3280 + mLength = trimmed.mLength; 1.3281 + SetupJustificationSpacing(false); 1.3282 +} 1.3283 + 1.3284 + 1.3285 +static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart, 1.3286 + const gfxSkipCharsIterator& aEnd) 1.3287 +{ 1.3288 + return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset(); 1.3289 +} 1.3290 + 1.3291 +void 1.3292 +PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart, 1.3293 + gfxSkipCharsIterator* aEnd) 1.3294 +{ 1.3295 + NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); 1.3296 + NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null"); 1.3297 + 1.3298 + aStart->SetOriginalOffset(mStart.GetOriginalOffset()); 1.3299 + aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength); 1.3300 + 1.3301 + // Ignore first cluster at start of line for justification purposes 1.3302 + if (mFrame->GetStateBits() & TEXT_START_OF_LINE) { 1.3303 + while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) { 1.3304 + aStart->AdvanceOriginal(1); 1.3305 + if (!aStart->IsOriginalCharSkipped() && 1.3306 + mTextRun->IsClusterStart(aStart->GetSkippedOffset())) 1.3307 + break; 1.3308 + } 1.3309 + } 1.3310 + 1.3311 + // Ignore trailing cluster at end of line for justification purposes 1.3312 + if (mFrame->GetStateBits() & TEXT_END_OF_LINE) { 1.3313 + while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) { 1.3314 + aEnd->AdvanceOriginal(-1); 1.3315 + if (!aEnd->IsOriginalCharSkipped() && 1.3316 + mTextRun->IsClusterStart(aEnd->GetSkippedOffset())) 1.3317 + break; 1.3318 + } 1.3319 + } 1.3320 +} 1.3321 + 1.3322 +void 1.3323 +PropertyProvider::SetupJustificationSpacing(bool aPostReflow) 1.3324 +{ 1.3325 + NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); 1.3326 + 1.3327 + if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) 1.3328 + return; 1.3329 + 1.3330 + gfxSkipCharsIterator start(mStart), end(mStart); 1.3331 + // We can't just use our mLength here; when InitializeForDisplay is 1.3332 + // called with false for aTrimAfter, we still shouldn't be assigning 1.3333 + // justification space to any trailing whitespace. 1.3334 + nsTextFrame::TrimmedOffsets trimmed = 1.3335 + mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow); 1.3336 + end.AdvanceOriginal(trimmed.mLength); 1.3337 + gfxSkipCharsIterator realEnd(end); 1.3338 + FindJustificationRange(&start, &end); 1.3339 + 1.3340 + int32_t justifiableCharacters = 1.3341 + ComputeJustifiableCharacters(start.GetOriginalOffset(), 1.3342 + end.GetOriginalOffset() - start.GetOriginalOffset()); 1.3343 + if (justifiableCharacters == 0) { 1.3344 + // Nothing to do, nothing is justifiable and we shouldn't have any 1.3345 + // justification space assigned 1.3346 + return; 1.3347 + } 1.3348 + 1.3349 + gfxFloat naturalWidth = 1.3350 + mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(), 1.3351 + GetSkippedDistance(mStart, realEnd), this); 1.3352 + if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) { 1.3353 + naturalWidth += GetHyphenWidth(); 1.3354 + } 1.3355 + gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth; 1.3356 + if (totalJustificationSpace <= 0) { 1.3357 + // No space available 1.3358 + return; 1.3359 + } 1.3360 + 1.3361 + mJustificationSpacing = totalJustificationSpace/justifiableCharacters; 1.3362 +} 1.3363 + 1.3364 +//---------------------------------------------------------------------- 1.3365 + 1.3366 +static nscolor 1.3367 +EnsureDifferentColors(nscolor colorA, nscolor colorB) 1.3368 +{ 1.3369 + if (colorA == colorB) { 1.3370 + nscolor res; 1.3371 + res = NS_RGB(NS_GET_R(colorA) ^ 0xff, 1.3372 + NS_GET_G(colorA) ^ 0xff, 1.3373 + NS_GET_B(colorA) ^ 0xff); 1.3374 + return res; 1.3375 + } 1.3376 + return colorA; 1.3377 +} 1.3378 + 1.3379 +//----------------------------------------------------------------------------- 1.3380 + 1.3381 +nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame) 1.3382 + : mFrame(aFrame), 1.3383 + mPresContext(aFrame->PresContext()), 1.3384 + mInitCommonColors(false), 1.3385 + mInitSelectionColorsAndShadow(false), 1.3386 + mResolveColors(true), 1.3387 + mHasSelectionShadow(false) 1.3388 +{ 1.3389 + for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++) 1.3390 + mSelectionStyle[i].mInit = false; 1.3391 +} 1.3392 + 1.3393 +bool 1.3394 +nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor) 1.3395 +{ 1.3396 + InitCommonColors(); 1.3397 + 1.3398 + // If the combination of selection background color and frame background color 1.3399 + // is sufficient contrast, don't exchange the selection colors. 1.3400 + int32_t backLuminosityDifference = 1.3401 + NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor); 1.3402 + if (backLuminosityDifference >= mSufficientContrast) 1.3403 + return false; 1.3404 + 1.3405 + // Otherwise, we should use the higher-contrast color for the selection 1.3406 + // background color. 1.3407 + int32_t foreLuminosityDifference = 1.3408 + NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor); 1.3409 + if (backLuminosityDifference < foreLuminosityDifference) { 1.3410 + nscolor tmpColor = *aForeColor; 1.3411 + *aForeColor = *aBackColor; 1.3412 + *aBackColor = tmpColor; 1.3413 + return true; 1.3414 + } 1.3415 + return false; 1.3416 +} 1.3417 + 1.3418 +nscolor 1.3419 +nsTextPaintStyle::GetTextColor() 1.3420 +{ 1.3421 + if (mFrame->IsSVGText()) { 1.3422 + if (!mResolveColors) 1.3423 + return NS_SAME_AS_FOREGROUND_COLOR; 1.3424 + 1.3425 + const nsStyleSVG* style = mFrame->StyleSVG(); 1.3426 + switch (style->mFill.mType) { 1.3427 + case eStyleSVGPaintType_None: 1.3428 + return NS_RGBA(0, 0, 0, 0); 1.3429 + case eStyleSVGPaintType_Color: 1.3430 + return nsLayoutUtils::GetColor(mFrame, eCSSProperty_fill); 1.3431 + default: 1.3432 + NS_ERROR("cannot resolve SVG paint to nscolor"); 1.3433 + return NS_RGBA(0, 0, 0, 255); 1.3434 + } 1.3435 + } 1.3436 + return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color); 1.3437 +} 1.3438 + 1.3439 +bool 1.3440 +nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor, 1.3441 + nscolor* aBackColor) 1.3442 +{ 1.3443 + NS_ASSERTION(aForeColor, "aForeColor is null"); 1.3444 + NS_ASSERTION(aBackColor, "aBackColor is null"); 1.3445 + 1.3446 + if (!InitSelectionColorsAndShadow()) 1.3447 + return false; 1.3448 + 1.3449 + *aForeColor = mSelectionTextColor; 1.3450 + *aBackColor = mSelectionBGColor; 1.3451 + return true; 1.3452 +} 1.3453 + 1.3454 +void 1.3455 +nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor, 1.3456 + nscolor* aBackColor) 1.3457 +{ 1.3458 + NS_ASSERTION(aForeColor, "aForeColor is null"); 1.3459 + NS_ASSERTION(aBackColor, "aBackColor is null"); 1.3460 + 1.3461 + nscolor backColor = 1.3462 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground); 1.3463 + nscolor foreColor = 1.3464 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground); 1.3465 + EnsureSufficientContrast(&foreColor, &backColor); 1.3466 + *aForeColor = foreColor; 1.3467 + *aBackColor = backColor; 1.3468 +} 1.3469 + 1.3470 +void 1.3471 +nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) 1.3472 +{ 1.3473 + NS_ASSERTION(aForeColor, "aForeColor is null"); 1.3474 + 1.3475 + nscolor textColor = GetTextColor(); 1.3476 + textColor = NS_RGBA(NS_GET_R(textColor), 1.3477 + NS_GET_G(textColor), 1.3478 + NS_GET_B(textColor), 1.3479 + (uint8_t)(255 * 0.5f)); 1.3480 + // Don't use true alpha color for readability. 1.3481 + InitCommonColors(); 1.3482 + *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor); 1.3483 +} 1.3484 + 1.3485 +void 1.3486 +nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex, 1.3487 + nscolor* aForeColor, 1.3488 + nscolor* aBackColor) 1.3489 +{ 1.3490 + NS_ASSERTION(aForeColor, "aForeColor is null"); 1.3491 + NS_ASSERTION(aBackColor, "aBackColor is null"); 1.3492 + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); 1.3493 + 1.3494 + nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); 1.3495 + *aForeColor = selectionStyle->mTextColor; 1.3496 + *aBackColor = selectionStyle->mBGColor; 1.3497 +} 1.3498 + 1.3499 +bool 1.3500 +nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex, 1.3501 + nscolor* aLineColor, 1.3502 + float* aRelativeSize, 1.3503 + uint8_t* aStyle) 1.3504 +{ 1.3505 + NS_ASSERTION(aLineColor, "aLineColor is null"); 1.3506 + NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); 1.3507 + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); 1.3508 + 1.3509 + nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); 1.3510 + if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE || 1.3511 + selectionStyle->mUnderlineColor == NS_TRANSPARENT || 1.3512 + selectionStyle->mUnderlineRelativeSize <= 0.0f) 1.3513 + return false; 1.3514 + 1.3515 + *aLineColor = selectionStyle->mUnderlineColor; 1.3516 + *aRelativeSize = selectionStyle->mUnderlineRelativeSize; 1.3517 + *aStyle = selectionStyle->mUnderlineStyle; 1.3518 + return true; 1.3519 +} 1.3520 + 1.3521 +void 1.3522 +nsTextPaintStyle::InitCommonColors() 1.3523 +{ 1.3524 + if (mInitCommonColors) 1.3525 + return; 1.3526 + 1.3527 + nsIFrame* bgFrame = 1.3528 + nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame); 1.3529 + NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame."); 1.3530 + nscolor bgColor = 1.3531 + bgFrame->GetVisitedDependentColor(eCSSProperty_background_color); 1.3532 + 1.3533 + nscolor defaultBgColor = mPresContext->DefaultBackgroundColor(); 1.3534 + mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor); 1.3535 + 1.3536 + if (bgFrame->IsThemed()) { 1.3537 + // Assume a native widget has sufficient contrast always 1.3538 + mSufficientContrast = 0; 1.3539 + mInitCommonColors = true; 1.3540 + return; 1.3541 + } 1.3542 + 1.3543 + NS_ASSERTION(NS_GET_A(defaultBgColor) == 255, 1.3544 + "default background color is not opaque"); 1.3545 + 1.3546 + nscolor defaultWindowBackgroundColor = 1.3547 + LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground); 1.3548 + nscolor selectionTextColor = 1.3549 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground); 1.3550 + nscolor selectionBGColor = 1.3551 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground); 1.3552 + 1.3553 + mSufficientContrast = 1.3554 + std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE, 1.3555 + NS_LUMINOSITY_DIFFERENCE(selectionTextColor, 1.3556 + selectionBGColor)), 1.3557 + NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, 1.3558 + selectionBGColor)); 1.3559 + 1.3560 + mInitCommonColors = true; 1.3561 +} 1.3562 + 1.3563 +static Element* 1.3564 +FindElementAncestorForMozSelection(nsIContent* aContent) 1.3565 +{ 1.3566 + NS_ENSURE_TRUE(aContent, nullptr); 1.3567 + while (aContent && aContent->IsInNativeAnonymousSubtree()) { 1.3568 + aContent = aContent->GetBindingParent(); 1.3569 + } 1.3570 + NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?"); 1.3571 + while (aContent && !aContent->IsElement()) { 1.3572 + aContent = aContent->GetParent(); 1.3573 + } 1.3574 + return aContent ? aContent->AsElement() : nullptr; 1.3575 +} 1.3576 + 1.3577 +bool 1.3578 +nsTextPaintStyle::InitSelectionColorsAndShadow() 1.3579 +{ 1.3580 + if (mInitSelectionColorsAndShadow) 1.3581 + return true; 1.3582 + 1.3583 + int16_t selectionFlags; 1.3584 + int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags); 1.3585 + if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) || 1.3586 + selectionStatus < nsISelectionController::SELECTION_ON) { 1.3587 + // Not displaying the normal selection. 1.3588 + // We're not caching this fact, so every call to GetSelectionColors 1.3589 + // will come through here. We could avoid this, but it's not really worth it. 1.3590 + return false; 1.3591 + } 1.3592 + 1.3593 + mInitSelectionColorsAndShadow = true; 1.3594 + 1.3595 + nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame); 1.3596 + Element* selectionElement = 1.3597 + FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent()); 1.3598 + 1.3599 + if (selectionElement && 1.3600 + selectionStatus == nsISelectionController::SELECTION_ON) { 1.3601 + nsRefPtr<nsStyleContext> sc = nullptr; 1.3602 + sc = mPresContext->StyleSet()-> 1.3603 + ProbePseudoElementStyle(selectionElement, 1.3604 + nsCSSPseudoElements::ePseudo_mozSelection, 1.3605 + mFrame->StyleContext()); 1.3606 + // Use -moz-selection pseudo class. 1.3607 + if (sc) { 1.3608 + mSelectionBGColor = 1.3609 + sc->GetVisitedDependentColor(eCSSProperty_background_color); 1.3610 + mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color); 1.3611 + mHasSelectionShadow = 1.3612 + nsRuleNode::HasAuthorSpecifiedRules(sc, 1.3613 + NS_AUTHOR_SPECIFIED_TEXT_SHADOW, 1.3614 + true); 1.3615 + if (mHasSelectionShadow) { 1.3616 + mSelectionShadow = sc->StyleText()->mTextShadow; 1.3617 + } 1.3618 + return true; 1.3619 + } 1.3620 + } 1.3621 + 1.3622 + nscolor selectionBGColor = 1.3623 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground); 1.3624 + 1.3625 + if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) { 1.3626 + mSelectionBGColor = 1.3627 + LookAndFeel::GetColor( 1.3628 + LookAndFeel::eColorID_TextSelectBackgroundAttention); 1.3629 + mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, 1.3630 + selectionBGColor); 1.3631 + } else if (selectionStatus != nsISelectionController::SELECTION_ON) { 1.3632 + mSelectionBGColor = 1.3633 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled); 1.3634 + mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, 1.3635 + selectionBGColor); 1.3636 + } else { 1.3637 + mSelectionBGColor = selectionBGColor; 1.3638 + } 1.3639 + 1.3640 + mSelectionTextColor = 1.3641 + LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground); 1.3642 + 1.3643 + if (mResolveColors) { 1.3644 + // On MacOS X, we don't exchange text color and BG color. 1.3645 + if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { 1.3646 + nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill : 1.3647 + eCSSProperty_color; 1.3648 + nscoord frameColor = mFrame->GetVisitedDependentColor(property); 1.3649 + mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor); 1.3650 + } else { 1.3651 + EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor); 1.3652 + } 1.3653 + } else { 1.3654 + if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { 1.3655 + mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR; 1.3656 + } 1.3657 + } 1.3658 + return true; 1.3659 +} 1.3660 + 1.3661 +nsTextPaintStyle::nsSelectionStyle* 1.3662 +nsTextPaintStyle::GetSelectionStyle(int32_t aIndex) 1.3663 +{ 1.3664 + InitSelectionStyle(aIndex); 1.3665 + return &mSelectionStyle[aIndex]; 1.3666 +} 1.3667 + 1.3668 +struct StyleIDs { 1.3669 + LookAndFeel::ColorID mForeground, mBackground, mLine; 1.3670 + LookAndFeel::IntID mLineStyle; 1.3671 + LookAndFeel::FloatID mLineRelativeSize; 1.3672 +}; 1.3673 +static StyleIDs SelectionStyleIDs[] = { 1.3674 + { LookAndFeel::eColorID_IMERawInputForeground, 1.3675 + LookAndFeel::eColorID_IMERawInputBackground, 1.3676 + LookAndFeel::eColorID_IMERawInputUnderline, 1.3677 + LookAndFeel::eIntID_IMERawInputUnderlineStyle, 1.3678 + LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, 1.3679 + { LookAndFeel::eColorID_IMESelectedRawTextForeground, 1.3680 + LookAndFeel::eColorID_IMESelectedRawTextBackground, 1.3681 + LookAndFeel::eColorID_IMESelectedRawTextUnderline, 1.3682 + LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle, 1.3683 + LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, 1.3684 + { LookAndFeel::eColorID_IMEConvertedTextForeground, 1.3685 + LookAndFeel::eColorID_IMEConvertedTextBackground, 1.3686 + LookAndFeel::eColorID_IMEConvertedTextUnderline, 1.3687 + LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle, 1.3688 + LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, 1.3689 + { LookAndFeel::eColorID_IMESelectedConvertedTextForeground, 1.3690 + LookAndFeel::eColorID_IMESelectedConvertedTextBackground, 1.3691 + LookAndFeel::eColorID_IMESelectedConvertedTextUnderline, 1.3692 + LookAndFeel::eIntID_IMESelectedConvertedTextUnderline, 1.3693 + LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, 1.3694 + { LookAndFeel::eColorID_LAST_COLOR, 1.3695 + LookAndFeel::eColorID_LAST_COLOR, 1.3696 + LookAndFeel::eColorID_SpellCheckerUnderline, 1.3697 + LookAndFeel::eIntID_SpellCheckerUnderlineStyle, 1.3698 + LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize } 1.3699 +}; 1.3700 + 1.3701 +void 1.3702 +nsTextPaintStyle::InitSelectionStyle(int32_t aIndex) 1.3703 +{ 1.3704 + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid"); 1.3705 + nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex]; 1.3706 + if (selectionStyle->mInit) 1.3707 + return; 1.3708 + 1.3709 + StyleIDs* styleIDs = &SelectionStyleIDs[aIndex]; 1.3710 + 1.3711 + nscolor foreColor, backColor; 1.3712 + if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) { 1.3713 + foreColor = NS_SAME_AS_FOREGROUND_COLOR; 1.3714 + } else { 1.3715 + foreColor = LookAndFeel::GetColor(styleIDs->mForeground); 1.3716 + } 1.3717 + if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) { 1.3718 + backColor = NS_TRANSPARENT; 1.3719 + } else { 1.3720 + backColor = LookAndFeel::GetColor(styleIDs->mBackground); 1.3721 + } 1.3722 + 1.3723 + // Convert special color to actual color 1.3724 + NS_ASSERTION(foreColor != NS_TRANSPARENT, 1.3725 + "foreColor cannot be NS_TRANSPARENT"); 1.3726 + NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR, 1.3727 + "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR"); 1.3728 + NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR, 1.3729 + "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR"); 1.3730 + 1.3731 + if (mResolveColors) { 1.3732 + foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor); 1.3733 + 1.3734 + if (NS_GET_A(backColor) > 0) 1.3735 + EnsureSufficientContrast(&foreColor, &backColor); 1.3736 + } 1.3737 + 1.3738 + nscolor lineColor; 1.3739 + float relativeSize; 1.3740 + uint8_t lineStyle; 1.3741 + GetSelectionUnderline(mPresContext, aIndex, 1.3742 + &lineColor, &relativeSize, &lineStyle); 1.3743 + 1.3744 + if (mResolveColors) 1.3745 + lineColor = GetResolvedForeColor(lineColor, foreColor, backColor); 1.3746 + 1.3747 + selectionStyle->mTextColor = foreColor; 1.3748 + selectionStyle->mBGColor = backColor; 1.3749 + selectionStyle->mUnderlineColor = lineColor; 1.3750 + selectionStyle->mUnderlineStyle = lineStyle; 1.3751 + selectionStyle->mUnderlineRelativeSize = relativeSize; 1.3752 + selectionStyle->mInit = true; 1.3753 +} 1.3754 + 1.3755 +/* static */ bool 1.3756 +nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext, 1.3757 + int32_t aIndex, 1.3758 + nscolor* aLineColor, 1.3759 + float* aRelativeSize, 1.3760 + uint8_t* aStyle) 1.3761 +{ 1.3762 + NS_ASSERTION(aPresContext, "aPresContext is null"); 1.3763 + NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); 1.3764 + NS_ASSERTION(aStyle, "aStyle is null"); 1.3765 + NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); 1.3766 + 1.3767 + StyleIDs& styleID = SelectionStyleIDs[aIndex]; 1.3768 + 1.3769 + nscolor color = LookAndFeel::GetColor(styleID.mLine); 1.3770 + int32_t style = LookAndFeel::GetInt(styleID.mLineStyle); 1.3771 + if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) { 1.3772 + NS_ERROR("Invalid underline style value is specified"); 1.3773 + style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; 1.3774 + } 1.3775 + float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize); 1.3776 + 1.3777 + NS_ASSERTION(size, "selection underline relative size must be larger than 0"); 1.3778 + 1.3779 + if (aLineColor) { 1.3780 + *aLineColor = color; 1.3781 + } 1.3782 + *aRelativeSize = size; 1.3783 + *aStyle = style; 1.3784 + 1.3785 + return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE && 1.3786 + color != NS_TRANSPARENT && 1.3787 + size > 0.0f; 1.3788 +} 1.3789 + 1.3790 +bool 1.3791 +nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow) 1.3792 +{ 1.3793 + if (!InitSelectionColorsAndShadow()) { 1.3794 + return false; 1.3795 + } 1.3796 + 1.3797 + if (mHasSelectionShadow) { 1.3798 + *aShadow = mSelectionShadow; 1.3799 + return true; 1.3800 + } 1.3801 + 1.3802 + return false; 1.3803 +} 1.3804 + 1.3805 +inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) 1.3806 +{ 1.3807 + nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), 1.3808 + NS_GET_G(aForeColor), 1.3809 + NS_GET_B(aForeColor), 1.3810 + (uint8_t)(255 * 0.4f)); 1.3811 + // Don't use true alpha color for readability. 1.3812 + return NS_ComposeColors(aBackColor, foreColor); 1.3813 +} 1.3814 + 1.3815 +nscolor 1.3816 +nsTextPaintStyle::GetResolvedForeColor(nscolor aColor, 1.3817 + nscolor aDefaultForeColor, 1.3818 + nscolor aBackColor) 1.3819 +{ 1.3820 + if (aColor == NS_SAME_AS_FOREGROUND_COLOR) 1.3821 + return aDefaultForeColor; 1.3822 + 1.3823 + if (aColor != NS_40PERCENT_FOREGROUND_COLOR) 1.3824 + return aColor; 1.3825 + 1.3826 + // Get actual background color 1.3827 + nscolor actualBGColor = aBackColor; 1.3828 + if (actualBGColor == NS_TRANSPARENT) { 1.3829 + InitCommonColors(); 1.3830 + actualBGColor = mFrameBackgroundColor; 1.3831 + } 1.3832 + return Get40PercentColor(aDefaultForeColor, actualBGColor); 1.3833 +} 1.3834 + 1.3835 +//----------------------------------------------------------------------------- 1.3836 + 1.3837 +#ifdef ACCESSIBILITY 1.3838 +a11y::AccType 1.3839 +nsTextFrame::AccessibleType() 1.3840 +{ 1.3841 + if (IsEmpty()) { 1.3842 + nsAutoString renderedWhitespace; 1.3843 + GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1); 1.3844 + if (renderedWhitespace.IsEmpty()) { 1.3845 + return a11y::eNoType; 1.3846 + } 1.3847 + } 1.3848 + 1.3849 + return a11y::eTextLeafType; 1.3850 +} 1.3851 +#endif 1.3852 + 1.3853 + 1.3854 +//----------------------------------------------------------------------------- 1.3855 +void 1.3856 +nsTextFrame::Init(nsIContent* aContent, 1.3857 + nsIFrame* aParent, 1.3858 + nsIFrame* aPrevInFlow) 1.3859 +{ 1.3860 + NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!"); 1.3861 + NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT), 1.3862 + "Bogus content!"); 1.3863 + 1.3864 + // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they 1.3865 + // might be invalid if the content was modified while there was no frame 1.3866 + aContent->DeleteProperty(nsGkAtoms::newline); 1.3867 + if (PresContext()->BidiEnabled()) { 1.3868 + aContent->DeleteProperty(nsGkAtoms::flowlength); 1.3869 + } 1.3870 + 1.3871 + // Since our content has a frame now, this flag is no longer needed. 1.3872 + aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); 1.3873 + 1.3874 + // We're not a continuing frame. 1.3875 + // mContentOffset = 0; not necessary since we get zeroed out at init 1.3876 + nsFrame::Init(aContent, aParent, aPrevInFlow); 1.3877 +} 1.3878 + 1.3879 +void 1.3880 +nsTextFrame::ClearFrameOffsetCache() 1.3881 +{ 1.3882 + // See if we need to remove ourselves from the offset cache 1.3883 + if (GetStateBits() & TEXT_IN_OFFSET_CACHE) { 1.3884 + nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); 1.3885 + if (primaryFrame) { 1.3886 + // The primary frame might be null here. For example, nsLineBox::DeleteLineList 1.3887 + // just destroys the frames in order, which means that the primary frame is already 1.3888 + // dead if we're a continuing text frame, in which case, all of its properties are 1.3889 + // gone, and we don't need to worry about deleting this property here. 1.3890 + primaryFrame->Properties().Delete(OffsetToFrameProperty()); 1.3891 + } 1.3892 + RemoveStateBits(TEXT_IN_OFFSET_CACHE); 1.3893 + } 1.3894 +} 1.3895 + 1.3896 +void 1.3897 +nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.3898 +{ 1.3899 + ClearFrameOffsetCache(); 1.3900 + 1.3901 + // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or 1.3902 + // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame 1.3903 + // type might be changing. Not clear whether it's worth it. 1.3904 + ClearTextRuns(); 1.3905 + if (mNextContinuation) { 1.3906 + mNextContinuation->SetPrevInFlow(nullptr); 1.3907 + } 1.3908 + // Let the base class destroy the frame 1.3909 + nsFrame::DestroyFrom(aDestructRoot); 1.3910 +} 1.3911 + 1.3912 +class nsContinuingTextFrame : public nsTextFrame { 1.3913 +public: 1.3914 + NS_DECL_FRAMEARENA_HELPERS 1.3915 + 1.3916 + friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); 1.3917 + 1.3918 + virtual void Init(nsIContent* aContent, 1.3919 + nsIFrame* aParent, 1.3920 + nsIFrame* aPrevInFlow) MOZ_OVERRIDE; 1.3921 + 1.3922 + virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; 1.3923 + 1.3924 + virtual nsIFrame* GetPrevContinuation() const MOZ_OVERRIDE { 1.3925 + return mPrevContinuation; 1.3926 + } 1.3927 + virtual void SetPrevContinuation(nsIFrame* aPrevContinuation) MOZ_OVERRIDE { 1.3928 + NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(), 1.3929 + "setting a prev continuation with incorrect type!"); 1.3930 + NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), 1.3931 + "creating a loop in continuation chain!"); 1.3932 + mPrevContinuation = aPrevContinuation; 1.3933 + RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 1.3934 + } 1.3935 + virtual nsIFrame* GetPrevInFlowVirtual() const MOZ_OVERRIDE { 1.3936 + return GetPrevInFlow(); 1.3937 + } 1.3938 + nsIFrame* GetPrevInFlow() const { 1.3939 + return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr; 1.3940 + } 1.3941 + virtual void SetPrevInFlow(nsIFrame* aPrevInFlow) MOZ_OVERRIDE { 1.3942 + NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(), 1.3943 + "setting a prev in flow with incorrect type!"); 1.3944 + NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), 1.3945 + "creating a loop in continuation chain!"); 1.3946 + mPrevContinuation = aPrevInFlow; 1.3947 + AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); 1.3948 + } 1.3949 + virtual nsIFrame* FirstInFlow() const MOZ_OVERRIDE; 1.3950 + virtual nsIFrame* FirstContinuation() const MOZ_OVERRIDE; 1.3951 + 1.3952 + virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext, 1.3953 + InlineMinWidthData *aData) MOZ_OVERRIDE; 1.3954 + virtual void AddInlinePrefWidth(nsRenderingContext *aRenderingContext, 1.3955 + InlinePrefWidthData *aData) MOZ_OVERRIDE; 1.3956 + 1.3957 + virtual nsresult GetRenderedText(nsAString* aString = nullptr, 1.3958 + gfxSkipChars* aSkipChars = nullptr, 1.3959 + gfxSkipCharsIterator* aSkipIter = nullptr, 1.3960 + uint32_t aSkippedStartOffset = 0, 1.3961 + uint32_t aSkippedMaxLength = UINT32_MAX) MOZ_OVERRIDE 1.3962 + { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only 1.3963 + 1.3964 +protected: 1.3965 + nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} 1.3966 + nsIFrame* mPrevContinuation; 1.3967 +}; 1.3968 + 1.3969 +void 1.3970 +nsContinuingTextFrame::Init(nsIContent* aContent, 1.3971 + nsIFrame* aParent, 1.3972 + nsIFrame* aPrevInFlow) 1.3973 +{ 1.3974 + NS_ASSERTION(aPrevInFlow, "Must be a continuation!"); 1.3975 + // NOTE: bypassing nsTextFrame::Init!!! 1.3976 + nsFrame::Init(aContent, aParent, aPrevInFlow); 1.3977 + 1.3978 + nsTextFrame* nextContinuation = 1.3979 + static_cast<nsTextFrame*>(aPrevInFlow->GetNextContinuation()); 1.3980 + // Hook the frame into the flow 1.3981 + SetPrevInFlow(aPrevInFlow); 1.3982 + aPrevInFlow->SetNextInFlow(this); 1.3983 + nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow); 1.3984 + mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); 1.3985 + NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()), 1.3986 + "Creating ContinuingTextFrame, but there is no more content"); 1.3987 + if (prev->StyleContext() != StyleContext()) { 1.3988 + // We're taking part of prev's text, and its style may be different 1.3989 + // so clear its textrun which may no longer be valid (and don't set ours) 1.3990 + prev->ClearTextRuns(); 1.3991 + } else { 1.3992 + float inflation = prev->GetFontSizeInflation(); 1.3993 + SetFontSizeInflation(inflation); 1.3994 + mTextRun = prev->GetTextRun(nsTextFrame::eInflated); 1.3995 + if (inflation != 1.0f) { 1.3996 + gfxTextRun *uninflatedTextRun = 1.3997 + prev->GetTextRun(nsTextFrame::eNotInflated); 1.3998 + if (uninflatedTextRun) { 1.3999 + SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f); 1.4000 + } 1.4001 + } 1.4002 + } 1.4003 + if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) { 1.4004 + FramePropertyTable *propTable = PresContext()->PropertyTable(); 1.4005 + // Get all the properties from the prev-in-flow first to take 1.4006 + // advantage of the propTable's cache and simplify the assertion below 1.4007 + void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty()); 1.4008 + void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty()); 1.4009 + void* paragraphDepth = propTable->Get(aPrevInFlow, ParagraphDepthProperty()); 1.4010 + propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel); 1.4011 + propTable->Set(this, BaseLevelProperty(), baseLevel); 1.4012 + propTable->Set(this, ParagraphDepthProperty(), paragraphDepth); 1.4013 + 1.4014 + if (nextContinuation) { 1.4015 + SetNextContinuation(nextContinuation); 1.4016 + nextContinuation->SetPrevContinuation(this); 1.4017 + // Adjust next-continuations' content offset as needed. 1.4018 + while (nextContinuation && 1.4019 + nextContinuation->GetContentOffset() < mContentOffset) { 1.4020 + NS_ASSERTION( 1.4021 + embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) && 1.4022 + baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()) && 1.4023 + paragraphDepth == propTable->Get(nextContinuation, ParagraphDepthProperty()), 1.4024 + "stealing text from different type of BIDI continuation"); 1.4025 + nextContinuation->mContentOffset = mContentOffset; 1.4026 + nextContinuation = static_cast<nsTextFrame*>(nextContinuation->GetNextContinuation()); 1.4027 + } 1.4028 + } 1.4029 + mState |= NS_FRAME_IS_BIDI; 1.4030 + } // prev frame is bidi 1.4031 +} 1.4032 + 1.4033 +void 1.4034 +nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot) 1.4035 +{ 1.4036 + ClearFrameOffsetCache(); 1.4037 + 1.4038 + // The text associated with this frame will become associated with our 1.4039 + // prev-continuation. If that means the text has changed style, then 1.4040 + // we need to wipe out the text run for the text. 1.4041 + // Note that mPrevContinuation can be null if we're destroying the whole 1.4042 + // frame chain from the start to the end. 1.4043 + // If this frame is mentioned in the userData for a textrun (say 1.4044 + // because there's a direction change at the start of this frame), then 1.4045 + // we have to clear the textrun because we're going away and the 1.4046 + // textrun had better not keep a dangling reference to us. 1.4047 + if (IsInTextRunUserData() || 1.4048 + (mPrevContinuation && 1.4049 + mPrevContinuation->StyleContext() != StyleContext())) { 1.4050 + ClearTextRuns(); 1.4051 + // Clear the previous continuation's text run also, so that it can rebuild 1.4052 + // the text run to include our text. 1.4053 + if (mPrevContinuation) { 1.4054 + nsTextFrame *prevContinuationText = 1.4055 + static_cast<nsTextFrame*>(mPrevContinuation); 1.4056 + prevContinuationText->ClearTextRuns(); 1.4057 + } 1.4058 + } 1.4059 + nsSplittableFrame::RemoveFromFlow(this); 1.4060 + // Let the base class destroy the frame 1.4061 + nsFrame::DestroyFrom(aDestructRoot); 1.4062 +} 1.4063 + 1.4064 +nsIFrame* 1.4065 +nsContinuingTextFrame::FirstInFlow() const 1.4066 +{ 1.4067 + // Can't cast to |nsContinuingTextFrame*| because the first one isn't. 1.4068 + nsIFrame *firstInFlow, 1.4069 + *previous = const_cast<nsIFrame*> 1.4070 + (static_cast<const nsIFrame*>(this)); 1.4071 + do { 1.4072 + firstInFlow = previous; 1.4073 + previous = firstInFlow->GetPrevInFlow(); 1.4074 + } while (previous); 1.4075 + MOZ_ASSERT(firstInFlow, "post-condition failed"); 1.4076 + return firstInFlow; 1.4077 +} 1.4078 + 1.4079 +nsIFrame* 1.4080 +nsContinuingTextFrame::FirstContinuation() const 1.4081 +{ 1.4082 + // Can't cast to |nsContinuingTextFrame*| because the first one isn't. 1.4083 + nsIFrame *firstContinuation, 1.4084 + *previous = const_cast<nsIFrame*> 1.4085 + (static_cast<const nsIFrame*>(mPrevContinuation)); 1.4086 + 1.4087 + NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?"); 1.4088 + 1.4089 + do { 1.4090 + firstContinuation = previous; 1.4091 + previous = firstContinuation->GetPrevContinuation(); 1.4092 + } while (previous); 1.4093 + MOZ_ASSERT(firstContinuation, "post-condition failed"); 1.4094 + return firstContinuation; 1.4095 +} 1.4096 + 1.4097 +// XXX Do we want to do all the work for the first-in-flow or do the 1.4098 +// work for each part? (Be careful of first-letter / first-line, though, 1.4099 +// especially first-line!) Doing all the work on the first-in-flow has 1.4100 +// the advantage of avoiding the potential for incremental reflow bugs, 1.4101 +// but depends on our maintining the frame tree in reasonable ways even 1.4102 +// for edge cases (block-within-inline splits, nextBidi, etc.) 1.4103 + 1.4104 +// XXX We really need to make :first-letter happen during frame 1.4105 +// construction. 1.4106 + 1.4107 +// Needed for text frames in XUL. 1.4108 +/* virtual */ nscoord 1.4109 +nsTextFrame::GetMinWidth(nsRenderingContext *aRenderingContext) 1.4110 +{ 1.4111 + return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); 1.4112 +} 1.4113 + 1.4114 +// Needed for text frames in XUL. 1.4115 +/* virtual */ nscoord 1.4116 +nsTextFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) 1.4117 +{ 1.4118 + return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); 1.4119 +} 1.4120 + 1.4121 +/* virtual */ void 1.4122 +nsContinuingTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, 1.4123 + InlineMinWidthData *aData) 1.4124 +{ 1.4125 + // Do nothing, since the first-in-flow accounts for everything. 1.4126 + return; 1.4127 +} 1.4128 + 1.4129 +/* virtual */ void 1.4130 +nsContinuingTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext, 1.4131 + InlinePrefWidthData *aData) 1.4132 +{ 1.4133 + // Do nothing, since the first-in-flow accounts for everything. 1.4134 + return; 1.4135 +} 1.4136 + 1.4137 +static void 1.4138 +DestroySelectionDetails(SelectionDetails* aDetails) 1.4139 +{ 1.4140 + while (aDetails) { 1.4141 + SelectionDetails* next = aDetails->mNext; 1.4142 + delete aDetails; 1.4143 + aDetails = next; 1.4144 + } 1.4145 +} 1.4146 + 1.4147 +//---------------------------------------------------------------------- 1.4148 + 1.4149 +#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) 1.4150 +static void 1.4151 +VerifyNotDirty(nsFrameState state) 1.4152 +{ 1.4153 + bool isZero = state & NS_FRAME_FIRST_REFLOW; 1.4154 + bool isDirty = state & NS_FRAME_IS_DIRTY; 1.4155 + if (!isZero && isDirty) 1.4156 + NS_WARNING("internal offsets may be out-of-sync"); 1.4157 +} 1.4158 +#define DEBUG_VERIFY_NOT_DIRTY(state) \ 1.4159 +VerifyNotDirty(state) 1.4160 +#else 1.4161 +#define DEBUG_VERIFY_NOT_DIRTY(state) 1.4162 +#endif 1.4163 + 1.4164 +nsIFrame* 1.4165 +NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.4166 +{ 1.4167 + return new (aPresShell) nsTextFrame(aContext); 1.4168 +} 1.4169 + 1.4170 +NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame) 1.4171 + 1.4172 +nsIFrame* 1.4173 +NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) 1.4174 +{ 1.4175 + return new (aPresShell) nsContinuingTextFrame(aContext); 1.4176 +} 1.4177 + 1.4178 +NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame) 1.4179 + 1.4180 +nsTextFrame::~nsTextFrame() 1.4181 +{ 1.4182 +} 1.4183 + 1.4184 +nsresult 1.4185 +nsTextFrame::GetCursor(const nsPoint& aPoint, 1.4186 + nsIFrame::Cursor& aCursor) 1.4187 +{ 1.4188 + FillCursorInformationFromStyle(StyleUserInterface(), aCursor); 1.4189 + if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { 1.4190 + aCursor.mCursor = NS_STYLE_CURSOR_TEXT; 1.4191 + // If this is editable, we should ignore tabindex value. 1.4192 + if (mContent->IsEditable()) { 1.4193 + return NS_OK; 1.4194 + } 1.4195 + 1.4196 + // If tabindex >= 0, use default cursor to indicate it's not selectable 1.4197 + nsIFrame *ancestorFrame = this; 1.4198 + while ((ancestorFrame = ancestorFrame->GetParent()) != nullptr) { 1.4199 + nsIContent *ancestorContent = ancestorFrame->GetContent(); 1.4200 + if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { 1.4201 + nsAutoString tabIndexStr; 1.4202 + ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); 1.4203 + if (!tabIndexStr.IsEmpty()) { 1.4204 + nsresult rv; 1.4205 + int32_t tabIndexVal = tabIndexStr.ToInteger(&rv); 1.4206 + if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) { 1.4207 + aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; 1.4208 + break; 1.4209 + } 1.4210 + } 1.4211 + } 1.4212 + } 1.4213 + } 1.4214 + 1.4215 + return NS_OK; 1.4216 +} 1.4217 + 1.4218 +nsIFrame* 1.4219 +nsTextFrame::LastInFlow() const 1.4220 +{ 1.4221 + nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this); 1.4222 + while (lastInFlow->GetNextInFlow()) { 1.4223 + lastInFlow = static_cast<nsTextFrame*>(lastInFlow->GetNextInFlow()); 1.4224 + } 1.4225 + MOZ_ASSERT(lastInFlow, "post-condition failed"); 1.4226 + return lastInFlow; 1.4227 +} 1.4228 + 1.4229 +nsIFrame* 1.4230 +nsTextFrame::LastContinuation() const 1.4231 +{ 1.4232 + nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this); 1.4233 + while (lastContinuation->mNextContinuation) { 1.4234 + lastContinuation = 1.4235 + static_cast<nsTextFrame*>(lastContinuation->mNextContinuation); 1.4236 + } 1.4237 + MOZ_ASSERT(lastContinuation, "post-condition failed"); 1.4238 + return lastContinuation; 1.4239 +} 1.4240 + 1.4241 +void 1.4242 +nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey) 1.4243 +{ 1.4244 + if (IsSVGText()) { 1.4245 + nsIFrame* svgTextFrame = 1.4246 + nsLayoutUtils::GetClosestFrameOfType(GetParent(), 1.4247 + nsGkAtoms::svgTextFrame); 1.4248 + svgTextFrame->InvalidateFrame(); 1.4249 + return; 1.4250 + } 1.4251 + nsTextFrameBase::InvalidateFrame(aDisplayItemKey); 1.4252 +} 1.4253 + 1.4254 +void 1.4255 +nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) 1.4256 +{ 1.4257 + if (IsSVGText()) { 1.4258 + nsIFrame* svgTextFrame = 1.4259 + nsLayoutUtils::GetClosestFrameOfType(GetParent(), 1.4260 + nsGkAtoms::svgTextFrame); 1.4261 + svgTextFrame->InvalidateFrame(); 1.4262 + return; 1.4263 + } 1.4264 + nsTextFrameBase::InvalidateFrameWithRect(aRect, aDisplayItemKey); 1.4265 +} 1.4266 + 1.4267 +gfxTextRun* 1.4268 +nsTextFrame::GetUninflatedTextRun() 1.4269 +{ 1.4270 + return static_cast<gfxTextRun*>( 1.4271 + Properties().Get(UninflatedTextRunProperty())); 1.4272 +} 1.4273 + 1.4274 +void 1.4275 +nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun, 1.4276 + float aInflation) 1.4277 +{ 1.4278 + NS_ASSERTION(aTextRun, "must have text run"); 1.4279 + 1.4280 + // Our inflated text run is always stored in mTextRun. In the cases 1.4281 + // where our current inflation is not 1.0, however, we store two text 1.4282 + // runs, and the uninflated one goes in a frame property. We never 1.4283 + // store a single text run in both. 1.4284 + if (aWhichTextRun == eInflated) { 1.4285 + if (HasFontSizeInflation() && aInflation == 1.0f) { 1.4286 + // FIXME: Probably shouldn't do this within each SetTextRun 1.4287 + // method, but it doesn't hurt. 1.4288 + ClearTextRun(nullptr, nsTextFrame::eNotInflated); 1.4289 + } 1.4290 + SetFontSizeInflation(aInflation); 1.4291 + } else { 1.4292 + NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation"); 1.4293 + if (HasFontSizeInflation()) { 1.4294 + Properties().Set(UninflatedTextRunProperty(), aTextRun); 1.4295 + return; 1.4296 + } 1.4297 + // fall through to setting mTextRun 1.4298 + } 1.4299 + 1.4300 + mTextRun = aTextRun; 1.4301 + 1.4302 + // FIXME: Add assertions testing the relationship between 1.4303 + // GetFontSizeInflation() and whether we have an uninflated text run 1.4304 + // (but be aware that text runs can go away). 1.4305 +} 1.4306 + 1.4307 +bool 1.4308 +nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) 1.4309 +{ 1.4310 + if (aTextRun == mTextRun) { 1.4311 + mTextRun = nullptr; 1.4312 + return true; 1.4313 + } 1.4314 + FrameProperties props = Properties(); 1.4315 + if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) && 1.4316 + props.Get(UninflatedTextRunProperty()) == aTextRun) { 1.4317 + props.Delete(UninflatedTextRunProperty()); 1.4318 + return true; 1.4319 + } 1.4320 + return false; 1.4321 +} 1.4322 + 1.4323 +void 1.4324 +nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation, 1.4325 + TextRunType aWhichTextRun) 1.4326 +{ 1.4327 + gfxTextRun* textRun = GetTextRun(aWhichTextRun); 1.4328 + if (!textRun) { 1.4329 + return; 1.4330 + } 1.4331 + 1.4332 + DebugOnly<bool> checkmTextrun = textRun == mTextRun; 1.4333 + UnhookTextRunFromFrames(textRun, aStartContinuation); 1.4334 + MOZ_ASSERT(checkmTextrun ? !mTextRun 1.4335 + : !Properties().Get(UninflatedTextRunProperty())); 1.4336 + 1.4337 + // see comments in BuildTextRunForFrames... 1.4338 +// if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) { 1.4339 +// NS_ERROR("Shouldn't reach here for now..."); 1.4340 +// // the textrun's text may be referencing a DOM node that has changed, 1.4341 +// // so we'd better kill this textrun now. 1.4342 +// if (textRun->GetExpirationState()->IsTracked()) { 1.4343 +// gTextRuns->RemoveFromCache(textRun); 1.4344 +// } 1.4345 +// delete textRun; 1.4346 +// return; 1.4347 +// } 1.4348 + 1.4349 + if (!textRun->GetUserData()) { 1.4350 + // Remove it now because it's not doing anything useful 1.4351 + gTextRuns->RemoveFromCache(textRun); 1.4352 + delete textRun; 1.4353 + } 1.4354 +} 1.4355 + 1.4356 +void 1.4357 +nsTextFrame::DisconnectTextRuns() 1.4358 +{ 1.4359 + MOZ_ASSERT(!IsInTextRunUserData(), 1.4360 + "Textrun mentions this frame in its user data so we can't just disconnect"); 1.4361 + mTextRun = nullptr; 1.4362 + if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) { 1.4363 + Properties().Delete(UninflatedTextRunProperty()); 1.4364 + } 1.4365 +} 1.4366 + 1.4367 +nsresult 1.4368 +nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) 1.4369 +{ 1.4370 + mContent->DeleteProperty(nsGkAtoms::newline); 1.4371 + if (PresContext()->BidiEnabled()) { 1.4372 + mContent->DeleteProperty(nsGkAtoms::flowlength); 1.4373 + } 1.4374 + 1.4375 + // Find the first frame whose text has changed. Frames that are entirely 1.4376 + // before the text change are completely unaffected. 1.4377 + nsTextFrame* next; 1.4378 + nsTextFrame* textFrame = this; 1.4379 + while (true) { 1.4380 + next = static_cast<nsTextFrame*>(textFrame->GetNextContinuation()); 1.4381 + if (!next || next->GetContentOffset() > int32_t(aInfo->mChangeStart)) 1.4382 + break; 1.4383 + textFrame = next; 1.4384 + } 1.4385 + 1.4386 + int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength; 1.4387 + nsTextFrame* lastDirtiedFrame = nullptr; 1.4388 + 1.4389 + nsIPresShell* shell = PresContext()->GetPresShell(); 1.4390 + do { 1.4391 + // textFrame contained deleted text (or the insertion point, 1.4392 + // if this was a pure insertion). 1.4393 + textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; 1.4394 + textFrame->ClearTextRuns(); 1.4395 + if (!lastDirtiedFrame || 1.4396 + lastDirtiedFrame->GetParent() != textFrame->GetParent()) { 1.4397 + // Ask the parent frame to reflow me. 1.4398 + shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange, 1.4399 + NS_FRAME_IS_DIRTY); 1.4400 + lastDirtiedFrame = textFrame; 1.4401 + } else { 1.4402 + // if the parent is a block, we're cheating here because we should 1.4403 + // be marking our line dirty, but we're not. nsTextFrame::SetLength 1.4404 + // will do that when it gets called during reflow. 1.4405 + textFrame->AddStateBits(NS_FRAME_IS_DIRTY); 1.4406 + } 1.4407 + textFrame->InvalidateFrame(); 1.4408 + 1.4409 + // Below, frames that start after the deleted text will be adjusted so that 1.4410 + // their offsets move with the trailing unchanged text. If this change 1.4411 + // deletes more text than it inserts, those frame offsets will decrease. 1.4412 + // We need to maintain the invariant that mContentOffset is non-decreasing 1.4413 + // along the continuation chain. So we need to ensure that frames that 1.4414 + // started in the deleted text are all still starting before the 1.4415 + // unchanged text. 1.4416 + if (textFrame->mContentOffset > endOfChangedText) { 1.4417 + textFrame->mContentOffset = endOfChangedText; 1.4418 + } 1.4419 + 1.4420 + textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation()); 1.4421 + } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo->mChangeEnd)); 1.4422 + 1.4423 + // This is how much the length of the string changed by --- i.e., 1.4424 + // how much the trailing unchanged text moved. 1.4425 + int32_t sizeChange = 1.4426 + aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd; 1.4427 + 1.4428 + if (sizeChange) { 1.4429 + // Fix the offsets of the text frames that start in the trailing 1.4430 + // unchanged text. 1.4431 + while (textFrame) { 1.4432 + textFrame->mContentOffset += sizeChange; 1.4433 + // XXX we could rescue some text runs by adjusting their user data 1.4434 + // to reflect the change in DOM offsets 1.4435 + textFrame->ClearTextRuns(); 1.4436 + textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation()); 1.4437 + } 1.4438 + } 1.4439 + 1.4440 + return NS_OK; 1.4441 +} 1.4442 + 1.4443 +/* virtual */ void 1.4444 +nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) 1.4445 +{ 1.4446 + nsFrame::DidSetStyleContext(aOldStyleContext); 1.4447 +} 1.4448 + 1.4449 +class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry 1.4450 +{ 1.4451 +public: 1.4452 + nsDisplayTextGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) 1.4453 + : nsDisplayItemGenericGeometry(aItem, aBuilder) 1.4454 + { 1.4455 + nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame()); 1.4456 + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations); 1.4457 + } 1.4458 + 1.4459 + /** 1.4460 + * We store the computed text decorations here since they are 1.4461 + * computed using style data from parent frames. Any changes to these 1.4462 + * styles will only invalidate the parent frame and not this frame. 1.4463 + */ 1.4464 + nsTextFrame::TextDecorations mDecorations; 1.4465 +}; 1.4466 + 1.4467 +class nsDisplayText : public nsCharClipDisplayItem { 1.4468 +public: 1.4469 + nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) : 1.4470 + nsCharClipDisplayItem(aBuilder, aFrame), 1.4471 + mDisableSubpixelAA(false) { 1.4472 + MOZ_COUNT_CTOR(nsDisplayText); 1.4473 + } 1.4474 +#ifdef NS_BUILD_REFCNT_LOGGING 1.4475 + virtual ~nsDisplayText() { 1.4476 + MOZ_COUNT_DTOR(nsDisplayText); 1.4477 + } 1.4478 +#endif 1.4479 + 1.4480 + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, 1.4481 + bool* aSnap) MOZ_OVERRIDE { 1.4482 + *aSnap = false; 1.4483 + nsRect temp = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); 1.4484 + // Bug 748228 1.4485 + temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); 1.4486 + return temp; 1.4487 + } 1.4488 + virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, 1.4489 + HitTestState* aState, 1.4490 + nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE { 1.4491 + if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) { 1.4492 + aOutFrames->AppendElement(mFrame); 1.4493 + } 1.4494 + } 1.4495 + virtual void Paint(nsDisplayListBuilder* aBuilder, 1.4496 + nsRenderingContext* aCtx) MOZ_OVERRIDE; 1.4497 + NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT) 1.4498 + 1.4499 + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE 1.4500 + { 1.4501 + bool snap; 1.4502 + return GetBounds(aBuilder, &snap); 1.4503 + } 1.4504 + 1.4505 + virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE 1.4506 + { 1.4507 + return new nsDisplayTextGeometry(this, aBuilder); 1.4508 + } 1.4509 + 1.4510 + virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, 1.4511 + const nsDisplayItemGeometry* aGeometry, 1.4512 + nsRegion *aInvalidRegion) MOZ_OVERRIDE 1.4513 + { 1.4514 + const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry); 1.4515 + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); 1.4516 + 1.4517 + nsTextFrame::TextDecorations decorations; 1.4518 + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations); 1.4519 + 1.4520 + bool snap; 1.4521 + nsRect newRect = geometry->mBounds; 1.4522 + nsRect oldRect = GetBounds(aBuilder, &snap); 1.4523 + if (decorations != geometry->mDecorations || 1.4524 + !oldRect.IsEqualInterior(newRect) || 1.4525 + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { 1.4526 + aInvalidRegion->Or(oldRect, newRect); 1.4527 + } 1.4528 + } 1.4529 + 1.4530 + virtual void DisableComponentAlpha() MOZ_OVERRIDE { 1.4531 + mDisableSubpixelAA = true; 1.4532 + } 1.4533 + 1.4534 + bool mDisableSubpixelAA; 1.4535 +}; 1.4536 + 1.4537 +void 1.4538 +nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, 1.4539 + nsRenderingContext* aCtx) { 1.4540 + PROFILER_LABEL("nsDisplayText", "Paint"); 1.4541 + // Add 1 pixel of dirty area around mVisibleRect to allow us to paint 1.4542 + // antialiased pixels beyond the measured text extents. 1.4543 + // This is temporary until we do this in the actual calculation of text extents. 1.4544 + nsRect extraVisible = mVisibleRect; 1.4545 + nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); 1.4546 + extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel); 1.4547 + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); 1.4548 + 1.4549 + gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(), 1.4550 + mDisableSubpixelAA); 1.4551 + NS_ASSERTION(mLeftEdge >= 0, "illegal left edge"); 1.4552 + NS_ASSERTION(mRightEdge >= 0, "illegal right edge"); 1.4553 + f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this); 1.4554 +} 1.4555 + 1.4556 +void 1.4557 +nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 1.4558 + const nsRect& aDirtyRect, 1.4559 + const nsDisplayListSet& aLists) 1.4560 +{ 1.4561 + if (!IsVisibleForPainting(aBuilder)) 1.4562 + return; 1.4563 + 1.4564 + DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); 1.4565 + 1.4566 + aLists.Content()->AppendNewToTop( 1.4567 + new (aBuilder) nsDisplayText(aBuilder, this)); 1.4568 +} 1.4569 + 1.4570 +static nsIFrame* 1.4571 +GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore) 1.4572 +{ 1.4573 + *aIsBefore = false; 1.4574 + while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { 1.4575 + if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) { 1.4576 + *aIsBefore = true; 1.4577 + } 1.4578 + aFrame = aFrame->GetParent(); 1.4579 + } 1.4580 + return aFrame; 1.4581 +} 1.4582 + 1.4583 +SelectionDetails* 1.4584 +nsTextFrame::GetSelectionDetails() 1.4585 +{ 1.4586 + const nsFrameSelection* frameSelection = GetConstFrameSelection(); 1.4587 + if (frameSelection->GetTableCellSelection()) { 1.4588 + return nullptr; 1.4589 + } 1.4590 + if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { 1.4591 + SelectionDetails* details = 1.4592 + frameSelection->LookUpSelection(mContent, GetContentOffset(), 1.4593 + GetContentLength(), false); 1.4594 + SelectionDetails* sd; 1.4595 + for (sd = details; sd; sd = sd->mNext) { 1.4596 + sd->mStart += mContentOffset; 1.4597 + sd->mEnd += mContentOffset; 1.4598 + } 1.4599 + return details; 1.4600 + } 1.4601 + 1.4602 + // Check if the beginning or end of the element is selected, depending on 1.4603 + // whether we're :before content or :after content. 1.4604 + bool isBefore; 1.4605 + nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore); 1.4606 + if (!owner || !owner->GetContent()) 1.4607 + return nullptr; 1.4608 + 1.4609 + SelectionDetails* details = 1.4610 + frameSelection->LookUpSelection(owner->GetContent(), 1.4611 + isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false); 1.4612 + SelectionDetails* sd; 1.4613 + for (sd = details; sd; sd = sd->mNext) { 1.4614 + // The entire text is selected! 1.4615 + sd->mStart = GetContentOffset(); 1.4616 + sd->mEnd = GetContentEnd(); 1.4617 + } 1.4618 + return details; 1.4619 +} 1.4620 + 1.4621 +static void 1.4622 +PaintSelectionBackground(gfxContext* aCtx, nsPresContext* aPresContext, 1.4623 + nscolor aColor, const gfxRect& aDirtyRect, 1.4624 + const gfxRect& aRect, 1.4625 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.4626 +{ 1.4627 + if (aCallbacks) { 1.4628 + aCallbacks->NotifyBeforeSelectionBackground(aColor); 1.4629 + } 1.4630 + 1.4631 + gfxRect r = aRect.Intersect(aDirtyRect); 1.4632 + // For now, we need to put this in pixel coordinates 1.4633 + int32_t app = aPresContext->AppUnitsPerDevPixel(); 1.4634 + aCtx->NewPath(); 1.4635 + // pixel-snap 1.4636 + aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app, 1.4637 + r.Width() / app, r.Height() / app), true); 1.4638 + 1.4639 + if (aCallbacks) { 1.4640 + aCallbacks->NotifySelectionBackgroundPathEmitted(); 1.4641 + } else { 1.4642 + aCtx->SetColor(gfxRGBA(aColor)); 1.4643 + aCtx->Fill(); 1.4644 + } 1.4645 +} 1.4646 + 1.4647 +void 1.4648 +nsTextFrame::GetTextDecorations( 1.4649 + nsPresContext* aPresContext, 1.4650 + nsTextFrame::TextDecorationColorResolution aColorResolution, 1.4651 + nsTextFrame::TextDecorations& aDecorations) 1.4652 +{ 1.4653 + const nsCompatibility compatMode = aPresContext->CompatibilityMode(); 1.4654 + 1.4655 + bool useOverride = false; 1.4656 + nscolor overrideColor = NS_RGBA(0, 0, 0, 0); 1.4657 + 1.4658 + // frameTopOffset represents the offset to f's top from our baseline in our 1.4659 + // coordinate space 1.4660 + // baselineOffset represents the offset from our baseline to f's baseline or 1.4661 + // the nearest block's baseline, in our coordinate space, whichever is closest 1.4662 + // during the particular iteration 1.4663 + nscoord frameTopOffset = mAscent, 1.4664 + baselineOffset = 0; 1.4665 + 1.4666 + bool nearestBlockFound = false; 1.4667 + 1.4668 + for (nsIFrame* f = this, *fChild = nullptr; 1.4669 + f; 1.4670 + fChild = f, 1.4671 + f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) 1.4672 + { 1.4673 + nsStyleContext *const context = f->StyleContext(); 1.4674 + if (!context->HasTextDecorationLines()) { 1.4675 + break; 1.4676 + } 1.4677 + 1.4678 + const nsStyleTextReset *const styleText = context->StyleTextReset(); 1.4679 + const uint8_t textDecorations = styleText->mTextDecorationLine; 1.4680 + 1.4681 + if (!useOverride && 1.4682 + (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) { 1.4683 + // This handles the <a href="blah.html"><font color="green">La 1.4684 + // la la</font></a> case. The link underline should be green. 1.4685 + useOverride = true; 1.4686 + overrideColor = 1.4687 + nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color); 1.4688 + } 1.4689 + 1.4690 + const bool firstBlock = !nearestBlockFound && nsLayoutUtils::GetAsBlock(f); 1.4691 + 1.4692 + // Not updating positions once we hit a parent block is equivalent to 1.4693 + // the CSS 2.1 spec that blocks should propagate decorations down to their 1.4694 + // children (albeit the style should be preserved) 1.4695 + // However, if we're vertically aligned within a block, then we need to 1.4696 + // recover the right baseline from the line by querying the FrameProperty 1.4697 + // that should be set (see nsLineLayout::VerticalAlignLine). 1.4698 + if (firstBlock) { 1.4699 + // At this point, fChild can't be null since TextFrames can't be blocks 1.4700 + if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) { 1.4701 + // Since offset is the offset in the child's coordinate space, we have 1.4702 + // to undo the accumulation to bring the transform out of the block's 1.4703 + // coordinate space 1.4704 + baselineOffset = 1.4705 + frameTopOffset - fChild->GetNormalPosition().y 1.4706 + - NS_PTR_TO_INT32( 1.4707 + fChild->Properties().Get(nsIFrame::LineBaselineOffset())); 1.4708 + } 1.4709 + } 1.4710 + else if (!nearestBlockFound) { 1.4711 + baselineOffset = frameTopOffset - f->GetBaseline(); 1.4712 + } 1.4713 + 1.4714 + nearestBlockFound = nearestBlockFound || firstBlock; 1.4715 + frameTopOffset += f->GetNormalPosition().y; 1.4716 + 1.4717 + const uint8_t style = styleText->GetDecorationStyle(); 1.4718 + if (textDecorations) { 1.4719 + nscolor color; 1.4720 + if (useOverride) { 1.4721 + color = overrideColor; 1.4722 + } else if (IsSVGText()) { 1.4723 + // XXX We might want to do something with text-decoration-color when 1.4724 + // painting SVG text, but it's not clear what we should do. We 1.4725 + // at least need SVG text decorations to paint with 'fill' if 1.4726 + // text-decoration-color has its initial value currentColor. 1.4727 + // We could choose to interpret currentColor as "currentFill" 1.4728 + // for SVG text, and have e.g. text-decoration-color:red to 1.4729 + // override the fill paint of the decoration. 1.4730 + color = aColorResolution == eResolvedColors ? 1.4731 + nsLayoutUtils::GetColor(f, eCSSProperty_fill) : 1.4732 + NS_SAME_AS_FOREGROUND_COLOR; 1.4733 + } else { 1.4734 + color = nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color); 1.4735 + } 1.4736 + 1.4737 + if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) { 1.4738 + aDecorations.mUnderlines.AppendElement( 1.4739 + nsTextFrame::LineDecoration(f, baselineOffset, color, style)); 1.4740 + } 1.4741 + if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) { 1.4742 + aDecorations.mOverlines.AppendElement( 1.4743 + nsTextFrame::LineDecoration(f, baselineOffset, color, style)); 1.4744 + } 1.4745 + if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) { 1.4746 + aDecorations.mStrikes.AppendElement( 1.4747 + nsTextFrame::LineDecoration(f, baselineOffset, color, style)); 1.4748 + } 1.4749 + } 1.4750 + 1.4751 + // In all modes, if we're on an inline-block or inline-table (or 1.4752 + // inline-stack, inline-box, inline-grid), we're done. 1.4753 + uint8_t display = f->GetDisplay(); 1.4754 + if (display != NS_STYLE_DISPLAY_INLINE && 1.4755 + nsStyleDisplay::IsDisplayTypeInlineOutside(display)) { 1.4756 + break; 1.4757 + } 1.4758 + 1.4759 + if (compatMode == eCompatibility_NavQuirks) { 1.4760 + // In quirks mode, if we're on an HTML table element, we're done. 1.4761 + if (f->GetContent()->IsHTML(nsGkAtoms::table)) { 1.4762 + break; 1.4763 + } 1.4764 + } else { 1.4765 + // In standards/almost-standards mode, if we're on an 1.4766 + // absolutely-positioned element or a floating element, we're done. 1.4767 + if (f->IsFloating() || f->IsAbsolutelyPositioned()) { 1.4768 + break; 1.4769 + } 1.4770 + } 1.4771 + } 1.4772 +} 1.4773 + 1.4774 +static float 1.4775 +GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize) 1.4776 +{ 1.4777 + if (aFrame->IsSVGText()) { 1.4778 + const nsIFrame* container = aFrame; 1.4779 + while (container->GetType() != nsGkAtoms::svgTextFrame) { 1.4780 + container = container->GetParent(); 1.4781 + } 1.4782 + NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame"); 1.4783 + return 1.4784 + static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor(); 1.4785 + } 1.4786 + return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize); 1.4787 +} 1.4788 + 1.4789 +void 1.4790 +nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 1.4791 + const nsHTMLReflowState& aBlockReflowState, 1.4792 + PropertyProvider& aProvider, 1.4793 + nsRect* aVisualOverflowRect, 1.4794 + bool aIncludeTextDecorations) 1.4795 +{ 1.4796 + // Text-shadow overflows 1.4797 + nsRect shadowRect = 1.4798 + nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this); 1.4799 + aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect); 1.4800 + 1.4801 + if (IsFloatingFirstLetterChild()) { 1.4802 + // The underline/overline drawable area must be contained in the overflow 1.4803 + // rect when this is in floating first letter frame at *both* modes. 1.4804 + nsIFrame* firstLetterFrame = aBlockReflowState.frame; 1.4805 + uint8_t decorationStyle = firstLetterFrame->StyleContext()-> 1.4806 + StyleTextReset()->GetDecorationStyle(); 1.4807 + // If the style is none, let's include decoration line rect as solid style 1.4808 + // since changing the style from none to solid/dotted/dashed doesn't cause 1.4809 + // reflow. 1.4810 + if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.4811 + decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; 1.4812 + } 1.4813 + nsFontMetrics* fontMetrics = aProvider.GetFontMetrics(); 1.4814 + nscoord underlineOffset, underlineSize; 1.4815 + fontMetrics->GetUnderline(underlineOffset, underlineSize); 1.4816 + nscoord maxAscent = fontMetrics->MaxAscent(); 1.4817 + 1.4818 + gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(); 1.4819 + gfxFloat gfxWidth = aVisualOverflowRect->width / appUnitsPerDevUnit; 1.4820 + gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit; 1.4821 + gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit; 1.4822 + gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit; 1.4823 + gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit; 1.4824 + nsRect underlineRect = 1.4825 + nsCSSRendering::GetTextDecorationRect(aPresContext, 1.4826 + gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset, 1.4827 + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle); 1.4828 + nsRect overlineRect = 1.4829 + nsCSSRendering::GetTextDecorationRect(aPresContext, 1.4830 + gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent, 1.4831 + NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle); 1.4832 + 1.4833 + aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect); 1.4834 + aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect); 1.4835 + 1.4836 + // XXX If strikeoutSize is much thicker than the underlineSize, it may 1.4837 + // cause overflowing from the overflow rect. However, such case 1.4838 + // isn't realistic, we don't need to compute it now. 1.4839 + } 1.4840 + if (aIncludeTextDecorations) { 1.4841 + // Since CSS 2.1 requires that text-decoration defined on ancestors maintain 1.4842 + // style and position, they can be drawn at virtually any y-offset, so 1.4843 + // maxima and minima are required to reliably generate the rectangle for 1.4844 + // them 1.4845 + TextDecorations textDecs; 1.4846 + GetTextDecorations(aPresContext, eResolvedColors, textDecs); 1.4847 + if (textDecs.HasDecorationLines()) { 1.4848 + nscoord inflationMinFontSize = 1.4849 + nsLayoutUtils::InflationMinFontSizeFor(aBlockReflowState.frame); 1.4850 + 1.4851 + const nscoord width = GetSize().width; 1.4852 + const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(), 1.4853 + gfxWidth = width / appUnitsPerDevUnit, 1.4854 + ascent = gfxFloat(mAscent) / appUnitsPerDevUnit; 1.4855 + nscoord top(nscoord_MAX), bottom(nscoord_MIN); 1.4856 + // Below we loop through all text decorations and compute the rectangle 1.4857 + // containing all of them, in this frame's coordinate space 1.4858 + for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) { 1.4859 + const LineDecoration& dec = textDecs.mUnderlines[i]; 1.4860 + uint8_t decorationStyle = dec.mStyle; 1.4861 + // If the style is solid, let's include decoration line rect of solid 1.4862 + // style since changing the style from none to solid/dotted/dashed 1.4863 + // doesn't cause reflow. 1.4864 + if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.4865 + decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; 1.4866 + } 1.4867 + 1.4868 + float inflation = 1.4869 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.4870 + const gfxFont::Metrics metrics = 1.4871 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.4872 + 1.4873 + const nsRect decorationRect = 1.4874 + nsCSSRendering::GetTextDecorationRect(aPresContext, 1.4875 + gfxSize(gfxWidth, metrics.underlineSize), 1.4876 + ascent, metrics.underlineOffset, 1.4877 + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle) + 1.4878 + nsPoint(0, -dec.mBaselineOffset); 1.4879 + 1.4880 + top = std::min(decorationRect.y, top); 1.4881 + bottom = std::max(decorationRect.YMost(), bottom); 1.4882 + } 1.4883 + for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) { 1.4884 + const LineDecoration& dec = textDecs.mOverlines[i]; 1.4885 + uint8_t decorationStyle = dec.mStyle; 1.4886 + // If the style is solid, let's include decoration line rect of solid 1.4887 + // style since changing the style from none to solid/dotted/dashed 1.4888 + // doesn't cause reflow. 1.4889 + if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.4890 + decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; 1.4891 + } 1.4892 + 1.4893 + float inflation = 1.4894 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.4895 + const gfxFont::Metrics metrics = 1.4896 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.4897 + 1.4898 + const nsRect decorationRect = 1.4899 + nsCSSRendering::GetTextDecorationRect(aPresContext, 1.4900 + gfxSize(gfxWidth, metrics.underlineSize), 1.4901 + ascent, metrics.maxAscent, 1.4902 + NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle) + 1.4903 + nsPoint(0, -dec.mBaselineOffset); 1.4904 + 1.4905 + top = std::min(decorationRect.y, top); 1.4906 + bottom = std::max(decorationRect.YMost(), bottom); 1.4907 + } 1.4908 + for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) { 1.4909 + const LineDecoration& dec = textDecs.mStrikes[i]; 1.4910 + uint8_t decorationStyle = dec.mStyle; 1.4911 + // If the style is solid, let's include decoration line rect of solid 1.4912 + // style since changing the style from none to solid/dotted/dashed 1.4913 + // doesn't cause reflow. 1.4914 + if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.4915 + decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; 1.4916 + } 1.4917 + 1.4918 + float inflation = 1.4919 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.4920 + const gfxFont::Metrics metrics = 1.4921 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.4922 + 1.4923 + const nsRect decorationRect = 1.4924 + nsCSSRendering::GetTextDecorationRect(aPresContext, 1.4925 + gfxSize(gfxWidth, metrics.strikeoutSize), 1.4926 + ascent, metrics.strikeoutOffset, 1.4927 + NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle) + 1.4928 + nsPoint(0, -dec.mBaselineOffset); 1.4929 + top = std::min(decorationRect.y, top); 1.4930 + bottom = std::max(decorationRect.YMost(), bottom); 1.4931 + } 1.4932 + 1.4933 + aVisualOverflowRect->UnionRect(*aVisualOverflowRect, 1.4934 + nsRect(0, top, width, bottom - top)); 1.4935 + } 1.4936 + } 1.4937 + // When this frame is not selected, the text-decoration area must be in 1.4938 + // frame bounds. 1.4939 + if (!IsSelected() || 1.4940 + !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect)) 1.4941 + return; 1.4942 + AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); 1.4943 +} 1.4944 + 1.4945 +static gfxFloat 1.4946 +ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext, 1.4947 + nsTextFrame* aFrame, 1.4948 + const gfxFont::Metrics& aFontMetrics) 1.4949 +{ 1.4950 + gfxFloat app = aPresContext->AppUnitsPerDevPixel(); 1.4951 + nscoord lineHeightApp = 1.4952 + nsHTMLReflowState::CalcLineHeight(aFrame->GetContent(), 1.4953 + aFrame->StyleContext(), NS_AUTOHEIGHT, 1.4954 + aFrame->GetFontSizeInflation()); 1.4955 + gfxFloat lineHeight = gfxFloat(lineHeightApp) / app; 1.4956 + if (lineHeight <= aFontMetrics.maxHeight) { 1.4957 + return aFontMetrics.maxDescent; 1.4958 + } 1.4959 + return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; 1.4960 +} 1.4961 + 1.4962 + 1.4963 +// Make sure this stays in sync with DrawSelectionDecorations below 1.4964 +static const SelectionType SelectionTypesWithDecorations = 1.4965 + nsISelectionController::SELECTION_SPELLCHECK | 1.4966 + nsISelectionController::SELECTION_IME_RAWINPUT | 1.4967 + nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | 1.4968 + nsISelectionController::SELECTION_IME_CONVERTEDTEXT | 1.4969 + nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; 1.4970 + 1.4971 +static gfxFloat 1.4972 +ComputeSelectionUnderlineHeight(nsPresContext* aPresContext, 1.4973 + const gfxFont::Metrics& aFontMetrics, 1.4974 + SelectionType aSelectionType) 1.4975 +{ 1.4976 + switch (aSelectionType) { 1.4977 + case nsISelectionController::SELECTION_IME_RAWINPUT: 1.4978 + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: 1.4979 + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: 1.4980 + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: 1.4981 + return aFontMetrics.underlineSize; 1.4982 + case nsISelectionController::SELECTION_SPELLCHECK: { 1.4983 + // The thickness of the spellchecker underline shouldn't honor the font 1.4984 + // metrics. It should be constant pixels value which is decided from the 1.4985 + // default font size. Note that if the actual font size is smaller than 1.4986 + // the default font size, we should use the actual font size because the 1.4987 + // computed value from the default font size can be too thick for the 1.4988 + // current font size. 1.4989 + int32_t defaultFontSize = 1.4990 + aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size); 1.4991 + gfxFloat fontSize = std::min(gfxFloat(defaultFontSize), 1.4992 + aFontMetrics.emHeight); 1.4993 + fontSize = std::max(fontSize, 1.0); 1.4994 + return ceil(fontSize / 20); 1.4995 + } 1.4996 + default: 1.4997 + NS_WARNING("Requested underline style is not valid"); 1.4998 + return aFontMetrics.underlineSize; 1.4999 + } 1.5000 +} 1.5001 + 1.5002 +enum DecorationType { 1.5003 + eNormalDecoration, 1.5004 + eSelectionDecoration 1.5005 +}; 1.5006 + 1.5007 +static void 1.5008 +PaintDecorationLine(nsIFrame* aFrame, 1.5009 + gfxContext* const aCtx, 1.5010 + const gfxRect& aDirtyRect, 1.5011 + nscolor aColor, 1.5012 + const nscolor* aOverrideColor, 1.5013 + const gfxPoint& aPt, 1.5014 + gfxFloat aXInFrame, 1.5015 + const gfxSize& aLineSize, 1.5016 + gfxFloat aAscent, 1.5017 + gfxFloat aOffset, 1.5018 + uint8_t aDecoration, 1.5019 + uint8_t aStyle, 1.5020 + DecorationType aDecorationType, 1.5021 + nsTextFrame::DrawPathCallbacks* aCallbacks, 1.5022 + gfxFloat aDescentLimit = -1.0) 1.5023 +{ 1.5024 + nscolor lineColor = aOverrideColor ? *aOverrideColor : aColor; 1.5025 + if (aCallbacks) { 1.5026 + if (aDecorationType == eNormalDecoration) { 1.5027 + aCallbacks->NotifyBeforeDecorationLine(lineColor); 1.5028 + } else { 1.5029 + aCallbacks->NotifyBeforeSelectionDecorationLine(lineColor); 1.5030 + } 1.5031 + nsCSSRendering::DecorationLineToPath(aFrame, aCtx, aDirtyRect, lineColor, 1.5032 + aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle, 1.5033 + aDescentLimit); 1.5034 + if (aDecorationType == eNormalDecoration) { 1.5035 + aCallbacks->NotifyDecorationLinePathEmitted(); 1.5036 + } else { 1.5037 + aCallbacks->NotifySelectionDecorationLinePathEmitted(); 1.5038 + } 1.5039 + } else { 1.5040 + nsCSSRendering::PaintDecorationLine(aFrame, aCtx, aDirtyRect, lineColor, 1.5041 + aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle, 1.5042 + aDescentLimit); 1.5043 + } 1.5044 +} 1.5045 + 1.5046 +/** 1.5047 + * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about 1.5048 + * drawing text decoration for selections. 1.5049 + */ 1.5050 +static void DrawSelectionDecorations(gfxContext* aContext, 1.5051 + const gfxRect& aDirtyRect, 1.5052 + SelectionType aType, 1.5053 + nsTextFrame* aFrame, 1.5054 + nsTextPaintStyle& aTextPaintStyle, 1.5055 + const TextRangeStyle &aRangeStyle, 1.5056 + const gfxPoint& aPt, gfxFloat aXInFrame, gfxFloat aWidth, 1.5057 + gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics, 1.5058 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5059 +{ 1.5060 + gfxPoint pt(aPt); 1.5061 + gfxSize size(aWidth, 1.5062 + ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(), 1.5063 + aFontMetrics, aType)); 1.5064 + gfxFloat descentLimit = 1.5065 + ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(), 1.5066 + aFrame, aFontMetrics); 1.5067 + 1.5068 + float relativeSize; 1.5069 + uint8_t style; 1.5070 + nscolor color; 1.5071 + int32_t index = 1.5072 + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType); 1.5073 + bool weDefineSelectionUnderline = 1.5074 + aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color, 1.5075 + &relativeSize, &style); 1.5076 + 1.5077 + switch (aType) { 1.5078 + case nsISelectionController::SELECTION_IME_RAWINPUT: 1.5079 + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: 1.5080 + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: 1.5081 + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: { 1.5082 + // IME decoration lines should not be drawn on the both ends, i.e., we 1.5083 + // need to cut both edges of the decoration lines. Because same style 1.5084 + // IME selections can adjoin, but the users need to be able to know 1.5085 + // where are the boundaries of the selections. 1.5086 + // 1.5087 + // X: underline 1.5088 + // 1.5089 + // IME selection #1 IME selection #2 IME selection #3 1.5090 + // | | | 1.5091 + // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX 1.5092 + // +---------------------+----------------------+-------------------- 1.5093 + // ^ ^ ^ ^ ^ 1.5094 + // gap gap gap 1.5095 + pt.x += 1.0; 1.5096 + size.width -= 2.0; 1.5097 + if (aRangeStyle.IsDefined()) { 1.5098 + // If IME defines the style, that should override our definition. 1.5099 + if (aRangeStyle.IsLineStyleDefined()) { 1.5100 + if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) { 1.5101 + return; 1.5102 + } 1.5103 + style = aRangeStyle.mLineStyle; 1.5104 + relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; 1.5105 + } else if (!weDefineSelectionUnderline) { 1.5106 + // There is no underline style definition. 1.5107 + return; 1.5108 + } 1.5109 + if (aRangeStyle.IsUnderlineColorDefined()) { 1.5110 + color = aRangeStyle.mUnderlineColor; 1.5111 + } else if (aRangeStyle.IsForegroundColorDefined()) { 1.5112 + color = aRangeStyle.mForegroundColor; 1.5113 + } else { 1.5114 + NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(), 1.5115 + "Only the background color is defined"); 1.5116 + color = aTextPaintStyle.GetTextColor(); 1.5117 + } 1.5118 + } else if (!weDefineSelectionUnderline) { 1.5119 + // IME doesn't specify the selection style and we don't define selection 1.5120 + // underline. 1.5121 + return; 1.5122 + } 1.5123 + break; 1.5124 + } 1.5125 + case nsISelectionController::SELECTION_SPELLCHECK: 1.5126 + if (!weDefineSelectionUnderline) 1.5127 + return; 1.5128 + break; 1.5129 + default: 1.5130 + NS_WARNING("Requested selection decorations when there aren't any"); 1.5131 + return; 1.5132 + } 1.5133 + size.height *= relativeSize; 1.5134 + PaintDecorationLine(aFrame, aContext, aDirtyRect, color, nullptr, pt, 1.5135 + pt.x - aPt.x + aXInFrame, size, aAscent, aFontMetrics.underlineOffset, 1.5136 + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, eSelectionDecoration, 1.5137 + aCallbacks, descentLimit); 1.5138 +} 1.5139 + 1.5140 +/** 1.5141 + * This function encapsulates all knowledge of how selections affect foreground 1.5142 + * and background colors. 1.5143 + * @return true if the selection affects colors, false otherwise 1.5144 + * @param aForeground the foreground color to use 1.5145 + * @param aBackground the background color to use, or RGBA(0,0,0,0) if no 1.5146 + * background should be painted 1.5147 + */ 1.5148 +static bool GetSelectionTextColors(SelectionType aType, 1.5149 + nsTextPaintStyle& aTextPaintStyle, 1.5150 + const TextRangeStyle &aRangeStyle, 1.5151 + nscolor* aForeground, nscolor* aBackground) 1.5152 +{ 1.5153 + switch (aType) { 1.5154 + case nsISelectionController::SELECTION_NORMAL: 1.5155 + return aTextPaintStyle.GetSelectionColors(aForeground, aBackground); 1.5156 + case nsISelectionController::SELECTION_FIND: 1.5157 + aTextPaintStyle.GetHighlightColors(aForeground, aBackground); 1.5158 + return true; 1.5159 + case nsISelectionController::SELECTION_URLSECONDARY: 1.5160 + aTextPaintStyle.GetURLSecondaryColor(aForeground); 1.5161 + *aBackground = NS_RGBA(0,0,0,0); 1.5162 + return true; 1.5163 + case nsISelectionController::SELECTION_IME_RAWINPUT: 1.5164 + case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: 1.5165 + case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: 1.5166 + case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: 1.5167 + if (aRangeStyle.IsDefined()) { 1.5168 + *aForeground = aTextPaintStyle.GetTextColor(); 1.5169 + *aBackground = NS_RGBA(0,0,0,0); 1.5170 + if (!aRangeStyle.IsForegroundColorDefined() && 1.5171 + !aRangeStyle.IsBackgroundColorDefined()) { 1.5172 + return false; 1.5173 + } 1.5174 + if (aRangeStyle.IsForegroundColorDefined()) { 1.5175 + *aForeground = aRangeStyle.mForegroundColor; 1.5176 + } 1.5177 + if (aRangeStyle.IsBackgroundColorDefined()) { 1.5178 + *aBackground = aRangeStyle.mBackgroundColor; 1.5179 + } 1.5180 + return true; 1.5181 + } 1.5182 + aTextPaintStyle.GetIMESelectionColors( 1.5183 + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType), 1.5184 + aForeground, aBackground); 1.5185 + return true; 1.5186 + default: 1.5187 + *aForeground = aTextPaintStyle.GetTextColor(); 1.5188 + *aBackground = NS_RGBA(0,0,0,0); 1.5189 + return false; 1.5190 + } 1.5191 +} 1.5192 + 1.5193 +/** 1.5194 + * This sets *aShadow to the appropriate shadow, if any, for the given 1.5195 + * type of selection. Returns true if *aShadow was set. 1.5196 + * If text-shadow was not specified, *aShadow is left untouched 1.5197 + * (NOT reset to null), and the function returns false. 1.5198 + */ 1.5199 +static bool GetSelectionTextShadow(nsIFrame* aFrame, 1.5200 + SelectionType aType, 1.5201 + nsTextPaintStyle& aTextPaintStyle, 1.5202 + nsCSSShadowArray** aShadow) 1.5203 +{ 1.5204 + switch (aType) { 1.5205 + case nsISelectionController::SELECTION_NORMAL: 1.5206 + return aTextPaintStyle.GetSelectionShadow(aShadow); 1.5207 + default: 1.5208 + return false; 1.5209 + } 1.5210 +} 1.5211 + 1.5212 +/** 1.5213 + * This class lets us iterate over chunks of text in a uniform selection state, 1.5214 + * observing cluster boundaries, in content order, maintaining the current 1.5215 + * x-offset as we go, and telling whether the text chunk has a hyphen after 1.5216 + * it or not. The caller is responsible for actually computing the advance 1.5217 + * width of each chunk. 1.5218 + */ 1.5219 +class SelectionIterator { 1.5220 +public: 1.5221 + /** 1.5222 + * aStart and aLength are in the original string. aSelectionDetails is 1.5223 + * according to the original string. 1.5224 + * @param aXOffset the offset from the origin of the frame to the start 1.5225 + * of the text (the left baseline origin for LTR, the right baseline origin 1.5226 + * for RTL) 1.5227 + */ 1.5228 + SelectionIterator(SelectionDetails** aSelectionDetails, 1.5229 + int32_t aStart, int32_t aLength, 1.5230 + PropertyProvider& aProvider, gfxTextRun* aTextRun, 1.5231 + gfxFloat aXOffset); 1.5232 + 1.5233 + /** 1.5234 + * Returns the next segment of uniformly selected (or not) text. 1.5235 + * @param aXOffset the offset from the origin of the frame to the start 1.5236 + * of the text (the left baseline origin for LTR, the right baseline origin 1.5237 + * for RTL) 1.5238 + * @param aOffset the transformed string offset of the text for this segment 1.5239 + * @param aLength the transformed string length of the text for this segment 1.5240 + * @param aHyphenWidth if a hyphen is to be rendered after the text, the 1.5241 + * width of the hyphen, otherwise zero 1.5242 + * @param aType the selection type for this segment 1.5243 + * @param aStyle the selection style for this segment 1.5244 + * @return false if there are no more segments 1.5245 + */ 1.5246 + bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength, 1.5247 + gfxFloat* aHyphenWidth, SelectionType* aType, 1.5248 + TextRangeStyle* aStyle); 1.5249 + void UpdateWithAdvance(gfxFloat aAdvance) { 1.5250 + mXOffset += aAdvance*mTextRun->GetDirection(); 1.5251 + } 1.5252 + 1.5253 +private: 1.5254 + SelectionDetails** mSelectionDetails; 1.5255 + PropertyProvider& mProvider; 1.5256 + gfxTextRun* mTextRun; 1.5257 + gfxSkipCharsIterator mIterator; 1.5258 + int32_t mOriginalStart; 1.5259 + int32_t mOriginalEnd; 1.5260 + gfxFloat mXOffset; 1.5261 +}; 1.5262 + 1.5263 +SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails, 1.5264 + int32_t aStart, int32_t aLength, PropertyProvider& aProvider, 1.5265 + gfxTextRun* aTextRun, gfxFloat aXOffset) 1.5266 + : mSelectionDetails(aSelectionDetails), mProvider(aProvider), 1.5267 + mTextRun(aTextRun), mIterator(aProvider.GetStart()), 1.5268 + mOriginalStart(aStart), mOriginalEnd(aStart + aLength), 1.5269 + mXOffset(aXOffset) 1.5270 +{ 1.5271 + mIterator.SetOriginalOffset(aStart); 1.5272 +} 1.5273 + 1.5274 +bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset, 1.5275 + uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth, 1.5276 + SelectionType* aType, TextRangeStyle* aStyle) 1.5277 +{ 1.5278 + if (mIterator.GetOriginalOffset() >= mOriginalEnd) 1.5279 + return false; 1.5280 + 1.5281 + // save offset into transformed string now 1.5282 + uint32_t runOffset = mIterator.GetSkippedOffset(); 1.5283 + 1.5284 + int32_t index = mIterator.GetOriginalOffset() - mOriginalStart; 1.5285 + SelectionDetails* sdptr = mSelectionDetails[index]; 1.5286 + SelectionType type = 1.5287 + sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE; 1.5288 + TextRangeStyle style; 1.5289 + if (sdptr) { 1.5290 + style = sdptr->mTextRangeStyle; 1.5291 + } 1.5292 + for (++index; mOriginalStart + index < mOriginalEnd; ++index) { 1.5293 + if (sdptr != mSelectionDetails[index]) 1.5294 + break; 1.5295 + } 1.5296 + mIterator.SetOriginalOffset(index + mOriginalStart); 1.5297 + 1.5298 + // Advance to the next cluster boundary 1.5299 + while (mIterator.GetOriginalOffset() < mOriginalEnd && 1.5300 + !mIterator.IsOriginalCharSkipped() && 1.5301 + !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { 1.5302 + mIterator.AdvanceOriginal(1); 1.5303 + } 1.5304 + 1.5305 + bool haveHyphenBreak = 1.5306 + (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0; 1.5307 + *aOffset = runOffset; 1.5308 + *aLength = mIterator.GetSkippedOffset() - runOffset; 1.5309 + *aXOffset = mXOffset; 1.5310 + *aHyphenWidth = 0; 1.5311 + if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) { 1.5312 + *aHyphenWidth = mProvider.GetHyphenWidth(); 1.5313 + } 1.5314 + *aType = type; 1.5315 + *aStyle = style; 1.5316 + return true; 1.5317 +} 1.5318 + 1.5319 +static void 1.5320 +AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun, 1.5321 + gfxTextRun::Metrics* aMetrics, 1.5322 + gfxFont::BoundingBoxType aBoundingBoxType, 1.5323 + gfxContext* aContext) 1.5324 +{ 1.5325 + // Fix up metrics to include hyphen 1.5326 + nsAutoPtr<gfxTextRun> hyphenTextRun( 1.5327 + GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame)); 1.5328 + if (!hyphenTextRun.get()) 1.5329 + return; 1.5330 + 1.5331 + gfxTextRun::Metrics hyphenMetrics = 1.5332 + hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(), 1.5333 + aBoundingBoxType, aContext, nullptr); 1.5334 + aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft()); 1.5335 +} 1.5336 + 1.5337 +void 1.5338 +nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength, 1.5339 + nsCSSShadowItem* aShadowDetails, 1.5340 + PropertyProvider* aProvider, const nsRect& aDirtyRect, 1.5341 + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, 1.5342 + gfxContext* aCtx, const nscolor& aForegroundColor, 1.5343 + const nsCharClipDisplayItem::ClipEdges& aClipEdges, 1.5344 + nscoord aLeftSideOffset, gfxRect& aBoundingBox) 1.5345 +{ 1.5346 + PROFILER_LABEL("nsTextFrame", "PaintOneShadow"); 1.5347 + gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset); 1.5348 + nscoord blurRadius = std::max(aShadowDetails->mRadius, 0); 1.5349 + 1.5350 + // This rect is the box which is equivalent to where the shadow will be painted. 1.5351 + // The origin of aBoundingBox is the text baseline left, so we must translate it by 1.5352 + // that much in order to make the origin the top-left corner of the text bounding box. 1.5353 + gfxRect shadowGfxRect = aBoundingBox + 1.5354 + gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset; 1.5355 + nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()), 1.5356 + NSToCoordRound(shadowGfxRect.Y()), 1.5357 + NSToCoordRound(shadowGfxRect.Width()), 1.5358 + NSToCoordRound(shadowGfxRect.Height())); 1.5359 + 1.5360 + nsContextBoxBlur contextBoxBlur; 1.5361 + gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius, 1.5362 + PresContext()->AppUnitsPerDevPixel(), 1.5363 + aCtx, aDirtyRect, nullptr); 1.5364 + if (!shadowContext) 1.5365 + return; 1.5366 + 1.5367 + nscolor shadowColor; 1.5368 + const nscolor* decorationOverrideColor; 1.5369 + if (aShadowDetails->mHasColor) { 1.5370 + shadowColor = aShadowDetails->mColor; 1.5371 + decorationOverrideColor = &shadowColor; 1.5372 + } else { 1.5373 + shadowColor = aForegroundColor; 1.5374 + decorationOverrideColor = nullptr; 1.5375 + } 1.5376 + 1.5377 + aCtx->Save(); 1.5378 + aCtx->NewPath(); 1.5379 + aCtx->SetColor(gfxRGBA(shadowColor)); 1.5380 + 1.5381 + // Draw the text onto our alpha-only surface to capture the alpha values. 1.5382 + // Remember that the box blur context has a device offset on it, so we don't need to 1.5383 + // translate any coordinates to fit on the surface. 1.5384 + gfxFloat advanceWidth; 1.5385 + gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, 1.5386 + aDirtyRect.width, aDirtyRect.height); 1.5387 + DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset, 1.5388 + aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider, 1.5389 + nsTextPaintStyle(this), 1.5390 + aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges, 1.5391 + advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, 1.5392 + decorationOverrideColor); 1.5393 + 1.5394 + contextBoxBlur.DoPaint(); 1.5395 + aCtx->Restore(); 1.5396 +} 1.5397 + 1.5398 +// Paints selection backgrounds and text in the correct colors. Also computes 1.5399 +// aAllTypes, the union of all selection types that are applying to this text. 1.5400 +bool 1.5401 +nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx, 1.5402 + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, 1.5403 + const gfxRect& aDirtyRect, 1.5404 + PropertyProvider& aProvider, 1.5405 + uint32_t aContentOffset, uint32_t aContentLength, 1.5406 + nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails, 1.5407 + SelectionType* aAllTypes, 1.5408 + const nsCharClipDisplayItem::ClipEdges& aClipEdges, 1.5409 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5410 +{ 1.5411 + // Figure out which selections control the colors to use for each character. 1.5412 + AutoFallibleTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer; 1.5413 + SelectionDetails** prevailingSelections = 1.5414 + prevailingSelectionsBuffer.AppendElements(aContentLength); 1.5415 + if (!prevailingSelections) { 1.5416 + return false; 1.5417 + } 1.5418 + 1.5419 + SelectionType allTypes = 0; 1.5420 + for (uint32_t i = 0; i < aContentLength; ++i) { 1.5421 + prevailingSelections[i] = nullptr; 1.5422 + } 1.5423 + 1.5424 + SelectionDetails *sdptr = aDetails; 1.5425 + bool anyBackgrounds = false; 1.5426 + while (sdptr) { 1.5427 + int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset)); 1.5428 + int32_t end = std::min(int32_t(aContentLength), 1.5429 + sdptr->mEnd - int32_t(aContentOffset)); 1.5430 + SelectionType type = sdptr->mType; 1.5431 + if (start < end) { 1.5432 + allTypes |= type; 1.5433 + // Ignore selections that don't set colors 1.5434 + nscolor foreground, background; 1.5435 + if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle, 1.5436 + &foreground, &background)) { 1.5437 + if (NS_GET_A(background) > 0) { 1.5438 + anyBackgrounds = true; 1.5439 + } 1.5440 + for (int32_t i = start; i < end; ++i) { 1.5441 + // Favour normal selection over IME selections 1.5442 + if (!prevailingSelections[i] || 1.5443 + type < prevailingSelections[i]->mType) { 1.5444 + prevailingSelections[i] = sdptr; 1.5445 + } 1.5446 + } 1.5447 + } 1.5448 + } 1.5449 + sdptr = sdptr->mNext; 1.5450 + } 1.5451 + *aAllTypes = allTypes; 1.5452 + 1.5453 + if (!allTypes) { 1.5454 + // Nothing is selected in the given text range. XXX can this still occur? 1.5455 + return false; 1.5456 + } 1.5457 + 1.5458 + const gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x; 1.5459 + gfxFloat xOffset, hyphenWidth; 1.5460 + uint32_t offset, length; // in transformed string 1.5461 + SelectionType type; 1.5462 + TextRangeStyle rangeStyle; 1.5463 + // Draw background colors 1.5464 + if (anyBackgrounds) { 1.5465 + SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength, 1.5466 + aProvider, mTextRun, startXOffset); 1.5467 + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, 1.5468 + &type, &rangeStyle)) { 1.5469 + nscolor foreground, background; 1.5470 + GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, 1.5471 + &foreground, &background); 1.5472 + // Draw background color 1.5473 + gfxFloat advance = hyphenWidth + 1.5474 + mTextRun->GetAdvanceWidth(offset, length, &aProvider); 1.5475 + if (NS_GET_A(background) > 0) { 1.5476 + gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0); 1.5477 + PaintSelectionBackground(aCtx, aTextPaintStyle.PresContext(), 1.5478 + background, aDirtyRect, 1.5479 + gfxRect(aFramePt.x + x, aFramePt.y, advance, 1.5480 + GetSize().height), aCallbacks); 1.5481 + } 1.5482 + iterator.UpdateWithAdvance(advance); 1.5483 + } 1.5484 + } 1.5485 + 1.5486 + // Draw text 1.5487 + const nsStyleText* textStyle = StyleText(); 1.5488 + nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y, 1.5489 + aDirtyRect.width, aDirtyRect.height); 1.5490 + SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength, 1.5491 + aProvider, mTextRun, startXOffset); 1.5492 + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, 1.5493 + &type, &rangeStyle)) { 1.5494 + nscolor foreground, background; 1.5495 + GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, 1.5496 + &foreground, &background); 1.5497 + gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y); 1.5498 + 1.5499 + // Determine what shadow, if any, to draw - either from textStyle 1.5500 + // or from the ::-moz-selection pseudo-class if specified there 1.5501 + nsCSSShadowArray* shadow = textStyle->GetTextShadow(); 1.5502 + GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow); 1.5503 + 1.5504 + // Draw shadows, if any 1.5505 + if (shadow) { 1.5506 + gfxTextRun::Metrics shadowMetrics = 1.5507 + mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, 1.5508 + nullptr, &aProvider); 1.5509 + if (GetStateBits() & TEXT_HYPHEN_BREAK) { 1.5510 + AddHyphenToMetrics(this, mTextRun, &shadowMetrics, 1.5511 + gfxFont::LOOSE_INK_EXTENTS, aCtx); 1.5512 + } 1.5513 + for (uint32_t i = shadow->Length(); i > 0; --i) { 1.5514 + PaintOneShadow(offset, length, 1.5515 + shadow->ShadowAt(i - 1), &aProvider, 1.5516 + dirtyRect, aFramePt, textBaselinePt, aCtx, 1.5517 + foreground, aClipEdges, 1.5518 + xOffset - (mTextRun->IsRightToLeft() ? 1.5519 + shadowMetrics.mBoundingBox.width : 0), 1.5520 + shadowMetrics.mBoundingBox); 1.5521 + } 1.5522 + } 1.5523 + 1.5524 + // Draw text segment 1.5525 + gfxFloat advance; 1.5526 + 1.5527 + DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt, 1.5528 + offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges, 1.5529 + advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks); 1.5530 + if (hyphenWidth) { 1.5531 + advance += hyphenWidth; 1.5532 + } 1.5533 + iterator.UpdateWithAdvance(advance); 1.5534 + } 1.5535 + return true; 1.5536 +} 1.5537 + 1.5538 +void 1.5539 +nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, 1.5540 + const gfxPoint& aFramePt, 1.5541 + const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, 1.5542 + PropertyProvider& aProvider, 1.5543 + uint32_t aContentOffset, uint32_t aContentLength, 1.5544 + nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails, 1.5545 + SelectionType aSelectionType, 1.5546 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5547 +{ 1.5548 + // Hide text decorations if we're currently hiding @font-face fallback text 1.5549 + if (aProvider.GetFontGroup()->ShouldSkipDrawing()) 1.5550 + return; 1.5551 + 1.5552 + // Figure out which characters will be decorated for this selection. 1.5553 + AutoFallibleTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer; 1.5554 + SelectionDetails** selectedChars = 1.5555 + selectedCharsBuffer.AppendElements(aContentLength); 1.5556 + if (!selectedChars) { 1.5557 + return; 1.5558 + } 1.5559 + for (uint32_t i = 0; i < aContentLength; ++i) { 1.5560 + selectedChars[i] = nullptr; 1.5561 + } 1.5562 + 1.5563 + SelectionDetails *sdptr = aDetails; 1.5564 + while (sdptr) { 1.5565 + if (sdptr->mType == aSelectionType) { 1.5566 + int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset)); 1.5567 + int32_t end = std::min(int32_t(aContentLength), 1.5568 + sdptr->mEnd - int32_t(aContentOffset)); 1.5569 + for (int32_t i = start; i < end; ++i) { 1.5570 + selectedChars[i] = sdptr; 1.5571 + } 1.5572 + } 1.5573 + sdptr = sdptr->mNext; 1.5574 + } 1.5575 + 1.5576 + gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); 1.5577 + if (!firstFont) 1.5578 + return; // OOM 1.5579 + gfxFont::Metrics decorationMetrics(firstFont->GetMetrics()); 1.5580 + decorationMetrics.underlineOffset = 1.5581 + aProvider.GetFontGroup()->GetUnderlineOffset(); 1.5582 + 1.5583 + gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x; 1.5584 + SelectionIterator iterator(selectedChars, aContentOffset, aContentLength, 1.5585 + aProvider, mTextRun, startXOffset); 1.5586 + gfxFloat xOffset, hyphenWidth; 1.5587 + uint32_t offset, length; 1.5588 + int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); 1.5589 + // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? 1.5590 + gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app); 1.5591 + gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app, 1.5592 + aDirtyRect.width / app, aDirtyRect.height / app); 1.5593 + SelectionType type; 1.5594 + TextRangeStyle selectedStyle; 1.5595 + while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, 1.5596 + &type, &selectedStyle)) { 1.5597 + gfxFloat advance = hyphenWidth + 1.5598 + mTextRun->GetAdvanceWidth(offset, length, &aProvider); 1.5599 + if (type == aSelectionType) { 1.5600 + pt.x = (aFramePt.x + xOffset - 1.5601 + (mTextRun->IsRightToLeft() ? advance : 0)) / app; 1.5602 + gfxFloat width = Abs(advance) / app; 1.5603 + gfxFloat xInFrame = pt.x - (aFramePt.x / app); 1.5604 + DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this, 1.5605 + aTextPaintStyle, selectedStyle, pt, xInFrame, 1.5606 + width, mAscent / app, decorationMetrics, 1.5607 + aCallbacks); 1.5608 + } 1.5609 + iterator.UpdateWithAdvance(advance); 1.5610 + } 1.5611 +} 1.5612 + 1.5613 +bool 1.5614 +nsTextFrame::PaintTextWithSelection(gfxContext* aCtx, 1.5615 + const gfxPoint& aFramePt, 1.5616 + const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, 1.5617 + PropertyProvider& aProvider, 1.5618 + uint32_t aContentOffset, uint32_t aContentLength, 1.5619 + nsTextPaintStyle& aTextPaintStyle, 1.5620 + const nsCharClipDisplayItem::ClipEdges& aClipEdges, 1.5621 + gfxTextContextPaint* aContextPaint, 1.5622 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5623 +{ 1.5624 + NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path"); 1.5625 + 1.5626 + SelectionDetails* details = GetSelectionDetails(); 1.5627 + if (!details) { 1.5628 + return false; 1.5629 + } 1.5630 + 1.5631 + SelectionType allTypes; 1.5632 + if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, 1.5633 + aProvider, aContentOffset, aContentLength, 1.5634 + aTextPaintStyle, details, &allTypes, 1.5635 + aClipEdges, aCallbacks)) { 1.5636 + DestroySelectionDetails(details); 1.5637 + return false; 1.5638 + } 1.5639 + // Iterate through just the selection types that paint decorations and 1.5640 + // paint decorations for any that actually occur in this frame. Paint 1.5641 + // higher-numbered selection types below lower-numered ones on the 1.5642 + // general principal that lower-numbered selections are higher priority. 1.5643 + allTypes &= SelectionTypesWithDecorations; 1.5644 + for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1; 1.5645 + i >= 1; --i) { 1.5646 + SelectionType type = 1 << (i - 1); 1.5647 + if (allTypes & type) { 1.5648 + // There is some selection of this type. Try to paint its decorations 1.5649 + // (there might not be any for this type but that's OK, 1.5650 + // PaintTextSelectionDecorations will exit early). 1.5651 + PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, 1.5652 + aProvider, aContentOffset, aContentLength, 1.5653 + aTextPaintStyle, details, type, 1.5654 + aCallbacks); 1.5655 + } 1.5656 + } 1.5657 + 1.5658 + DestroySelectionDetails(details); 1.5659 + return true; 1.5660 +} 1.5661 + 1.5662 +nscolor 1.5663 +nsTextFrame::GetCaretColorAt(int32_t aOffset) 1.5664 +{ 1.5665 + NS_PRECONDITION(aOffset >= 0, "aOffset must be positive"); 1.5666 + 1.5667 + nscolor result = nsFrame::GetCaretColorAt(aOffset); 1.5668 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.5669 + PropertyProvider provider(this, iter, nsTextFrame::eInflated); 1.5670 + int32_t contentOffset = provider.GetStart().GetOriginalOffset(); 1.5671 + int32_t contentLength = provider.GetOriginalLength(); 1.5672 + NS_PRECONDITION(aOffset >= contentOffset && 1.5673 + aOffset <= contentOffset + contentLength, 1.5674 + "aOffset must be in the frame's range"); 1.5675 + int32_t offsetInFrame = aOffset - contentOffset; 1.5676 + if (offsetInFrame < 0 || offsetInFrame >= contentLength) { 1.5677 + return result; 1.5678 + } 1.5679 + 1.5680 + bool isSolidTextColor = true; 1.5681 + if (IsSVGText()) { 1.5682 + const nsStyleSVG* style = StyleSVG(); 1.5683 + if (style->mFill.mType != eStyleSVGPaintType_None && 1.5684 + style->mFill.mType != eStyleSVGPaintType_Color) { 1.5685 + isSolidTextColor = false; 1.5686 + } 1.5687 + } 1.5688 + 1.5689 + nsTextPaintStyle textPaintStyle(this); 1.5690 + textPaintStyle.SetResolveColors(isSolidTextColor); 1.5691 + SelectionDetails* details = GetSelectionDetails(); 1.5692 + SelectionDetails* sdptr = details; 1.5693 + SelectionType type = 0; 1.5694 + while (sdptr) { 1.5695 + int32_t start = std::max(0, sdptr->mStart - contentOffset); 1.5696 + int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset); 1.5697 + if (start <= offsetInFrame && offsetInFrame < end && 1.5698 + (type == 0 || sdptr->mType < type)) { 1.5699 + nscolor foreground, background; 1.5700 + if (GetSelectionTextColors(sdptr->mType, textPaintStyle, 1.5701 + sdptr->mTextRangeStyle, 1.5702 + &foreground, &background)) { 1.5703 + if (!isSolidTextColor && 1.5704 + NS_IS_SELECTION_SPECIAL_COLOR(foreground)) { 1.5705 + result = NS_RGBA(0, 0, 0, 255); 1.5706 + } else { 1.5707 + result = foreground; 1.5708 + } 1.5709 + type = sdptr->mType; 1.5710 + } 1.5711 + } 1.5712 + sdptr = sdptr->mNext; 1.5713 + } 1.5714 + 1.5715 + DestroySelectionDetails(details); 1.5716 + return result; 1.5717 +} 1.5718 + 1.5719 +static uint32_t 1.5720 +ComputeTransformedLength(PropertyProvider& aProvider) 1.5721 +{ 1.5722 + gfxSkipCharsIterator iter(aProvider.GetStart()); 1.5723 + uint32_t start = iter.GetSkippedOffset(); 1.5724 + iter.AdvanceOriginal(aProvider.GetOriginalLength()); 1.5725 + return iter.GetSkippedOffset() - start; 1.5726 +} 1.5727 + 1.5728 +bool 1.5729 +nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge, 1.5730 + nscoord* aSnappedLeftEdge, 1.5731 + nscoord* aSnappedRightEdge) 1.5732 +{ 1.5733 + // We need a *reference* rendering context (not one that might have a 1.5734 + // transform), so we don't have a rendering context argument. 1.5735 + // XXX get the block and line passed to us somehow! This is slow! 1.5736 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.5737 + if (!mTextRun) 1.5738 + return false; 1.5739 + 1.5740 + PropertyProvider provider(this, iter, nsTextFrame::eInflated); 1.5741 + // Trim trailing whitespace 1.5742 + provider.InitializeForDisplay(true); 1.5743 + 1.5744 + uint32_t startOffset = provider.GetStart().GetSkippedOffset(); 1.5745 + uint32_t maxLength = ComputeTransformedLength(provider); 1.5746 + return MeasureCharClippedText(provider, aLeftEdge, aRightEdge, 1.5747 + &startOffset, &maxLength, 1.5748 + aSnappedLeftEdge, aSnappedRightEdge); 1.5749 +} 1.5750 + 1.5751 +static uint32_t GetClusterLength(gfxTextRun* aTextRun, 1.5752 + uint32_t aStartOffset, 1.5753 + uint32_t aMaxLength, 1.5754 + bool aIsRTL) 1.5755 +{ 1.5756 + uint32_t clusterLength = aIsRTL ? 0 : 1; 1.5757 + while (clusterLength < aMaxLength) { 1.5758 + if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) { 1.5759 + if (aIsRTL) { 1.5760 + ++clusterLength; 1.5761 + } 1.5762 + break; 1.5763 + } 1.5764 + ++clusterLength; 1.5765 + } 1.5766 + return clusterLength; 1.5767 +} 1.5768 + 1.5769 +bool 1.5770 +nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider, 1.5771 + nscoord aLeftEdge, nscoord aRightEdge, 1.5772 + uint32_t* aStartOffset, 1.5773 + uint32_t* aMaxLength, 1.5774 + nscoord* aSnappedLeftEdge, 1.5775 + nscoord* aSnappedRightEdge) 1.5776 +{ 1.5777 + *aSnappedLeftEdge = 0; 1.5778 + *aSnappedRightEdge = 0; 1.5779 + if (aLeftEdge <= 0 && aRightEdge <= 0) { 1.5780 + return true; 1.5781 + } 1.5782 + 1.5783 + uint32_t offset = *aStartOffset; 1.5784 + uint32_t maxLength = *aMaxLength; 1.5785 + const nscoord frameWidth = GetSize().width; 1.5786 + const bool rtl = mTextRun->IsRightToLeft(); 1.5787 + gfxFloat advanceWidth = 0; 1.5788 + const nscoord startEdge = rtl ? aRightEdge : aLeftEdge; 1.5789 + if (startEdge > 0) { 1.5790 + const gfxFloat maxAdvance = gfxFloat(startEdge); 1.5791 + while (maxLength > 0) { 1.5792 + uint32_t clusterLength = 1.5793 + GetClusterLength(mTextRun, offset, maxLength, rtl); 1.5794 + advanceWidth += 1.5795 + mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider); 1.5796 + maxLength -= clusterLength; 1.5797 + offset += clusterLength; 1.5798 + if (advanceWidth >= maxAdvance) { 1.5799 + break; 1.5800 + } 1.5801 + } 1.5802 + nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge; 1.5803 + *snappedStartEdge = NSToCoordFloor(advanceWidth); 1.5804 + *aStartOffset = offset; 1.5805 + } 1.5806 + 1.5807 + const nscoord endEdge = rtl ? aLeftEdge : aRightEdge; 1.5808 + if (endEdge > 0) { 1.5809 + const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge); 1.5810 + while (maxLength > 0) { 1.5811 + uint32_t clusterLength = 1.5812 + GetClusterLength(mTextRun, offset, maxLength, rtl); 1.5813 + gfxFloat nextAdvance = advanceWidth + 1.5814 + mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider); 1.5815 + if (nextAdvance > maxAdvance) { 1.5816 + break; 1.5817 + } 1.5818 + // This cluster fits, include it. 1.5819 + advanceWidth = nextAdvance; 1.5820 + maxLength -= clusterLength; 1.5821 + offset += clusterLength; 1.5822 + } 1.5823 + maxLength = offset - *aStartOffset; 1.5824 + nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge; 1.5825 + *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth); 1.5826 + } 1.5827 + *aMaxLength = maxLength; 1.5828 + return maxLength != 0; 1.5829 +} 1.5830 + 1.5831 +void 1.5832 +nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt, 1.5833 + const nsRect& aDirtyRect, 1.5834 + const nsCharClipDisplayItem& aItem, 1.5835 + gfxTextContextPaint* aContextPaint, 1.5836 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5837 +{ 1.5838 + // Don't pass in aRenderingContext here, because we need a *reference* 1.5839 + // context and aRenderingContext might have some transform in it 1.5840 + // XXX get the block and line passed to us somehow! This is slow! 1.5841 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.5842 + if (!mTextRun) 1.5843 + return; 1.5844 + 1.5845 + PropertyProvider provider(this, iter, nsTextFrame::eInflated); 1.5846 + // Trim trailing whitespace 1.5847 + provider.InitializeForDisplay(true); 1.5848 + 1.5849 + gfxContext* ctx = aRenderingContext->ThebesContext(); 1.5850 + const bool rtl = mTextRun->IsRightToLeft(); 1.5851 + const nscoord frameWidth = GetSize().width; 1.5852 + gfxPoint framePt(aPt.x, aPt.y); 1.5853 + gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x, 1.5854 + nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent)); 1.5855 + uint32_t startOffset = provider.GetStart().GetSkippedOffset(); 1.5856 + uint32_t maxLength = ComputeTransformedLength(provider); 1.5857 + nscoord snappedLeftEdge, snappedRightEdge; 1.5858 + if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge, 1.5859 + &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) { 1.5860 + return; 1.5861 + } 1.5862 + textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge; 1.5863 + nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge, 1.5864 + snappedRightEdge); 1.5865 + nsTextPaintStyle textPaintStyle(this); 1.5866 + textPaintStyle.SetResolveColors(!aCallbacks); 1.5867 + 1.5868 + gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, 1.5869 + aDirtyRect.width, aDirtyRect.height); 1.5870 + // Fork off to the (slower) paint-with-selection path if necessary. 1.5871 + if (IsSelected()) { 1.5872 + gfxSkipCharsIterator tmp(provider.GetStart()); 1.5873 + int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset); 1.5874 + int32_t contentLength = 1.5875 + tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset; 1.5876 + if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect, 1.5877 + provider, contentOffset, contentLength, 1.5878 + textPaintStyle, clipEdges, aContextPaint, 1.5879 + aCallbacks)) { 1.5880 + return; 1.5881 + } 1.5882 + } 1.5883 + 1.5884 + nscolor foregroundColor = textPaintStyle.GetTextColor(); 1.5885 + if (!aCallbacks) { 1.5886 + const nsStyleText* textStyle = StyleText(); 1.5887 + if (textStyle->HasTextShadow()) { 1.5888 + // Text shadow happens with the last value being painted at the back, 1.5889 + // ie. it is painted first. 1.5890 + gfxTextRun::Metrics shadowMetrics = 1.5891 + mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS, 1.5892 + nullptr, &provider); 1.5893 + for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) { 1.5894 + PaintOneShadow(startOffset, maxLength, 1.5895 + textStyle->mTextShadow->ShadowAt(i - 1), &provider, 1.5896 + aDirtyRect, framePt, textBaselinePt, ctx, 1.5897 + foregroundColor, clipEdges, 1.5898 + snappedLeftEdge, shadowMetrics.mBoundingBox); 1.5899 + } 1.5900 + } 1.5901 + } 1.5902 + 1.5903 + gfxFloat advanceWidth; 1.5904 + DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider, 1.5905 + textPaintStyle, foregroundColor, clipEdges, advanceWidth, 1.5906 + (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, 1.5907 + nullptr, aContextPaint, aCallbacks); 1.5908 +} 1.5909 + 1.5910 +static void 1.5911 +DrawTextRun(gfxTextRun* aTextRun, 1.5912 + gfxContext* const aCtx, 1.5913 + const gfxPoint& aTextBaselinePt, 1.5914 + uint32_t aOffset, uint32_t aLength, 1.5915 + PropertyProvider* aProvider, 1.5916 + nscolor aTextColor, 1.5917 + gfxFloat* aAdvanceWidth, 1.5918 + gfxTextContextPaint* aContextPaint, 1.5919 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5920 +{ 1.5921 + DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH : 1.5922 + DrawMode::GLYPH_FILL; 1.5923 + if (aCallbacks) { 1.5924 + aCallbacks->NotifyBeforeText(aTextColor); 1.5925 + aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength, 1.5926 + aProvider, aAdvanceWidth, aContextPaint, aCallbacks); 1.5927 + aCallbacks->NotifyAfterText(); 1.5928 + } else { 1.5929 + aCtx->SetColor(gfxRGBA(aTextColor)); 1.5930 + aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength, 1.5931 + aProvider, aAdvanceWidth, aContextPaint); 1.5932 + } 1.5933 +} 1.5934 + 1.5935 +void 1.5936 +nsTextFrame::DrawTextRun(gfxContext* const aCtx, 1.5937 + const gfxPoint& aTextBaselinePt, 1.5938 + uint32_t aOffset, uint32_t aLength, 1.5939 + PropertyProvider& aProvider, 1.5940 + nscolor aTextColor, 1.5941 + gfxFloat& aAdvanceWidth, 1.5942 + bool aDrawSoftHyphen, 1.5943 + gfxTextContextPaint* aContextPaint, 1.5944 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5945 +{ 1.5946 + ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider, 1.5947 + aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks); 1.5948 + 1.5949 + if (aDrawSoftHyphen) { 1.5950 + // Don't use ctx as the context, because we need a reference context here, 1.5951 + // ctx may be transformed. 1.5952 + nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this)); 1.5953 + if (hyphenTextRun.get()) { 1.5954 + // For right-to-left text runs, the soft-hyphen is positioned at the left 1.5955 + // of the text, minus its own width 1.5956 + gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth - 1.5957 + (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nullptr) : 0); 1.5958 + ::DrawTextRun(hyphenTextRun.get(), aCtx, 1.5959 + gfxPoint(hyphenBaselineX, aTextBaselinePt.y), 1.5960 + 0, hyphenTextRun->GetLength(), 1.5961 + nullptr, aTextColor, nullptr, aContextPaint, aCallbacks); 1.5962 + } 1.5963 + } 1.5964 +} 1.5965 + 1.5966 +void 1.5967 +nsTextFrame::DrawTextRunAndDecorations( 1.5968 + gfxContext* const aCtx, const gfxRect& aDirtyRect, 1.5969 + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, 1.5970 + uint32_t aOffset, uint32_t aLength, 1.5971 + PropertyProvider& aProvider, 1.5972 + const nsTextPaintStyle& aTextStyle, 1.5973 + nscolor aTextColor, 1.5974 + const nsCharClipDisplayItem::ClipEdges& aClipEdges, 1.5975 + gfxFloat& aAdvanceWidth, 1.5976 + bool aDrawSoftHyphen, 1.5977 + const TextDecorations& aDecorations, 1.5978 + const nscolor* const aDecorationOverrideColor, 1.5979 + gfxTextContextPaint* aContextPaint, 1.5980 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.5981 +{ 1.5982 + const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel(); 1.5983 + 1.5984 + // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? 1.5985 + nscoord x = NSToCoordRound(aFramePt.x); 1.5986 + nscoord width = GetRect().width; 1.5987 + aClipEdges.Intersect(&x, &width); 1.5988 + 1.5989 + gfxPoint decPt(x / app, 0); 1.5990 + gfxSize decSize(width / app, 0); 1.5991 + const gfxFloat ascent = gfxFloat(mAscent) / app; 1.5992 + const gfxFloat frameTop = aFramePt.y; 1.5993 + 1.5994 + gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app, 1.5995 + aDirtyRect.Width() / app, aDirtyRect.Height() / app); 1.5996 + 1.5997 + nscoord inflationMinFontSize = 1.5998 + nsLayoutUtils::InflationMinFontSizeFor(this); 1.5999 + 1.6000 + // Underlines 1.6001 + for (uint32_t i = aDecorations.mUnderlines.Length(); i-- > 0; ) { 1.6002 + const LineDecoration& dec = aDecorations.mUnderlines[i]; 1.6003 + if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.6004 + continue; 1.6005 + } 1.6006 + 1.6007 + float inflation = 1.6008 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.6009 + const gfxFont::Metrics metrics = 1.6010 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.6011 + 1.6012 + decSize.height = metrics.underlineSize; 1.6013 + decPt.y = (frameTop - dec.mBaselineOffset) / app; 1.6014 + 1.6015 + PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, 1.6016 + aDecorationOverrideColor, decPt, 0.0, decSize, ascent, 1.6017 + metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, 1.6018 + dec.mStyle, eNormalDecoration, aCallbacks); 1.6019 + } 1.6020 + // Overlines 1.6021 + for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) { 1.6022 + const LineDecoration& dec = aDecorations.mOverlines[i]; 1.6023 + if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.6024 + continue; 1.6025 + } 1.6026 + 1.6027 + float inflation = 1.6028 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.6029 + const gfxFont::Metrics metrics = 1.6030 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.6031 + 1.6032 + decSize.height = metrics.underlineSize; 1.6033 + decPt.y = (frameTop - dec.mBaselineOffset) / app; 1.6034 + 1.6035 + PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, 1.6036 + aDecorationOverrideColor, decPt, 0.0, decSize, ascent, 1.6037 + metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle, 1.6038 + eNormalDecoration, aCallbacks); 1.6039 + } 1.6040 + 1.6041 + // CSS 2.1 mandates that text be painted after over/underlines, and *then* 1.6042 + // line-throughs 1.6043 + DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor, 1.6044 + aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks); 1.6045 + 1.6046 + // Line-throughs 1.6047 + for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) { 1.6048 + const LineDecoration& dec = aDecorations.mStrikes[i]; 1.6049 + if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { 1.6050 + continue; 1.6051 + } 1.6052 + 1.6053 + float inflation = 1.6054 + GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); 1.6055 + const gfxFont::Metrics metrics = 1.6056 + GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); 1.6057 + 1.6058 + decSize.height = metrics.strikeoutSize; 1.6059 + decPt.y = (frameTop - dec.mBaselineOffset) / app; 1.6060 + 1.6061 + PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, 1.6062 + aDecorationOverrideColor, decPt, 0.0, decSize, ascent, 1.6063 + metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, 1.6064 + dec.mStyle, eNormalDecoration, aCallbacks); 1.6065 + } 1.6066 +} 1.6067 + 1.6068 +void 1.6069 +nsTextFrame::DrawText( 1.6070 + gfxContext* const aCtx, const gfxRect& aDirtyRect, 1.6071 + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, 1.6072 + uint32_t aOffset, uint32_t aLength, 1.6073 + PropertyProvider& aProvider, 1.6074 + const nsTextPaintStyle& aTextStyle, 1.6075 + nscolor aTextColor, 1.6076 + const nsCharClipDisplayItem::ClipEdges& aClipEdges, 1.6077 + gfxFloat& aAdvanceWidth, 1.6078 + bool aDrawSoftHyphen, 1.6079 + const nscolor* const aDecorationOverrideColor, 1.6080 + gfxTextContextPaint* aContextPaint, 1.6081 + nsTextFrame::DrawPathCallbacks* aCallbacks) 1.6082 +{ 1.6083 + TextDecorations decorations; 1.6084 + GetTextDecorations(aTextStyle.PresContext(), 1.6085 + aCallbacks ? eUnresolvedColors : eResolvedColors, 1.6086 + decorations); 1.6087 + 1.6088 + // Hide text decorations if we're currently hiding @font-face fallback text 1.6089 + const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() && 1.6090 + decorations.HasDecorationLines(); 1.6091 + if (drawDecorations) { 1.6092 + DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength, 1.6093 + aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth, 1.6094 + aDrawSoftHyphen, decorations, 1.6095 + aDecorationOverrideColor, aContextPaint, aCallbacks); 1.6096 + } else { 1.6097 + DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, 1.6098 + aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks); 1.6099 + } 1.6100 +} 1.6101 + 1.6102 +int16_t 1.6103 +nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) 1.6104 +{ 1.6105 + // get the selection controller 1.6106 + nsCOMPtr<nsISelectionController> selectionController; 1.6107 + nsresult rv = GetSelectionController(PresContext(), 1.6108 + getter_AddRefs(selectionController)); 1.6109 + if (NS_FAILED(rv) || !selectionController) 1.6110 + return nsISelectionController::SELECTION_OFF; 1.6111 + 1.6112 + selectionController->GetSelectionFlags(aSelectionFlags); 1.6113 + 1.6114 + int16_t selectionValue; 1.6115 + selectionController->GetDisplaySelection(&selectionValue); 1.6116 + 1.6117 + return selectionValue; 1.6118 +} 1.6119 + 1.6120 +bool 1.6121 +nsTextFrame::IsVisibleInSelection(nsISelection* aSelection) 1.6122 +{ 1.6123 + // Check the quick way first 1.6124 + if (!GetContent()->IsSelectionDescendant()) 1.6125 + return false; 1.6126 + 1.6127 + SelectionDetails* details = GetSelectionDetails(); 1.6128 + bool found = false; 1.6129 + 1.6130 + // where are the selection points "really" 1.6131 + SelectionDetails *sdptr = details; 1.6132 + while (sdptr) { 1.6133 + if (sdptr->mEnd > GetContentOffset() && 1.6134 + sdptr->mStart < GetContentEnd() && 1.6135 + sdptr->mType == nsISelectionController::SELECTION_NORMAL) { 1.6136 + found = true; 1.6137 + break; 1.6138 + } 1.6139 + sdptr = sdptr->mNext; 1.6140 + } 1.6141 + DestroySelectionDetails(details); 1.6142 + 1.6143 + return found; 1.6144 +} 1.6145 + 1.6146 +/** 1.6147 + * Compute the longest prefix of text whose width is <= aWidth. Return 1.6148 + * the length of the prefix. Also returns the width of the prefix in aFitWidth. 1.6149 + */ 1.6150 +static uint32_t 1.6151 +CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength, 1.6152 + gfxFloat aWidth, PropertyProvider* aProvider, 1.6153 + gfxFloat* aFitWidth) 1.6154 +{ 1.6155 + uint32_t last = 0; 1.6156 + gfxFloat width = 0; 1.6157 + for (uint32_t i = 1; i <= aLength; ++i) { 1.6158 + if (i == aLength || aTextRun->IsClusterStart(aStart + i)) { 1.6159 + gfxFloat nextWidth = width + 1.6160 + aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider); 1.6161 + if (nextWidth > aWidth) 1.6162 + break; 1.6163 + last = i; 1.6164 + width = nextWidth; 1.6165 + } 1.6166 + } 1.6167 + *aFitWidth = width; 1.6168 + return last; 1.6169 +} 1.6170 + 1.6171 +nsIFrame::ContentOffsets 1.6172 +nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) 1.6173 +{ 1.6174 + return GetCharacterOffsetAtFramePointInternal(aPoint, true); 1.6175 +} 1.6176 + 1.6177 +nsIFrame::ContentOffsets 1.6178 +nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint) 1.6179 +{ 1.6180 + return GetCharacterOffsetAtFramePointInternal(aPoint, false); 1.6181 +} 1.6182 + 1.6183 +nsIFrame::ContentOffsets 1.6184 +nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint, 1.6185 + bool aForInsertionPoint) 1.6186 +{ 1.6187 + ContentOffsets offsets; 1.6188 + 1.6189 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.6190 + if (!mTextRun) 1.6191 + return offsets; 1.6192 + 1.6193 + PropertyProvider provider(this, iter, nsTextFrame::eInflated); 1.6194 + // Trim leading but not trailing whitespace if possible 1.6195 + provider.InitializeForDisplay(false); 1.6196 + gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x; 1.6197 + gfxFloat fitWidth; 1.6198 + uint32_t skippedLength = ComputeTransformedLength(provider); 1.6199 + 1.6200 + uint32_t charsFit = CountCharsFit(mTextRun, 1.6201 + provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth); 1.6202 + 1.6203 + int32_t selectedOffset; 1.6204 + if (charsFit < skippedLength) { 1.6205 + // charsFit characters fitted, but no more could fit. See if we're 1.6206 + // more than halfway through the cluster.. If we are, choose the next 1.6207 + // cluster. 1.6208 + gfxSkipCharsIterator extraCluster(provider.GetStart()); 1.6209 + extraCluster.AdvanceSkipped(charsFit); 1.6210 + gfxSkipCharsIterator extraClusterLastChar(extraCluster); 1.6211 + FindClusterEnd(mTextRun, 1.6212 + provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), 1.6213 + &extraClusterLastChar); 1.6214 + gfxFloat charWidth = 1.6215 + mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(), 1.6216 + GetSkippedDistance(extraCluster, extraClusterLastChar) + 1, 1.6217 + &provider); 1.6218 + selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2 1.6219 + ? extraCluster.GetOriginalOffset() 1.6220 + : extraClusterLastChar.GetOriginalOffset() + 1; 1.6221 + } else { 1.6222 + // All characters fitted, we're at (or beyond) the end of the text. 1.6223 + // XXX This could be some pathological situation where negative spacing 1.6224 + // caused characters to move backwards. We can't really handle that 1.6225 + // in the current frame system because frames can't have negative 1.6226 + // intrinsic widths. 1.6227 + selectedOffset = 1.6228 + provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); 1.6229 + // If we're at the end of a preformatted line which has a terminating 1.6230 + // linefeed, we want to reduce the offset by one to make sure that the 1.6231 + // selection is placed before the linefeed character. 1.6232 + if (HasSignificantTerminalNewline()) { 1.6233 + --selectedOffset; 1.6234 + } 1.6235 + } 1.6236 + 1.6237 + offsets.content = GetContent(); 1.6238 + offsets.offset = offsets.secondaryOffset = selectedOffset; 1.6239 + offsets.associateWithNext = mContentOffset == offsets.offset; 1.6240 + return offsets; 1.6241 +} 1.6242 + 1.6243 +bool 1.6244 +nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, 1.6245 + nsRect& aRect) 1.6246 +{ 1.6247 + if (aRect.IsEmpty()) 1.6248 + return false; 1.6249 + 1.6250 + nsRect givenRect = aRect; 1.6251 + 1.6252 + nsRefPtr<nsFontMetrics> fm; 1.6253 + nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), 1.6254 + GetFontSizeInflation()); 1.6255 + gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); 1.6256 + gfxFont* firstFont = fontGroup->GetFontAt(0); 1.6257 + if (!firstFont) 1.6258 + return false; // OOM 1.6259 + const gfxFont::Metrics& metrics = firstFont->GetMetrics(); 1.6260 + gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); 1.6261 + gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent); 1.6262 + gfxFloat descentLimit = 1.6263 + ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics); 1.6264 + 1.6265 + SelectionDetails *details = GetSelectionDetails(); 1.6266 + for (SelectionDetails *sd = details; sd; sd = sd->mNext) { 1.6267 + if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations)) 1.6268 + continue; 1.6269 + 1.6270 + uint8_t style; 1.6271 + float relativeSize; 1.6272 + int32_t index = 1.6273 + nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType); 1.6274 + if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) { 1.6275 + if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr, 1.6276 + &relativeSize, &style)) { 1.6277 + continue; 1.6278 + } 1.6279 + } else { 1.6280 + // IME selections 1.6281 + TextRangeStyle& rangeStyle = sd->mTextRangeStyle; 1.6282 + if (rangeStyle.IsDefined()) { 1.6283 + if (!rangeStyle.IsLineStyleDefined() || 1.6284 + rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) { 1.6285 + continue; 1.6286 + } 1.6287 + style = rangeStyle.mLineStyle; 1.6288 + relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; 1.6289 + } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, 1.6290 + nullptr, &relativeSize, 1.6291 + &style)) { 1.6292 + continue; 1.6293 + } 1.6294 + } 1.6295 + nsRect decorationArea; 1.6296 + gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width), 1.6297 + ComputeSelectionUnderlineHeight(aPresContext, 1.6298 + metrics, sd->mType)); 1.6299 + relativeSize = std::max(relativeSize, 1.0f); 1.6300 + size.height *= relativeSize; 1.6301 + decorationArea = 1.6302 + nsCSSRendering::GetTextDecorationRect(aPresContext, size, 1.6303 + ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, 1.6304 + style, descentLimit); 1.6305 + aRect.UnionRect(aRect, decorationArea); 1.6306 + } 1.6307 + DestroySelectionDetails(details); 1.6308 + 1.6309 + return !aRect.IsEmpty() && !givenRect.Contains(aRect); 1.6310 +} 1.6311 + 1.6312 +bool 1.6313 +nsTextFrame::IsFrameSelected() const 1.6314 +{ 1.6315 + NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(), 1.6316 + "use the public IsSelected() instead"); 1.6317 + return nsRange::IsNodeSelected(GetContent(), GetContentOffset(), 1.6318 + GetContentEnd()); 1.6319 +} 1.6320 + 1.6321 +void 1.6322 +nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected, 1.6323 + SelectionType aType) 1.6324 +{ 1.6325 + NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame"); 1.6326 + DEBUG_VERIFY_NOT_DIRTY(mState); 1.6327 + 1.6328 + // Selection is collapsed, which can't affect text frame rendering 1.6329 + if (aStart == aEnd) 1.6330 + return; 1.6331 + 1.6332 + nsTextFrame* f = this; 1.6333 + while (f && f->GetContentEnd() <= int32_t(aStart)) { 1.6334 + f = static_cast<nsTextFrame*>(f->GetNextContinuation()); 1.6335 + } 1.6336 + 1.6337 + nsPresContext* presContext = PresContext(); 1.6338 + while (f && f->GetContentOffset() < int32_t(aEnd)) { 1.6339 + // We may need to reflow to recompute the overflow area for 1.6340 + // spellchecking or IME underline if their underline is thicker than 1.6341 + // the normal decoration line. 1.6342 + if (aType & SelectionTypesWithDecorations) { 1.6343 + bool didHaveOverflowingSelection = 1.6344 + (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0; 1.6345 + nsRect r(nsPoint(0, 0), GetSize()); 1.6346 + bool willHaveOverflowingSelection = 1.6347 + aSelected && f->CombineSelectionUnderlineRect(presContext, r); 1.6348 + if (didHaveOverflowingSelection || willHaveOverflowingSelection) { 1.6349 + presContext->PresShell()->FrameNeedsReflow(f, 1.6350 + nsIPresShell::eStyleChange, 1.6351 + NS_FRAME_IS_DIRTY); 1.6352 + } 1.6353 + } 1.6354 + // Selection might change anything. Invalidate the overflow area. 1.6355 + f->InvalidateFrame(); 1.6356 + 1.6357 + f = static_cast<nsTextFrame*>(f->GetNextContinuation()); 1.6358 + } 1.6359 +} 1.6360 + 1.6361 +nsresult 1.6362 +nsTextFrame::GetPointFromOffset(int32_t inOffset, 1.6363 + nsPoint* outPoint) 1.6364 +{ 1.6365 + if (!outPoint) 1.6366 + return NS_ERROR_NULL_POINTER; 1.6367 + 1.6368 + outPoint->x = 0; 1.6369 + outPoint->y = 0; 1.6370 + 1.6371 + DEBUG_VERIFY_NOT_DIRTY(mState); 1.6372 + if (mState & NS_FRAME_IS_DIRTY) 1.6373 + return NS_ERROR_UNEXPECTED; 1.6374 + 1.6375 + if (GetContentLength() <= 0) { 1.6376 + return NS_OK; 1.6377 + } 1.6378 + 1.6379 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.6380 + if (!mTextRun) 1.6381 + return NS_ERROR_FAILURE; 1.6382 + 1.6383 + PropertyProvider properties(this, iter, nsTextFrame::eInflated); 1.6384 + // Don't trim trailing whitespace, we want the caret to appear in the right 1.6385 + // place if it's positioned there 1.6386 + properties.InitializeForDisplay(false); 1.6387 + 1.6388 + if (inOffset < GetContentOffset()){ 1.6389 + NS_WARNING("offset before this frame's content"); 1.6390 + inOffset = GetContentOffset(); 1.6391 + } else if (inOffset > GetContentEnd()) { 1.6392 + NS_WARNING("offset after this frame's content"); 1.6393 + inOffset = GetContentEnd(); 1.6394 + } 1.6395 + int32_t trimmedOffset = properties.GetStart().GetOriginalOffset(); 1.6396 + int32_t trimmedEnd = trimmedOffset + properties.GetOriginalLength(); 1.6397 + inOffset = std::max(inOffset, trimmedOffset); 1.6398 + inOffset = std::min(inOffset, trimmedEnd); 1.6399 + 1.6400 + iter.SetOriginalOffset(inOffset); 1.6401 + 1.6402 + if (inOffset < trimmedEnd && 1.6403 + !iter.IsOriginalCharSkipped() && 1.6404 + !mTextRun->IsClusterStart(iter.GetSkippedOffset())) { 1.6405 + NS_WARNING("GetPointFromOffset called for non-cluster boundary"); 1.6406 + FindClusterStart(mTextRun, trimmedOffset, &iter); 1.6407 + } 1.6408 + 1.6409 + gfxFloat advanceWidth = 1.6410 + mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(), 1.6411 + GetSkippedDistance(properties.GetStart(), iter), 1.6412 + &properties); 1.6413 + nscoord width = NSToCoordCeilClamped(advanceWidth); 1.6414 + 1.6415 + if (mTextRun->IsRightToLeft()) { 1.6416 + outPoint->x = mRect.width - width; 1.6417 + } else { 1.6418 + outPoint->x = width; 1.6419 + } 1.6420 + outPoint->y = 0; 1.6421 + 1.6422 + return NS_OK; 1.6423 +} 1.6424 + 1.6425 +nsresult 1.6426 +nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset, 1.6427 + bool aHint, 1.6428 + int32_t* aOutOffset, 1.6429 + nsIFrame**aOutFrame) 1.6430 +{ 1.6431 + DEBUG_VERIFY_NOT_DIRTY(mState); 1.6432 +#if 0 //XXXrbs disable due to bug 310227 1.6433 + if (mState & NS_FRAME_IS_DIRTY) 1.6434 + return NS_ERROR_UNEXPECTED; 1.6435 +#endif 1.6436 + 1.6437 + NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters"); 1.6438 + NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!"); 1.6439 + nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); 1.6440 + if (this != primaryFrame) { 1.6441 + // This call needs to happen on the primary frame 1.6442 + return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint, 1.6443 + aOutOffset, aOutFrame); 1.6444 + } 1.6445 + 1.6446 + nsTextFrame* f = this; 1.6447 + int32_t offset = mContentOffset; 1.6448 + 1.6449 + // Try to look up the offset to frame property 1.6450 + nsTextFrame* cachedFrame = static_cast<nsTextFrame*> 1.6451 + (Properties().Get(OffsetToFrameProperty())); 1.6452 + 1.6453 + if (cachedFrame) { 1.6454 + f = cachedFrame; 1.6455 + offset = f->GetContentOffset(); 1.6456 + 1.6457 + f->RemoveStateBits(TEXT_IN_OFFSET_CACHE); 1.6458 + } 1.6459 + 1.6460 + if ((aContentOffset >= offset) && 1.6461 + (aHint || aContentOffset != offset)) { 1.6462 + while (true) { 1.6463 + nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextContinuation()); 1.6464 + if (!next || aContentOffset < next->GetContentOffset()) 1.6465 + break; 1.6466 + if (aContentOffset == next->GetContentOffset()) { 1.6467 + if (aHint) { 1.6468 + f = next; 1.6469 + if (f->GetContentLength() == 0) { 1.6470 + continue; // use the last of the empty frames with this offset 1.6471 + } 1.6472 + } 1.6473 + break; 1.6474 + } 1.6475 + f = next; 1.6476 + } 1.6477 + } else { 1.6478 + while (true) { 1.6479 + nsTextFrame* prev = static_cast<nsTextFrame*>(f->GetPrevContinuation()); 1.6480 + if (!prev || aContentOffset > f->GetContentOffset()) 1.6481 + break; 1.6482 + if (aContentOffset == f->GetContentOffset()) { 1.6483 + if (!aHint) { 1.6484 + f = prev; 1.6485 + if (f->GetContentLength() == 0) { 1.6486 + continue; // use the first of the empty frames with this offset 1.6487 + } 1.6488 + } 1.6489 + break; 1.6490 + } 1.6491 + f = prev; 1.6492 + } 1.6493 + } 1.6494 + 1.6495 + *aOutOffset = aContentOffset - f->GetContentOffset(); 1.6496 + *aOutFrame = f; 1.6497 + 1.6498 + // cache the frame we found 1.6499 + Properties().Set(OffsetToFrameProperty(), f); 1.6500 + f->AddStateBits(TEXT_IN_OFFSET_CACHE); 1.6501 + 1.6502 + return NS_OK; 1.6503 +} 1.6504 + 1.6505 +nsIFrame::FrameSearchResult 1.6506 +nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset) 1.6507 +{ 1.6508 + NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range"); 1.6509 + 1.6510 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.6511 + if (!mTextRun) 1.6512 + return CONTINUE_EMPTY; 1.6513 + 1.6514 + TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true); 1.6515 + // Check whether there are nonskipped characters in the trimmmed range 1.6516 + return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > 1.6517 + iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE; 1.6518 +} 1.6519 + 1.6520 +/** 1.6521 + * This class iterates through the clusters before or after the given 1.6522 + * aPosition (which is a content offset). You can test each cluster 1.6523 + * to see if it's whitespace (as far as selection/caret movement is concerned), 1.6524 + * or punctuation, or if there is a word break before the cluster. ("Before" 1.6525 + * is interpreted according to aDirection, so if aDirection is -1, "before" 1.6526 + * means actually *after* the cluster content.) 1.6527 + */ 1.6528 +class MOZ_STACK_CLASS ClusterIterator { 1.6529 +public: 1.6530 + ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection, 1.6531 + nsString& aContext); 1.6532 + 1.6533 + bool NextCluster(); 1.6534 + bool IsWhitespace(); 1.6535 + bool IsPunctuation(); 1.6536 + bool HaveWordBreakBefore() { return mHaveWordBreak; } 1.6537 + int32_t GetAfterOffset(); 1.6538 + int32_t GetBeforeOffset(); 1.6539 + 1.6540 +private: 1.6541 + gfxSkipCharsIterator mIterator; 1.6542 + const nsTextFragment* mFrag; 1.6543 + nsTextFrame* mTextFrame; 1.6544 + int32_t mDirection; 1.6545 + int32_t mCharIndex; 1.6546 + nsTextFrame::TrimmedOffsets mTrimmed; 1.6547 + nsTArray<bool> mWordBreaks; 1.6548 + bool mHaveWordBreak; 1.6549 +}; 1.6550 + 1.6551 +static bool 1.6552 +IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, 1.6553 + bool aRespectClusters, 1.6554 + gfxTextRun* aTextRun, 1.6555 + nsIFrame* aFrame) 1.6556 +{ 1.6557 + if (aIter.IsOriginalCharSkipped()) 1.6558 + return false; 1.6559 + uint32_t index = aIter.GetSkippedOffset(); 1.6560 + if (aRespectClusters && !aTextRun->IsClusterStart(index)) 1.6561 + return false; 1.6562 + if (index > 0) { 1.6563 + // Check whether the proposed position is in between the two halves of a 1.6564 + // surrogate pair; if so, this is not a valid character boundary. 1.6565 + // (In the case where we are respecting clusters, we won't actually get 1.6566 + // this far because the low surrogate is also marked as non-clusterStart 1.6567 + // so we'll return FALSE above.) 1.6568 + if (aTextRun->CharIsLowSurrogate(index)) { 1.6569 + return false; 1.6570 + } 1.6571 + } 1.6572 + return true; 1.6573 +} 1.6574 + 1.6575 +nsIFrame::FrameSearchResult 1.6576 +nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset, 1.6577 + bool aRespectClusters) 1.6578 +{ 1.6579 + int32_t contentLength = GetContentLength(); 1.6580 + NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range"); 1.6581 + 1.6582 + bool selectable; 1.6583 + uint8_t selectStyle; 1.6584 + IsSelectable(&selectable, &selectStyle); 1.6585 + if (selectStyle == NS_STYLE_USER_SELECT_ALL) 1.6586 + return CONTINUE_UNSELECTABLE; 1.6587 + 1.6588 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.6589 + if (!mTextRun) 1.6590 + return CONTINUE_EMPTY; 1.6591 + 1.6592 + TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false); 1.6593 + 1.6594 + // A negative offset means "end of frame". 1.6595 + int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); 1.6596 + 1.6597 + if (!aForward) { 1.6598 + // If at the beginning of the line, look at the previous continuation 1.6599 + for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1; 1.6600 + i >= trimmed.mStart; --i) { 1.6601 + iter.SetOriginalOffset(i); 1.6602 + if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) { 1.6603 + *aOffset = i - mContentOffset; 1.6604 + return FOUND; 1.6605 + } 1.6606 + } 1.6607 + *aOffset = 0; 1.6608 + } else { 1.6609 + // If we're at the end of a line, look at the next continuation 1.6610 + iter.SetOriginalOffset(startOffset); 1.6611 + if (startOffset <= trimmed.GetEnd() && 1.6612 + !(startOffset < trimmed.GetEnd() && 1.6613 + StyleText()->NewlineIsSignificant() && 1.6614 + iter.GetSkippedOffset() < mTextRun->GetLength() && 1.6615 + mTextRun->CharIsNewline(iter.GetSkippedOffset()))) { 1.6616 + for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) { 1.6617 + iter.SetOriginalOffset(i); 1.6618 + if (i == trimmed.GetEnd() || 1.6619 + IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) { 1.6620 + *aOffset = i - mContentOffset; 1.6621 + return FOUND; 1.6622 + } 1.6623 + } 1.6624 + } 1.6625 + *aOffset = contentLength; 1.6626 + } 1.6627 + 1.6628 + return CONTINUE; 1.6629 +} 1.6630 + 1.6631 +bool 1.6632 +ClusterIterator::IsWhitespace() 1.6633 +{ 1.6634 + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 1.6635 + return IsSelectionSpace(mFrag, mCharIndex); 1.6636 +} 1.6637 + 1.6638 +bool 1.6639 +ClusterIterator::IsPunctuation() 1.6640 +{ 1.6641 + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 1.6642 + nsIUGenCategory::nsUGenCategory c = 1.6643 + mozilla::unicode::GetGenCategory(mFrag->CharAt(mCharIndex)); 1.6644 + return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol; 1.6645 +} 1.6646 + 1.6647 +int32_t 1.6648 +ClusterIterator::GetBeforeOffset() 1.6649 +{ 1.6650 + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 1.6651 + return mCharIndex + (mDirection > 0 ? 0 : 1); 1.6652 +} 1.6653 + 1.6654 +int32_t 1.6655 +ClusterIterator::GetAfterOffset() 1.6656 +{ 1.6657 + NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); 1.6658 + return mCharIndex + (mDirection > 0 ? 1 : 0); 1.6659 +} 1.6660 + 1.6661 +bool 1.6662 +ClusterIterator::NextCluster() 1.6663 +{ 1.6664 + if (!mDirection) 1.6665 + return false; 1.6666 + gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated); 1.6667 + 1.6668 + mHaveWordBreak = false; 1.6669 + while (true) { 1.6670 + bool keepGoing = false; 1.6671 + if (mDirection > 0) { 1.6672 + if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) 1.6673 + return false; 1.6674 + keepGoing = mIterator.IsOriginalCharSkipped() || 1.6675 + mIterator.GetOriginalOffset() < mTrimmed.mStart || 1.6676 + !textRun->IsClusterStart(mIterator.GetSkippedOffset()); 1.6677 + mCharIndex = mIterator.GetOriginalOffset(); 1.6678 + mIterator.AdvanceOriginal(1); 1.6679 + } else { 1.6680 + if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) 1.6681 + return false; 1.6682 + mIterator.AdvanceOriginal(-1); 1.6683 + keepGoing = mIterator.IsOriginalCharSkipped() || 1.6684 + mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || 1.6685 + !textRun->IsClusterStart(mIterator.GetSkippedOffset()); 1.6686 + mCharIndex = mIterator.GetOriginalOffset(); 1.6687 + } 1.6688 + 1.6689 + if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { 1.6690 + mHaveWordBreak = true; 1.6691 + } 1.6692 + if (!keepGoing) 1.6693 + return true; 1.6694 + } 1.6695 +} 1.6696 + 1.6697 +ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, 1.6698 + int32_t aDirection, nsString& aContext) 1.6699 + : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1) 1.6700 +{ 1.6701 + mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated); 1.6702 + if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) { 1.6703 + mDirection = 0; // signal failure 1.6704 + return; 1.6705 + } 1.6706 + mIterator.SetOriginalOffset(aPosition); 1.6707 + 1.6708 + mFrag = aTextFrame->GetContent()->GetText(); 1.6709 + mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true); 1.6710 + 1.6711 + int32_t textOffset = aTextFrame->GetContentOffset(); 1.6712 + int32_t textLen = aTextFrame->GetContentLength(); 1.6713 + if (!mWordBreaks.AppendElements(textLen + 1)) { 1.6714 + mDirection = 0; // signal failure 1.6715 + return; 1.6716 + } 1.6717 + memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool)); 1.6718 + int32_t textStart; 1.6719 + if (aDirection > 0) { 1.6720 + if (aContext.IsEmpty()) { 1.6721 + // No previous context, so it must be the start of a line or text run 1.6722 + mWordBreaks[0] = true; 1.6723 + } 1.6724 + textStart = aContext.Length(); 1.6725 + mFrag->AppendTo(aContext, textOffset, textLen); 1.6726 + } else { 1.6727 + if (aContext.IsEmpty()) { 1.6728 + // No following context, so it must be the end of a line or text run 1.6729 + mWordBreaks[textLen] = true; 1.6730 + } 1.6731 + textStart = 0; 1.6732 + nsAutoString str; 1.6733 + mFrag->AppendTo(str, textOffset, textLen); 1.6734 + aContext.Insert(str, 0); 1.6735 + } 1.6736 + nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); 1.6737 + for (int32_t i = 0; i <= textLen; ++i) { 1.6738 + int32_t indexInText = i + textStart; 1.6739 + mWordBreaks[i] |= 1.6740 + wordBreaker->BreakInBetween(aContext.get(), indexInText, 1.6741 + aContext.get() + indexInText, 1.6742 + aContext.Length() - indexInText); 1.6743 + } 1.6744 +} 1.6745 + 1.6746 +nsIFrame::FrameSearchResult 1.6747 +nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, 1.6748 + int32_t* aOffset, PeekWordState* aState) 1.6749 +{ 1.6750 + int32_t contentLength = GetContentLength(); 1.6751 + NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range"); 1.6752 + 1.6753 + bool selectable; 1.6754 + uint8_t selectStyle; 1.6755 + IsSelectable(&selectable, &selectStyle); 1.6756 + if (selectStyle == NS_STYLE_USER_SELECT_ALL) 1.6757 + return CONTINUE_UNSELECTABLE; 1.6758 + 1.6759 + int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); 1.6760 + ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext); 1.6761 + 1.6762 + if (!cIter.NextCluster()) 1.6763 + return CONTINUE_EMPTY; 1.6764 + 1.6765 + do { 1.6766 + bool isPunctuation = cIter.IsPunctuation(); 1.6767 + bool isWhitespace = cIter.IsWhitespace(); 1.6768 + bool isWordBreakBefore = cIter.HaveWordBreakBefore(); 1.6769 + if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { 1.6770 + aState->SetSawBeforeType(); 1.6771 + aState->Update(isPunctuation, isWhitespace); 1.6772 + continue; 1.6773 + } 1.6774 + // See if we can break before the current cluster 1.6775 + if (!aState->mAtStart) { 1.6776 + bool canBreak; 1.6777 + if (isPunctuation != aState->mLastCharWasPunctuation) { 1.6778 + canBreak = BreakWordBetweenPunctuation(aState, aForward, 1.6779 + isPunctuation, isWhitespace, aIsKeyboardSelect); 1.6780 + } else if (!aState->mLastCharWasWhitespace && 1.6781 + !isWhitespace && !isPunctuation && isWordBreakBefore) { 1.6782 + // if both the previous and the current character are not white 1.6783 + // space but this can be word break before, we don't need to eat 1.6784 + // a white space in this case. This case happens in some languages 1.6785 + // that their words are not separated by white spaces. E.g., 1.6786 + // Japanese and Chinese. 1.6787 + canBreak = true; 1.6788 + } else { 1.6789 + canBreak = isWordBreakBefore && aState->mSawBeforeType && 1.6790 + (aWordSelectEatSpace != isWhitespace); 1.6791 + } 1.6792 + if (canBreak) { 1.6793 + *aOffset = cIter.GetBeforeOffset() - mContentOffset; 1.6794 + return FOUND; 1.6795 + } 1.6796 + } 1.6797 + aState->Update(isPunctuation, isWhitespace); 1.6798 + } while (cIter.NextCluster()); 1.6799 + 1.6800 + *aOffset = cIter.GetAfterOffset() - mContentOffset; 1.6801 + return CONTINUE; 1.6802 +} 1.6803 + 1.6804 + // TODO this needs to be deCOMtaminated with the interface fixed in 1.6805 +// nsIFrame.h, but we won't do that until the old textframe is gone. 1.6806 +nsresult 1.6807 +nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex, 1.6808 + int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval) 1.6809 +{ 1.6810 + if (!aRetval) 1.6811 + return NS_ERROR_NULL_POINTER; 1.6812 + 1.6813 + // Text in the range is visible if there is at least one character in the range 1.6814 + // that is not skipped and is mapped by this frame (which is the primary frame) 1.6815 + // or one of its continuations. 1.6816 + for (nsTextFrame* f = this; f; 1.6817 + f = static_cast<nsTextFrame*>(GetNextContinuation())) { 1.6818 + int32_t dummyOffset = 0; 1.6819 + if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) { 1.6820 + *aRetval = true; 1.6821 + return NS_OK; 1.6822 + } 1.6823 + } 1.6824 + 1.6825 + *aRetval = false; 1.6826 + return NS_OK; 1.6827 +} 1.6828 + 1.6829 +nsresult 1.6830 +nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const 1.6831 +{ 1.6832 + start = GetContentOffset(); 1.6833 + end = GetContentEnd(); 1.6834 + return NS_OK; 1.6835 +} 1.6836 + 1.6837 +static int32_t 1.6838 +FindEndOfPunctuationRun(const nsTextFragment* aFrag, 1.6839 + gfxTextRun* aTextRun, 1.6840 + gfxSkipCharsIterator* aIter, 1.6841 + int32_t aOffset, 1.6842 + int32_t aStart, 1.6843 + int32_t aEnd) 1.6844 +{ 1.6845 + int32_t i; 1.6846 + 1.6847 + for (i = aStart; i < aEnd - aOffset; ++i) { 1.6848 + if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) { 1.6849 + aIter->SetOriginalOffset(aOffset + i); 1.6850 + FindClusterEnd(aTextRun, aEnd, aIter); 1.6851 + i = aIter->GetOriginalOffset() - aOffset; 1.6852 + } else { 1.6853 + break; 1.6854 + } 1.6855 + } 1.6856 + return i; 1.6857 +} 1.6858 + 1.6859 +/** 1.6860 + * Returns true if this text frame completes the first-letter, false 1.6861 + * if it does not contain a true "letter". 1.6862 + * If returns true, then it also updates aLength to cover just the first-letter 1.6863 + * text. 1.6864 + * 1.6865 + * XXX :first-letter should be handled during frame construction 1.6866 + * (and it has a good bit in common with nextBidi) 1.6867 + * 1.6868 + * @param aLength an in/out parameter: on entry contains the maximum length to 1.6869 + * return, on exit returns length of the first-letter fragment (which may 1.6870 + * include leading and trailing punctuation, for example) 1.6871 + */ 1.6872 +static bool 1.6873 +FindFirstLetterRange(const nsTextFragment* aFrag, 1.6874 + gfxTextRun* aTextRun, 1.6875 + int32_t aOffset, const gfxSkipCharsIterator& aIter, 1.6876 + int32_t* aLength) 1.6877 +{ 1.6878 + int32_t i; 1.6879 + int32_t length = *aLength; 1.6880 + int32_t endOffset = aOffset + length; 1.6881 + gfxSkipCharsIterator iter(aIter); 1.6882 + 1.6883 + // skip leading whitespace, then consume clusters that start with punctuation 1.6884 + i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, 1.6885 + GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), 1.6886 + endOffset); 1.6887 + if (i == length) 1.6888 + return false; 1.6889 + 1.6890 + // If the next character is not a letter or number, there is no first-letter. 1.6891 + // Return true so that we don't go on looking, but set aLength to 0. 1.6892 + if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) { 1.6893 + *aLength = 0; 1.6894 + return true; 1.6895 + } 1.6896 + 1.6897 + // consume another cluster (the actual first letter) 1.6898 + iter.SetOriginalOffset(aOffset + i); 1.6899 + FindClusterEnd(aTextRun, endOffset, &iter); 1.6900 + i = iter.GetOriginalOffset() - aOffset; 1.6901 + if (i + 1 == length) 1.6902 + return true; 1.6903 + 1.6904 + // consume clusters that start with punctuation 1.6905 + i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset); 1.6906 + if (i < length) 1.6907 + *aLength = i; 1.6908 + return true; 1.6909 +} 1.6910 + 1.6911 +static uint32_t 1.6912 +FindStartAfterSkippingWhitespace(PropertyProvider* aProvider, 1.6913 + nsIFrame::InlineIntrinsicWidthData* aData, 1.6914 + const nsStyleText* aTextStyle, 1.6915 + gfxSkipCharsIterator* aIterator, 1.6916 + uint32_t aFlowEndInTextRun) 1.6917 +{ 1.6918 + if (aData->skipWhitespace) { 1.6919 + while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && 1.6920 + IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) { 1.6921 + aIterator->AdvanceOriginal(1); 1.6922 + } 1.6923 + } 1.6924 + return aIterator->GetSkippedOffset(); 1.6925 +} 1.6926 + 1.6927 +union VoidPtrOrFloat { 1.6928 + VoidPtrOrFloat() : p(nullptr) {} 1.6929 + 1.6930 + void *p; 1.6931 + float f; 1.6932 +}; 1.6933 + 1.6934 +float 1.6935 +nsTextFrame::GetFontSizeInflation() const 1.6936 +{ 1.6937 + if (!HasFontSizeInflation()) { 1.6938 + return 1.0f; 1.6939 + } 1.6940 + VoidPtrOrFloat u; 1.6941 + u.p = Properties().Get(FontSizeInflationProperty()); 1.6942 + return u.f; 1.6943 +} 1.6944 + 1.6945 +void 1.6946 +nsTextFrame::SetFontSizeInflation(float aInflation) 1.6947 +{ 1.6948 + if (aInflation == 1.0f) { 1.6949 + if (HasFontSizeInflation()) { 1.6950 + RemoveStateBits(TEXT_HAS_FONT_INFLATION); 1.6951 + Properties().Delete(FontSizeInflationProperty()); 1.6952 + } 1.6953 + return; 1.6954 + } 1.6955 + 1.6956 + AddStateBits(TEXT_HAS_FONT_INFLATION); 1.6957 + VoidPtrOrFloat u; 1.6958 + u.f = aInflation; 1.6959 + Properties().Set(FontSizeInflationProperty(), u.p); 1.6960 +} 1.6961 + 1.6962 +/* virtual */ 1.6963 +void nsTextFrame::MarkIntrinsicWidthsDirty() 1.6964 +{ 1.6965 + ClearTextRuns(); 1.6966 + nsFrame::MarkIntrinsicWidthsDirty(); 1.6967 +} 1.6968 + 1.6969 +// XXX this doesn't handle characters shaped by line endings. We need to 1.6970 +// temporarily override the "current line ending" settings. 1.6971 +void 1.6972 +nsTextFrame::AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext, 1.6973 + nsIFrame::InlineMinWidthData *aData, 1.6974 + TextRunType aTextRunType) 1.6975 +{ 1.6976 + uint32_t flowEndInTextRun; 1.6977 + gfxContext* ctx = aRenderingContext->ThebesContext(); 1.6978 + gfxSkipCharsIterator iter = 1.6979 + EnsureTextRun(aTextRunType, ctx, aData->lineContainer, 1.6980 + aData->line, &flowEndInTextRun); 1.6981 + gfxTextRun *textRun = GetTextRun(aTextRunType); 1.6982 + if (!textRun) 1.6983 + return; 1.6984 + 1.6985 + // Pass null for the line container. This will disable tab spacing, but that's 1.6986 + // OK since we can't really handle tabs for intrinsic sizing anyway. 1.6987 + const nsStyleText* textStyle = StyleText(); 1.6988 + const nsTextFragment* frag = mContent->GetText(); 1.6989 + 1.6990 + // If we're hyphenating, the PropertyProvider needs the actual length; 1.6991 + // otherwise we can just pass INT32_MAX to mean "all the text" 1.6992 + int32_t len = INT32_MAX; 1.6993 + bool hyphenating = frag->GetLength() > 0 && 1.6994 + (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO || 1.6995 + (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL && 1.6996 + (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); 1.6997 + if (hyphenating) { 1.6998 + gfxSkipCharsIterator tmp(iter); 1.6999 + len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(), 1.7000 + tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset(); 1.7001 + } 1.7002 + PropertyProvider provider(textRun, textStyle, frag, this, 1.7003 + iter, len, nullptr, 0, aTextRunType); 1.7004 + 1.7005 + bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); 1.7006 + bool preformatNewlines = textStyle->NewlineIsSignificant(); 1.7007 + bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); 1.7008 + gfxFloat tabWidth = -1; 1.7009 + uint32_t start = 1.7010 + FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); 1.7011 + 1.7012 + AutoFallibleTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer; 1.7013 + bool *hyphBreakBefore = nullptr; 1.7014 + if (hyphenating) { 1.7015 + hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start); 1.7016 + if (hyphBreakBefore) { 1.7017 + provider.GetHyphenationBreaks(start, flowEndInTextRun - start, 1.7018 + hyphBreakBefore); 1.7019 + } 1.7020 + } 1.7021 + 1.7022 + for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) { 1.7023 + bool preformattedNewline = false; 1.7024 + bool preformattedTab = false; 1.7025 + if (i < flowEndInTextRun) { 1.7026 + // XXXldb Shouldn't we be including the newline as part of the 1.7027 + // segment that it ends rather than part of the segment that it 1.7028 + // starts? 1.7029 + preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); 1.7030 + preformattedTab = preformatTabs && textRun->CharIsTab(i); 1.7031 + if (!textRun->CanBreakLineBefore(i) && 1.7032 + !preformattedNewline && 1.7033 + !preformattedTab && 1.7034 + (!hyphBreakBefore || !hyphBreakBefore[i - start])) 1.7035 + { 1.7036 + // we can't break here (and it's not the end of the flow) 1.7037 + continue; 1.7038 + } 1.7039 + } 1.7040 + 1.7041 + if (i > wordStart) { 1.7042 + nscoord width = 1.7043 + NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider)); 1.7044 + aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); 1.7045 + aData->atStartOfLine = false; 1.7046 + 1.7047 + if (collapseWhitespace) { 1.7048 + uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter); 1.7049 + if (trimStart == start) { 1.7050 + // This is *all* trimmable whitespace, so whatever trailingWhitespace 1.7051 + // we saw previously is still trailing... 1.7052 + aData->trailingWhitespace += width; 1.7053 + } else { 1.7054 + // Some non-whitespace so the old trailingWhitespace is no longer trailing 1.7055 + aData->trailingWhitespace = 1.7056 + NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); 1.7057 + } 1.7058 + } else { 1.7059 + aData->trailingWhitespace = 0; 1.7060 + } 1.7061 + } 1.7062 + 1.7063 + if (preformattedTab) { 1.7064 + PropertyProvider::Spacing spacing; 1.7065 + provider.GetSpacing(i, 1, &spacing); 1.7066 + aData->currentLine += nscoord(spacing.mBefore); 1.7067 + gfxFloat afterTab = 1.7068 + AdvanceToNextTab(aData->currentLine, this, 1.7069 + textRun, &tabWidth); 1.7070 + aData->currentLine = nscoord(afterTab + spacing.mAfter); 1.7071 + wordStart = i + 1; 1.7072 + } else if (i < flowEndInTextRun || 1.7073 + (i == textRun->GetLength() && 1.7074 + (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) { 1.7075 + if (preformattedNewline) { 1.7076 + aData->ForceBreak(aRenderingContext); 1.7077 + } else if (i < flowEndInTextRun && hyphBreakBefore && 1.7078 + hyphBreakBefore[i - start]) 1.7079 + { 1.7080 + aData->OptionallyBreak(aRenderingContext, 1.7081 + NSToCoordRound(provider.GetHyphenWidth())); 1.7082 + } else { 1.7083 + aData->OptionallyBreak(aRenderingContext); 1.7084 + } 1.7085 + wordStart = i; 1.7086 + } 1.7087 + } 1.7088 + 1.7089 + if (start < flowEndInTextRun) { 1.7090 + // Check if we have collapsible whitespace at the end 1.7091 + aData->skipWhitespace = 1.7092 + IsTrimmableSpace(provider.GetFragment(), 1.7093 + iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), 1.7094 + textStyle); 1.7095 + } 1.7096 +} 1.7097 + 1.7098 +bool nsTextFrame::IsCurrentFontInflation(float aInflation) const { 1.7099 + return fabsf(aInflation - GetFontSizeInflation()) < 1e-6; 1.7100 +} 1.7101 + 1.7102 +// XXX Need to do something here to avoid incremental reflow bugs due to 1.7103 +// first-line and first-letter changing min-width 1.7104 +/* virtual */ void 1.7105 +nsTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, 1.7106 + nsIFrame::InlineMinWidthData *aData) 1.7107 +{ 1.7108 + float inflation = nsLayoutUtils::FontSizeInflationFor(this); 1.7109 + TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; 1.7110 + 1.7111 + if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { 1.7112 + // FIXME: Ideally, if we already have a text run, we'd move it to be 1.7113 + // the uninflated text run. 1.7114 + ClearTextRun(nullptr, nsTextFrame::eInflated); 1.7115 + } 1.7116 + 1.7117 + nsTextFrame* f; 1.7118 + gfxTextRun* lastTextRun = nullptr; 1.7119 + // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames 1.7120 + // in the flow are handled right here. 1.7121 + for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 1.7122 + // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we 1.7123 + // haven't set up textruns yet for f. Except in OOM situations, 1.7124 + // lastTextRun will only be null for the first text frame. 1.7125 + if (f == this || f->GetTextRun(trtype) != lastTextRun) { 1.7126 + nsIFrame* lc; 1.7127 + if (aData->lineContainer && 1.7128 + aData->lineContainer != (lc = FindLineContainer(f))) { 1.7129 + NS_ASSERTION(f != this, "wrong InlineMinWidthData container" 1.7130 + " for first continuation"); 1.7131 + aData->line = nullptr; 1.7132 + aData->lineContainer = lc; 1.7133 + } 1.7134 + 1.7135 + // This will process all the text frames that share the same textrun as f. 1.7136 + f->AddInlineMinWidthForFlow(aRenderingContext, aData, trtype); 1.7137 + lastTextRun = f->GetTextRun(trtype); 1.7138 + } 1.7139 + } 1.7140 +} 1.7141 + 1.7142 +// XXX this doesn't handle characters shaped by line endings. We need to 1.7143 +// temporarily override the "current line ending" settings. 1.7144 +void 1.7145 +nsTextFrame::AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext, 1.7146 + nsIFrame::InlinePrefWidthData *aData, 1.7147 + TextRunType aTextRunType) 1.7148 +{ 1.7149 + uint32_t flowEndInTextRun; 1.7150 + gfxContext* ctx = aRenderingContext->ThebesContext(); 1.7151 + gfxSkipCharsIterator iter = 1.7152 + EnsureTextRun(aTextRunType, ctx, aData->lineContainer, 1.7153 + aData->line, &flowEndInTextRun); 1.7154 + gfxTextRun *textRun = GetTextRun(aTextRunType); 1.7155 + if (!textRun) 1.7156 + return; 1.7157 + 1.7158 + // Pass null for the line container. This will disable tab spacing, but that's 1.7159 + // OK since we can't really handle tabs for intrinsic sizing anyway. 1.7160 + 1.7161 + const nsStyleText* textStyle = StyleText(); 1.7162 + const nsTextFragment* frag = mContent->GetText(); 1.7163 + PropertyProvider provider(textRun, textStyle, frag, this, 1.7164 + iter, INT32_MAX, nullptr, 0, aTextRunType); 1.7165 + 1.7166 + bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); 1.7167 + bool preformatNewlines = textStyle->NewlineIsSignificant(); 1.7168 + bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); 1.7169 + gfxFloat tabWidth = -1; 1.7170 + uint32_t start = 1.7171 + FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); 1.7172 + 1.7173 + // XXX Should we consider hyphenation here? 1.7174 + // If newlines and tabs aren't preformatted, nothing to do inside 1.7175 + // the loop so make i skip to the end 1.7176 + uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; 1.7177 + for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) { 1.7178 + bool preformattedNewline = false; 1.7179 + bool preformattedTab = false; 1.7180 + if (i < flowEndInTextRun) { 1.7181 + // XXXldb Shouldn't we be including the newline as part of the 1.7182 + // segment that it ends rather than part of the segment that it 1.7183 + // starts? 1.7184 + NS_ASSERTION(preformatNewlines || textStyle->NewlineIsDiscarded(), 1.7185 + "We can't be here unless newlines are hard breaks or are discarded"); 1.7186 + preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); 1.7187 + preformattedTab = preformatTabs && textRun->CharIsTab(i); 1.7188 + if (!preformattedNewline && !preformattedTab) { 1.7189 + // we needn't break here (and it's not the end of the flow) 1.7190 + continue; 1.7191 + } 1.7192 + } 1.7193 + 1.7194 + if (i > lineStart) { 1.7195 + nscoord width = 1.7196 + NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider)); 1.7197 + aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); 1.7198 + 1.7199 + if (collapseWhitespace) { 1.7200 + uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter); 1.7201 + if (trimStart == start) { 1.7202 + // This is *all* trimmable whitespace, so whatever trailingWhitespace 1.7203 + // we saw previously is still trailing... 1.7204 + aData->trailingWhitespace += width; 1.7205 + } else { 1.7206 + // Some non-whitespace so the old trailingWhitespace is no longer trailing 1.7207 + aData->trailingWhitespace = 1.7208 + NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); 1.7209 + } 1.7210 + } else { 1.7211 + aData->trailingWhitespace = 0; 1.7212 + } 1.7213 + } 1.7214 + 1.7215 + if (preformattedTab) { 1.7216 + PropertyProvider::Spacing spacing; 1.7217 + provider.GetSpacing(i, 1, &spacing); 1.7218 + aData->currentLine += nscoord(spacing.mBefore); 1.7219 + gfxFloat afterTab = 1.7220 + AdvanceToNextTab(aData->currentLine, this, 1.7221 + textRun, &tabWidth); 1.7222 + aData->currentLine = nscoord(afterTab + spacing.mAfter); 1.7223 + lineStart = i + 1; 1.7224 + } else if (preformattedNewline) { 1.7225 + aData->ForceBreak(aRenderingContext); 1.7226 + lineStart = i; 1.7227 + } 1.7228 + } 1.7229 + 1.7230 + // Check if we have collapsible whitespace at the end 1.7231 + if (start < flowEndInTextRun) { 1.7232 + aData->skipWhitespace = 1.7233 + IsTrimmableSpace(provider.GetFragment(), 1.7234 + iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), 1.7235 + textStyle); 1.7236 + } 1.7237 +} 1.7238 + 1.7239 +// XXX Need to do something here to avoid incremental reflow bugs due to 1.7240 +// first-line and first-letter changing pref-width 1.7241 +/* virtual */ void 1.7242 +nsTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext, 1.7243 + nsIFrame::InlinePrefWidthData *aData) 1.7244 +{ 1.7245 + float inflation = nsLayoutUtils::FontSizeInflationFor(this); 1.7246 + TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; 1.7247 + 1.7248 + if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { 1.7249 + // FIXME: Ideally, if we already have a text run, we'd move it to be 1.7250 + // the uninflated text run. 1.7251 + ClearTextRun(nullptr, nsTextFrame::eInflated); 1.7252 + } 1.7253 + 1.7254 + nsTextFrame* f; 1.7255 + gfxTextRun* lastTextRun = nullptr; 1.7256 + // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames 1.7257 + // in the flow are handled right here. 1.7258 + for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 1.7259 + // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we 1.7260 + // haven't set up textruns yet for f. Except in OOM situations, 1.7261 + // lastTextRun will only be null for the first text frame. 1.7262 + if (f == this || f->GetTextRun(trtype) != lastTextRun) { 1.7263 + nsIFrame* lc; 1.7264 + if (aData->lineContainer && 1.7265 + aData->lineContainer != (lc = FindLineContainer(f))) { 1.7266 + NS_ASSERTION(f != this, "wrong InlinePrefWidthData container" 1.7267 + " for first continuation"); 1.7268 + aData->line = nullptr; 1.7269 + aData->lineContainer = lc; 1.7270 + } 1.7271 + 1.7272 + // This will process all the text frames that share the same textrun as f. 1.7273 + f->AddInlinePrefWidthForFlow(aRenderingContext, aData, trtype); 1.7274 + lastTextRun = f->GetTextRun(trtype); 1.7275 + } 1.7276 + } 1.7277 +} 1.7278 + 1.7279 +/* virtual */ nsSize 1.7280 +nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext, 1.7281 + nsSize aCBSize, nscoord aAvailableWidth, 1.7282 + nsSize aMargin, nsSize aBorder, nsSize aPadding, 1.7283 + uint32_t aFlags) 1.7284 +{ 1.7285 + // Inlines and text don't compute size before reflow. 1.7286 + return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); 1.7287 +} 1.7288 + 1.7289 +static nsRect 1.7290 +RoundOut(const gfxRect& aRect) 1.7291 +{ 1.7292 + nsRect r; 1.7293 + r.x = NSToCoordFloor(aRect.X()); 1.7294 + r.y = NSToCoordFloor(aRect.Y()); 1.7295 + r.width = NSToCoordCeil(aRect.XMost()) - r.x; 1.7296 + r.height = NSToCoordCeil(aRect.YMost()) - r.y; 1.7297 + return r; 1.7298 +} 1.7299 + 1.7300 +nsRect 1.7301 +nsTextFrame::ComputeTightBounds(gfxContext* aContext) const 1.7302 +{ 1.7303 + if (StyleContext()->HasTextDecorationLines() || 1.7304 + (GetStateBits() & TEXT_HYPHEN_BREAK)) { 1.7305 + // This is conservative, but OK. 1.7306 + return GetVisualOverflowRect(); 1.7307 + } 1.7308 + 1.7309 + gfxSkipCharsIterator iter = 1.7310 + const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated); 1.7311 + if (!mTextRun) 1.7312 + return nsRect(0, 0, 0, 0); 1.7313 + 1.7314 + PropertyProvider provider(const_cast<nsTextFrame*>(this), iter, 1.7315 + nsTextFrame::eInflated); 1.7316 + // Trim trailing whitespace 1.7317 + provider.InitializeForDisplay(true); 1.7318 + 1.7319 + gfxTextRun::Metrics metrics = 1.7320 + mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), 1.7321 + ComputeTransformedLength(provider), 1.7322 + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, 1.7323 + aContext, &provider); 1.7324 + // mAscent should be the same as metrics.mAscent, but it's what we use to 1.7325 + // paint so that's the one we'll use. 1.7326 + return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent); 1.7327 +} 1.7328 + 1.7329 +/* virtual */ nsresult 1.7330 +nsTextFrame::GetPrefWidthTightBounds(nsRenderingContext* aContext, 1.7331 + nscoord* aX, 1.7332 + nscoord* aXMost) 1.7333 +{ 1.7334 + gfxSkipCharsIterator iter = 1.7335 + const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated); 1.7336 + if (!mTextRun) 1.7337 + return NS_ERROR_FAILURE; 1.7338 + 1.7339 + PropertyProvider provider(const_cast<nsTextFrame*>(this), iter, 1.7340 + nsTextFrame::eInflated); 1.7341 + provider.InitializeForMeasure(); 1.7342 + 1.7343 + gfxTextRun::Metrics metrics = 1.7344 + mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), 1.7345 + ComputeTransformedLength(provider), 1.7346 + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, 1.7347 + aContext->ThebesContext(), &provider); 1.7348 + // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency. 1.7349 + *aX = NSToCoordFloor(metrics.mBoundingBox.x); 1.7350 + *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost()); 1.7351 + 1.7352 + return NS_OK; 1.7353 +} 1.7354 + 1.7355 +static bool 1.7356 +HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun, 1.7357 + int32_t aStartOffset, const gfxSkipCharsIterator& aIter) 1.7358 +{ 1.7359 + if (aIter.GetSkippedOffset() < aTextRun->GetLength() && 1.7360 + aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) { 1.7361 + return true; 1.7362 + } 1.7363 + if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY)) 1.7364 + return false; 1.7365 + gfxSkipCharsIterator iter = aIter; 1.7366 + while (iter.GetOriginalOffset() > aStartOffset) { 1.7367 + iter.AdvanceOriginal(-1); 1.7368 + if (!iter.IsOriginalCharSkipped()) 1.7369 + break; 1.7370 + if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY) 1.7371 + return true; 1.7372 + } 1.7373 + return false; 1.7374 +} 1.7375 + 1.7376 +/** 1.7377 + * Removes all frames from aFrame up to (but not including) aFirstToNotRemove, 1.7378 + * because their text has all been taken and reflowed by earlier frames. 1.7379 + */ 1.7380 +static void 1.7381 +RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove) 1.7382 +{ 1.7383 + NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly"); 1.7384 + // We have to be careful here, because some RemoveFrame implementations 1.7385 + // remove and destroy not only the passed-in frame but also all its following 1.7386 + // in-flows (and sometimes all its following continuations in general). So 1.7387 + // we remove |f| and everything up to but not including firstToNotRemove from 1.7388 + // the flow first, to make sure that only the things we want destroyed are 1.7389 + // destroyed. 1.7390 + 1.7391 + // This sadly duplicates some of the logic from 1.7392 + // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating 1.7393 + // all of it, because we know that the prev-continuation links of 1.7394 + // firstToNotRemove and f are fluid, and non-null. 1.7395 + NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() == 1.7396 + aFirstToNotRemove->GetPrevInFlow() && 1.7397 + aFirstToNotRemove->GetPrevInFlow() != nullptr, 1.7398 + "aFirstToNotRemove should have a fluid prev continuation"); 1.7399 + NS_ASSERTION(aFrame->GetPrevContinuation() == 1.7400 + aFrame->GetPrevInFlow() && 1.7401 + aFrame->GetPrevInFlow() != nullptr, 1.7402 + "aFrame should have a fluid prev continuation"); 1.7403 + 1.7404 + nsIFrame* prevContinuation = aFrame->GetPrevContinuation(); 1.7405 + nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation(); 1.7406 + 1.7407 + for (nsTextFrame* f = aFrame; f != aFirstToNotRemove; 1.7408 + f = static_cast<nsTextFrame*>(f->GetNextContinuation())) { 1.7409 + // f is going to be destroyed soon, after it is unlinked from the 1.7410 + // continuation chain. If its textrun is going to be destroyed we need to 1.7411 + // do it now, before we unlink the frames to remove from the flow, 1.7412 + // because DestroyFrom calls ClearTextRuns() and that will start at the 1.7413 + // first frame with the text run and walk the continuations. 1.7414 + if (f->IsInTextRunUserData()) { 1.7415 + f->ClearTextRuns(); 1.7416 + } else { 1.7417 + f->DisconnectTextRuns(); 1.7418 + } 1.7419 + } 1.7420 + 1.7421 + prevContinuation->SetNextInFlow(aFirstToNotRemove); 1.7422 + aFirstToNotRemove->SetPrevInFlow(prevContinuation); 1.7423 + 1.7424 + aFrame->SetPrevInFlow(nullptr); 1.7425 + lastRemoved->SetNextInFlow(nullptr); 1.7426 + 1.7427 + nsIFrame* parent = aFrame->GetParent(); 1.7428 + nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent); 1.7429 + if (parentBlock) { 1.7430 + // Manually call DoRemoveFrame so we can tell it that we're 1.7431 + // removing empty frames; this will keep it from blowing away 1.7432 + // text runs. 1.7433 + parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY); 1.7434 + } else { 1.7435 + // Just remove it normally; use kNoReflowPrincipalList to avoid posting 1.7436 + // new reflows. 1.7437 + parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame); 1.7438 + } 1.7439 +} 1.7440 + 1.7441 +void 1.7442 +nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout, 1.7443 + uint32_t aSetLengthFlags) 1.7444 +{ 1.7445 + mContentLengthHint = aLength; 1.7446 + int32_t end = GetContentOffset() + aLength; 1.7447 + nsTextFrame* f = static_cast<nsTextFrame*>(GetNextInFlow()); 1.7448 + if (!f) 1.7449 + return; 1.7450 + 1.7451 + // If our end offset is moving, then even if frames are not being pushed or 1.7452 + // pulled, content is moving to or from the next line and the next line 1.7453 + // must be reflowed. 1.7454 + // If the next-continuation is dirty, then we should dirty the next line now 1.7455 + // because we may have skipped doing it if we dirtied it in 1.7456 + // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow 1.7457 + // and ChildIsDirty to handle a range of frames would be worse. 1.7458 + if (aLineLayout && 1.7459 + (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) { 1.7460 + aLineLayout->SetDirtyNextLine(); 1.7461 + } 1.7462 + 1.7463 + if (end < f->mContentOffset) { 1.7464 + // Our frame is shrinking. Give the text to our next in flow. 1.7465 + if (aLineLayout && 1.7466 + HasSignificantTerminalNewline() && 1.7467 + GetParent()->GetType() != nsGkAtoms::letterFrame && 1.7468 + (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { 1.7469 + // Whatever text we hand to our next-in-flow will end up in a frame all of 1.7470 + // its own, since it ends in a forced linebreak. Might as well just put 1.7471 + // it in a separate frame now. This is important to prevent text run 1.7472 + // churn; if we did not do that, then we'd likely end up rebuilding 1.7473 + // textruns for all our following continuations. 1.7474 + // We skip this optimization when the parent is a first-letter frame 1.7475 + // because it doesn't deal well with more than one child frame. 1.7476 + // We also skip this optimization if we were called during bidi 1.7477 + // resolution, so as not to create a new frame which doesn't appear in 1.7478 + // the bidi resolver's list of frames 1.7479 + nsPresContext* presContext = PresContext(); 1.7480 + nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()-> 1.7481 + CreateContinuingFrame(presContext, this, GetParent()); 1.7482 + nsTextFrame* next = static_cast<nsTextFrame*>(newFrame); 1.7483 + nsFrameList temp(next, next); 1.7484 + GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp); 1.7485 + f = next; 1.7486 + } 1.7487 + 1.7488 + f->mContentOffset = end; 1.7489 + if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { 1.7490 + ClearTextRuns(); 1.7491 + f->ClearTextRuns(); 1.7492 + } 1.7493 + return; 1.7494 + } 1.7495 + // Our frame is growing. Take text from our in-flow(s). 1.7496 + // We can take text from frames in lines beyond just the next line. 1.7497 + // We don't dirty those lines. That's OK, because when we reflow 1.7498 + // our empty next-in-flow, it will take text from its next-in-flow and 1.7499 + // dirty that line. 1.7500 + 1.7501 + // Note that in the process we may end up removing some frames from 1.7502 + // the flow if they end up empty. 1.7503 + nsTextFrame* framesToRemove = nullptr; 1.7504 + while (f && f->mContentOffset < end) { 1.7505 + f->mContentOffset = end; 1.7506 + if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { 1.7507 + ClearTextRuns(); 1.7508 + f->ClearTextRuns(); 1.7509 + } 1.7510 + nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextInFlow()); 1.7511 + // Note: the "f->GetNextSibling() == next" check below is to restrict 1.7512 + // this optimization to the case where they are on the same child list. 1.7513 + // Otherwise we might remove the only child of a nsFirstLetterFrame 1.7514 + // for example and it can't handle that. See bug 597627 for details. 1.7515 + if (next && next->mContentOffset <= end && f->GetNextSibling() == next && 1.7516 + (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { 1.7517 + // |f| is now empty. We may as well remove it, instead of copying all 1.7518 + // the text from |next| into it instead; the latter leads to use 1.7519 + // rebuilding textruns for all following continuations. 1.7520 + // We skip this optimization if we were called during bidi resolution, 1.7521 + // since the bidi resolver may try to handle the destroyed frame later 1.7522 + // and crash 1.7523 + if (!framesToRemove) { 1.7524 + // Remember that we have to remove this frame. 1.7525 + framesToRemove = f; 1.7526 + } 1.7527 + } else if (framesToRemove) { 1.7528 + RemoveEmptyInFlows(framesToRemove, f); 1.7529 + framesToRemove = nullptr; 1.7530 + } 1.7531 + f = next; 1.7532 + } 1.7533 + NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end), 1.7534 + "How did we exit the loop if we null out framesToRemove if " 1.7535 + "!next || next->mContentOffset > end ?"); 1.7536 + if (framesToRemove) { 1.7537 + // We are guaranteed that we exited the loop with f not null, per the 1.7538 + // postcondition above 1.7539 + RemoveEmptyInFlows(framesToRemove, f); 1.7540 + } 1.7541 + 1.7542 +#ifdef DEBUG 1.7543 + f = this; 1.7544 + int32_t iterations = 0; 1.7545 + while (f && iterations < 10) { 1.7546 + f->GetContentLength(); // Assert if negative length 1.7547 + f = static_cast<nsTextFrame*>(f->GetNextContinuation()); 1.7548 + ++iterations; 1.7549 + } 1.7550 + f = this; 1.7551 + iterations = 0; 1.7552 + while (f && iterations < 10) { 1.7553 + f->GetContentLength(); // Assert if negative length 1.7554 + f = static_cast<nsTextFrame*>(f->GetPrevContinuation()); 1.7555 + ++iterations; 1.7556 + } 1.7557 +#endif 1.7558 +} 1.7559 + 1.7560 +bool 1.7561 +nsTextFrame::IsFloatingFirstLetterChild() const 1.7562 +{ 1.7563 + nsIFrame* frame = GetParent(); 1.7564 + return frame && frame->IsFloating() && 1.7565 + frame->GetType() == nsGkAtoms::letterFrame; 1.7566 +} 1.7567 + 1.7568 +struct NewlineProperty { 1.7569 + int32_t mStartOffset; 1.7570 + // The offset of the first \n after mStartOffset, or -1 if there is none 1.7571 + int32_t mNewlineOffset; 1.7572 +}; 1.7573 + 1.7574 +nsresult 1.7575 +nsTextFrame::Reflow(nsPresContext* aPresContext, 1.7576 + nsHTMLReflowMetrics& aMetrics, 1.7577 + const nsHTMLReflowState& aReflowState, 1.7578 + nsReflowStatus& aStatus) 1.7579 +{ 1.7580 + DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); 1.7581 + DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); 1.7582 + 1.7583 + // XXX If there's no line layout, we shouldn't even have created this 1.7584 + // frame. This may happen if, for example, this is text inside a table 1.7585 + // but not inside a cell. For now, just don't reflow. 1.7586 + if (!aReflowState.mLineLayout) { 1.7587 + ClearMetrics(aMetrics); 1.7588 + aStatus = NS_FRAME_COMPLETE; 1.7589 + return NS_OK; 1.7590 + } 1.7591 + 1.7592 + ReflowText(*aReflowState.mLineLayout, aReflowState.AvailableWidth(), 1.7593 + aReflowState.rendContext, aMetrics, aStatus); 1.7594 + 1.7595 + NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); 1.7596 + return NS_OK; 1.7597 +} 1.7598 + 1.7599 +#ifdef ACCESSIBILITY 1.7600 +/** 1.7601 + * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText. 1.7602 + */ 1.7603 +class MOZ_STACK_CLASS ReflowTextA11yNotifier 1.7604 +{ 1.7605 +public: 1.7606 + ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) : 1.7607 + mContent(aContent), mPresContext(aPresContext) 1.7608 + { 1.7609 + } 1.7610 + ~ReflowTextA11yNotifier() 1.7611 + { 1.7612 + nsAccessibilityService* accService = nsIPresShell::AccService(); 1.7613 + if (accService) { 1.7614 + accService->UpdateText(mPresContext->PresShell(), mContent); 1.7615 + } 1.7616 + } 1.7617 +private: 1.7618 + ReflowTextA11yNotifier(); 1.7619 + ReflowTextA11yNotifier(const ReflowTextA11yNotifier&); 1.7620 + ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&); 1.7621 + 1.7622 + nsIContent* mContent; 1.7623 + nsPresContext* mPresContext; 1.7624 +}; 1.7625 +#endif 1.7626 + 1.7627 +void 1.7628 +nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, 1.7629 + nsRenderingContext* aRenderingContext, 1.7630 + nsHTMLReflowMetrics& aMetrics, 1.7631 + nsReflowStatus& aStatus) 1.7632 +{ 1.7633 +#ifdef NOISY_REFLOW 1.7634 + ListTag(stdout); 1.7635 + printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth); 1.7636 +#endif 1.7637 + 1.7638 + nsPresContext* presContext = PresContext(); 1.7639 + 1.7640 +#ifdef ACCESSIBILITY 1.7641 + // Schedule the update of accessible tree since rendered text might be changed. 1.7642 + ReflowTextA11yNotifier(presContext, mContent); 1.7643 +#endif 1.7644 + 1.7645 + ///////////////////////////////////////////////////////////////////// 1.7646 + // Set up flags and clear out state 1.7647 + ///////////////////////////////////////////////////////////////////// 1.7648 + 1.7649 + // Clear out the reflow state flags in mState. We also clear the whitespace 1.7650 + // flags because this can change whether the frame maps whitespace-only text 1.7651 + // or not. 1.7652 + RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); 1.7653 + 1.7654 + // Temporarily map all possible content while we construct our new textrun. 1.7655 + // so that when doing reflow our styles prevail over any part of the 1.7656 + // textrun we look at. Note that next-in-flows may be mapping the same 1.7657 + // content; gfxTextRun construction logic will ensure that we take priority. 1.7658 + int32_t maxContentLength = GetInFlowContentLength(); 1.7659 + 1.7660 + // We don't need to reflow if there is no content. 1.7661 + if (!maxContentLength) { 1.7662 + ClearMetrics(aMetrics); 1.7663 + aStatus = NS_FRAME_COMPLETE; 1.7664 + return; 1.7665 + } 1.7666 + 1.7667 +#ifdef NOISY_BIDI 1.7668 + printf("Reflowed textframe\n"); 1.7669 +#endif 1.7670 + 1.7671 + const nsStyleText* textStyle = StyleText(); 1.7672 + 1.7673 + bool atStartOfLine = aLineLayout.LineAtStart(); 1.7674 + if (atStartOfLine) { 1.7675 + AddStateBits(TEXT_START_OF_LINE); 1.7676 + } 1.7677 + 1.7678 + uint32_t flowEndInTextRun; 1.7679 + nsIFrame* lineContainer = aLineLayout.LineContainerFrame(); 1.7680 + gfxContext* ctx = aRenderingContext->ThebesContext(); 1.7681 + const nsTextFragment* frag = mContent->GetText(); 1.7682 + 1.7683 + // DOM offsets of the text range we need to measure, after trimming 1.7684 + // whitespace, restricting to first-letter, and restricting preformatted text 1.7685 + // to nearest newline 1.7686 + int32_t length = maxContentLength; 1.7687 + int32_t offset = GetContentOffset(); 1.7688 + 1.7689 + // Restrict preformatted text to the nearest newline 1.7690 + int32_t newLineOffset = -1; // this will be -1 or a content offset 1.7691 + int32_t contentNewLineOffset = -1; 1.7692 + // Pointer to the nsGkAtoms::newline set on this frame's element 1.7693 + NewlineProperty* cachedNewlineOffset = nullptr; 1.7694 + if (textStyle->NewlineIsSignificant()) { 1.7695 + cachedNewlineOffset = 1.7696 + static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline)); 1.7697 + if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset && 1.7698 + (cachedNewlineOffset->mNewlineOffset == -1 || 1.7699 + cachedNewlineOffset->mNewlineOffset >= offset)) { 1.7700 + contentNewLineOffset = cachedNewlineOffset->mNewlineOffset; 1.7701 + } else { 1.7702 + contentNewLineOffset = FindChar(frag, offset, 1.7703 + mContent->TextLength() - offset, '\n'); 1.7704 + } 1.7705 + if (contentNewLineOffset < offset + length) { 1.7706 + /* 1.7707 + The new line offset could be outside this frame if the frame has been 1.7708 + split by bidi resolution. In that case we won't use it in this reflow 1.7709 + (newLineOffset will remain -1), but we will still cache it in mContent 1.7710 + */ 1.7711 + newLineOffset = contentNewLineOffset; 1.7712 + } 1.7713 + if (newLineOffset >= 0) { 1.7714 + length = newLineOffset + 1 - offset; 1.7715 + } 1.7716 + } 1.7717 + if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) || 1.7718 + (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { 1.7719 + // Skip leading whitespace. Make sure we don't skip a 'pre-line' 1.7720 + // newline if there is one. 1.7721 + int32_t skipLength = newLineOffset >= 0 ? length - 1 : length; 1.7722 + int32_t whitespaceCount = 1.7723 + GetTrimmableWhitespaceCount(frag, offset, skipLength, 1); 1.7724 + if (whitespaceCount) { 1.7725 + offset += whitespaceCount; 1.7726 + length -= whitespaceCount; 1.7727 + // Make sure this frame maps the trimmable whitespace. 1.7728 + if (MOZ_UNLIKELY(offset > GetContentEnd())) { 1.7729 + SetLength(offset - GetContentOffset(), &aLineLayout, 1.7730 + ALLOW_FRAME_CREATION_AND_DESTRUCTION); 1.7731 + } 1.7732 + } 1.7733 + } 1.7734 + 1.7735 + bool completedFirstLetter = false; 1.7736 + // Layout dependent styles are a problem because we need to reconstruct 1.7737 + // the gfxTextRun based on our layout. 1.7738 + if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) { 1.7739 + SetLength(maxContentLength, &aLineLayout, 1.7740 + ALLOW_FRAME_CREATION_AND_DESTRUCTION); 1.7741 + 1.7742 + if (aLineLayout.GetInFirstLetter()) { 1.7743 + // floating first-letter boundaries are significant in textrun 1.7744 + // construction, so clear the textrun out every time we hit a first-letter 1.7745 + // and have changed our length (which controls the first-letter boundary) 1.7746 + ClearTextRuns(); 1.7747 + // Find the length of the first-letter. We need a textrun for this. 1.7748 + // REVIEW: maybe-bogus inflation should be ok (fixed below) 1.7749 + gfxSkipCharsIterator iter = 1.7750 + EnsureTextRun(nsTextFrame::eInflated, ctx, 1.7751 + lineContainer, aLineLayout.GetLine(), 1.7752 + &flowEndInTextRun); 1.7753 + 1.7754 + if (mTextRun) { 1.7755 + int32_t firstLetterLength = length; 1.7756 + if (aLineLayout.GetFirstLetterStyleOK()) { 1.7757 + completedFirstLetter = 1.7758 + FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength); 1.7759 + if (newLineOffset >= 0) { 1.7760 + // Don't allow a preformatted newline to be part of a first-letter. 1.7761 + firstLetterLength = std::min(firstLetterLength, length - 1); 1.7762 + if (length == 1) { 1.7763 + // There is no text to be consumed by the first-letter before the 1.7764 + // preformatted newline. Note that the first letter is therefore 1.7765 + // complete (FindFirstLetterRange will have returned false). 1.7766 + completedFirstLetter = true; 1.7767 + } 1.7768 + } 1.7769 + } else { 1.7770 + // We're in a first-letter frame's first in flow, so if there 1.7771 + // was a first-letter, we'd be it. However, for one reason 1.7772 + // or another (e.g., preformatted line break before this text), 1.7773 + // we're not actually supposed to have first-letter style. So 1.7774 + // just make a zero-length first-letter. 1.7775 + firstLetterLength = 0; 1.7776 + completedFirstLetter = true; 1.7777 + } 1.7778 + length = firstLetterLength; 1.7779 + if (length) { 1.7780 + AddStateBits(TEXT_FIRST_LETTER); 1.7781 + } 1.7782 + // Change this frame's length to the first-letter length right now 1.7783 + // so that when we rebuild the textrun it will be built with the 1.7784 + // right first-letter boundary 1.7785 + SetLength(offset + length - GetContentOffset(), &aLineLayout, 1.7786 + ALLOW_FRAME_CREATION_AND_DESTRUCTION); 1.7787 + // Ensure that the textrun will be rebuilt 1.7788 + ClearTextRuns(); 1.7789 + } 1.7790 + } 1.7791 + } 1.7792 + 1.7793 + float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); 1.7794 + 1.7795 + if (!IsCurrentFontInflation(fontSizeInflation)) { 1.7796 + // FIXME: Ideally, if we already have a text run, we'd move it to be 1.7797 + // the uninflated text run. 1.7798 + ClearTextRun(nullptr, nsTextFrame::eInflated); 1.7799 + } 1.7800 + 1.7801 + gfxSkipCharsIterator iter = 1.7802 + EnsureTextRun(nsTextFrame::eInflated, ctx, 1.7803 + lineContainer, aLineLayout.GetLine(), &flowEndInTextRun); 1.7804 + 1.7805 + NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation), 1.7806 + "EnsureTextRun should have set font size inflation"); 1.7807 + 1.7808 + if (mTextRun && iter.GetOriginalEnd() < offset + length) { 1.7809 + // The textrun does not map enough text for this frame. This can happen 1.7810 + // when the textrun was ended in the middle of a text node because a 1.7811 + // preformatted newline was encountered, and prev-in-flow frames have 1.7812 + // consumed all the text of the textrun. We need a new textrun. 1.7813 + ClearTextRuns(); 1.7814 + iter = EnsureTextRun(nsTextFrame::eInflated, ctx, 1.7815 + lineContainer, aLineLayout.GetLine(), 1.7816 + &flowEndInTextRun); 1.7817 + } 1.7818 + 1.7819 + if (!mTextRun) { 1.7820 + ClearMetrics(aMetrics); 1.7821 + aStatus = NS_FRAME_COMPLETE; 1.7822 + return; 1.7823 + } 1.7824 + 1.7825 + NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length) 1.7826 + <= mTextRun->GetLength(), 1.7827 + "Text run does not map enough text for our reflow"); 1.7828 + 1.7829 + ///////////////////////////////////////////////////////////////////// 1.7830 + // See how much text should belong to this text frame, and measure it 1.7831 + ///////////////////////////////////////////////////////////////////// 1.7832 + 1.7833 + iter.SetOriginalOffset(offset); 1.7834 + nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ? 1.7835 + (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() - 1.7836 + lineContainer->GetUsedBorderAndPadding().left) 1.7837 + : -1; 1.7838 + PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length, 1.7839 + lineContainer, xOffsetForTabs, nsTextFrame::eInflated); 1.7840 + 1.7841 + uint32_t transformedOffset = provider.GetStart().GetSkippedOffset(); 1.7842 + 1.7843 + // The metrics for the text go in here 1.7844 + gfxTextRun::Metrics textMetrics; 1.7845 + gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ? 1.7846 + gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS : 1.7847 + gfxFont::LOOSE_INK_EXTENTS; 1.7848 + NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags), 1.7849 + "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore"); 1.7850 + 1.7851 + int32_t limitLength = length; 1.7852 + int32_t forceBreak = aLineLayout.GetForcedBreakPosition(mContent); 1.7853 + bool forceBreakAfter = false; 1.7854 + if (forceBreak >= offset + length) { 1.7855 + forceBreakAfter = forceBreak == offset + length; 1.7856 + // The break is not within the text considered for this textframe. 1.7857 + forceBreak = -1; 1.7858 + } 1.7859 + if (forceBreak >= 0) { 1.7860 + limitLength = forceBreak - offset; 1.7861 + NS_ASSERTION(limitLength >= 0, "Weird break found!"); 1.7862 + } 1.7863 + // This is the heart of text reflow right here! We don't know where 1.7864 + // to break, so we need to see how much text fits in the available width. 1.7865 + uint32_t transformedLength; 1.7866 + if (offset + limitLength >= int32_t(frag->GetLength())) { 1.7867 + NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()), 1.7868 + "Content offset/length out of bounds"); 1.7869 + NS_ASSERTION(flowEndInTextRun >= transformedOffset, 1.7870 + "Negative flow length?"); 1.7871 + transformedLength = flowEndInTextRun - transformedOffset; 1.7872 + } else { 1.7873 + // we're not looking at all the content, so we need to compute the 1.7874 + // length of the transformed substring we're looking at 1.7875 + gfxSkipCharsIterator iter(provider.GetStart()); 1.7876 + iter.SetOriginalOffset(offset + limitLength); 1.7877 + transformedLength = iter.GetSkippedOffset() - transformedOffset; 1.7878 + } 1.7879 + uint32_t transformedLastBreak = 0; 1.7880 + bool usedHyphenation; 1.7881 + gfxFloat trimmedWidth = 0; 1.7882 + gfxFloat availWidth = aAvailableWidth; 1.7883 + bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() || 1.7884 + (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML); 1.7885 + int32_t unusedOffset; 1.7886 + gfxBreakPriority breakPriority; 1.7887 + aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority); 1.7888 + uint32_t transformedCharsFit = 1.7889 + mTextRun->BreakAndMeasureText(transformedOffset, transformedLength, 1.7890 + (GetStateBits() & TEXT_START_OF_LINE) != 0, 1.7891 + availWidth, 1.7892 + &provider, !aLineLayout.LineIsBreakable(), 1.7893 + canTrimTrailingWhitespace ? &trimmedWidth : nullptr, 1.7894 + &textMetrics, boundingBoxType, ctx, 1.7895 + &usedHyphenation, &transformedLastBreak, 1.7896 + textStyle->WordCanWrap(this), &breakPriority); 1.7897 + if (!length && !textMetrics.mAscent && !textMetrics.mDescent) { 1.7898 + // If we're measuring a zero-length piece of text, update 1.7899 + // the height manually. 1.7900 + nsFontMetrics* fm = provider.GetFontMetrics(); 1.7901 + if (fm) { 1.7902 + textMetrics.mAscent = gfxFloat(fm->MaxAscent()); 1.7903 + textMetrics.mDescent = gfxFloat(fm->MaxDescent()); 1.7904 + } 1.7905 + } 1.7906 + // The "end" iterator points to the first character after the string mapped 1.7907 + // by this frame. Basically, its original-string offset is offset+charsFit 1.7908 + // after we've computed charsFit. 1.7909 + gfxSkipCharsIterator end(provider.GetEndHint()); 1.7910 + end.SetSkippedOffset(transformedOffset + transformedCharsFit); 1.7911 + int32_t charsFit = end.GetOriginalOffset() - offset; 1.7912 + if (offset + charsFit == newLineOffset) { 1.7913 + // We broke before a trailing preformatted '\n'. The newline should 1.7914 + // be assigned to this frame. Note that newLineOffset will be -1 if 1.7915 + // there was no preformatted newline, so we wouldn't get here in that 1.7916 + // case. 1.7917 + ++charsFit; 1.7918 + } 1.7919 + // That might have taken us beyond our assigned content range (because 1.7920 + // we might have advanced over some skipped chars that extend outside 1.7921 + // this frame), so get back in. 1.7922 + int32_t lastBreak = -1; 1.7923 + if (charsFit >= limitLength) { 1.7924 + charsFit = limitLength; 1.7925 + if (transformedLastBreak != UINT32_MAX) { 1.7926 + // lastBreak is needed. 1.7927 + // This may set lastBreak greater than 'length', but that's OK 1.7928 + lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak); 1.7929 + } 1.7930 + end.SetOriginalOffset(offset + charsFit); 1.7931 + // If we were forced to fit, and the break position is after a soft hyphen, 1.7932 + // note that this is a hyphenation break. 1.7933 + if ((forceBreak >= 0 || forceBreakAfter) && 1.7934 + HasSoftHyphenBefore(frag, mTextRun, offset, end)) { 1.7935 + usedHyphenation = true; 1.7936 + } 1.7937 + } 1.7938 + if (usedHyphenation) { 1.7939 + // Fix up metrics to include hyphen 1.7940 + AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx); 1.7941 + AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); 1.7942 + } 1.7943 + 1.7944 + gfxFloat trimmableWidth = 0; 1.7945 + bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; 1.7946 + if (canTrimTrailingWhitespace) { 1.7947 + // Optimization: if we trimmed trailing whitespace, and we can be sure 1.7948 + // this frame will be at the end of the line, then leave it trimmed off. 1.7949 + // Otherwise we have to undo the trimming, in case we're not at the end of 1.7950 + // the line. (If we actually do end up at the end of the line, we'll have 1.7951 + // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid 1.7952 + // having to re-do it.) 1.7953 + if (brokeText || 1.7954 + (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { 1.7955 + // We're definitely going to break so our trailing whitespace should 1.7956 + // definitely be trimmed. Record that we've already done it. 1.7957 + AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); 1.7958 + } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { 1.7959 + // We might not be at the end of the line. (Note that even if this frame 1.7960 + // ends in breakable whitespace, it might not be at the end of the line 1.7961 + // because it might be followed by breakable, but preformatted, whitespace.) 1.7962 + // Undo the trimming. 1.7963 + textMetrics.mAdvanceWidth += trimmedWidth; 1.7964 + trimmableWidth = trimmedWidth; 1.7965 + if (mTextRun->IsRightToLeft()) { 1.7966 + // Space comes before text, so the bounding box is moved to the 1.7967 + // right by trimmdWidth 1.7968 + textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0)); 1.7969 + } 1.7970 + } 1.7971 + } 1.7972 + 1.7973 + if (!brokeText && lastBreak >= 0) { 1.7974 + // Since everything fit and no break was forced, 1.7975 + // record the last break opportunity 1.7976 + NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth, 1.7977 + "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?"); 1.7978 + aLineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, true, breakPriority); 1.7979 + } 1.7980 + 1.7981 + int32_t contentLength = offset + charsFit - GetContentOffset(); 1.7982 + 1.7983 + ///////////////////////////////////////////////////////////////////// 1.7984 + // Compute output metrics 1.7985 + ///////////////////////////////////////////////////////////////////// 1.7986 + 1.7987 + // first-letter frames should use the tight bounding box metrics for ascent/descent 1.7988 + // for good drop-cap effects 1.7989 + if (GetStateBits() & TEXT_FIRST_LETTER) { 1.7990 + textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y()); 1.7991 + textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost()); 1.7992 + } 1.7993 + 1.7994 + // Setup metrics for caller 1.7995 + // Disallow negative widths 1.7996 + aMetrics.Width() = NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth)); 1.7997 + 1.7998 + if (transformedCharsFit == 0 && !usedHyphenation) { 1.7999 + aMetrics.SetTopAscent(0); 1.8000 + aMetrics.Height() = 0; 1.8001 + } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { 1.8002 + // Use actual text metrics for floating first letter frame. 1.8003 + aMetrics.SetTopAscent(NSToCoordCeil(textMetrics.mAscent)); 1.8004 + aMetrics.Height() = aMetrics.TopAscent() + NSToCoordCeil(textMetrics.mDescent); 1.8005 + } else { 1.8006 + // Otherwise, ascent should contain the overline drawable area. 1.8007 + // And also descent should contain the underline drawable area. 1.8008 + // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them. 1.8009 + nsFontMetrics* fm = provider.GetFontMetrics(); 1.8010 + nscoord fontAscent = fm->MaxAscent(); 1.8011 + nscoord fontDescent = fm->MaxDescent(); 1.8012 + aMetrics.SetTopAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent)); 1.8013 + nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent); 1.8014 + aMetrics.Height() = aMetrics.TopAscent() + descent; 1.8015 + } 1.8016 + 1.8017 + NS_ASSERTION(aMetrics.TopAscent() >= 0, "Negative ascent???"); 1.8018 + NS_ASSERTION(aMetrics.Height() - aMetrics.TopAscent() >= 0, "Negative descent???"); 1.8019 + 1.8020 + mAscent = aMetrics.TopAscent(); 1.8021 + 1.8022 + // Handle text that runs outside its normal bounds. 1.8023 + nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); 1.8024 + aMetrics.SetOverflowAreasToDesiredBounds(); 1.8025 + aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox); 1.8026 + 1.8027 + // When we have text decorations, we don't need to compute their overflow now 1.8028 + // because we're guaranteed to do it later 1.8029 + // (see nsLineLayout::RelativePositionFrames) 1.8030 + UnionAdditionalOverflow(presContext, *aLineLayout.LineContainerRS(), 1.8031 + provider, &aMetrics.VisualOverflow(), false); 1.8032 + 1.8033 + ///////////////////////////////////////////////////////////////////// 1.8034 + // Clean up, update state 1.8035 + ///////////////////////////////////////////////////////////////////// 1.8036 + 1.8037 + // If all our characters are discarded or collapsed, then trimmable width 1.8038 + // from the last textframe should be preserved. Otherwise the trimmable width 1.8039 + // from this textframe overrides. (Currently in CSS trimmable width can be 1.8040 + // at most one space so there's no way for trimmable width from a previous 1.8041 + // frame to accumulate with trimmable width from this frame.) 1.8042 + if (transformedCharsFit > 0) { 1.8043 + aLineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth)); 1.8044 + AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); 1.8045 + } 1.8046 + if (charsFit > 0 && charsFit == length && 1.8047 + textStyle->mHyphens != NS_STYLE_HYPHENS_NONE && 1.8048 + HasSoftHyphenBefore(frag, mTextRun, offset, end)) { 1.8049 + // Record a potential break after final soft hyphen 1.8050 + aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length, 1.8051 + textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth, 1.8052 + gfxBreakPriority::eNormalBreak); 1.8053 + } 1.8054 + bool breakAfter = forceBreakAfter; 1.8055 + // length == 0 means either the text is empty or it's all collapsed away 1.8056 + bool emptyTextAtStartOfLine = atStartOfLine && length == 0; 1.8057 + if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && 1.8058 + transformedOffset + transformedLength == mTextRun->GetLength() && 1.8059 + (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) { 1.8060 + // We placed all the text in the textrun and we have a break opportunity at 1.8061 + // the end of the textrun. We need to record it because the following 1.8062 + // content may not care about nsLineBreaker. 1.8063 + 1.8064 + // Note that because we didn't break, we can be sure that (thanks to the 1.8065 + // code up above) textMetrics.mAdvanceWidth includes the width of any 1.8066 + // trailing whitespace. So we need to subtract trimmableWidth here 1.8067 + // because if we did break at this point, that much width would be trimmed. 1.8068 + if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) { 1.8069 + breakAfter = true; 1.8070 + } else { 1.8071 + aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length, 1.8072 + true, gfxBreakPriority::eNormalBreak); 1.8073 + } 1.8074 + } 1.8075 + 1.8076 + // Compute reflow status 1.8077 + aStatus = contentLength == maxContentLength 1.8078 + ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; 1.8079 + 1.8080 + if (charsFit == 0 && length > 0 && !usedHyphenation) { 1.8081 + // Couldn't place any text 1.8082 + aStatus = NS_INLINE_LINE_BREAK_BEFORE(); 1.8083 + } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) { 1.8084 + // Ends in \n 1.8085 + aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); 1.8086 + aLineLayout.SetLineEndsInBR(true); 1.8087 + } else if (breakAfter) { 1.8088 + aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); 1.8089 + } 1.8090 + if (completedFirstLetter) { 1.8091 + aLineLayout.SetFirstLetterStyleOK(false); 1.8092 + aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE; 1.8093 + } 1.8094 + 1.8095 + // Updated the cached NewlineProperty, or delete it. 1.8096 + if (contentLength < maxContentLength && 1.8097 + textStyle->NewlineIsSignificant() && 1.8098 + (contentNewLineOffset < 0 || 1.8099 + mContentOffset + contentLength <= contentNewLineOffset)) { 1.8100 + if (!cachedNewlineOffset) { 1.8101 + cachedNewlineOffset = new NewlineProperty; 1.8102 + if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset, 1.8103 + nsINode::DeleteProperty<NewlineProperty>))) { 1.8104 + delete cachedNewlineOffset; 1.8105 + cachedNewlineOffset = nullptr; 1.8106 + } 1.8107 + } 1.8108 + if (cachedNewlineOffset) { 1.8109 + cachedNewlineOffset->mStartOffset = offset; 1.8110 + cachedNewlineOffset->mNewlineOffset = contentNewLineOffset; 1.8111 + } 1.8112 + } else if (cachedNewlineOffset) { 1.8113 + mContent->DeleteProperty(nsGkAtoms::newline); 1.8114 + } 1.8115 + 1.8116 + // Compute space and letter counts for justification, if required 1.8117 + if (!textStyle->WhiteSpaceIsSignificant() && 1.8118 + (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY || 1.8119 + lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) && 1.8120 + !lineContainer->IsSVGText()) { 1.8121 + AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present. 1.8122 + // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. 1.8123 + int32_t numJustifiableCharacters = 1.8124 + provider.ComputeJustifiableCharacters(offset, charsFit); 1.8125 + 1.8126 + NS_ASSERTION(numJustifiableCharacters <= charsFit, 1.8127 + "Bad justifiable character count"); 1.8128 + aLineLayout.SetTextJustificationWeights(numJustifiableCharacters, 1.8129 + charsFit - numJustifiableCharacters); 1.8130 + } 1.8131 + 1.8132 + SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION); 1.8133 + 1.8134 + InvalidateFrame(); 1.8135 + 1.8136 +#ifdef NOISY_REFLOW 1.8137 + ListTag(stdout); 1.8138 + printf(": desiredSize=%d,%d(b=%d) status=%x\n", 1.8139 + aMetrics.Width(), aMetrics.Height(), aMetrics.TopAscent(), 1.8140 + aStatus); 1.8141 +#endif 1.8142 +} 1.8143 + 1.8144 +/* virtual */ bool 1.8145 +nsTextFrame::CanContinueTextRun() const 1.8146 +{ 1.8147 + // We can continue a text run through a text frame 1.8148 + return true; 1.8149 +} 1.8150 + 1.8151 +nsTextFrame::TrimOutput 1.8152 +nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC) 1.8153 +{ 1.8154 + TrimOutput result; 1.8155 + result.mChanged = false; 1.8156 + result.mLastCharIsJustifiable = false; 1.8157 + result.mDeltaWidth = 0; 1.8158 + 1.8159 + AddStateBits(TEXT_END_OF_LINE); 1.8160 + 1.8161 + int32_t contentLength = GetContentLength(); 1.8162 + if (!contentLength) 1.8163 + return result; 1.8164 + 1.8165 + gfxContext* ctx = aRC->ThebesContext(); 1.8166 + gfxSkipCharsIterator start = 1.8167 + EnsureTextRun(nsTextFrame::eInflated, ctx); 1.8168 + NS_ENSURE_TRUE(mTextRun, result); 1.8169 + 1.8170 + uint32_t trimmedStart = start.GetSkippedOffset(); 1.8171 + 1.8172 + const nsTextFragment* frag = mContent->GetText(); 1.8173 + TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true); 1.8174 + gfxSkipCharsIterator trimmedEndIter = start; 1.8175 + const nsStyleText* textStyle = StyleText(); 1.8176 + gfxFloat delta = 0; 1.8177 + uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); 1.8178 + 1.8179 + if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) { 1.8180 + // We pre-trimmed this frame, so the last character is justifiable 1.8181 + result.mLastCharIsJustifiable = true; 1.8182 + } else if (trimmed.GetEnd() < GetContentEnd()) { 1.8183 + gfxSkipCharsIterator end = trimmedEndIter; 1.8184 + uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); 1.8185 + if (trimmedEnd < endOffset) { 1.8186 + // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's 1.8187 + // OK to pass null for the line container. 1.8188 + PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, 1.8189 + nullptr, 0, nsTextFrame::eInflated); 1.8190 + delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider); 1.8191 + // non-compressed whitespace being skipped at end of line -> justifiable 1.8192 + // XXX should we actually *count* justifiable characters that should be 1.8193 + // removed from the overall count? I think so... 1.8194 + result.mLastCharIsJustifiable = true; 1.8195 + result.mChanged = true; 1.8196 + } 1.8197 + } 1.8198 + 1.8199 + if (!result.mLastCharIsJustifiable && 1.8200 + (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) { 1.8201 + // Check if any character in the last cluster is justifiable 1.8202 + PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, 1.8203 + nullptr, 0, nsTextFrame::eInflated); 1.8204 + bool isCJK = IsChineseOrJapanese(this); 1.8205 + gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter); 1.8206 + provider.FindJustificationRange(&justificationStart, &justificationEnd); 1.8207 + 1.8208 + for (int32_t i = justificationEnd.GetOriginalOffset(); 1.8209 + i < trimmed.GetEnd(); ++i) { 1.8210 + if (IsJustifiableCharacter(frag, i, isCJK)) { 1.8211 + result.mLastCharIsJustifiable = true; 1.8212 + } 1.8213 + } 1.8214 + } 1.8215 + 1.8216 + gfxFloat advanceDelta; 1.8217 + mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart, 1.8218 + (GetStateBits() & TEXT_START_OF_LINE) != 0, true, 1.8219 + &advanceDelta, ctx); 1.8220 + if (advanceDelta != 0) { 1.8221 + result.mChanged = true; 1.8222 + } 1.8223 + 1.8224 + // aDeltaWidth is *subtracted* from our width. 1.8225 + // If advanceDelta is positive then setting the line break made us longer, 1.8226 + // so aDeltaWidth could go negative. 1.8227 + result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); 1.8228 + // If aDeltaWidth goes negative, that means this frame might not actually fit 1.8229 + // anymore!!! We need higher level line layout to recover somehow. 1.8230 + // If it's because the frame has a soft hyphen that is now being displayed, 1.8231 + // this should actually be OK, because our reflow recorded the break 1.8232 + // opportunity that allowed the soft hyphen to be used, and we wouldn't 1.8233 + // have recorded the opportunity unless the hyphen fit (or was the first 1.8234 + // opportunity on the line). 1.8235 + // Otherwise this can/ really only happen when we have glyphs with special 1.8236 + // shapes at the end of lines, I think. Breaking inside a kerning pair won't 1.8237 + // do it because that would mean we broke inside this textrun, and 1.8238 + // BreakAndMeasureText should make sure the resulting shaped substring fits. 1.8239 + // Maybe if we passed a maxTextLength? But that only happens at direction 1.8240 + // changes (so we wouldn't kern across the boundary) or for first-letter 1.8241 + // (which always fits because it starts the line!). 1.8242 + NS_WARN_IF_FALSE(result.mDeltaWidth >= 0, 1.8243 + "Negative deltawidth, something odd is happening"); 1.8244 + 1.8245 +#ifdef NOISY_TRIM 1.8246 + ListTag(stdout); 1.8247 + printf(": trim => %d\n", result.mDeltaWidth); 1.8248 +#endif 1.8249 + return result; 1.8250 +} 1.8251 + 1.8252 +nsOverflowAreas 1.8253 +nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState) 1.8254 +{ 1.8255 + nsRect bounds(nsPoint(0, 0), GetSize()); 1.8256 + nsOverflowAreas result(bounds, bounds); 1.8257 + 1.8258 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.8259 + if (!mTextRun) 1.8260 + return result; 1.8261 + 1.8262 + PropertyProvider provider(this, iter, nsTextFrame::eInflated); 1.8263 + provider.InitializeForDisplay(true); 1.8264 + 1.8265 + gfxTextRun::Metrics textMetrics = 1.8266 + mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), 1.8267 + ComputeTransformedLength(provider), 1.8268 + gfxFont::LOOSE_INK_EXTENTS, nullptr, 1.8269 + &provider); 1.8270 + nsRect &vis = result.VisualOverflow(); 1.8271 + vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent)); 1.8272 + UnionAdditionalOverflow(PresContext(), aBlockReflowState, provider, 1.8273 + &vis, true); 1.8274 + return result; 1.8275 +} 1.8276 +static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun, 1.8277 + uint32_t aSkippedOffset, char16_t aChar) 1.8278 +{ 1.8279 + if (aChar == '\n') { 1.8280 + return aStyle->NewlineIsSignificant() || aStyle->NewlineIsDiscarded() ? 1.8281 + aChar : ' '; 1.8282 + } 1.8283 + switch (aStyle->mTextTransform) { 1.8284 + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: 1.8285 + aChar = ToLowerCase(aChar); 1.8286 + break; 1.8287 + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: 1.8288 + aChar = ToUpperCase(aChar); 1.8289 + break; 1.8290 + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: 1.8291 + if (aTextRun->CanBreakLineBefore(aSkippedOffset)) { 1.8292 + aChar = ToTitleCase(aChar); 1.8293 + } 1.8294 + break; 1.8295 + } 1.8296 + 1.8297 + return aChar; 1.8298 +} 1.8299 + 1.8300 +nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, 1.8301 + gfxSkipChars* aSkipChars, 1.8302 + gfxSkipCharsIterator* aSkipIter, 1.8303 + uint32_t aSkippedStartOffset, 1.8304 + uint32_t aSkippedMaxLength) 1.8305 +{ 1.8306 + // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... 1.8307 + gfxSkipChars skipChars; 1.8308 + nsTextFrame* textFrame; 1.8309 + const nsTextFragment* textFrag = mContent->GetText(); 1.8310 + uint32_t keptCharsLength = 0; 1.8311 + uint32_t validCharsLength = 0; 1.8312 + 1.8313 + // Build skipChars and copy text, for each text frame in this continuation block 1.8314 + for (textFrame = this; textFrame; 1.8315 + textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) { 1.8316 + // For each text frame continuation in this block ... 1.8317 + 1.8318 + if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) { 1.8319 + // We don't trust dirty frames, expecially when computing rendered text. 1.8320 + break; 1.8321 + } 1.8322 + 1.8323 + // Ensure the text run and grab the gfxSkipCharsIterator for it 1.8324 + gfxSkipCharsIterator iter = 1.8325 + textFrame->EnsureTextRun(nsTextFrame::eInflated); 1.8326 + if (!textFrame->mTextRun) 1.8327 + return NS_ERROR_FAILURE; 1.8328 + 1.8329 + // Skip to the start of the text run, past ignored chars at start of line 1.8330 + // XXX In the future we may decide to trim extra spaces before a hard line 1.8331 + // break, in which case we need to accurately detect those sitations and 1.8332 + // call GetTrimmedOffsets() with true to trim whitespace at the line's end 1.8333 + TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false); 1.8334 + int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; 1.8335 + if (startOfLineSkipChars > 0) { 1.8336 + skipChars.SkipChars(startOfLineSkipChars); 1.8337 + iter.SetOriginalOffset(trimmedContentOffsets.mStart); 1.8338 + } 1.8339 + 1.8340 + // Keep and copy the appropriate chars withing the caller's requested range 1.8341 + const nsStyleText* textStyle = textFrame->StyleText(); 1.8342 + while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && 1.8343 + keptCharsLength < aSkippedMaxLength) { 1.8344 + // For each original char from content text 1.8345 + if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { 1.8346 + skipChars.SkipChar(); 1.8347 + } else { 1.8348 + ++keptCharsLength; 1.8349 + skipChars.KeepChar(); 1.8350 + if (aAppendToString) { 1.8351 + aAppendToString->Append( 1.8352 + TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(), 1.8353 + textFrag->CharAt(iter.GetOriginalOffset()))); 1.8354 + } 1.8355 + } 1.8356 + iter.AdvanceOriginal(1); 1.8357 + } 1.8358 + if (keptCharsLength >= aSkippedMaxLength) { 1.8359 + break; // Already past the end, don't build string or gfxSkipCharsIter anymore 1.8360 + } 1.8361 + } 1.8362 + 1.8363 + if (aSkipChars) { 1.8364 + aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars 1.8365 + if (aSkipIter) { 1.8366 + // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator, 1.8367 + // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars. 1.8368 + *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength()); 1.8369 + } 1.8370 + } 1.8371 + 1.8372 + return NS_OK; 1.8373 +} 1.8374 + 1.8375 +nsIAtom* 1.8376 +nsTextFrame::GetType() const 1.8377 +{ 1.8378 + return nsGkAtoms::textFrame; 1.8379 +} 1.8380 + 1.8381 +/* virtual */ bool 1.8382 +nsTextFrame::IsEmpty() 1.8383 +{ 1.8384 + NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) || 1.8385 + !(mState & TEXT_ISNOT_ONLY_WHITESPACE), 1.8386 + "Invalid state"); 1.8387 + 1.8388 + // XXXldb Should this check compatibility mode as well??? 1.8389 + const nsStyleText* textStyle = StyleText(); 1.8390 + if (textStyle->WhiteSpaceIsSignificant() && 1.8391 + textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES) { 1.8392 + // XXX shouldn't we return true if the length is zero? 1.8393 + return false; 1.8394 + } 1.8395 + 1.8396 + if (mState & TEXT_ISNOT_ONLY_WHITESPACE) { 1.8397 + return false; 1.8398 + } 1.8399 + 1.8400 + if (mState & TEXT_IS_ONLY_WHITESPACE) { 1.8401 + return true; 1.8402 + } 1.8403 + 1.8404 + bool isEmpty = 1.8405 + textStyle->mWhiteSpace == NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES ? 1.8406 + IsAllNewlines(mContent->GetText()) : 1.8407 + IsAllWhitespace(mContent->GetText(), 1.8408 + textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE); 1.8409 + mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); 1.8410 + return isEmpty; 1.8411 +} 1.8412 + 1.8413 +#ifdef DEBUG_FRAME_DUMP 1.8414 +// Translate the mapped content into a string that's printable 1.8415 +void 1.8416 +nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const 1.8417 +{ 1.8418 + // Get the frames text content 1.8419 + const nsTextFragment* frag = mContent->GetText(); 1.8420 + if (!frag) { 1.8421 + return; 1.8422 + } 1.8423 + 1.8424 + // Compute the total length of the text content. 1.8425 + *aTotalContentLength = frag->GetLength(); 1.8426 + 1.8427 + int32_t contentLength = GetContentLength(); 1.8428 + // Set current fragment and current fragment offset 1.8429 + if (0 == contentLength) { 1.8430 + return; 1.8431 + } 1.8432 + int32_t fragOffset = GetContentOffset(); 1.8433 + int32_t n = fragOffset + contentLength; 1.8434 + while (fragOffset < n) { 1.8435 + char16_t ch = frag->CharAt(fragOffset++); 1.8436 + if (ch == '\r') { 1.8437 + aBuf.AppendLiteral("\\r"); 1.8438 + } else if (ch == '\n') { 1.8439 + aBuf.AppendLiteral("\\n"); 1.8440 + } else if (ch == '\t') { 1.8441 + aBuf.AppendLiteral("\\t"); 1.8442 + } else if ((ch < ' ') || (ch >= 127)) { 1.8443 + aBuf.Append(nsPrintfCString("\\u%04x", ch)); 1.8444 + } else { 1.8445 + aBuf.Append(ch); 1.8446 + } 1.8447 + } 1.8448 +} 1.8449 + 1.8450 +nsresult 1.8451 +nsTextFrame::GetFrameName(nsAString& aResult) const 1.8452 +{ 1.8453 + MakeFrameName(NS_LITERAL_STRING("Text"), aResult); 1.8454 + int32_t totalContentLength; 1.8455 + nsAutoCString tmp; 1.8456 + ToCString(tmp, &totalContentLength); 1.8457 + tmp.SetLength(std::min(tmp.Length(), 50u)); 1.8458 + aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\""); 1.8459 + return NS_OK; 1.8460 +} 1.8461 + 1.8462 +void 1.8463 +nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const 1.8464 +{ 1.8465 + nsCString str; 1.8466 + ListGeneric(str, aPrefix, aFlags); 1.8467 + 1.8468 + str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun)); 1.8469 + 1.8470 + // Output the first/last content offset and prev/next in flow info 1.8471 + bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength(); 1.8472 + str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(), 1.8473 + isComplete ? 'T':'F'); 1.8474 + 1.8475 + if (IsSelected()) { 1.8476 + str += " SELECTED"; 1.8477 + } 1.8478 + fprintf_stderr(out, "%s\n", str.get()); 1.8479 +} 1.8480 +#endif 1.8481 + 1.8482 +#ifdef DEBUG 1.8483 +nsFrameState 1.8484 +nsTextFrame::GetDebugStateBits() const 1.8485 +{ 1.8486 + // mask out our emptystate flags; those are just caches 1.8487 + return nsFrame::GetDebugStateBits() & 1.8488 + ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS); 1.8489 +} 1.8490 +#endif 1.8491 + 1.8492 +void 1.8493 +nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) 1.8494 +{ 1.8495 + AddStateBits(NS_FRAME_IS_BIDI); 1.8496 + mContent->DeleteProperty(nsGkAtoms::flowlength); 1.8497 + 1.8498 + /* 1.8499 + * After Bidi resolution we may need to reassign text runs. 1.8500 + * This is called during bidi resolution from the block container, so we 1.8501 + * shouldn't be holding a local reference to a textrun anywhere. 1.8502 + */ 1.8503 + ClearTextRuns(); 1.8504 + 1.8505 + nsTextFrame* prev = static_cast<nsTextFrame*>(GetPrevContinuation()); 1.8506 + if (prev) { 1.8507 + // the bidi resolver can be very evil when columns/pages are involved. Don't 1.8508 + // let it violate our invariants. 1.8509 + int32_t prevOffset = prev->GetContentOffset(); 1.8510 + aStart = std::max(aStart, prevOffset); 1.8511 + aEnd = std::max(aEnd, prevOffset); 1.8512 + prev->ClearTextRuns(); 1.8513 + } 1.8514 + 1.8515 + mContentOffset = aStart; 1.8516 + SetLength(aEnd - aStart, nullptr, 0); 1.8517 + 1.8518 + /** 1.8519 + * After inserting text the caret Bidi level must be set to the level of the 1.8520 + * inserted text.This is difficult, because we cannot know what the level is 1.8521 + * until after the Bidi algorithm is applied to the whole paragraph. 1.8522 + * 1.8523 + * So we set the caret Bidi level to UNDEFINED here, and the caret code will 1.8524 + * set it correctly later 1.8525 + */ 1.8526 + nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); 1.8527 + if (frameSelection) { 1.8528 + frameSelection->UndefineCaretBidiLevel(); 1.8529 + } 1.8530 +} 1.8531 + 1.8532 +/** 1.8533 + * @return true if this text frame ends with a newline character. It should return 1.8534 + * false if it is not a text frame. 1.8535 + */ 1.8536 +bool 1.8537 +nsTextFrame::HasSignificantTerminalNewline() const 1.8538 +{ 1.8539 + return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(); 1.8540 +} 1.8541 + 1.8542 +bool 1.8543 +nsTextFrame::IsAtEndOfLine() const 1.8544 +{ 1.8545 + return (GetStateBits() & TEXT_END_OF_LINE) != 0; 1.8546 +} 1.8547 + 1.8548 +nscoord 1.8549 +nsTextFrame::GetBaseline() const 1.8550 +{ 1.8551 + return mAscent; 1.8552 +} 1.8553 + 1.8554 +bool 1.8555 +nsTextFrame::HasAnyNoncollapsedCharacters() 1.8556 +{ 1.8557 + gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); 1.8558 + int32_t offset = GetContentOffset(), 1.8559 + offsetEnd = GetContentEnd(); 1.8560 + int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset); 1.8561 + int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd); 1.8562 + return skippedOffset != skippedOffsetEnd; 1.8563 +}