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